Python - 如何查询定义了一个方法的类?

3
我的问题有些类似于 这个问题,但是与模块内容不同,它涉及对象方法。我想知道是否可以使用 inspect 模块仅获取在我所询问的类中定义的方法,而不是其父类中的方法。
我需要这个功能,因为我的子类定义了“宏”方法,该方法在更高层次上访问父类的方法,我不希望用户担心从继承树向上定义的所有低级别方法。
下面是一个简化的示例:
class Foo(object):
    def __init__(self): pass
    def f1(self): return 3
    def f2(self): return 1

class Bar(Foo):
    def __init__(self): Foo.__init__(self)
    def g1(self): return self.f1() + self.f2()
    def g2(self): return self.f1() - self.f2()

import inspect
inspect.getmembers(Bar, inspect.ismethod)

输出:

[('__init__', <unbound method Bar.__init__>), ('f1', <unbound method Bar.f1>), ('f2', <unbound method Bar.f2>), ('g1', <unbound method Bar.g1>), ('g2', <unbound method Bar.g2>)]

用户无需知道或关心 f 的存在,因为她只对 g 感兴趣。 (当然,在实例化对象时,所有这些方法都将绑定到对象,因此在绝大多数情况下,这个输出是有意义的。)对于长的继承树,返回的列表可能会变得非常长,并且充满了用户不关心的内容。
如何让列表中去掉 f1 和 f2?是否有类似于 __module__ 属性的东西适用于类中定义的方法?更好的方法是能否对实例方法执行相同的操作?

在一个实例 Bar 上绑定的方法中询问 im_class 是不起作用的,未绑定的方法也是如此。 - Benjamin Hodgson
1
object.hasOwnPro… 等等,这不对。 (抱歉,我只是无法相信 JavaScript 比 Python 更容易做某些事情。) - kojiro
术语“im_class”似乎是Python 2属性,在Python 3中不再存在。如果您继续阅读,最终会理解它的含义,但为了让您快速入门,请查看此答案https://stackoverflow.com/a/59548793。请注意,Python 3方法不仅仅是具有附加第一个参数的函数,它们还具有由于绑定到类或实例而产生的属性,例如“__self__”。 - NeilG
2个回答

5

方法有一个 im_class 属性,指向相关的类。您可以在属于该类成员函数上使用该过滤器:

inspect.getmembers(Bar,
    lambda m: inspect.ismethod(m) and m.__func__ in m.im_class.__dict__.values())

这将为您提供:
[
    ('__init__', <unbound method Bar.__init__>),
    ('f1', <unbound method Bar.f1>), 
    ('f2', <unbound method Bar.f2>)
]

当然,您也可以完全绕过getmembers
[m for m in Bar.__dict__.values() if inspect.isfunction(m)]

提供:

[<function __init__ at 0x100a28de8>, <function g1 at 0x100a28e60>, <function g2 at 0x100a28ed8>]

这适用于绑定或未绑定的方法,它们都有相同的.__func__(或im_func,旧名称)属性。绑定和未绑定之间的区别在于.__self__属性的值(未绑定时为None)。
这些“秘密”属性在Python Data Model中都有记录。

1
哇...这似乎有点像黑魔法。你是在哪里学到这些东西的?(+1) - mgilson
1
@poorsod:__dict__是一个类的基本命名空间;dir()inspect.getmembers()都会使用它以及其他信息来构建更大的结构(包括递归下去.__bases__类层次结构)。 - Martijn Pieters
@poorsod:方法是绑定函数;__dict__保存绑定之前的函数。实例的__dict__包含实例值,但从它的类继承方法(您可以将方法添加到一个实例中,与其类无关)。 - Martijn Pieters
@poorsod:绑定方法是带有类指针和可选实例指针(im_class__self__)的函数。函数是描述符,请在那里了解整个过程。 :-) - Martijn Pieters
感谢@MartijnPieters,我认为我没有时间去理解Python描述符来完成这项工作,并且正如SO所指出的那样,我认为这个讨论已经超出了这个媒介,所以我不会再发表评论。我非常感激你的确切澄清。谢谢。附:https://link.springer.com/book/10.1007/978-1-4842-3727-4 - NeilG
显示剩余5条评论

2

希望有人能提供更好的解决方案,但是以下方法可行:

foo_members = inspect.getmembers(Foo,inspect.ismethod)
bar_members = inspect.getmembers(Bar,inspect.ismethod)

set(bar_members) - set(foo_members)

当然,在实际情况下,您可能需要遍历Bar.__bases__来真正摆脱您不想要的一切。
例如:
set(bar_members) - set(sum([inspect.getmembers(base,inspect.ismethod) for base in Bar.__bases__],[]))

或者更为通用的解决方案是使用 Bar.bases 而不是硬编码 Foo。 - Alfe
@Alfe -- 将__mro__更改为__bases__并添加了一个示例。 - mgilson

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