首页 > 解决方案 > 使用 Descriptor 和 Decorator 检查类型属性:访问属性时“__get__() 接受 2 个位置参数,但给出了 3 个”

问题描述

目标

我尝试实现一个像类型检查功能一样的装饰器。我还没到那里,因为验证运行良好,但是当我尝试访问该属性时,我得到了错误: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()

你看到解决这个问题的方法了吗?

标签: pythondecoratortypecheckingpython-descriptors

解决方案


你签名错了 调用时__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 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仅当描述符在类创建时已经存在时才调用该方法。这意味着您不能setattr在已经存在的类对象上动态设置它,但您必须创建一个新类!

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

推荐阅读