Question regarding unexpected behavior in using __enter__ method

Cameron Simpson cs at cskk.id.au
Thu Apr 20 20:25:46 EDT 2023


On 21Apr2023 00:44, Lorenzo Catoni <l.catoni.99 at gmail.com> wrote:
>I am writing to seek your assistance in understanding an unexpected
>behavior that I encountered while using the __enter__ method. I have
>provided a code snippet below to illustrate the problem:
>
>```
>>>> class X:
>...     __enter__ = int
>...     __exit__ = lambda *_: None
>...
>>>> with X() as x:
>...     pass
>...
>>>> x
>0
>```
>As you can see, the __enter__ method does not throw any exceptions and
>returns the output of "int()" correctly. However, one would normally expect
>the input parameter "self" to be passed to the function.

My descriptor fu is weak, but I believe this is because `int` is not a 
plain function but a type.

Consider this class definition:

     class X:
        x = 1
        def y(self):
            return "y"

When you define a class, the body of the class is run in a namespace, 
and on completion, the namespace is _used_ to construct the class.  
During that process, the various names are considered. Here we've got 2 
names: "x" and "y".

"x" refers to an int and is just stored as a class attribute, unchanged.

"y" refers to a function, and is promoted to a descriptor of an unbound 
method.

So later: X.x return 1 but X.y returns a unbound method. If we make an 
instance:

     objx = X()

then obj.x returns 1 (by not fining an "x" on "obj", but finding one on 
"type(obj)" i.e. the class attribute.

By contrast, obj.y returns a bound method, a function already curried 
with a leading parameter "obj" (which will be "self"). There's no "y" 
attribute directly on "obj" but there's an unbound method on 
"type(obj).y", which gets bound by saying "obj.y".

The means that what happens to a name when you define the class depends 
on the typeof the value bound to the name.

A plain function gets turned into an unbound instance method, but other 
things are left alone.

When you went:

     __enter__ = int

That's not a plain function and so "obj.__enter__" doesn't turn into a 
bound method - it it just `int`.

Cheers,
Cameron Simpson <cs at cskk.id.au>


More information about the Python-list mailing list