【问题标题】:Calling class staticmethod within the class body?在类体内调用类静态方法?
【发布时间】:2018-02-15 14:57:55
【问题描述】:

当我尝试从类的主体中使用静态方法,并使用内置的 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”的方式来完成此任务?

【问题讨论】:

  • 如果您询问 Pythonicity,那么标准建议是根本不要使用 staticmethod。它们通常作为模块级函数更有用,在这种情况下,您的问题不是问题。 classmethod,另一方面...
  • @poorsod:是的,我知道这种选择。但是,在我遇到此问题的实际代码中,将函数设为静态方法而不是将其置于模块级别比在我的问题中使用的简单示例中更有意义。

标签: python decorator static-methods


【解决方案1】:

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

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

【讨论】:

  • 这类似于我第一次尝试解决问题,但我更喜欢类内部的东西......部分原因是所涉及的属性具有前导下划线并且是私有的。
  • 可以先调用 Klass._ANS = Klass.stat_func() 再定义类吗?这是不允许的吗,我尝试这个时看到错误类不存在?
【解决方案2】:

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

【讨论】:

  • 很好的更新...我正想问你是怎么知道的,因为我在文档中没有看到它——这让我对使用它有点紧张,因为它可能是一个“实施细节”。
  • @martineau 不在此上下文中。绑定和未绑定的方法对象都有一个im_func属性用于获取原始函数,这些方法对象的__func__属性与im_func相同。 staticmethod 对象没有 im_func 属性(如我上面的 dir 帖子所示,并在实际的解释器会话中确认)。
  • 我明白了,所以从技术上讲,它在这种情况下没有记录。
  • @AkshayHazari 静态方法的__func__ 属性为您提供对原始函数的引用,就像您从未使用过staticmethod 装饰器一样。因此,如果您的函数需要参数,则必须在调用 __func__ 时传递它们。您的错误消息听起来好像您没有给它任何参数。如果本文示例中的 stat_func 带有两个参数,您将使用 _ANS = stat_func.__func__(arg1, arg2)
  • @AkshayHazari 我不确定我是否理解。它们可以是变量,它们只需要是范围内的变量:要么在定义类的同一范围内更早地定义(通常是全局的),要么在类范围内更早地定义(stat_func 本身就是这样一个变量)。你的意思是你不能使用你定义的类的实例属性?这是真的,但不足为奇。我们没有类的实例,因为我们仍在定义类!但无论如何,我只是说它们是你想通过的任何论点的代表;你可以在那里使用文字。
【解决方案3】:

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

来自源代码:

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

但不是在定义类时直接从类内部进行。

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

【讨论】:

  • 正如我在问题中所说,我理解为什么原始代码不起作用。你能解释一下(或提供一个链接)为什么它被认为是非 Pythonic 的吗?
  • 静态方法需要类对象本身才能正常工作。但是,如果您从类级别的类中调用它,则此时该类并没有真正完全定义。所以你还不能引用它。在定义之后,可以从类外部调用静态方法。但是“Pythonic”实际上并没有很好的定义,很像美学。我可以告诉你我自己对该代码的第一印象并不好。
  • 印象是哪个版本(或两者)?具体为什么?
  • 不,我想要做的其实很简单——即分解出一些通用代码并重用它,既可以在创建类时创建私有类属性,也可以稍后在一个或多个类方法。这个通用代码在类之外没有任何用处,所以我自然希望它成为它的一部分。使用元类(或类装饰器)会起作用,但是对于应该很容易做的事情来说,这似乎有点过头了,恕我直言。
  • 这对于在我的简化示例代码中是微不足道的常量的类属性来说很好,但如果它们不是并且需要一些计算,那就不是这样了。
【解决方案4】:

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

【讨论】:

  • +1 表示创造力,但我不再担心使用 __func__,因为它现在已被正式记录(请参阅 Python 2.7 中的新增功能的 Other Language Changes 部分及其对 @987654322 的引用@)。您的解决方案更加便携,因为它可能也适用于 2.6 之前的 Python 版本(当 __func__ 首次作为 im_func 的同义词引入时)。
  • 这是唯一适用于 Python 2.6 的解决方案。
  • @benselme:我无法验证您的说法,因为我没有安装 Python 2.6,但严重怀疑它是唯一的...
【解决方案5】:

这是我喜欢的方式:

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 principle。 让我想起了 Python 3 中的 reason why there is a new super() :)

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

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

【讨论】:

  • 虽然我同意在常规方法中使用self.__class__.stat_func() 比使用Klass.stat_func() 具有优势(DRY 和所有这些),但这并不是我问题的真正主题——事实上我避免使用前者以免混淆不一致的问题。
  • 这并不是 DRY 本身的问题。 self.__class__ 更好,因为如果子类覆盖stat_func,那么Subclass.method 将调用子类的stat_func。但老实说,在这种情况下,最好只使用真实方法而不是静态方法。
  • @asmeurer:我不能使用真正的方法,因为还没有创建类的实例——它们不能创建,因为类本身甚至还没有完全定义。跨度>
  • 我想要一种方法来调用我的类的静态方法(它是继承的;因此父级实际上具有该功能),这似乎是迄今为止最好的(如果我稍后覆盖它仍然可以工作) .
【解决方案6】:

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

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 类中的类变量,但在其他情况下,最好使用其他答案中的一种替代方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-13
    • 2012-05-24
    • 2010-10-30
    相关资源
    最近更新 更多