获取定义方法的类

120

如何在Python中获取定义方法的类?

我想让下面的示例打印出 "__main__.FooClass":

class FooClass:
    def foo_method(self):
        print "foo"

class BarClass(FooClass):
    pass

bar = BarClass()
print get_class_that_defined_method(bar.foo_method)

你使用的Python版本是什么? 在2.2之前,你可以使用im_class,但现在已经改为显示绑定self对象的类型。 - Kathy Van Stone
1
好的,知道了。但是我正在使用2.6版本。 - Jesse Aldridge
13个回答

80
import inspect

def get_class_that_defined_method(meth):
    for cls in inspect.getmro(meth.im_class):
        if meth.__name__ in cls.__dict__: 
            return cls
    return None

3
注意,不是所有的类都实现了 __dict__! 有时会使用 __slots__。最好使用 getattr 测试方法是否在类中。 - Codie CodeMonkey
18
请参考这个答案,针对 Python 3 进行翻译:这个答案 - Yoel
38
我得到的错误信息是:"'function' object has no attribute 'im_class'"。 - Zitrax
5
在Python 2.7中它无法工作。同样的错误提示缺少'im_class'。 - RedX
5
对于那些仅使用Python 3的人,使用meth.__qualname__有什么问题吗? - Marc
显示剩余3条评论

12

我不知道为什么没有人提到过这个问题,或者为什么最佳答案得到了50个赞,因为它实在太慢了,但你也可以尝试以下方法:

def get_class_that_defined_method(meth):
    return meth.im_class.__name__

我认为对于Python 3,这个已经改变了,你需要查看.__qualname__


3
哦,我在Python 2.7中没有看到定义类的地方 - 我得到的是调用该方法的类,而不是定义它的那个类... - F1Rumors
所有那些过于复杂的答案都不如这个在Python 3中表现得出色 - 做得好。 - ncaadam
1
meth.__qualname__ 可以获取方法的限定名称,其中包括定义该方法的类的名称。 - Princy

8

在Python 3中,如果需要实际的类对象,可以执行以下操作:

import sys
f = Foo.my_function
vars(sys.modules[f.__module__])[f.__qualname__.split('.')[0]]  # Gets Foo object

如果函数属于嵌套类,您需要按以下方式迭代:

f = Foo.Bar.my_function
vals = vars(sys.modules[f.__module__])
for attr in f.__qualname__.split('.')[:-1]:
    vals = vals[attr]
# vals is now the class Foo.Bar

8

感谢Sr2222指出我的问题...

这里是已更正的方法,与Alex的方法完全一样,但不需要导入任何内容。我认为这并没有改进,除非存在大量继承类的层次结构,因为该方法会在找到定义的类后立即停止,而不像getmro返回整个继承。正如先前所说,这是一个非常不可能的情况。

def get_class_that_defined_method(method):
    method_name = method.__name__
    if method.__self__:    
        classes = [method.__self__.__class__]
    else:
        #unbound method
        classes = [method.im_class]
    while classes:
        c = classes.pop()
        if method_name in c.__dict__:
            return c
        else:
            classes = list(c.__bases__) + classes
    return None

并且这是一个例子:

>>> class A(object):
...     def test(self): pass
>>> class B(A): pass
>>> class C(B): pass
>>> class D(A):
...     def test(self): print 1
>>> class E(D,C): pass

>>> get_class_that_defined_method(A().test)
<class '__main__.A'>
>>> get_class_that_defined_method(A.test)
<class '__main__.A'>
>>> get_class_that_defined_method(B.test)
<class '__main__.A'>
>>> get_class_that_defined_method(C.test)
<class '__main__.A'>
>>> get_class_that_defined_method(D.test)
<class '__main__.D'>
>>> get_class_that_defined_method(E().test)
<class '__main__.D'>
>>> get_class_that_defined_method(E.test)
<class '__main__.D'>
>>> E().test()
1

Alex的解决方案返回相同的结果。只要可以使用Alex的方法,我会使用它而不是这个。


Cls().meth.__self__ 只会给你绑定到特定实例的 methCls 实例。这类似于 Cls().meth.im_class。如果你有 class SCls(Cls)SCls().meth.__self__ 将会得到一个 SCls 实例,而不是一个 Cls 实例。OP 想要的是获取 Cls,似乎只能像 @Alex Martelli 一样遍历 MRO 来实现。 - Silas Ray
@sr2222 你是对的。我已经修改了答案,虽然我认为Alex的解决方案更紧凑。 - estani
1
如果您需要避免导入,那么这是一个不错的解决方案,但由于您基本上只是重新实现了MRO,所以不能保证永远有效。MRO可能会保持不变,但在Python的过去已经更改过一次,如果再次更改,这段代码将导致微妙而普遍的错误。 - Silas Ray
编程中常常会发生一些“非常不可能的情况”,很少情况下会导致灾难。通常,思考模式“XY.Z%永远不会发生”是一种极其糟糕的编码思维工具,不要使用它。撰写100%正确的代码。 - ulidtko
@ulidtko 我认为你误读了解释。这不是关于正确性,而是关于速度。没有一个“完美”的解决方案适用于所有情况,否则就只会有一种排序算法。在这里提出的解决方案应该在“罕见”的情况下更快。由于速度在99%的情况下都比可读性更重要,所以这个解决方案在“仅仅”那个罕见的情况下可能是更好的解决方案。如果你没有看过代码,那么它是100%正确的,如果这是你担心的话。 - estani
警告:im_class是仅适用于Python 2的属性,因此这在Python 3中无法使用未绑定方法(它们只是常规函数)。 - Anakhand

