Python 如何在元类中键入动态设置的类属性?
动态设置类的属性时:Python 如何在元类中键入动态设置的类属性?,python,python-3.x,metaprogramming,type-hinting,Python,Python 3.x,Metaprogramming,Type Hinting,动态设置类的属性时: 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):
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")
Django是如何做到这一点的?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
可以完全理解这一点。谢谢 因为您在类上定义了字段,所以实际的方法是键入提示字段。请注意,您必须告诉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
库提供其遗留提示的方式。此样式允许隐藏注释的所有魔力/技巧
由于元类可以检查注释,因此不需要在字段中存储类型。您可以对元数据使用裸
字段
,对类型使用注释:
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()
我不确定Django的情况,但这种模式经常使用。在每种情况下,
type(Person.age)
是什么?@jdehesa我将打印结果放在注释中。在这两种情况下,它都是
。不,我的意思是Person.age
,class属性,而不是实例。啊,我的错。我添加到代码片段中。有趣的是,在我的例子中,在实例化某些东西之前它已经是一个int。使用mypy 0.670,它推断age
为typeAny
——基本上没有类型。
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()