【问题标题】:What is the fastest way to check if a class has a function defined?检查类是否定义了函数的最快方法是什么?
【发布时间】:2011-07-13 05:01:08
【问题描述】:

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

【问题讨论】:

  • “hasattr 是通过调用 getattr 并检查它是否引发的来实现的,这不是我想要的。” 为什么不呢?你为什么关心实现的功能?
  • has_op = lambda obj, op: callable(getattr(obj, op, None))
  • 试试:hasattr(connection, 'invert_opt').

标签: python


【解决方案1】:

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

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

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

【讨论】:

  • 另请注意,在这种情况下,getattr 的实现会静默捕获异常并返回默认值,就像 hasattr 所做的那样,OP 出于某种原因反对。
  • 如果函数不在该类中,而是在父类中怎么办?在这种情况下,我得到一个 True,即使孩子们从未实现该功能(使用 hasattr)
【解决方案2】:

它适用于 Python 2 和 Python 3

hasattr(connection, 'invert_opt')

hasattr 返回 True 如果连接对象定义了函数 invert_opt。这是你要吃草的文档

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

【讨论】:

  • 虽然代码很受欢迎,但它应该始终有一个附带的解释。这不必很长,但在意料之中。
  • 好,你可以指向一篇文章,虽然它不会伤害:)
  • 如果连接有属性connection.invert_opt = 'foo',这也返回True。
【解决方案3】:

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

你为什么反对?在大多数 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】:

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

    【讨论】:

    • 如果方法引发任何异常,这将是真的。您还应该检查co_names 是否等于('NotImplementedError',)。然而,我不确定这是否使它或多或少变得邪恶。
    【解决方案5】:

    这里的响应检查字符串是否是对象属性的名称。需要一个额外的步骤(使用 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
    

    【讨论】:

    【解决方案6】:

    我喜欢 Nathan Ostgard 的回答,并投了赞成票。但是另一种解决问题的方法是使用记忆装饰器,它会缓存函数调用的结果。因此,您可以继续使用一个昂贵的函数来解决问题,但是当您一遍又一遍地调用它时,后续调用会很快;函数的记忆化版本在字典中查找参数,在实际函数计算结果时在字典中找到结果,并立即返回结果。

    这是 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

    【讨论】:

      【解决方案7】:

      你也可以复习一下:

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

      【讨论】:

        【解决方案8】:

        虽然检查 __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)
        

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

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

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

        【讨论】:

        • __dict__ 可以被覆盖。它不能被信任。
        • @Xiao 几乎所有东西都可以在 Python 中被覆盖。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多