2

我发现在Python3中__qualname__很有用。

我像这样测试它:

class Cls(object):
     def func(self):
             print('1')

c = Cls()
print(c.func.__qualname__)
# output is: 'Cls.func'

def single_func():
     print(2)

print(single_func.__module__)
# output: '__main__'
print(single_func.__qualname__)
# output: 'single_func'

在我的测试中,我发现这里有另一个答案。


1

我尝试了类似的方法来检查基类中的存根方法是否在子类中被实现。无论我如何尝试,都无法检测到中间类是否实际上实现了该方法(下面是d.run_method()的情况)。

最后,我通过设置一个方法属性并在后面测试其存在性来解决了这个问题:

class A():
    def method(self):
        pass
    method._orig = None # This attribute will be gone once the method is implemented

    def run_method(self, *args, **kwargs):
        if hasattr(self.method, '_orig'):
            raise Exception('method not implemented')
        self.method(*args, **kwargs)

class B(A):
    pass

class C(B):
    def method(self):
        pass

class D(C):
    pass

B().run_method() # ==> Raises Exception: method not implemented
C().run_method() # OK
D().run_method() # OK

附注:这并没有直接回答问题...在我看来,人们想要知道哪个类定义了一个方法的主要原因有两个;一个是在调试代码(例如在异常处理中)时指向一个类,另一个是确定方法是否已被重新实现(其中方法是一个桩,旨在由程序员实现)。这个答案用不同的方式解决了第二种情况。


注意:我可能没有仔细查看,或者这可能是Python2的限制,因为我刚刚在此代码上执行了pprint(self.method),它清楚地打印出拥有类,因此数据在某个地方。 - Thomas Guyot-Sionnest

1

Python 3

以下是一种非常简单的解决方法:

str(bar.foo_method).split(" ", 3)[-2]

这将返回

'FooClass.foo_method'

按照句点分割以单独获取类名和函数名。


4
这也可以简化为 bar.foo_method.__qualname__ ,得到 'FooClass.foo_method'。我不知道是否有特殊情况,但它对于当前的问题有效。 - FMc

1

1

inspect._findclass 对于任何函数/方法似乎都工作正常。

import inspect
import sys


class SomeClass:
    @staticmethod
    def staticMethod():
        print('staticMethod')

    @classmethod
    def classMethod(cls):
        print('classMethod')

    def someMethod(self):
        print('bound method')

def myGlblFunc():
    print('Global function')


if __name__ == '__main__':
    static_method = SomeClass.staticMethod
    class_method = SomeClass.classMethod
    unbound_method = SomeClass.someMethod
    bound_method = SomeClass().someMethod
    glbl_func = myGlblFunc

    static_method()
    print(inspect._findclass(static_method), end='\n\n')

    class_method()
    print(inspect._findclass(class_method), end='\n\n')

    print('unbound method')
    print(inspect._findclass(unbound_method), end='\n\n')

    bound_method()
    print(inspect._findclass(bound_method), end='\n\n')

    glbl_func()
    print(inspect._findclass(glbl_func), end='\n\n')

    sys.exit(0)

# Output:
    # staticMethod
    # <class '__main__.SomeClass'>
    #
    # classMethod
    # <class '__main__.SomeClass'>
    #
    # unbound method
    # <class '__main__.SomeClass'>
    #
    # bound method
    # <class '__main__.SomeClass'>
    #
    # Global function
    # None

0

如果您遇到此错误:

'function' object has no attribute 'im_class'

尝试这个:
import inspect

def get_class_that_defined_method(meth):
    class_func_defided = meth.__globals__[meth.__qualname__.split('.')[0]]
    #full_func_name = "%s.%s.%s"%(class_func_defided.__module__,class_func_defided.__name__,meth.__name__)
    
    if inspect.isfunction(class_func_defided):
        print("%s is not part of a class."%meth.__name__)
        return None
    return class_func_defided

示例测试:

class ExampleClass:
    @staticmethod
    def ex_static_method():
        print("hello from static method")
    
    def ex_instance_method(self):
        print("hello from instance method")

def ex_funct(self):
    print("hello from simple function")
    
if __name__ == "__main__":
    static_method_class = get_class_that_defined_method(ExampleClass.ex_static_method)
    static_method_class.ex_static_method()
    
    instance_method_class = get_class_that_defined_method(ExampleClass.ex_instance_method)
    instance_method_class().ex_instance_method()
    
    function_class = get_class_that_defined_method(ex_funct)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接