显式 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 和个实例。
我正在编写 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 和个实例。