如何在元类中键入提示动态设置的类属性?

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


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不能遵循。


有没有办法让 MyPy 理解?


Django 已经实现了:


from django.db import models


class Person(models.Model):

    age = models.IntegerField()


person = Person()

print(type(Person.age)) # <class 'django.db.models.query_utils.DeferredAttribute'>

print(type(person.age)) # <class 'int'>

person.age = 25  # No error

Django 是如何做到这一点的?


慕村9548890
浏览 154回答 2
2回答

UYOU

由于您在类上定义字段,实用的方法是对字段进行类型提示。请注意,您必须告诉mypy不要检查线路本身。class Person(PersonBase):&nbsp; &nbsp; age: int = IntField()&nbsp; # type: ignore这是最小的变化,但相当不灵活。您可以使用带有假签名的辅助函数来创建自动键入的通用提示:from typing import Type, TypeVarT = TypeVar('T')class __Field__:&nbsp; &nbsp; """The actual field specification"""&nbsp; &nbsp; def __init__(self, *args, **kwargs):&nbsp; &nbsp; &nbsp; &nbsp; self.args, self.kwargs = args, kwargsdef Field(tp: Type[T], *args, **kwargs) -> T:&nbsp; &nbsp; """Helper to fake the correct return type"""&nbsp; &nbsp; return __Field__(tp, *args, **kwargs)&nbsp; # type: ignoreclass Person:&nbsp; &nbsp; # Field takes arbitrary arguments&nbsp; &nbsp; # You can @overload Fields to have them checked as well&nbsp; &nbsp; age = Field(int, True, object())这就是attrs库提供其遗留提示的方式。这种风格允许隐藏注释的所有魔法/技巧。由于元类可以检查注释,因此无需在 Field 上存储类型。您可以Field对元数据使用裸,对类型使用注释:from typing import Anyclass Field(Any):&nbsp; # the (Any) part is only valid in a .pyi file!&nbsp; &nbsp; """Field description for Any type"""class MetaPerson(type):&nbsp; &nbsp; """Metaclass that creates default class attributes based on fields"""&nbsp; &nbsp; def __new__(mcs, name, bases, namespace, **kwds):&nbsp; &nbsp; &nbsp; &nbsp; for name, value in namespace.copy().items():&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if isinstance(value, Field):&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; # look up type from annotation&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; field_type = namespace['__annotations__'][name]&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; namespace[name] = field_type()&nbsp; &nbsp; &nbsp; &nbsp; return super().__new__(mcs, name, bases, namespace, **kwds)class Person(metaclass=MetaPerson):&nbsp; &nbsp; age: int = Field()这就是attrs提供其 Python 3.6+ 属性的方式。它既通用又符合注释风格。请注意,这也可以与常规基类而不是元类一起使用。class BasePerson:&nbsp; &nbsp; &nbsp;def __init__(self):&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;for name, value in type(self).__dict__.items():&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;if isinstance(value, Field):&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;field_type = self.__annotations__[name]&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;setattr(self, name, field_type())class Person(BasePerson):&nbsp; &nbsp; age: int = Field()

catspeake

Patrick Haugh 是对的,我试图以错误的方式解决这个问题。描述符是要走的路:from typing import TypeVar, Generic, Optional, ClassVar, Any, TypeFieldValueType = TypeVar('FieldValueType')class Field(Generic[FieldValueType]):&nbsp; &nbsp; value_type: Type[FieldValueType]&nbsp; &nbsp; def __init__(self) -> None:&nbsp; &nbsp; &nbsp; &nbsp; self.value: FieldValueType = self.value_type()&nbsp; &nbsp; def __get__(self, obj, objtype) -> 'Field':&nbsp; &nbsp; &nbsp; &nbsp; print('Retrieving', self.__class__)&nbsp; &nbsp; &nbsp; &nbsp; return self&nbsp; &nbsp; def __set__(self, obj, value):&nbsp; &nbsp; &nbsp; &nbsp; print('Updating', self.__class__)&nbsp; &nbsp; &nbsp; &nbsp; self.value = value&nbsp; &nbsp; def to_string(self):&nbsp; &nbsp; &nbsp; &nbsp; return self.valueclass StringField(Field[str]):&nbsp; &nbsp; value_type = strclass IntField(Field[int]):&nbsp; &nbsp; value_type = int&nbsp; &nbsp; def to_string(self):&nbsp; &nbsp; &nbsp; &nbsp; return str(self.value)class Person:&nbsp; &nbsp; age = IntField()person = Person()person.age = 25print(person.age.to_string())MyPy可以完全理解这一点。谢谢!
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Python