显式类对象需要作为自实现classd方法中的第一个参数

2024-09-29 01:20:19 发布

您现在位置:Python中文网/ 问答频道 /正文

为了更好地理解decorator和classmethod,我编写了classmethoddecorator的一个简单实现。这是我面临的问题。当我用类的实例调用class方法时,一切正常,但用class对象调用方法失败,并出现错误:

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

当我以显式类对象作为参数调用方法时,它成功了,但是从类对象调用classmethod应该将类对象本身作为第一个参数传递,对吗?你知道吗

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

Tags: 对象方法nameselfreturndefwrapperclass
1条回答
网友
1楼 · 发布于 2024-09-29 01:20:19

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

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

这向我们展示了MyDecoratedMethods.classname只是您在decorator中创建的函数。这个函数对调用它的类一无所知。你知道吗

但是,我们可以用Descriptors覆盖这个行为。描述符“知道”何时从类或实例访问它,最重要的是,它可以区分这些情况(这就是创建常规方法的方式)。你知道吗

这里是第一次尝试:

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方法将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"

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

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

旁注:检查调用函数的实例或类是否也有缺陷(inspect.isclass(cls))。它适用于“普通”类,但不适用于元类,因为inspect.isclass为类及其实例返回True。你知道吗

相关问题 更多 >