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 On
Behalf Of Rob Cliffe via Python-list
Sent: Saturday, April 22, 2023 9:56 AM
To: Lorenzo Catoni ; 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 normal