我想要做的是复制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
),{{ 1}}实际上是在做一些特别针对SQLAlchemy的幕后魔术,但我并不需要它那么好/好,我只是想能够以编程方式添加类型提示,从班级的元信息。
所以,简而言之,如果我有一个类,
PyCharm
我希望能够像上面的class Simple:
id: int = 0
name: str = ''
age: int = 0
一样初始化类,但也可以获得类型提示。我可以中途(功能),但不是类型提示。
如果我像SQLAlchemy那样设置它,那么
Simple(id=1, name='asdf')
它有效,但我没有提示类型,
如果我传递参数,我实际上会收到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
答案 0 :(得分:1)
从__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__
方法总是设置为即使对于超类中定义的属性,也要更正注释。