Kay*_*gts 9 python metaprogramming type-hinting python-3.x
当我动态设置类的属性时:
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")
Run Code Online (Sandbox Code Playgroud)
该类型的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
Run Code Online (Sandbox Code Playgroud)
Django是如何做到这一点的?
由于您在类上定义字段,实用的方法是对字段进行类型提示。请注意,您必须告诉mypy不要检查线路本身。
class Person(PersonBase):
age: int = IntField() # type: ignore
Run Code Online (Sandbox Code Playgroud)
这是最小的变化,但相当不灵活。
您可以使用带有假签名的辅助函数来创建自动键入的通用提示:
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())
Run Code Online (Sandbox Code Playgroud)
这就是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()
Run Code Online (Sandbox Code Playgroud)
这就是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()
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
634 次 |
| 最近记录: |