首页 文章

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

提问于
浏览
80

我正在写一个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并检查它是否会引发来实现的,这不是我想要的 .

7 回答

  • 24

    是的,使用 getattr() 获取属性,使用 callable() 验证它是一个方法:

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

    请注意,当属性不存在时, getattr() 通常会抛出异常 . 但是,如果指定默认值(在本例中为 None ),则会返回该值 .

  • 3

    它适用于Python 2和Python 3

    hasattr(connection, 'invert_opt')
    

    如果连接对象具有定义的函数 invert_opthasattr 将返回 True . 这是您放牧的文档

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

  • 0

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

    你为什么反对呢?在大多数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领域没有任何问题!

  • 2

    我喜欢Nathan Ostgard的答案,我对它进行了投票 . 但另一种解决问题的方法是使用memoizing装饰器,它会缓存函数调用的结果 . 因此,您可以继续使用昂贵的功能来解决问题,但是当您一遍又一遍地调用它时,后续调用速度很快;函数的memoized版本在dict中查找参数,在实际函数计算结果时从dict中查找结果,并立即返回结果 .

    这是Raymond Hettinger的一个名为“lru_cache”的memoizing装饰器的配方 . 现在,它的一个版本在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

  • 18

    这里的响应检查字符串是否是对象的属性的名称 . 需要额外的步骤(使用可调用的)来检查属性是否是方法 .

    所以它归结为:检查对象obj是否具有属性属性的最快方法是什么 . 答案是

    'attrib' in obj.__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
    
  • 134

    像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'
    

    请帮我们一个忙,只要继续做你在问题中所做的事情, DON'T 永远使用这个,除非你是在PyPy团队攻击Python解释器 . 你有什么Pythonic,我在这里有纯粹的 EVIL .

  • 3

    虽然检查__dict__属性中的属性非常快,但您不能将其用于方法,因为它们不会出现在__dict__哈希中 . 但是,如果性能至关重要,您可以在课堂上使用hackish workaround:

    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)
    

    并不是说我鼓励这种方法,但似乎有效 .

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

    >>%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)
    

相关问题