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

标签 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):
                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 上。您可以为元数据使用裸字段,并为类型使用注释:

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()

关于python - 如何在元类中键入提示动态设置的类属性?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54946944/

相关文章:

C++11:根据项目计数在 std::array 和 std::unordered_map 之间实现一个选择器

python - 在 Python 中的同一行上打印多个

python - 通过每个项目的两个第一个值优化列表列表中的频率

python - 覆盖 sphinx 的 autodoc 中的函数声明

python - 检查 python 是否删除文件夹成功,如果成功,则继续

C++模板元编程功夫挑战(替换一个宏函数定义)

python - 使用公式更新单元格值会导致带有 Sheets API 的撇号前缀

python - 为反向遗传关系创建序列化器

python - 抓取表的困难(Python,BeautifulSoup)

java - 有没有办法在 Java 中使用注解来代替访问器?