• Question regarding unexpected behavior in using __enter__ method

    From Lorenzo Catoni@21:1/5 to All on Fri Apr 21 00:44:38 2023
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From aapost@21:1/5 to Lorenzo Catoni on Thu Apr 20 21:36:18 2023
    On 4/20/23 18:44, Lorenzo Catoni wrote:
    Here, the TypeError occurred because "self" was passed as an input

    Instantiate X and observe it there

    x2 = X()

    X.__enter__
    <class 'int'>
    X.__exit__
    <function X.<lambda> at 0x...>
    x2.__enter__
    <class 'int'>
    x2.__exit__
    <bound method X.<lambda> of <__main__.X object at 0x...>>


    To receive self the method must be bound. __enter__ = int doesn't bind
    the int type/class on instantiation, so it never gets self. Your custom function binds and receives self.

    I am not sure if there is documentation to explain better specifically
    what makes makes type different and not bindable.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From dn@21:1/5 to Lorenzo Catoni on Fri Apr 21 14:49:49 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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Cameron Simpson@21:1/5 to Lorenzo Catoni on Fri Apr 21 10:25:46 2023
    On 21Apr2023 00:44, Lorenzo Catoni <l.catoni.99@gmail.com> 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:

    ```
    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.

    My descriptor fu is weak, but I believe this is because `int` is not a
    plain function but a type.

    Consider this class definition:

    class X:
    x = 1
    def y(self):
    return "y"

    When you define a class, the body of the class is run in a namespace,
    and on completion, the namespace is _used_ to construct the class.
    During that process, the various names are considered. Here we've got 2
    names: "x" and "y".

    "x" refers to an int and is just stored as a class attribute, unchanged.

    "y" refers to a function, and is promoted to a descriptor of an unbound
    method.

    So later: X.x return 1 but X.y returns a unbound method. If we make an instance:

    objx = X()

    then obj.x returns 1 (by not fining an "x" on "obj", but finding one on "type(obj)" i.e. the class attribute.

    By contrast, obj.y returns a bound method, a function already curried
    with a leading parameter "obj" (which will be "self"). There's no "y"
    attribute directly on "obj" but there's an unbound method on
    "type(obj).y", which gets bound by saying "obj.y".

    The means that what happens to a name when you define the class depends
    on the typeof the value bound to the name.

    A plain function gets turned into an unbound instance method, but other
    things are left alone.

    When you went:

    __enter__ = int

    That's not a plain function and so "obj.__enter__" doesn't turn into a
    bound method - it it just `int`.

    Cheers,
    Cameron Simpson <cs@cskk.id.au>

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Lorenzo Catoni@21:1/5 to Cameron Simpson on Fri Apr 21 09:46:00 2023
    Thankyou for your answer,
    i think i found the reason for this behavior, is has to do with the
    function being user defined or not, rather than being a plain function or
    type, as stated here https://docs.python.org/3/reference/datamodel.html#:~:text=Also%20notice%20that%20this%20transformation%20only%20happens%20for%20user%2Ddefined%20functions%3B%20other%20callable%20objects%20(and%20all%20non%2Dcallable%20objects)%20are%20retrieved%
    20without%20transformation

    Regards,
    Lorenzo Catoni

    On Fri, 21 Apr 2023 at 07:21, Cameron Simpson <cs@cskk.id.au> wrote:

    On 21Apr2023 00:44, Lorenzo Catoni <l.catoni.99@gmail.com> 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:

    ```
    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.

    My descriptor fu is weak, but I believe this is because `int` is not a
    plain function but a type.

    Consider this class definition:

    class X:
    x = 1
    def y(self):
    return "y"

    When you define a class, the body of the class is run in a namespace,
    and on completion, the namespace is _used_ to construct the class.
    During that process, the various names are considered. Here we've got 2 names: "x" and "y".

    "x" refers to an int and is just stored as a class attribute, unchanged.

    "y" refers to a function, and is promoted to a descriptor of an unbound method.

    So later: X.x return 1 but X.y returns a unbound method. If we make an instance:

    objx = X()

    then obj.x returns 1 (by not fining an "x" on "obj", but finding one on "type(obj)" i.e. the class attribute.

    By contrast, obj.y returns a bound method, a function already curried
    with a leading parameter "obj" (which will be "self"). There's no "y" attribute directly on "obj" but there's an unbound method on
    "type(obj).y", which gets bound by saying "obj.y".

    The means that what happens to a name when you define the class depends
    on the typeof the value bound to the name.

    A plain function gets turned into an unbound instance method, but other things are left alone.

    When you went:

    __enter__ = int

    That's not a plain function and so "obj.__enter__" doesn't turn into a
    bound method - it it just `int`.

    Cheers,
    Cameron Simpson <cs@cskk.id.au>
    --
    https://mail.python.org/mailman/listinfo/python-list


    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Peter Otten@21:1/5 to Lorenzo Catoni on Fri Apr 21 09:41:07 2023
    On 21/04/2023 00: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?

    Cameron is right, it's the descriptor protocol. Technically

    inst.attr

    invokes attr.__get__(...) if it exists:

    class A:
    def __get__(self, *args): return args


    class B: pass

    class X:
    a = A()
    b = B()


    x = X()
    x.b
    <__main__.B object at 0x02C2E388>
    x.a
    (<__main__.X object at 0x02C2E280>, <class '__main__.X'>)

    Python functions support the descriptor protocol

    hasattr(lambda: None, "__get__")
    True

    while builtin functions don't:

    hasattr(ord, "__get__")
    False


    I'm unsure whether to regard int as a class or or function, but as there
    is no __get__

    hasattr(int, "__get__")
    False

    it behaves like builtin functions in this case.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Rob Cliffe@21:1/5 to Lorenzo Catoni on Sat Apr 22 14:56:02 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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From avi.e.gross@gmail.com@21:1/5 to Lorenzo Catoni on Tue Apr 25 23:47:06 2023
    I think you got that right, Rob. A method created in a class is normally expected to care about the class in the sense that it often wants to access internal aspects and is given a "this" or "self" or whatever name you choose as a first argument. As
    noted, it is sometimes possible to create a function attached not to an object but to the class itself as in, I think, the math class that is not normally instantiated as an object but lets you use things like math.pi and math.cos() and so on.

    A comment on dunder methods in python is that they have a sort of purpose albeit you can hijack some to do other things. The protocol for WITH is a bit slippery as __enter__() and __exit__ are expected to do some abstract things that loosely are intended
    to set up something at the start in a way that will be (guaranteed) to be done if the exit routine is called when done. This can be about opening a file, or network connection and later closing it, or setting up some data structure and freeing the memory
    at the end, but it could be ANYTHING you feel like. For example, it can turn logging of some kind on and off and also compress the log file at the end. Or it could set up changes to the object that are there for the duration of the WITH and then reset
    the changes back at the end.

    An imaginary example might be to start caching what some methods are doing or replace a method by another, then empty the cache at the end or put back the redirected one.

    And if what you want done at the beginning or end is outside the object being worked on, fine. Consider wrapping your function call in a simple function that calls the one you want after ignoring or removing the first argument. There are decorators that
    can do things like that.

    So if you want int() or some existing plain non-member function, define an f() whose body calls int() with all arguments passed along other than the first.

    I just wrote and tested a trivial example where for some reason you just want to call sum() either with an iterable argument or with a second unnamed or named argument that specified a start you can add to. If this is written as a class method, it would
    have a first argument of "self" to ignore so I simulate that here:

    def plusone(first, *rest, **named):
    return(sum(*rest, **named))

    If you call this as below with valid arguments, it sort of swallows the first argument and passes the rest along:

    plusone("ignore", [])
    0
    plusone("ignore", [1,2,3])
    6
    plusone("ignore", [1,2,3], 100)
    106
    plusone("ignore", range(7), start=100)
    121

    Yes, anything like this adds overhead. It does add flexibility and allows you to hijack the WITH protocol to do other things perhaps never anticipated but that may make sense, such as changing a companion object rather than the current one. But you need
    to live within some rules to do things and that means knowing there will be a first argument.

    Avi
    -----Original Message-----
    From: Python-list <python-list-bounces+avi.e.gross=gmail.com@python.org> On Behalf Of Rob Cliffe via Python-list
    Sent: Saturday, April 22, 2023 9:56 AM
    To: Lorenzo Catoni <l.catoni.99@gmail.com>; python-list@python.org
    Subject: Re: Question regarding unexpected behavior in using __enter__ method

    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

    --
    https://mail.python.org/mailman/listinfo/python-list

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Mark Bourne@21:1/5 to Lorenzo Catoni on Wed Apr 26 20:41:50 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.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)