classification
Title: __class__ not exists in the method which bounded by types.MethodType.
Type: behavior Stage: resolved
Components: Library (Lib) Versions: Python 3.6, Python 3.5
process
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: Nosy List: lanfon72, ncoghlan, xiang.zhang
Priority: normal Keywords:

Created on 2016-12-30 05:53 by lanfon72, last changed 2016-12-30 16:38 by ncoghlan. This issue is now closed.

Messages (5)
msg284313 - (view) Author: Lanfon (lanfon72) * Date: 2016-12-30 05:53
test code below:

```python

>>> from types import MethodType
>>> class A:
...   def f(self):
...     print(__class__)
...
>>> a = A()
>>> a.fn = MethodType(lambda s: print(__class__), a)
>>>
>>> a.f()
<class '__main__.A'>
>>> a.fn()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <lambda>
NameError: name '__class__' is not defined
```

this behavior affect `super()` not work in patched function scope, 
of course we can use old super as super(self.__class__, self) to make it work as expected, but I think it should work as original method if we already bounded the function to instance(class).
msg284321 - (view) Author: Xiang Zhang (xiang.zhang) * (Python committer) Date: 2016-12-30 07:12
You can't do it like this. The document explicitly states it only works inside a class definition:

"Also note that, aside from the zero argument form, super() is not limited to use inside methods. The two argument form specifies the arguments exactly and makes the appropriate references. The zero argument form only works inside a class definition, as the compiler fills in the necessary details to correctly retrieve the class being defined, as well as accessing the current instance for ordinary methods."

So if treat it as an enhancement, is it reasonable Nick?
msg284330 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2016-12-30 14:01
Right, this isn't actually a bug, it just requires a bit more creativity to bind and resolve `__class__` correctly when defining the replacement method.

Here's what happens normally with zero-argument super():

>>> class A:
...     def f(self):
...         print(__class__)
... 
>>> A().f()
<class '__main__.A'>
>>> A.f.__closure__
(<cell at 0x7f0ea25f1f48: type object at 0x55ea30974318>,)
>>> inspect.getclosurevars(A.f)
ClosureVars(nonlocals={'__class__': <class '__main__.A'>}, globals={}, builtins={'print': <built-in function print>}, unbound=set())

And here's one way of emulating the __class__ resolution part:

>>> def make_method():
...     __class__ = A
...     def fn(self):
...         print(__class__)
...     return fn
... 
>>> A.fn = make_method()
>>> A().fn()
<class '__main__.A'>

And applying that to get zero-argument super() working in a substitute method gives:

>>> class B(A):
...     def f(self):
...         super().f()
... 
>>> B().f() # Normal super()
<class '__main__.A'>
>>> def make_B_method():
...     __class__ = B
...     def fn(self):
...         return super().fn()
...     return fn
... 
>>> B.fn = make_B_method()
>>> B().fn()  # Emulated super()
<class '__main__.A'>
msg284334 - (view) Author: Lanfon (lanfon72) * Date: 2016-12-30 16:15
thanks xiang, Nick,

I might misunderstood MethodType to bound method will work as class definition.

It might not good to enhance MethodType to work as class definition, same problem on patching method to a class as Nick explained.
msg284336 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2016-12-30 16:38
Sorry, I wasn't at all clear about that part - the above techniques should work with explicitly created types.MethodType instances as well.

That is, this should work the same as in my earlier examples (although I don't have that interpreter session around to test it any more):

    >>> b = B()
    >>> b.fn = types.MethodType(make_B_method(), b)
    >>> b.fn()  # Emulated super()
    <class '__main__.A'>

If you were to build on top of what I wrote above (i.e. setting "B.fn = make_B_method()"), that explicit bound method creation would be akin to doing:

    >>> b = B()
    >>> b.fn = b.fn # Cache the bound method on the instance
History
Date User Action Args
2016-12-30 16:38:21ncoghlansetmessages: + msg284336
2016-12-30 16:15:17lanfon72setmessages: + msg284334
2016-12-30 14:01:34ncoghlansetstatus: open -> closed
resolution: not a bug
messages: + msg284330

stage: resolved
2016-12-30 07:12:01xiang.zhangsetnosy: + xiang.zhang, ncoghlan
messages: + msg284321
2016-12-30 05:53:38lanfon72create