使用描述符和装饰器检查类型属性:访问属性时“__get__() 接受 2 个位置参数,但给出了 3 个”

Check a type attribute with a Descriptor and a Decorator : "__get__() takes 2 positional arguments but 3 were given" when accessing the attribute

目标

我尝试实现一个可以充当类型检查功能的装饰器。我还没有到那里,因为验证工作正常,但是当我尝试访问该属性时,我收到错误消息:TypeError: __get__() takes 2 positional arguments but 3 were given.

我正在使用 Python 3.9.7.

描述

# Descriptor definition
class PositiveNumber:
    def __get__(self, obj):
        return self.value

    def __set__(self, obj, value):
        if not isinstance(value, (int, float)):
            raise TypeError('value must be a number')
        if value < 0:
            raise ValueError('Value cannot be negative.')
        self.value = value

# Decorator definition
def positive_number(attr):
    def decorator(cls):
        setattr(cls, attr, PositiveNumber())
        return cls
    return decorator

# class that actually check for the right type
@positive_number('number')
class MyClass(object):
    def __init__(self, value):
        self.number = value


def main():
    a = MyClass(3)
    print(a.number)


if __name__ == '__main__':
    main()

你知道解决这个问题的方法吗?

你签名有误。当调用__get__时,传递了3个参数(包括你是否使用它的类型):

class PositiveNumber:
    def __get__(self, obj, objtype=None):
        return self.value
    # ...

另一件事是错误的:只有一个 描述符实例,因此 MyClass 的所有实例都将共享该对象的 value 属性!

a = MyClass(3)
b = MyClass(5)
a.number
# 5

因此,您最好将设置值与实例相关联来解决此问题:

class PositiveNumber:
    def __get__(self, obj, objtype=None):
        return obj._value

    def __set__(self, obj, value):
        if not isinstance(value, (int, float)):
            raise TypeError('value must be a number')
        if value < 0:
            raise ValueError('Value cannot be negative.')
        obj._value = value

也可以根据实际的字段名称使用更具体的属性名称class:

class PositiveNumber:
    def __set_name__(self, owner, name):
        self.private_name = '_' + name  # e.g. "_number"

    def __get__(self, obj, objtype=None):
        return getattr(obj, self.private_name)

    def __set__(self, obj, value):
        if not isinstance(value, (int, float)):
            raise TypeError('value must be a number')
        if value < 0:
            raise ValueError('Value cannot be negative.')
        setattr(obj, self.private_name, value)

set_name 方法只有在描述符在 class 创建时已经存在时才会被调用。这意味着您不能在已经存在的 class 对象上使用 setattr 动态设置它,但您必须创建一个新的 class!

def positive_number(attr):
    def decorator(cls):
        return type(cls.__name__, (cls,), {attr: PositiveNumber()})
    return decorator