Question regarding unexpected behavior in using __enter__ method
dn
PythonList at DancesWithMice.info
Thu Apr 20 22:49:49 EDT 2023
On 21/04/2023 10.44, Lorenzo Catoni 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:
It is expected behavior - just not what WE might have expected!
>>>> class X:
> ... __enter__ = int
> ... __exit__ = lambda *_: None
> ...
>>>> with X() as x:
> ... pass
> ...
>>>> x
> 0
Note that what is happening is the creation of an alias for the int
built-in function.
The docs say:
«
class int(x=0)
class int(x, base=10)
Return an integer object constructed from a number or string x, or
return 0 if no arguments are given. If x defines __int__(), int(x)
returns x.__int__(). If x defines __index__(), it returns x.__index__().
If x defines __trunc__(), it returns x.__trunc__(). For floating point
numbers, this truncates towards zero.
...
»
(https://docs.python.org/3/library/functions.html#int)
No argument is given. int() delivers as-promised. Hence, the x == 0
result obtained.
> 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.
(you know this next part!)
However, if int() is fed an X-object, which in no way represents a
numeric or numeric-able value, then it crashes. After the class
definition, try:
int( X )
=> int 0
Right-y-ho: the evidence.
Firstly, what happens when int() is called with no argument?
print( "int", int(), )
Note that whereas int and myint are both called with no argument(s), and
therefore int() defaults to 0, myint is attempting to use the arguments
as part of its return-value - "pass-through".
As identified, the first argument (the a-tuple's zero-th element) is
going to be x's self - which is NOT numeric, stored in a tuple - which
is NOT numeric...
Thus, if we "shadow" the built-in int() with a user-function, we can see
what is being passed-in
def int( *a, **kw, ):
print( locals(), )
print( "a", a, type( a ), id( a ), )
print( len( a ), a[ 0 ], type( a[ 0 ], ) )
print( "kw", kw, type( kw ), id( kw ), )
return 42
class X:
__enter__ = int
__exit__ = lambda *_: None
with X() as x:
pass
print( "After first CM", x, "\n\n")
del( int )
def myint(*a, **kw):
print( locals(), )
print( "a", a, type( a ), id( a ), )
print( len( a ), a[ 0 ], type( a[ 0 ], ) )
print( "kw", kw, type( kw ), id( kw ), )
return int(*a, **kw)
class Y:
__enter__ = myint
__exit__ = lambda *_: None
print( Y, type( Y ), id( Y ), )
with Y() as y:
print( y, type( y ), id( y ), )
pass
print( y )
=>
{'a': (<__main__.X object at 0x7f9b6bf13b90>,), 'kw': {}}
a (<__main__.X object at 0x7f9b6bf13b90>,) <class 'tuple'> 140305733882528
1 <__main__.X object at 0x7f9b6bf13b90> <class '__main__.X'>
kw {} <class 'dict'> 140305734120576
After first CM 42
<class '__main__.Y'> <class 'type'> 93904023389520
{'a': (<__main__.Y object at 0x7f9b6bf2c0d0>,), 'kw': {}}
a (<__main__.Y object at 0x7f9b6bf2c0d0>,) <class 'tuple'> 140305507712640
1 <__main__.Y object at 0x7f9b6bf2c0d0> <class '__main__.Y'>
kw {} <class 'dict'> 140305507621376
Traceback (most recent call last):
File "/home/dn/Projects/analyzer/lorenzo.py", line 53, in <module>
with Y() as y:
File "/home/dn/Projects/analyzer/lorenzo.py", line 44, in myint
return int(*a, **kw)
^^^^^^^^^^^^^
TypeError: int() argument must be a string, a bytes-like object or a
real number, not 'Y'
If you remover the del() and leave my play-version of int(), Python can
make it work...
(the second half of the output)
<class '__main__.Y'> <class 'type'> 94557671306576
{'a': (<__main__.Y object at 0x7f950ee2c0d0>,), 'kw': {}}
a (<__main__.Y object at 0x7f950ee2c0d0>,) <class 'tuple'> 140278176579200
1 <__main__.Y object at 0x7f950ee2c0d0> <class '__main__.Y'>
kw {} <class 'dict'> 140278176487936
{'a': (<__main__.Y object at 0x7f950ee2c0d0>,), 'kw': {}}
a (<__main__.Y object at 0x7f950ee2c0d0>,) <class 'tuple'> 140278176579152
1 <__main__.Y object at 0x7f950ee2c0d0> <class '__main__.Y'>
kw {} <class 'dict'> 140278176482368
42 <class 'int'> 140278410201800
42
So, it rather depends upon what you want returned from the actual
myint() function.
Web.Refs:
https://docs.python.org/3/reference/datamodel.html?highlight=context%20manager#with-statement-context-managers
https://docs.python.org/3/reference/compound_stmts.html?highlight=context%20manager#the-with-statement
I'm curious though, why not:
class X:
def __enter__( etc
def __exit__( etc
with the actual code from myint9) as the suite/body of the __enter__()?
(in which case, the rôle of self is 'standard operating procedure' and
may be more obvious)
--
Regards,
=dn
More information about the Python-list
mailing list