204

当我尝试从类的主体中使用静态方法,并使用内置staticmethod函数作为装饰器定义静态方法时,如下所示:

class Klass(object):

    @staticmethod  # use as decorator
    def _stat_func():
        return 42

    _ANS = _stat_func()  # call the staticmethod

    def method(self):
        ret = Klass._stat_func() + Klass._ANS
        return ret

我收到以下错误:

Traceback (most recent call last):
  File "call_staticmethod.py", line 1, in <module>
    class Klass(object): 
  File "call_staticmethod.py", line 7, in Klass
    _ANS = _stat_func() 
  TypeError: 'staticmethod' object is not callable

我理解为什么会发生这种情况(描述符绑定),并且可以通过_stat_func()在上次使用后手动转换为静态方法来解决它,如下所示:

class Klass(object):

    def _stat_func():
        return 42

    _ANS = _stat_func()  # use the non-staticmethod version

    _stat_func = staticmethod(_stat_func)  # convert function to a static method

    def method(self):
        ret = Klass._stat_func() + Klass._ANS
        return ret

所以我的问题是:

    是否有更清洁或更“Pythonic”的方法来完成此任务?

4

6 回答 6

226

staticmethod对象显然具有__func__存储原始原始函数的属性(它们必须这样做是有道理的)。所以这将起作用:

class Klass(object):

    @staticmethod  # use as decorator
    def stat_func():
        return 42

    _ANS = stat_func.__func__()  # call the staticmethod

    def method(self):
        ret = Klass.stat_func()
        return ret

顺便说一句,虽然我怀疑 staticmethod 对象具有某种存储原始函数的属性,但我不知道具体情况。本着教别人钓鱼而不是给他们一条鱼的精神,这就是我所做的调查并找出答案(我的 Python 会话中的 C&P):

>>> class Foo(object):
...     @staticmethod
...     def foo():
...         return 3
...     global z
...     z = foo

>>> z
<staticmethod object at 0x0000000002E40558>
>>> Foo.foo
<function foo at 0x0000000002E3CBA8>
>>> dir(z)
['__class__', '__delattr__', '__doc__', '__format__', '__func__', '__get__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
>>> z.__func__
<function foo at 0x0000000002E3CBA8>

在交互式会话中进行类似的挖掘(dir非常有帮助)通常可以非常快速地解决这些问题。

于 2012-10-03T23:24:28.450 回答
36

这是我喜欢的方式:

class Klass(object):

    @staticmethod
    def stat_func():
        return 42

    _ANS = stat_func.__func__()

    def method(self):
        return self.__class__.stat_func() + self.__class__._ANS

Klass.stat_func由于DRY 原则,我更喜欢这种解决方案。让我想起了Python 3 中有一个新功能的原因:)super()

但我同意其他人的观点,通常最好的选择是定义一个模块级函数。

例如对于@staticmethod函数,递归可能看起来不太好(您需要通过调用Klass.stat_funcinside来打破 DRY 原则Klass.stat_func)。那是因为您没有引用self内部静态方法。有了模块级功能,一切都会好起来的。

于 2014-12-10T16:42:26.537 回答
12

在类定义之后注入类属性怎么样?

class Klass(object):

    @staticmethod  # use as decorator
    def stat_func():
        return 42

    def method(self):
        ret = Klass.stat_func()
        return ret

Klass._ANS = Klass.stat_func()  # inject the class attribute with static method value
于 2012-10-03T23:21:05.120 回答
12

这是因为 staticmethod 是一个描述符,并且需要一个类级别的属性获取来执行描述符协议并获得真正的可调用对象。

从源代码:

它可以在类(例如C.f())或实例(例如C().f())上调用;该实例被忽略,除了它的类。

但不是直接从类内部定义它。

但正如一位评论者所提到的,这根本不是真正的“Pythonic”设计。只需使用模块级功能即可。

于 2012-10-03T23:40:18.797 回答
8

这个解决方案怎么样?它不依赖于@staticmethod装饰器实现的知识。内部类 StaticMethod 充当静态初始化函数的容器。

class Klass(object):

    class StaticMethod:
        @staticmethod  # use as decorator
        def _stat_func():
            return 42

    _ANS = StaticMethod._stat_func()  # call the staticmethod

    def method(self):
        ret = self.StaticMethod._stat_func() + Klass._ANS
        return ret
于 2014-05-15T23:23:53.343 回答
2

如果“核心问题”是使用函数分配类变量,则另一种方法是使用元类(这有点“烦人”和“神奇”,我同意静态方法应该在类内可调用,但不幸的是它不是t)。这样,我们可以将行为重构为一个独立的函数,而不会使类混乱。

class KlassMetaClass(type(object)):
    @staticmethod
    def _stat_func():
        return 42

    def __new__(cls, clsname, bases, attrs):
        # Call the __new__ method from the Object metaclass
        super_new = super().__new__(cls, clsname, bases, attrs)
        # Modify class variable "_ANS"
        super_new._ANS = cls._stat_func()
        return super_new

class Klass(object, metaclass=KlassMetaClass):
    """
    Class that will have class variables set pseudo-dynamically by the metaclass
    """
    pass

print(Klass._ANS) # prints 42

在“现实世界”中使用这种替代方法可能会有问题。我不得不使用它来覆盖 Django 类中的类变量,但在其他情况下,最好使用其他答案中的一种替代方法。

于 2020-10-20T22:01:22.623 回答