显式 class 对象需要作为自实现 class 方法装饰器中的第一个参数

Explicit class object needed as first parameter in self implemented classmethod decorator

我正在编写 class 方法装饰器的简单实现,以便更好地理解装饰器和 class 方法。这是我遇到的问题。当我用 class 的实例调用 class 方法时,一切都很好,但是用 class 对象调用方法失败并出现错误:

>>**TypeError: wrapper() missing 1 required positional argument: 'cls'**

当我使用显式 class 对象作为参数调用方法时它成功了,但是从 class 对象调用 class 方法应该传递 class对象本身作为第一个参数,对吧?

import functools
import inspect

def myclassmethod(meth):
    @functools.wraps(meth)
    def wrapper(cls, *args, **kwargs):
        #print(f'obj:{cls}, cls:{cls.__class__}, isclass:{inspect.isclass(cls)}')
        return meth(cls if inspect.isclass(cls) else cls.__class__, *args, **kwargs)
    return wrapper

class MyDecoratedMethods(object):
    _name = 'ClassName'

    def __init__(self):
        self._name = 'InstanceName'

    def __repr__(self):
        return f'{self.__class__.__name__}({self._name!r})'

    @myclassmethod
    def classname(cls):
        return cls._name

MyDecoratedMethods().classname()
#MyDecoratedMethods.classname()
MyDecoratedMethods.classname(MyDecoratedMethods) # This works

为了看看发生了什么,我删除了 @functools.wraps(meth) 行,然后是 运行:

print(MyDecoratedMethods.classname)
# <function __main__.myclassmethod.<locals>.wrapper(cls, *args, **kwargs)>

这向我们表明,MyDecoratedMethods.classname 只是您在装饰器中创建的函数。而这个函数对调用它的class一无所知。

但是,我们可以使用 Descriptors 覆盖此行为。当从 class 或实例访问描述符 "knows" 时,最重要的是,它可以区分这些情况(这是创建常规方法的方式)。

这是第一次尝试:

class ClassMethod:
    def __init__(self, function):
        self.function = function

    def __get__(self, instance, cls):
        print(cls, instance)

class MyDecoratedMethods(object):
    ...

    @ClassMethod
    def classname(cls):
        ...

MyDecoratedMethods.classname
# prints <class '__main__.MyDecoratedMethods'> None
MyDecoratedMethods().classname
# prints <class '__main__.MyDecoratedMethods'> <__main__.MyDecoratedMethods object ...>

所以我们看到从 class 访问 class 方法将 instance 设置为 None 并从实例访问它设置 instance 到那个非常反对。

但实际上我们根本不需要实例。我们可以在没有它的情况下实现逻辑。

from functools import partial

class ClassMethod:
    def __init__(self, function):
        self.function = function

    def __get__(self, instance, cls):
        # create a new function and set cls to the first argument
        return partial(self.function, cls)

...

MyDecoratedMethods().classname()
# "ClassName"
MyDecoratedMethods.classname()
# "ClassName"

我们完成了。我们的自定义描述符完成了两件事:

  • 它阻止函数在从 class 的实例调用它时将实例绑定到第一个参数(就像函数通常会成为方法一样)
  • 当从 class 或实例访问时,它总是将 class 绑定到第一个参数。

旁注:您检查实例或 class 调用函数的方法也有缺陷 (inspect.isclass(cls))。它适用于 "normal" classes 但不适用于 meta classes,因为 inspect.isclass returns True 对于 class 个实例。