如何在元类中键入hint动态设置类属性?

2024-09-30 16:28:59 发布

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

当我动态设置类的属性时:

from typing import TypeVar, Generic, Optional, ClassVar, Any

class IntField:
    type = int

class PersonBase(type):
    def __new__(cls):
        for attr, value in cls.__dict__.items():
            if not isinstance(value, IntField):
                continue
            setattr(cls, attr, value.type())
        return cls

class Person(PersonBase):
    age = IntField()

person = Person()

print(type(Person.age)) # <class 'int'>
print(type(person.age)) # <class 'int'>
person.age = 25 # Incompatible types in assignment (expression has type "int", variable has type "IntField")

age属性的类型将是int类型,但MyPy不能跟在后面。在

有没有办法让我明白?在

Django已实施:

^{pr2}$

Django是怎么做到的?在


Tags: inage属性valuetypeclassintperson
2条回答

由于您在类上定义了字段,实际的方法是键入hint字段。注意,您必须告诉mypy不要检查行本身。在

class Person(PersonBase):
    age: int = IntField()  # type: ignore

这是最小的改变,但相当僵硬。在


通过使用带有伪签名的helper函数,可以创建自动键入的通用提示:

^{pr2}$

这就是attrib库提供其遗留提示的方式。这种风格允许隐藏注释的所有魔力/技巧。在


因为元类可以检查注释,所以不需要在字段中存储类型。您可以使用空的Field作为元数据,并为类型使用注释:

from typing import Any


class Field(Any):  # the (Any) part is only valid in a .pyi file!
    """Field description for Any type"""


class MetaPerson(type):
    """Metaclass that creates default class attributes based on fields"""
    def __new__(mcs, name, bases, namespace, **kwds):
        for name, value in namespace.copy().items():
            if isinstance(value, Field):
                # look up type from annotation
                field_type = namespace['__annotations__'][name]
                namespace[name] = field_type()
        return super().__new__(mcs, name, bases, namespace, **kwds)


class Person(metaclass=MetaPerson):
    age: int = Field()

这就是attrib提供其python3.6+属性的方式。它既通用又符合注释样式。请注意,这也可以用于常规基类而不是元类。在

class BasePerson:
     def __init__(self):
         for name, value in type(self).__dict__.items():
             if isinstance(value, Field):
                 field_type = self.__annotations__[name]
                 setattr(self, name, field_type())


class Person(BasePerson):
    age: int = Field()

帕特里克·豪是对的,我试图用错误的方式解决这个问题。描述词是正确的方法:

from typing import TypeVar, Generic, Optional, ClassVar, Any, Type

FieldValueType = TypeVar('FieldValueType')


class Field(Generic[FieldValueType]):

    value_type: Type[FieldValueType]

    def __init__(self) -> None:
        self.value: FieldValueType = self.value_type()

    def __get__(self, obj, objtype) -> 'Field':
        print('Retrieving', self.__class__)
        return self

    def __set__(self, obj, value):
        print('Updating', self.__class__)
        self.value = value

    def to_string(self):
        return self.value

class StringField(Field[str]):
    value_type = str

class IntField(Field[int]):
    value_type = int

    def to_string(self):
        return str(self.value)


class Person:
    age = IntField()

person = Person()
person.age = 25
print(person.age.to_string())

MyPy完全可以理解这一点。谢谢!在

相关问题 更多 >