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

Reply via email to