Jay
Jay

Reputation: 3515

[Py 2/3 migration]: How to verify class method type in py3?

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

Answers (1)

user2357112
user2357112

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

Related Questions