Reputation: 3515
During the py2/3 migration process, our test harness spotted the following issue:
import types;
class Test(object):
def foo(self):
class InnerClass(object):
def innerFn():
pass
innerInst = InnerClass()
instanceRef = isinstance(innerInst.innerFn, types.MethodType)
classRef = isinstance(InnerClass.innerFn, types.MethodType)
print(type(innerInst.innerFn)) # py3: <class 'method'>
print(type(InnerClass.innerFn) # py3: <class 'function'>
# in py2: both are <type 'instancemethod'>
assert(instanceRef) # succeeds in py2 and py3
assert(classRef) # fails in py3 but succeeds in py2
What is the recommended way to identify both class and instance method types in py3? The original source code has the following check that is causing the issue:
target = getTarget() # target can be either class method or instance method ref
if isinstance(target, types.MethodType):
do_something
Links to REPL:
Upvotes: 1
Views: 107
Reputation: 281476
InnerClass.innerFn
was never a class method. It was an unbound method object. Unbound method objects no longer exist in Python 3; InnerClass.innerFn
now resolves to an ordinary function object.
Depending on why you were performing this check, you may not want to treat InnerClass.innerFn
as a method at all. If you do, you can get something similar but not equivalent to the old check by checking the function's __qualname__
:
def check(obj):
parts = obj.__qualname__.split('.')
return len(parts) > 1 and not parts[-2].startswith('<')
A function's __qualname__
has at least one .
in it if it was defined inside a class or another function scope. If it was defined in a function scope, the second-to-last component of the __qualname__
is usually <locals>
, but it can also be a few other things like <listcomp>
or <genexp>
in the function scope created by a list comprehension, generator expression, or other similar contexts. I'm pretty sure all the function scope cases start with <
, though, and the class scope cases never do.
This isn't equivalent to the old check if a function was defined outside the class and then set as a class attribute, or in other cases where the __qualname__
metadata doesn't line up with whether the function was accessed as a class attribute, and I haven't bothered to handle non-function input types.
Upvotes: 5