| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> Python知识库 -> 流畅的python笔记(二十一)类元编程 -> 正文阅读 |
|
[Python知识库]流畅的python笔记(二十一)类元编程 |
目录 前言类元编程是指在运行时创建或定制类的技艺。python中类是一等对象,因此任何时候都可以使用函数新建类,而无需使用class关键字。类装饰器也是函数,不过能够审查、修改甚至把被装饰的类替换成其他类。 ? ? ? ? 最后,元类是类元编程最高级的工具:使用元类可以创建具有某种特质的全新类种,比如抽象基类。 ? ? ? ? 除非开发框架,否则不要编写元类! 一、类工厂函数最常见的类工厂函数比如collections.namedtuple,我们把一个类名和几个属性名传给这个函数,它就会创建一个tuple的子类,其中元素可以通过名称获取,即具名元组,也可以认为是不可变的字典。 ? ? ? ? 假设编写一个宠物店应用程序,创建一个狗类: 我们可以看到,在编写上面代码的时候,各个字段的名称都出现了三次,参数列表中一次,=号两边各一次,这样很麻烦,且字符串表示形式也不好。 参考collections.namedtuple,下面我们创建一个record_factory函数,即时创建简单的类(比如Dog),record_factory函数的用法如下所示:
把三个参数传给type是动态创建类的常用方式。通常我们把type当成函数用,type(my_object)获取对象所属的类,作用与my_object.__class__相同。然而type实际上是一个类,当成类使用时,传入三个参数可以新建一个类(type的三个参数分别是name、bases和dict,dict是一个字典,指定新类的属性名和值): 上述代码作用与下述代码相同: type类的实例是类,比如这里的MyClass类。 ????????用record_factory函数创建的类,其实例有个局限------不能序列化,即不能使用pickle模块中的dump/load函数处理。此问题暂不解决。 二、定制描述符的类装饰器在本书上一章节即第二十章处,LineItem示例还有问题尚待解决:储存属性的名称不具有描述性。即属性(如weight)的值存储在名为_Quantity#0的实例属性中,这样的名称不便调试。从托管实例中的描述符实例的storage_name属性能获取储存属性的名称: 如果储存属性的名称中能包含托管属性的名称更好: 上一章节中,之所以储存属性用了_Quantity#0泽中奇怪的名字,是因为实例化描述符时还无法得知托管属性的名称(比如weight)。但是一旦组建好整个类,而且把描述符绑定到类属性上之后,我们就可以审查类,并为描述符设置合理的储存属性名称。LineItem类的__new__方法可以做到这一点,因此在__init__方法中使用描述符时,储存属性已经设置了正确的名称。为了解决这个问题而使用__new__纯属白费力气:每次新建LineItem实例时都会运行__new__方法中的逻辑,可以,一旦LineItem类构建好了,描述符与托管属性之间的绑定就不会变了。即我们要在创建类时设置储存属性的名称,使用类装饰器或元类可以做到这一点。 ? ? ? ? 类装饰器与函数装饰器非常类似,是参数为类对象的函数,返回原来的类或修改后的类。 ?
类装饰器能以较简单的方式做到以前需要使用元类去做的事情------创建类时定制类。类装饰器有个重大缺点:只对直接装饰的类有效,这意味着被装饰的类的子类可能继承也可能不继承装饰器所作的改动,具体情况视改动的方式而定。 ? 三、导入时和运行时比较python程序员会区分导入时和运行时,但是这两个术语没有严格区分,且二者之间存在灰色地带。 ????????导入时,解释器会从上到下一次性解析完.py模块的源码,然后生成用于执行的字节码。如果句法有错误,就在此时报告。如果本地的__pycache__文件夹中有最新的.pyc文件,解释器会跳过上述步骤,因为已经有运行所需的字节码了。编译肯定是导入时的活动,但那个时期还会做些其他事,由于python中的语句几乎都是可执行的,也就是说语句可能会运行用户代码,修改用户程序的状态。尤其是import语句,它不只是声明,在进程中首次导入模块时,还会运行所导入模块中的全部顶层代码,以后导入相同的模块则使用缓存,只做名称绑定。那些顶层代码可以做任何事,包括通常在运行时做的事,例如连接数据库。因此导入时和运行时之间的界限是模糊的:import语句可以触发任何运行时行为。 ? ? ? ? 导入模块时,解释器会执行顶层的def语句,首次导入模块是,解释器会编译函数的定义体,把函数对象绑定到对应的全局名称上,但是解释器不会执行函数的定义体。通常着意味着解释器在导入时定义顶层函数,但是仅当在运行时调用函数时才会执行函数的定义体。 ? ? ? ? 对类来说,情况不同:在导入时解释器会执行每个类的定义体,甚至会执行嵌套类的定义体。执行类定义体的结果是,定义了类的属性和方法,并构建了类对象。从这个意义上理解,类的定义体属于顶层代码,因为它在导入时运行。 场景练习????????下面是几个场景练习,假设在evaltime.py脚本中导入了evalsupport.py模块: ? ? ? 场景1解答
对于场景1,有几点注意:
场景2解答
场景2主要表明类装饰器可能对子类没有影响。示例中ClassFour定义为ClassThree的子类。ClassThree类上依附的@deco_alpha装饰器把method_y方法替换了,但是这对ClassFour类根本没有影响,但是如果ClassFour.method_y方法使用super()调用ClassThree.method_y方法,那么便会看到装饰器起作用,执行inner_1函数,综上,类装饰器究竟能不能影响到子类,关键看子类的具体实现。 四、元类基础知识元类是用于构建类的类。根据python对象模型,类也是对象,因此类肯定是另外某个类的实例。默认情况下,python中的类是type类的实例,即type是大多数内置类和用户定义的类的元类。为了避免无限回溯,type是其自身的实例。 object类和type类之间的关系很神奇:object类是type类的实例,而type类是object类的子类。这种神奇的关系没办法用python代码表述,因为定义其中一个之前另一个必须存在。type是自身的实例这一点也很神奇。 ? ? ? ? 除了type,标准库中还有一些别的元类,例如ABCMeta和Enum,collections.Iterable是一个抽象类,也是ABCMeta类的实例,ABCMeta类又是type类的实例。所有类都直接或间接地是type的实例,不过只有元类同时也是type的子类,即元类从type类继承了构建类的能力。 ? ? 所有类都直接或间接地是type的实例,但是元类还是type的子类,因此可以作为制造类的工厂。具体地说,元类可以通过实现__init__方法定制实例,元类的__init__方法可以做到类装饰器能做的任何事情,但作用更大。 场景练习?? 场景3解答
python解释器在计算ClassFive类的定义体时直接调用了MetaAleph类的__init__方法,其有四个参数:
MetaAleph.__init__方法的定义体中定义了inner_2函数,然后将其绑定给cls.method_z。MetaAleph.__init__方法签名中的cls指代要创建的类(例如ClassFive),而inner_2函数签名中的self指代我们在创建的类的实例(例如ClassFive类的实例)。 场景4解答
ClassSix类没有直接引用MetaAleph类,但是收到了影响,因为它是ClassFive的子类,进而也是MetaAleph类的实例,所以由MetaAleph.__init__方法初始化。 五、定制描述符的元类
model_v7模块中要定义一个元类,而model.Entity是元类的实例。
? ? 六、元类的特殊方法__prepare__某些应用中,可能需要知道类的属性(包括数据属性与方法)定义的顺序。 ? ? ? ? type构造方法以及元类的__new__和__init__方法都会收到要计算的类的定义体,形式是名称到属性的映射,默认情况下该映射是字典。即,原来或类装饰器获取映射时,属性在类定义体中的顺序已经丢失了。 ? ? ? ? 该问题解决方法是python3引入的特殊方法__prepare__,该特殊方法只在元类中有用,且必须声明为类方法(@classmethod装饰器定义)。解释器调用元类的__new__方法之前会先调用__prepare__方法,使用类定义体中的属性创建映射。__prepare__方法的第一个参数是元类,第二个参数是要构建的类的名称,第三个参数是基类组成的元组,返回值必须是映射。元类构建新类时,__prepare__方法返回的映射会传给__new__方法的最后一个参数,然后再传给__init__方法。
七、类作为对象python数据模型为每个类定义了很多属性,常见的比如__mro__、__class__和__name__。
?dir()函数不会列出本节提到的任何一个属性。 ???????? ? ? ? ? ? ? |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/15 23:05:09- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |