Python版本<=3.9:在类主体内调用类静态方法?

224

当我在类的内部使用静态方法,并使用内置的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

所以我的问题是:

有没有更简洁、更符合 Python 风格的方法来实现这个功能?


5
如果你在询问Pythonicity,那么标准建议是不要使用staticmethod。它们通常作为模块级别的函数更有用,这种情况下你的问题就不是问题了。另一方面,classmethod... - Benjamin Hodgson
1
@poorsod:是的,我知道那个替代方案。但是在我遇到这个问题的实际代码中,将函数作为静态方法而不是放在模块级别更有意义,而不像我问题中使用的简单示例那样。 - martineau
在Python 3.10中,你可以从类体中轻松调用staticmethod函数。更多信息请参见此处:https://dev59.com/g2cs5IYBdhLWcg3wmlIK#75628150 - Trevor Boyd Smith
7个回答

250

针对Python版本>= 3.10进行更新:staticmethod函数可以在类作用域中正常调用(有关更多信息请参见:Python问题跟踪器“新特性”此处


针对Python版本<= 3.9继续阅读

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会话中复制):

>>> 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非常有帮助),通常可以很快解决这些问题。


3
好的更新...我正想问你是怎么知道这个的,因为我在文档中没看到 -- 这让我对使用它有点紧张,因为它可能是一些“实现细节”。 - martineau
1
@martineau 在这种情况下不是这样的。绑定和非绑定方法对象具有im_func属性以获取原始函数,而这些方法对象的__func__属性与im_func相同。staticmethod对象没有im_func属性(如我上面的dir帖子所示,并在实际解释器会话中确认)。 - Ben
2
我明白了,所以从技术上讲,在这种情况下它是未记录的。 - martineau
2
@AkshayHazari 静态方法的__func__属性可以让你获取到原始函数的引用,就好像你从未使用过staticmethod修饰符一样。因此,如果你的函数需要参数,在调用__func__时必须传递它们。你所遇到的错误信息听起来像是你没有给它任何参数。如果这篇文章中的stat_func函数需要两个参数,那么你可以使用_ANS = stat_func.__func__(arg1, arg2) - Ben
1
@AkshayHazari 我不确定我理解了。它们可以是变量,只要它们是在作用域内的变量:要么在定义类的同一作用域内定义(通常是全局变量),要么在类作用域内先前定义(stat_func本身就是这样的变量)。你的意思是你不能使用正在定义的类的实例属性吗?那很正确,但并不奇怪;因为我们仍在定义该类,所以我们没有该类的实例!但是无论如何,我只是把它们当作你想传递的任何参数的替代品;你可以在那里使用文字。 - Ben
显示剩余5条评论

41

这是我更喜欢的方式:

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_func 来打破 DRY 原则)。 这是因为静态方法内部没有对 self 的引用。 使用模块级函数,一切都会看起来很好。

虽然我同意在常规方法中使用 self.__class__.stat_func() 比使用 Klass.stat_func() 有优势(DRY 等等),但这并不是我的问题的主题 - 实际上,我避免使用前者以不影响不一致性的问题。 - martineau
3
不是DRY的问题。使用self.__class__更好,因为如果子类重写了stat_func,那么Subclass.method将调用子类的stat_func。但说实话,在这种情况下,最好只是使用一个真正的方法而不是静态方法。 - asmeurer
@asmeurer:我无法使用真实的方法,因为该类的实例尚未创建 - 这是不可能的,因为该类本身甚至还没有完全定义。 - martineau
我想要一种调用我的类的静态方法的方式(该类是继承的;因此父类实际上拥有该函数),这似乎是迄今为止最好的方法(如果我稍后覆盖它,它仍将起作用)。 - whitey04

14

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

根据源代码:

它可以在类上调用(例如C.f()),也可以在实例上调用(例如C().f());但实例除了其类之外被忽略。

但不能在定义类时直接从类内部调用。

但正如一位评论者所提到的,这并不是真正的“Pythonic”设计。只需使用模块级别的函数代替即可。


哪个版本给您留下了印象(或两个版本都有)?为什么,具体来说呢? - martineau
我只能猜测您的最终目标是什么,但我认为您想要一个动态的类级属性。这看起来像是元类的工作。但可能还有更简单的方法,或者另一种设计方式可以在不牺牲功能的情况下消除和简化。 - Keith
5
不,我想做的事情实际上非常简单——就是将一些公共代码分解出来并重复使用,既要在类创建时创建一个私有类属性,也要在一个或多个类方法中重复使用。这些公共代码只在类内部使用,因此我希望它自然成为类的一部分。使用元类(或类装饰器)可以实现这一点,但我认为这对于应该很容易实现的事情来说过于繁琐了。 - martineau
你可以在类创建时使用以下代码在类作用域中创建一个类属性:_ANS = 42。这是最常见的方法。 - Keith
1
这对于类属性是平凡常数的情况是可以的,就像我简化的示例代码一样,但如果它们不是常数,而需要一些计算,则不太适用。 - martineau
显示剩余3条评论

12

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

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

2
这与我第一次尝试的解决方法类似,但我更喜欢在类内部实现...部分原因是涉及的属性具有前导下划线并且是私有的。 - martineau
我可以先调用Klass._ANS = Klass.stat_func(),然后再定义类吗?这样做不允许吗?当我尝试这样做时,我看到一个错误,提示类不存在。 - merlachandra

10

这个方案怎么样?它不依赖于对@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

2
+1 的创意不错,但我不再担心使用 __func__ 因为它现在已经正式文档化了(请参见 Python 2.7 中的“其他语言更改”一节及其对问题5982的引用)。您的解决方案甚至更具可移植性,因为它可能也适用于 Python 2.6 之前的版本(当时 __func__ 被首次引入作为 im_func 的同义词)。 - martineau
这是唯一与Python 2.6兼容的解决方案。 - benselme
@benselme:我无法验证你的说法,因为我没有安装Python 2.6,但我严重怀疑它不是唯一的一个…… - martineau

2
如果“核心问题”是使用函数分配类变量,那么另一种选择是使用元类(它有点“烦人”和“神奇”,我同意静态方法应该在类内可调用,但不幸的是它不能)。这样,我们可以将行为重构为独立函数,并避免使类混乱。
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类中使用它来覆盖类变量,但在其他情况下,也许最好选择其他答案中的一种替代方案。


1

对于 Python 版本 >= 3.10,staticmethod 函数可以在类范围内正常调用

class Tmp:
    @staticmethod
    def func1():
        return 1234
    X = func1() 
print(Tmp.X)
  • 我的测试结果显示:
    • Python3.9 出现错误
    • Python3.10 正常运行(无错误)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接