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