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
--
https://mail.python.org/mailman/listinfo/python-list