为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

慕村9548890 2021-11-23 16:33:16
当我动态设置类的属性时:from typing import TypeVar, Generic, Optional, ClassVar, Anyclass IntField:    type = intclass PersonBase(type):    def __new__(cls):        for attr, value in cls.__dict__.items():            if not isinstance(value, IntField):                continue            setattr(cls, attr, value.type())        return clsclass 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 modelsclass 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 errorDjango 是如何做到这一点的?
查看完整描述

2 回答

?
UYOU

TA贡献1878条经验 获得超4个赞

由于您在类上定义字段,实用的方法是对字段进行类型提示。请注意,您必须告诉mypy不要检查线路本身。


class Person(PersonBase):

    age: int = IntField()  # type: ignore

这是最小的变化,但相当不灵活。


您可以使用带有假签名的辅助函数来创建自动键入的通用提示:


from typing import Type, TypeVar



T = TypeVar('T')



class __Field__:

    """The actual field specification"""

    def __init__(self, *args, **kwargs):

        self.args, self.kwargs = args, kwargs



def Field(tp: Type[T], *args, **kwargs) -> T:

    """Helper to fake the correct return type"""

    return __Field__(tp, *args, **kwargs)  # type: ignore



class Person:

    # Field takes arbitrary arguments

    # You can @overload Fields to have them checked as well

    age = Field(int, True, object())

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


由于元类可以检查注释,因此无需在 Field 上存储类型。您可以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()

这就是attrs提供其 Python 3.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()


查看完整回答
反对 回复 2021-11-23
?
catspeake

TA贡献1111条经验 获得超0个赞

Patrick Haugh 是对的,我试图以错误的方式解决这个问题。描述符是要走的路:


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可以完全理解这一点。谢谢!


查看完整回答
反对 回复 2021-11-23
  • 2 回答
  • 0 关注
  • 153 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信