Question regarding unexpected behavior in using __enter__ method

Rob Cliffe rob.cliffe at btinternet.com
Sat Apr 22 09:56:02 EDT 2023


This puzzled me at first, but I think others have nailed it.  It is not 
to do with the 'with' statement, but with the way functions are defined.
When a class is instantiated, as in x=X():
     the instance object gets (at least in effect), as attributes, 
copies of functions defined *in the class* (using def or lambda) but 
they become "bound methods", i.e. bound to the instance.  Whenever they 
are called, they will be called with the instance as the first argument, 
aka self:
     class X(object):
         def func(*args, **kargs): pass
     x = X()
     y = ()
x.func and y.func are two *different" functions.  When x.func is called, 
x is added as the first argument.  When y.func is called. y is added as 
the first argument.
      boundFunc = y.func
     boundFunc() # Adds y as first argument.
Indeed, these functions have an attribute called __self__ whose value is 
... you guessed it ... the object they are bound to
When a function is defined outside of a class, it remains a simple 
function, not bound to any object.  It does not have a __self__ 
attribute.  Neither does a built-in type such as 'int'.
Nor for that matter does the class function X.func:
     X.func() # Called with no arguments

Best wishes
Rob Cliffe

On 20/04/2023 23:44, 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!
>
> Best regards,
> Lorenzo Catoni



More information about the Python-list mailing list