[英]How can I type hint a dynamically set class attribute in a metaclass?
當我動態設置類的屬性時:
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 是如何做到這一點的?
由於您在類上定義字段,實用的方法是對字段進行類型提示。 請注意,您必須告訴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()
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
可以完全理解這一點。 謝謝!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.