tuhriel 17 hours ago

One big hurdle, I guess most new pythonistas stumble over, is the one where you add an create an empty default list in a class:

You would think it creates a new empty list, for each object of this class, but it actually creates one which is shared between all of the objects. I had a lot of fun with that one once.

  • zahlman 3 hours ago

    > but it actually creates one which is shared between all of the objects

    More precisely: it creates one which belongs to the class. Everything inside the `class` block is thus - both data and methods. After all, the methods are not special - from the class' perspective, they are just functions, and functions are objects, and they become attributes of the class object the same way as "ordinary data". (Further: the `def` statement is a form of assignment.)

    "Methods" don't really exist ahead of time; they're created on demand when they're looked up from an instance, found in a class, and furthermore discovered to implement the descriptor protocol. The interesting part is that looking up an attribute on an instance can find it in the class. That's what allows method calls to work properly (in Python's implementation) without storing per-instance data — objects representing the "bound" method are instead created on demand (and typically thrown away right after)! This is necessary because anywhere that `foo.bar()` is valid, `foo.bar` must also be valid, and furthermore needs to evaluate to something — since it's a subexpression of a valid subexpression, and Python has no "void return values", only a strong expression/statement distinction. This feature also enables you to do `baz = foo.bar; baz()`.

    (There are several sources I could quote for all of this, but usually it's tangential to what the source is actually trying to discuss.)

    More fundamentally, Python beginners often fail to distinguish assignment from mutation. So they are further surprised when `instance.items.append(item)` affects the other instances, but `instance.items = [item]` does not. But this is entirely to be expected: assignment to an instance attribute only ever stores it within the instance, and does not look it up or replace it in the class. Of course it doesn't — if it did, there would be no way to implement `__init__`. And now that the instance has a separate attribute with the same name, that shadows the class' attribute, and will be found by future lookups on that instance only.