[Python-Dev] Single- vs. Multi-pass iterability

Ka-Ping Yee ping@zesty.ca
Sun, 21 Jul 2002 06:51:30 -0700 (PDT)


On Sun, 21 Jul 2002, Tim Peters wrote:
>     x1 = y.z
>     x2 = y.z
>
> Are x1 and x2 the same object after that?  At least equal?  Did either line
> mutate y?  You simply can't know without knowing how y's type implements
> __getattr__, and with the introduction of computed attributes (properties)
> it's just going to get muddier.

That's not the point.  You could claim that *any* polymorphism in
Python is useless by the same argument.  But Python is not useless;
Python code really is reusable; and that's because there are good
conventions about what the behaviour *should* be.  People who do
really find this upsetting should go use a strongly-typed language.

In general, getting "y.z" should be idempotent, and should not mutate y.
I think everyone would agree on the concept.  If it does mutate y with
visible effects, then the implementor is breaking the convention.

Sure, Python won't prevent you from writing a file-like class where you
write the string "blah" to the file by fetching f.blah and you close the
file by mentioning f[42].  But when users of this class then come running
after you with pointed sticks, i'm not going to fight them off. :)

This is a list of all the type slots accessible from Python, before
iterators (i.e. pre-2.2).  Beside each is the answer to the question:

    Suppose you look at the value of x, then do this operation to x,
    then look at the value of x.  Should we expect the two observed
    values to be the same or different?

    nb_add                              same
    nb_subtract                         same
    nb_multiply                         same
    nb_divide                           same
    nb_remainder                        same
    nb_divmod                           same
    nb_power                            same
    nb_negative                         same
    nb_positive                         same
    nb_absolute                         same
    nb_nonzero                          same
    nb_invert                           same
    nb_lshift                           same
    nb_rshift                           same
    nb_and                              same
    nb_xor                              same
    nb_or                               same
    nb_coerce                           same
    nb_int                              same
    nb_long                             same
    nb_float                            same
    nb_oct                              same
    nb_hex                              same
    nb_inplace_add                      different
    nb_inplace_subtract                 different
    nb_inplace_multiply                 different
    nb_inplace_divide                   different
    nb_inplace_remainder                different
    nb_inplace_power                    different
    nb_inplace_lshift                   different
    nb_inplace_rshift                   different
    nb_inplace_and                      different
    nb_inplace_xor                      different
    nb_inplace_or                       different
    nb_floor_divide                     same
    nb_true_divide                      same
    nb_inplace_floor_divide             different
    nb_inplace_true_divide              different

    sq_length                           same
    sq_concat                           same
    sq_repeat                           same
    sq_item                             same
    sq_slice                            same
    sq_ass_item                         different
    sq_ass_slice                        different
    sq_contains                         same
    sq_inplace_concat                   different
    sq_inplace_repeat                   different

    mp_length                           same
    mp_subscript                        same
    mp_ass_subscript                    different

    bf_getreadbuffer                    same
    bf_getwritebuffer                   same
    bf_getsegcount                      same
    bf_getcharbuffer                    same

    tp_print                            same
    tp_getattr                          same
    tp_setattr                          different
    tp_compare                          same
    tp_repr                             same
    tp_hash                             same
    tp_call                             ?
    tp_str                              same
    tp_getattro                         same
    tp_setattro                         different

In every case except for __call__, there exists a canonical answer.
We all rely on these conventions every time we write a Python program.
And learning these conventions is a necessary part of learning Python.

You can argue, as Guido has, that in the particular case of for-loops
distinguishing between mutating and non-mutating behaviour is not worth
the trouble.  But you can't say that we should give up on the whole
concept *in general*.


-- ?!ng