[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