繁体   English   中英

来自 Python 类元信息的 __init__ 函数的类型提示

[英]Type-hinting for the __init__ function from class meta information in Python

我想做的是复制SQLAlchemy功能,使用它的DeclarativeMeta类。 有了这个代码,

from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()


class Person(Base):
    __tablename__ = 'person'
    id = Column(Integer, primary_key=True)

    name = Column(String)
    age = Column(Integer)

当你在PyCharm创建一个人时, Person(... ,你会得到关于id: int, name: str, age: int输入提示,

使用 Python、PyCharm 和 SQLAlchemy 进行类型提示

它在运行时的工作方式是通过 SQLAlchemy 的_declarative_constructor函数,

def _declarative_constructor(self, **kwargs):
    cls_ = type(self)
    for k in kwargs:
        if not hasattr(cls_, k):
            raise TypeError(
                "%r is an invalid keyword argument for %s" %
                (k, cls_.__name__))
        setattr(self, k, kwargs[k])
_declarative_constructor.__name__ = '__init__'

为了获得非常好的类型提示(如果您的类有一个 id 字段,则Column(Integer)您的构造函数将其类型提示为id: int ), PyCharm实际上正在做一些PyCharm魔法,特定于 SQLAlchemy ,但我不需要它那么好/好,我只想能够从类的元信息中以编程方式添加类型提示。

所以,简而言之,如果我有一个类,

class Simple:
    id: int = 0

    name: str = ''
    age: int = 0

我希望能够像上面一样初始化这个类, Simple(id=1, name='asdf') ,但同时也得到类型提示。 我可以得到一半(功能),但不能得到类型提示。

如果我像 SQLAlchemy 那样进行设置,

class SimpleMeta(type):
    def __init__(cls, classname, bases, dict_):
        type.__init__(cls, classname, bases, dict_)


metaclass = SimpleMeta(
    'Meta', (object,), dict(__init__=_declarative_constructor))


class Simple(metaclass):
    id: int = 0

    name: str = ''
    age: int = 0


print('cls', typing.get_type_hints(Simple))
print('init before', typing.get_type_hints(Simple.__init__))
Simple.__init__.__annotations__.update(Simple.__annotations__)
print('init after ', typing.get_type_hints(Simple.__init__))
s = Simple(id=1, name='asdf')
print(s.id, s.name)

有效,但我没有得到任何类型提示,

没有 __init__ 类型提示

如果我确实传递了参数,我实际上会收到一个Unexpected Argument警告,

意外的争论

在代码中,我手动更新了__annotations__ ,这使得get_type_hints返回正确的东西,

cls {'id': <class 'int'>, 'name': <class 'str'>, 'age': <class 'int'>}
init before {}
init after  {'id': <class 'int'>, 'name': <class 'str'>, 'age': <class 'int'>}
1 asdf

__init__更新__annotations__是去那里的正确方法。 可以在基类上使用元类,类装饰器或适当的__init_subclass__方法来执行此操作。

但是,PyCharm提出这个警告应该被视为Pycharm本身的一个错误:Python已经记录了语言中的机制,因此如果__init__被定义, object.__new__将忽略类实例化(这是一个“类调用”)的额外参数在继承链的任何子类中。 在产生此警告时,pycharm的行为实际上与语言规范不同。

围绕它的工作是使用相同的机制来更新__init__以创建具有相同签名的代理__new__方法。 但是,这个方法必须吞下任何args本身 - 所以如果你的类层次结构需要某个实际的__new__方法,那么获得正确的行为是一个复杂的边缘情况。

__init_subclass__的版本__init_subclass__会:

class Base:
    def __init_subclass__(cls, *args, **kw):
        super().__init_subclass__(*args, **kw)
        if not "__init__" in cls.__dict__:
            cls.__init__ = lambda self, *args, **kw: super(self.__class__, self).__init__(*args, **kw)
        cls.__init__.__annotations__.update(cls.__annotations__)
        if "__new__" not in cls.__dict__:
            cls.__new__ = lambda cls, *args, **kw: super(cls, cls).__new__(cls)
                cls.__new__.__annotations__.update(cls.__annotations__)

Python在继承时正确地更新了一个类' .__annotations__属性,因此即使这个简单的代码也可以继承(和多重继承) - 即使对于超类中定义的属性, __init____new__方法总是使用正确的注释进行设置。

从上面的python 3.7,您可以通过使用@dataclass并在实例字段中添加适当的类型提示来实现相同的效果。

https://docs.python.org/3/library/dataclasses.html

类型提示的屏幕截图

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM