Python惰性数据类,导入速度提高20倍

2020-05-13 17:21:01

Cluegen是一个允许您使用Python类型线索定义数据类的库。下面是一个如何使用它的示例:

生成的类以良好的文明方式工作,提供通常您必须手动键入的__init__()和__repr__()方法:

继承也同样有效--如果您在子类中添加新属性,它们将添加到现有属性中。例如:

在这一点上,反对者很快就会指出,好吧,实际上您可以使用标准库中的@dataclass。";Othe.rs migh.t help.Fully suggest.est usag.e of the attr.s library.ary.他们可能是有道理的。我的意思是,当然,你可以这样写你的类:

是。是的,如果你想让你的类导入缓慢,被1000多行错综复杂的装饰物魔法所包裹,并且不灵活,你可以这么做。或者你可以用线索!Cluegen很小,可扩展,提供了同样的记号便利,并且产生的类的移植速度大约快了20倍(有关基准,请参阅Perf.py文件)。

在幕后,Cluegen通过为__init__()和__repr__()等方法动态创建代码来工作。此代码看起来与您通常手写的代码完全相同。它与@DataClass装饰器创建的代码类型相同。不过,Cluegen的一个显著特征是,它的所有代码生成都是惰性的。也就是说,在程序执行过程中真正需要方法之前,不会生成任何方法。对于程序可能只使用已定义数据类的子集的情况,这大大减少了导入和启动时间。您也不会因为您不使用的功能而受到惩罚。即使确实使用了所有功能,它仍然比数据类快。哈哈!

线索可以通过有趣的方式进行自定义。例如,假设您想要将您自己的自定义代码生成方法添加到Datum类中。下面是你如何做到这一点的一个例子:

From Cluegen import Datum,Cluegen,All_Cluesclass Mytum(Datum):@cluegen def as_dict(Cls):Clues=all_Clues(Cls)return(';def as_dict(Self):\n';+';\n';\n';\n';.join(f';{key!r}):sel.{key},\n&#。)类点(Mytum):x:int y:int

在上面的示例中,类中提供了修饰后的as_dict()方法。在本例中,CLS将为Point。函数的作用是:从类中收集所有类型线索,包括基类中的线索。对于本例,它返回一个字典{';x';:int,';y';:int}。as_dict()返回的值是一个文本字符串,其中包含实际的as_dict()方法的实现,就像您手工编写它一样。此文本字符串执行一次,以创建替换修饰版本的方法。从这一点开始,类改用生成的代码。

Cluegen没有太多其他花哨的东西--整个实现大约有100行代码。它是你可以理解、修改和玩弄的东西。

曾经,有一个开发商。为了这个故事,让我们称他为戴夫。正如戴夫想做的那样,他喜欢编写编译器。编译器是一个很自然的地方,可以使用一些奇特的东西,比如adataclass--特别是对于所有的树结构。所以,戴夫就这么做了:

从数据类导入dataclass@dataclassclass节点:pass@dataclassclass表达式(节点):pass@dataclassclass语句(节点):pass@dataclassclass整数(表达式):value:int@dataclassclass BinOp(表达式):op:str左侧:表达式右侧:expression@dataclassclass UnaryOp(表达式):op:str操作数:expression@dataclassclass PrintStatement(语句。

这一切都很有效--实际上比预期的要好。然而,有一天,Dave认为给所有节点添加一个可选的行号属性会很有用。自然,这看起来像是可以在基类上轻松完成的事情:

戴夫想错了!如果这样做,数据类就会像火球一样爆炸。不,不是可选属性。不是,是基类。唉,唯一的解决方案似乎涉及复制一个lineno属性来结束每个类。如果Dave有一点关于线索的线索,他只需在__init__()的代码生成中添加一个小调整,就可以很容易地解决这个问题:

从线索导入数据,ALL_CLOES,CLOLEGEN类节点(DATUM):lineno=None@cluegen def__init__(Cls):Claes=all_Claes(CLS)args=';,';.join(f';{name}={getattr(cls,name)!r}';if hasattr(cls,name),而不是isinstance(getattr(CLS,name).Join(f';{name}={getattr(cls,name)!r}';if hasattr(cls,name)and not isinstance(getattr(CLS,name)。sel.{name}={name}';表示线索中的名称)Body+=';\n self.lineno=lineno';return f';def__init__(self,{args},*,lineno=None):\n{body}\n';class expression(Nodum):passclass语句(Nodump):passclass Integer(Expression):value:intclass BinOp(Expression):

这个故事的寓意是,线索代表了一种不同的力量--做你想做的事情的权力,而不是他们允许的权力。这一切都与你有关!

提供的Datum类为一组常见的defaultmethod生成代码。如果你想去一个完全不同的方向,你真的不需要使用这个。例如,假设您希望放弃类型提示,转而基于__槽__生成代码。下面是你如何做到这一点的一个例子:

从cluegen import DatumBase中,cluegendef all_slot(CLS):CLS中CLS的插槽=[]。__mro__:slot[0:0]=getattr(cls,';__slot_';,[])返回slotsclass Slotum(DatumBase):__slot__=()@cluegen def__init_(Cls):slot=all_slot。.join(插槽)+';):\n';+';\n';.join(f';sel.{name}={name}';for name in slot))@cluegen def__repr__(Cls):slot=all_slot(Cls)return(';def_repr__(Self):\n';+f';return f&#。,';.join(';%s={sel.%s!r}';%(name,name)表示插槽中的名称)+';)";';)。

一些字符串格式可能需要一些考虑。不过,这里有一个您如何使用Slotum的示例:

>;>;>;类积分(SLOTUM):.__插槽__=(';x';,';y';).。>;>;>;p=Point(2,3)>;>;>;pPoint(x=2,y=3)>;>;>;类Point3(点):.__插槽__=(';z';,).。>;>;>;p3=Point3(2,3,4)>;>;>;p3Point3(x=2,y=3,z=4)>;>;

Cluegen基于Python的描述符协议。简而言之,每当您访问类的属性时,Python都会查找实现魔术__get__()方法的对象。如果找到,它将使用关联的实例和类调用__get__()。Cluegen使用它在首次访问特殊方法(如__init__())时生成代码。这里有一个机器在运转的例子。

现在,看看类字典中的__init__()方法。您将看到这是某种奇怪的ClueGen实例:

此对象表示";未生成的";方法。如果您以任何方式触摸类上的__init__属性,您将看到Cluegenobject消失,并由适当的函数替换:

这是基本思想--第一次访问属性时生成代码。继承给方程式增加了额外的褶皱。假设您定义了一个子类:

在这里,您将看到ClueGen;对象返回到类字典。此外,当它第一次被访问时,它会被替换。下面是创建实例时在较低级别上发生的情况:

>;>;>;i=Point3.__dict__[';__init__';]>;>;>;i.__Get__(None,Point3)<;函数__init_位于0x102e20950>;>;Point3.__init__<;函数__init__在0x102e20950>;>;p=Point3(1,2,3)>;>;pPoint3(x=1,y=2,z=3)>;>;>;

要获得更多阅读,请查找有关Python描述符协议的信息。这与使对象系统的属性、类方法和其他功能工作的机制相同。

答:没有。类型仅仅是关于值可能是什么的线索,Python语言本身不提供任何强制。这些类型在执行类型检查或linting的IDE或第三方工具中可能很有用。不过,如果需要的话,您可以扩展提示来强制执行类型。

答:没有。Datum基类是一个普通的Python类。它定义了一个__init_subclass__()方法来协助子类的管理,但是除了__init__()、__repr_()、__iter_()和__eq__()等标准的特殊方法外,没有定义任何其他方法。Python的描述符协议用于驱动代码生成。

答:没有setup.py文件、安装程序或官方版本。您可以通过将代码复制到您自己的项目中来安装它。cluegen.py很小。我们鼓励您根据自己的目的对其进行复制和修改。

答:你应该把它读成“kuludg-in”,就像“runnin”或者“trippin”一样。所以,如果有人问你在做什么,你不能说“m usingcluegen”。不,你会说“我上了一些课”。真的在做。准确性很重要。