从Python中的类元信息类型提示__init__函数
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从Python中的类元信息类型提示__init__函数相关的知识,希望对你有一定的参考价值。
我想做的是用它的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
的打字提示,
它在运行时的工作原理是通过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
实际上做了一些幕后魔法,特定于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)
它有效,但我没有提示,
如果我传递参数,我实际上得到一个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
从__annotations__
更新__init__
是去那里的正确方法。可以在基类上使用元类,classdecorator或适当的__init_subclass__
方法来实现。
然而,PyCharm提出这个警告应该被视为Pycharm本身的一个错误:Python已经记录了语言中的机制,以便object.__new__
将忽略类实例化(这是一个“类调用”)的额外参数,如果在任何一个中定义了__init__
继承链中的子类。在产生此警告时,pycharm的行为实际上与语言规范不同。
围绕它的工作是使用相同的机制来更新__init__
以创建具有相同签名的代理__new__
方法。但是,此方法必须吞下任何args本身 - 因此,如果您的类层次结构需要某个实际的__new__
方法,则获得正确的行为是一个复杂的边缘情况。
使用__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中的类元信息类型提示__init__函数的主要内容,如果未能解决你的问题,请参考以下文章
python中的__new__与__init__,新式类和经典类(2.x)