检查类是否已定义函数的最快方法是什么?


132

我正在编写AI状态空间搜索算法,并且有一个通用类可以用于快速实现搜索算法。子类将定义必要的操作,然后算法执行其余操作。

这是我遇到的问题:我想避免一遍又一遍地重新生成父状态,所以我有以下函数,该函数返回可以合法地应用于任何状态的操作:

def get_operations(self, include_parent=True):
    ops = self._get_operations()
    if not include_parent and self.path.parent_op:
        try:
            parent_inverse = self.invert_op(self.path.parent_op)
            ops.remove(parent_inverse)
        except NotImplementedError:
            pass
    return ops

并且invert_op函数默认情况下抛出。

有没有比捕获异常更快的方法来检查函数是否未定义?

我在检查dir中是否存在内容时正在思考,但这似乎不正确。hasattr是通过调用getattr并检查它是否引发来实现的,这不是我想要的。


8
“ hasattr是通过调用getattr并检查它是否引发来实现的,这不是我想要的。” 为什么不?您为什么要关心实现的作用?
2011年

4
has_op = lambda obj, op: callable(getattr(obj, op, None))
Samplebias 2011年

1
试试:hasattr(connection, 'invert_opt')
kenorb

Answers:


204

是的,用于getattr()获取属性并callable()验证它是否为方法:

invert_op = getattr(self, "invert_op", None)
if callable(invert_op):
    invert_op(self.path.parent_op)

请注意,getattr()当属性不存在时,通常会引发异常。但是,如果您指定默认值(None在本例中为),它将返回该值。


3
还要注意,getattr在这种情况下,的实现会以静默方式捕获异常,并返回默认值,就像hasattrOP出于某种原因所针对的那样。
圣诞老人

3
如果该函数不在该类中,但在父类中,该怎么办?在这种情况下,即使孩子们从未实现该功能(使用hasattr),我也得到了True
darkgaze

46

它同时适用于Python 2和Python 3

hasattr(connection, 'invert_opt')

hasattrTrue如果连接对象已invert_opt定义函数,则返回。这是供您放牧的文档

https://docs.python.org/2/library/functions.html#hasattr https://docs.python.org/3/library/functions.html#hasattr


5
尽管该代码是值得赞赏的,但应始终附有解释。这不必很长,但是可以预期。
peterh-恢复莫妮卡2015年

好人,您可以指向一篇文章,尽管它不会造成伤害:)
Vitaliy Terziev

5
如果connection具有attribute,则它也返回True connection.invert_opt = 'foo'
罗伯特·霍尼格(RobertHönig),

20

有没有比捕获异常更快的方法来检查函数是否未定义?

你为什么反对那个?在大多数Pythonic情况下,最好是请求宽恕而不是允许。;-)

hasattr是通过调用getattr并检查它是否引发来实现的,这不是我想要的。

同样,为什么呢?以下是相当Pythonic的内容:

    try:
        invert_op = self.invert_op
    except AttributeError:
        pass
    else:
        parent_inverse = invert_op(self.path.parent_op)
        ops.remove(parent_inverse)

要么,

    # if you supply the optional `default` parameter, no exception is thrown
    invert_op = getattr(self, 'invert_op', None)  
    if invert_op is not None:
        parent_inverse = invert_op(self.path.parent_op)
        ops.remove(parent_inverse)

但是请注意,这getattr(obj, attr, default)基本上也是通过捕获异常来实现的。Python领域没有错!


4

这里的响应检查字符串是否是对象的属性的名称。需要一个额外的步骤(使用callable)来检查属性是否为方法。

因此,可以归结为:检查对象obj是否具有属性attrib的最快方法是什么。答案是

'attrib' in obj.__dict__

之所以如此,是因为dict对其键进行了哈希处理,因此可以快速检查键的存在。

请参见下面的时序比较。

>>> class SomeClass():
...         pass
...
>>> obj = SomeClass()
>>>
>>> getattr(obj, "invert_op", None)
>>>
>>> %timeit getattr(obj, "invert_op", None)
1000000 loops, best of 3: 723 ns per loop
>>> %timeit hasattr(obj, "invert_op")
The slowest run took 4.60 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 3: 674 ns per loop
>>> %timeit "invert_op" in obj.__dict__
The slowest run took 12.19 times longer than the fastest. This could mean that an intermediate result is being cached.
10000000 loops, best of 3: 176 ns per loop

这在使用的类上失败__slots____slots__有助于将属性访问速度提高约10%。stackoverflow.com/a/14119024/1459669
noɥʇʎԀʎzɐɹƆ

3

我喜欢内森·奥斯特加德的回答,并对此进行了投票。但是解决问题的另一种方法是使用记忆修饰符,该修饰符将缓存函数调用的结果。因此,您可以继续使用具有昂贵功能的功能来解决某些问题,但是当您一遍又一遍地调用它时,后续调用很快。函数的记忆版本会在字典中查找参数,然后从实际函数计算结果时开始在字典中查找结果,然后立即返回结果。

这是雷蒙德·海廷格(Raymond Hettinger)称为“ lru_cache”的记忆修饰器的食谱。现在,此版本是Python 3.2中functools模块的标准版本。

http://code.activestate.com/recipes/498245-lru-and-lfu-cache-decorators/

http://docs.python.org/release/3.2/library/functools.html


2

像Python中的任何东西一样,如果您尽力而为,那么您就可以直截了当地去做一些令人讨厌的事情。现在,这是令人讨厌的部分:

def invert_op(self, op):
    raise NotImplementedError

def is_invert_op_implemented(self):
    # Only works in CPython 2.x of course
    return self.invert_op.__code__.co_code == 't\x00\x00\x82\x01\x00d\x00\x00S'

请帮我们一个忙,只要继续解决您的问题,就不要使用它,除非您是PyPy团队的黑客,他们正在侵入Python解释器。您所拥有的是Pythonic,我在这里拥有的是纯EVIL


如果该方法引发任何异常,则为true。您还应该检查是否co_names等于('NotImplementedError',)。我不确定这是否会或多或少地带来邪恶。
kindall 2011年

1

您也可以遍历课程:

import inspect


def get_methods(cls_):
    methods = inspect.getmembers(cls_, inspect.isfunction)
    return dict(methods)

# Example
class A(object):
    pass

class B(object):
    def foo():
        print('B')


# If you only have an object, you can use `cls_ = obj.__class__`
if 'foo' in get_methods(A):
    print('A has foo')

if 'foo' in get_methods(B):
    print('B has foo')

0

虽然在__dict__属性中检查属性确实非常快,但是您不能将其用于方法,因为它们不会出现在__dict__哈希中。但是,如果性能至关重要,则可以在课堂上采用棘手的解决方法:

class Test():
    def __init__():
        # redefine your method as attribute
        self.custom_method = self.custom_method

    def custom_method(self):
        pass

然后检查方法为:

t = Test()
'custom_method' in t.__dict__

时间比较getattr

>>%timeit 'custom_method' in t.__dict__
55.9 ns ± 0.626 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

>>%timeit getattr(t, 'custom_method', None)
116 ns ± 0.765 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

我并不是在鼓励这种方法,但是它似乎有效。

[EDIT]当方法名称不在给定的类中时,性能提升甚至更高:

>>%timeit 'rubbish' in t.__dict__
65.5 ns ± 11 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

>>%timeit getattr(t, 'rubbish', None)
385 ns ± 12.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

1
__dict__可能会被覆盖。无法信任。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.