查找定义方法的类

时间:2014-09-09 15:09:48

标签: python

我想从方法本身中找出定义某个方法的类的类型(实质上是方法的封闭静态范围),而不是明确指定它,例如。

class SomeClass:
    def do_it(self):
        cls = enclosing_class() # <-- I need this.
        print(cls)

class DerivedClass(SomeClass):
    pass

obj = DerivedClass()
# I want this to print 'SomeClass'.
obj.do_it()

这可能吗?

6 个答案:

答案 0 :(得分:5)

如果你需要在Python 3.x中使用它,请参阅我的另一个答案 - 闭包单元__class__就是你所需要的。


如果您需要在CPython 2.6-2.7中执行此操作,RickyA的答案很接近,但它不起作用,因为它依赖于这个方法不会覆盖任何其他方法的事实。一样的名字。尝试在他的回答中添加Foo.do_it方法,它会打印出Foo,而不是SomeClass

解决这个问题的方法是找到代码对象与当前框架代码对象相同的方法:

def do_it(self):
    mro = inspect.getmro(self.__class__)
    method_code = inspect.currentframe().f_code
    method_name = method_code.co_name
    for base in reversed(mro):
        try:
            if getattr(base, method_name).func_code is method_code:
                print(base.__name__)
                break
        except AttributeError:
            pass

