Question regarding unexpected behavior in using __enter__ method

Mark Bourne nntp.mbourne at spamgourmet.com
Wed Apr 26 15:41:50 EDT 2023


Lorenzo Catoni wrote:
> Dear Python Mailing List members,
> 
> 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.
> 
> On the other hand, when I implemented a custom function in place of the
> __enter__ method, I encountered the following TypeError:
> 
> ```
>>>> def myint(*a, **kw):
> ...     return int(*a, **kw)
> ...
>>>> class X:
> ...     __enter__ = myint
> ...     __exit__ = lambda *_: None
> ...
>>>> with X() as x:
> ...     pass
> ...
> Traceback (most recent call last):
>    File "<stdin>", line 1, in <module>
>    File "<stdin>", line 2, in myint
> TypeError: int() argument must be a string, a bytes-like object or a real
> number, not 'X'
> ```
> Here, the TypeError occurred because "self" was passed as an input
> parameter to "myint". Can someone explain why this unexpected behavior
> occurs only in the latter case?
> 
> I tested this issue on the following Python versions, and the problem
> persists on all of them:
> - Python 3.8.10 (default, Nov 14 2022, 12:59:47) [GCC 9.4.0] on linux
> - Python 3.10.10 (main, Feb  8 2023, 14:50:01) [GCC 9.4.0] on linux
> - Python 3.10.7 (tags/v3.10.7:6cc6b13, Sep  5 2022, 14:08:36) [MSC v.1933
> 64 bit (AMD64)] on win32
> 
> I appreciate any input or insights that you might have on this matter.
> 
> Thank you for your help in advance!

Aside from other explanations and suggestions, the following definition 
of X also works:

class X:
     __enter__ = staticmethod(myint)
     __exit__ = lambda *_: None

Wrapping `myint` in a call to `staticmethod` is the same as using 
`@staticmethod` as a decorator on a method within the class, so the 
`self` parameter doesn't get passed.  Equivalent to:

class X:
     @staticmethod
     def __enter__(*a, **kw):
         return int(*a, **kw)
     __exit__ = lambda *_: None

Which in turn is just a neater way of doing:

class X:
     def __enter__(*a, **kw):
         return int(*a, **kw)
     __enter__ = staticmethod(__enter__)
     __exit__ = lambda *_: None

Those equivalents are a bit pointless, since no arguments will be passed 
into `__enter__` anyway in normal usage, but perhaps make it a bit 
clearer that the second example behaves as would be expected for a 
method of X (where the instance is passed as the first argument), and 
that it's the first example (with `__enter__ = int`) that should be a 
bit more surprising.  (I'm not sure there's much practical use for the 
original `__enter__ = int` either, but presumably that's just used as a 
cut-down demonstration).

-- 
Mark.


More information about the Python-list mailing list