【问题标题】:Python static method is not always callablePython静态方法并不总是可调用的
【发布时间】:2018-01-04 16:02:02
【问题描述】:

使用__dict__ 解析属性时,我的@staticmethod 不是callable

Python 2.7.5 (default, Aug 29 2016, 10:12:21)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-4)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from __future__ import (absolute_import, division, print_function)
>>> class C(object):
...   @staticmethod
...   def foo():
...     for name, val in C.__dict__.items():
...       if name[:2] != '__':
...          print(name, callable(val), type(val))
...
>>> C.foo()
foo  False  <type 'staticmethod'>
  • 这怎么可能?
  • 如何检查静态方法是否可调用?

我在下面提供了一个更详细的例子:

脚本test.py

from __future__ import (absolute_import, division, print_function)

class C(object):

  @staticmethod
  def foo():
    return 42

  def bar(self):
    print('Is bar() callable?', callable(C.bar))
    print('Is foo() callable?', callable(C.foo))
    for attribute, value in C.__dict__.items():
      if attribute[:2] != '__':
        print(attribute, '\t', callable(value), '\t', type(value))

c = C()
c.bar()

python2 的结果

> python2.7 test.py
Is bar() callable? True
Is foo() callable? True
bar      True    <type 'function'>
foo      False   <type 'staticmethod'>

python3 的结果相同

> python3.4 test.py
Is bar() callable? True
Is foo() callable? True
bar      True    <class 'function'>
foo      False   <class 'staticmethod'>

【问题讨论】:

    标签: python python-2.7 python-3.x static-methods callable


    【解决方案1】:

    这种行为的原因是描述符协议。 C.foo 不会返回 staticmethod 而是一个普通函数,而 __dict__ 中的 'foo'staticmethod(而 staticmethod 是描述符)。

    简而言之,C.foo 在这种情况下与 C.__dict__['foo'] 不同 - 而是 C.__dict__['foo'].__get__(C)(另请参阅 Data model on descriptors 文档中的部分):

    >>> callable(C.__dict__['foo'].__get__(C))
    True
    >>> type(C.__dict__['foo'].__get__(C))
    function
    
    >>> callable(C.foo)
    True
    >>> type(C.foo)
    function
    
    >>> C.foo is C.__dict__['foo'].__get__(C)
    True
    

    在您的情况下,我将使用 getattr(知道描述符以及如何访问它们)而不是作为值存储在类 __dict__ 中的内容来检查可调用对象:

    def bar(self):
        print('Is bar() callable?', callable(C.bar))
        print('Is foo() callable?', callable(C.foo))
        for attribute in C.__dict__.keys():
            if attribute[:2] != '__':
                value = getattr(C, attribute)
                print(attribute, '\t', callable(value), '\t', type(value))
    

    哪些打印(在 python-3.x 上):

    Is bar() callable? True
    Is foo() callable? True
    bar      True    <class 'function'>
    foo      True    <class 'function'>
    

    python-2.x 上的类型不同,但callable 的结果是一样的:

    Is bar() callable? True
    Is foo() callable? True
    bar      True    <type 'instancemethod'>
    foo      True    <type 'function'>
    

    【讨论】:

    • 感谢您的解释。为了知道foocallable,我应该在我的第一个sn-p 中更改什么?我应该使用print(name, callable(val.__get__(C))) 吗?或者我应该使用if type(name) == types.StaticmethodType 之类的东西吗?请提出修复建议;-) 干杯
    • 啊,我知道我忘记了什么。我更新了答案:)
    • ? 是的 getattr(C, attribute) 完成了这项工作,我看到您的 type(value) 现在在我的代码中返回 &lt;type 'instancemethod'&gt; 而不是 &lt;type 'staticmethod'&gt;
    【解决方案2】:

    您无法检查 staticmethod 对象是否可调用。这在Issue 20309 -- Not all method descriptors are callable 的跟踪器上进行了讨论,并以“不是错误”的形式关闭。

    简而言之,没有理由为 staticmethod 对象实现 __call__。内置的callable 无法知道staticmethod 对象本质上是“持有”一个可调用对象。

    虽然您可以实现它(对于staticmethods 和classmethods),但如前所述,这将是一个维护负担,没有真正的激励用例。


    对于您的情况,您可以使用getattr(C, name) 来查找名为name 的对象;这相当于执行C.&lt;name&gt;getattr,在找到 staticmethod 对象后,将调用它的__get__ 来取回它正在管理的可调用对象。然后你可以使用callable

    可以在文档中找到关于描述符的不错入门,请查看 Descriptor HOWTO

    【讨论】:

    • 感谢您出色的理性。我要求与 MSeifert 相同:为了知道 foocallable,我应该在我的第一个 sn-p 中更改什么?我应该使用print(name, callable(val.__get__(C))) 吗?或者我应该使用if type(name) == types.StaticmethodType 之类的东西吗?请提出修复建议;-) 干杯
    • @olibre callable(getattr(C, name)) 是一种选择,它总是比抓垃圾更好 (__get__)
    • ? 我刚刚成功测试了getattr(C, attribute)。您可以在答案中写下您的评论。我看到type(getattr(C, attribute)) 在我的代码中返回&lt;type 'instancemethod'&gt; 而不是&lt;type 'staticmethod'&gt;。你也可以在你的答案中解释它。干杯
    • getattr 绝对是无可争议的正确选择。整个问题是obj.attr 不仅仅是obj.__dict__['attr'],只是盲目地尝试做obj.__class__.__dict__['attr'].__get__(obj.__class__) 也是不对的,因为它忽略了实例属性、mro 和非描述符属性。 getattr 知道所有的复杂性并做正确的事。
    • @olibre 很好。这两个对象(取决于 Python 版本)都是可调用的。使用getattr,您调用了描述符协议(调用staticmethod_obj.__get__)并取回了它包装的可调用对象。请查看descriptors 上的教程以获得很好的概述。
    猜你喜欢
    • 1970-01-01
    • 2016-11-16
    • 2018-05-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-27
    • 2014-09-08
    • 1970-01-01
    相关资源
    最近更新 更多