如何检查方法是 class 方法还是 metaclass 中的静态方法?

How can I check whether a method is a class method or a static method in a metaclass?

这里是一个非常简单的Baseclass,其中包含一个静态方法和一个class方法:

class Base():

    @staticmethod
    def f():
        print("Base.f")

    @classmethod
    def g(cls):
        print("Base.g")

    def h(self):
        print("Base.h")

如果要从 Base 派生出 class 并覆盖 fg,则 staticmethodclassmethod需要在覆盖方法上再次使用装饰器。

class A(Base):

    @staticmethod
    def f():
        print("A.f")

class B(Base):

    @classmethod
    def g(cls):
        print("B.g")

所以,一开始我以为我会创建一个元class,自动使 f 成为 staticmethodg 成为 staticmethod

class BaseMeta(type):

    def __init__(cls, name, bases, namespace):
        super().__init__(name, bases, namespace)
        if 'f' in namespace: cls.f = staticmethod(cls.f)
        if 'g' in namespace: cls.g = classmethod(cls.g)

现在 class 的其余部分不需要明确使用 staticmethodclassmethod

class Base(metaclass=BaseMeta):

    def f():
        print("Base.f")

    def g(cls):
        print("Base.g")

    def h(self):
        print("Base.h")

class A(Base):

    def f():
        print("A.f")

class B(Base):

    def g(cls):
        print("B.g")

这可行,但我不喜欢它的外观。现在,我意识到 staticmethodclassmethod 装饰器 应该 被显式使用(毕竟,显式优于隐式,不是吗?)

所以我想我可以保留元class,但这次我应该检查[=52而不是强制装饰器=] 是否使用过,如果没有使用则抛出异常。

class BaseMeta(type):

    def __init__(cls, name, bases, namespace):
        super().__init__(name, bases, namespace)
        # check if cls.f is a static method
        if not inspect.isfunction(cls.f):
            raise Exception("f should be a static method")
        # check if cls.g is a static method
        if not (inspect.ismethod(cls.g) and cls.g.__self__ == cls):
            raise Exception("g should be a class method")

不幸的是,这不起作用。似乎在 metaclasse 的 __init__ 中,一切都被认为只是一个函数(只需打印 cls.fcls.g 就可以看出这一点)。

我在这里遗漏了什么吗?

好的,所以看起来试图检查 cls.fcls.g 是静态的还是元 class 中的 class 方法是没有意义的,它们不是似乎 绑定 了。

然而,在方法上使用了 staticmethodclassmethod 装饰器肯定会留下它的印记。折腾了一番,最终发现原来想做的事情可以实现如下:

class BaseMeta(type):

    def __init__(cls, name, bases, namespace):
        super().__init__(name, bases, namespace)
        # check if cls.f is a static method
        if 'f' in namespace and not isinstance(namespace['f'], staticmethod):
            raise Exception(cls.__name__ + ".f should be a static method")
        # check if cls.g is a class method
        if 'g' in namespace and not isinstance(namespace['g'], classmethod):
            raise Exception(cls.__name__ + ".g should be a class method")

所以,原问题的答案是:

Checking whether a method has been decorated with staticmethod or classmethod is possible in a metaclass, by retrieving the method from the namespace and checking whether it is an instance of 'staticmethod' or 'classmethod'.

这个:

if not (inspect.ismethod(cls.g) and cls.g.__self__ == cls):
    raise Exception("g should be a class method")

工作正常,但是这个:

if not inspect.isfunction(cls.f):
    raise Exception("f should be a static method")

不会,因为在 Python 3 上,无论是否应用 staticmethod 装饰器,cls.f 都将是 f 函数。 (在 Python 2 上,它会是一个没有装饰器的未绑定方法对象。)


与其访问 cls.fcls.g 并尝试根据描述符协议的结果计算出你经历了哪种描述符,不如绕过描述符协议并访问原始内容class 定义的命名空间:

if 'f' in namespace and not isinstance(namespace['f'], staticmethod):
    whatever()
if 'g' in namespace and not isinstance(namespace['g'], classmethod):
    whatever()

对于Python 3.2+,您可以使用inspect.getattr_static:

Retrieve attributes without triggering dynamic lookup via the descriptor protocol, getattr() or getattribute().

示例:

import inspect


class A:
    @staticmethod
    def f():
        pass

    @classmethod
    def g(cls):
        pass

    def r():
        pass


a = A()
print(isinstance(inspect.getattr_static(a, "f"), staticmethod))
print(isinstance(inspect.getattr_static(A, "f"), staticmethod))
print(isinstance(inspect.getattr_static(a, "g"), classmethod))
print(isinstance(inspect.getattr_static(A, "g"), classmethod))
print(isinstance(inspect.getattr_static(a, "r"), classmethod))
print(isinstance(inspect.getattr_static(A, "r"), staticmethod))

将输出:

True
True
True
True
False
False