(请注意,AttributeError可以由base提出,而不是名为do_it的内容,或base提出的名为do_it的内容不是{ 39; ta函数,因此没有func_code。但我们并不关心哪个;无论哪种方式,base都不是我们正在寻找的匹配。 )

可能在其他Python 2.6+实现中工作。 Python不需要存在框架对象,如果不存在,inspect.currentframe()将返回None。而且我很确定它也不需要代码对象存在,这意味着func_code可能是None

同时,如果您想在2.7+和3.0+中使用此功能,请将func_code更改为__code__,但这会破坏与早期2.x的兼容性。


如果您需要CPython 2.5或更早版本,您只需将inpsect调用替换为特定于实现的CPython属性:

def do_it(self):
    mro = self.__class__.mro()
    method_code = sys._getframe().f_code
    method_name = method_code.co_name
    for base in reversed(mro):
        try:
            if getattr(base, method_name).func_code is method_code:
                print(base.__name__)
                break
        except AttributeError:
            pass

请注意,mro()的使用不适用于经典类;如果你真的想要处理那些(你真的不应该......),那么你必须编写自己的mro函数,这个函数只是走在老派的层次结构......或者只是复制它来自2.6 inspect来源。

这只适用于Python 2.x实现,它们向后弯曲以与CPython兼容......但至少包括PyPy。 inspect应该更具可移植性,但是如果一个实现要定义具有与CPython相同属性的framecode个对象,那么它可以支持所有{{1}没有很好的理由不让它们成为属性并首先提供inspect ...

答案 1 :(得分:3)

首先,这几乎肯定是一个坏主意,而不是你想要解决的任何问题,而是拒绝告诉我们......

话虽如此,有一种非常简单的方法,至少在Python 3.0+中。 (如果你需要2.x,请参阅我的其他答案。)

请注意,Python 3.x的super几乎必须以某种方式执行此操作。 super()还有super(THISCLASS, self)还有什么意思THISCLASSsuper正是您所要求的?*

现在,有很多方法__class__ 可以实施......但是PEP 3135详细说明了如何实现它:

  

每个函数都有一个名为__class__的单元格,其中包含定义函数的类对象。

这不是Python参考文档的一部分,因此其他一些Python 3.x实现可以采用不同的方式...但至少从3.2+开始,它们仍然需要super()函数,因为Creating the class object明确地说:

  

此类对象将由__class__的零参数形式引用。 __class__是编译器创建的隐式闭包引用,如果类主体中的任何方法引用supersuper()。这允许super的零参数形式正确地识别基于词法作用域定义的类,而用于进行当前调用的类或实例是基于传递给方法的第一个参数来识别的。 / p>

(不用说,这至少是CPython 3.0-3.5和PyPy3 2.0-2.1至少如何实现In [1]: class C: ...: def f(self): ...: print(__class__) In [2]: class D(C): ...: pass In [3]: D().f() <class '__main__.C'> 。)

__class__.__name__

当然这会得到实际的类对象,而不是类的名称,这显然是你所追求的。但这很容易;你只需要决定你的意思是__class__.__qualname__还是super(在这个简单的例子中,它们是相同的)并打印出来。


*实际上,这是反对它的论据之一:在不改变语言语法的情况下这样做的唯一可行方法是为每个函数添加一个新的闭包单元格,或者需要一些可怕的框架黑客攻击这在Python的其他实现中甚至可能都不可行。你不能只使用编译魔术,因为编译器无法判断某些任意表达式会在运行时评估{{1}}函数...

答案 2 :(得分:1)

如果您可以使用@ abarnert的方法,请执行此操作。

否则,您可以使用一些硬核内省(对于python2.7):

import inspect
from http://stackoverflow.com/a/22898743/2096752 import getMethodClass

def enclosing_class():
    frame = inspect.currentframe().f_back
    caller_self = frame.f_locals['self']
    caller_method_name = frame.f_code.co_name
    return getMethodClass(caller_self.__class__, caller_method_name)

class SomeClass:
    def do_it(self):
        print(enclosing_class())

class DerivedClass(SomeClass):
    pass

DerivedClass().do_it() # prints 'SomeClass'

显然,如果出现以下情况,可能会引发错误:

  • 从常规函数/ staticmethod / classmethod
  • 调用
  • 调用函数对self有一个不同的名称(正如@abarnert恰当指出的那样,这可以通过使用frame.f_code.co_varnames[0]来解决)

答案 3 :(得分:0)

你可以做@mgilson建议或采取其他方法。

class SomeClass:
    pass

class DerivedClass(SomeClass):
    pass

这使SomeClass成为DerivedClass的基类 当您通常尝试获取__class__.name__时,它将引用派生类而不是父类。

当您致电do_it()时,它确实将DerivedClass作为自我传递,这就是您最有可能打印DerivedClass的原因。

相反,试试这个:

class SomeClass:
    pass

class DerivedClass(SomeClass):
    def do_it(self):
        for base in self.__class__.__bases__:
            print base.__name__
obj = DerivedClass()
obj.do_it() # Prints SomeClass

编辑:
在再次阅读你的问题之后,我想我明白了你的想法。

class SomeClass:
    def do_it(self):
        cls = self.__class__.__bases__[0].__name__
        print cls

class DerivedClass(SomeClass):
    pass

obj = DerivedClass()
obj.do_it() # prints SomeClass

答案 4 :(得分:0)

将帖子 一个更通用的解决方案:

import inspect

class Foo:
    pass

class SomeClass(Foo):
    def do_it(self):
        mro = inspect.getmro(self.__class__)
        method_name = inspect.currentframe().f_code.co_name
        for base in reversed(mro):
            if hasattr(base, method_name):
                print(base.__name__)
                break

class DerivedClass(SomeClass):
    pass

class DerivedClass2(DerivedClass):
    pass

DerivedClass().do_it()
>> 'SomeClass'

DerivedClass2().do_it()
>> 'SomeClass'

SomeClass().do_it()
>> 'SomeClass'

当堆栈中的其他类具有属性“do_it”时,这会失败,因为这是停止行走mro的信号名称。

答案 5 :(得分:0)

很抱歉还在写另一个答案,但是这里是如何做你真正想要做的事情,而不是你要求的:

  

这是关于向代码库添加检测以便能够生成方法调用计数的报告,以便检查某些近似运行时不变量(例如&#34;方法ClassA.x()的次数执行大约等于在复杂程序运行过程中执行方法ClassB.y()的次数。

这样做的方法是让你的仪器功能静态地注入信息。毕竟,它必须知道它注入代码的类和方法。

  

我必须亲自操作许多课程,并且为了防止错误,我想避免在任何地方输入类名。从本质上讲,输入super()比输入super(ClassX,self)更可取的原因相同。

如果您的仪器功能是&#34;手动执行&#34;,您首先要将其转换为实际功能而不是手动执行。因为你显然只需要静态注入,使用装饰器,无论是在类上(如果你想要检测每个方法),还是在每个方法上(如果你不这样做)都会使它变得美观和可读。 (或者,如果你想要检测每个类的每个方法,你可能想要定义一个元类并让你的根类使用它,而不是装饰每个类。)

例如,这是一种简单的方法来检测一个类的每个方法:

import collections
import functools
import inspect

_calls = {}
def inject(cls):
    cls._calls = collections.Counter()
    _calls[cls.__name__] = cls._calls
    for name, method in cls.__dict__.items():
        if inspect.isfunction(method):
            @functools.wraps(method)
            def wrapper(*args, **kwargs):
                cls._calls[name] += 1
                return method(*args, **kwargs)
            setattr(cls, name, wrapper)
    return cls

@inject
class A(object):
    def f(self):
        print('A.f here')

@inject
class B(A):
    def f(self):
        print('B.f here')

@inject
class C(B):
    pass

@inject
class D(C):
    def f(self):
        print('D.f here')

d = D()
d.f()
B.f(d)

print(_calls)

输出:

{'A': Counter(), 
 'C': Counter(), 
 'B': Counter({'f': 1}), 
 'D': Counter({'f': 1})}

你想要什么,对吗?