8.1修改实例的字符串标识
讨论理解
__repr__()的标准做法是让它产生的字符串文本能够满足eval(repx(x)) == x。
或者产生一个一段有帮助意义的文本
没有定义__str__会用__repr__输出来备份
最后写一种%强制__repr__格式化输出
8.2自定义字符串的输出格式
像让对象通过foramt()函数和字符串方法支持自定义的输出格式。
讨论
__format__()方法在Python的字符串格式化功能中提供了一个钩子。需要强调的是,对格式化代码的解释完全取决与类本身。因此,格式化代码几乎可以为任何形式
8.3让对象支持上下问管理协议
我们想让对象支持上下文管理协议(context-management protocol, 通过with语句触发)
要让对象能够兼容with语句,必须实现__enter__()和__exit__()方法
讨论:
上下文管理器最常用于需要管理类似文件、网络链接和锁这样的资源程序中。
通过对前面的代码进行修改,可以实现嵌套式的with语句一次创建多个链接。
这样的情况下,可以在with里面嵌套式使用,一次创建多个连接。
8.4当创建大量实例时如何节省内存
问题:
当创建百万级别的实例,为此占用了大量的内存
解决方案:
可以在类里面增加__slot__属性,减少对内存的使用,这样每个实例不在创建一个__dict__的字典属性,而且围绕着一个固定长度的小型数组来构建,
__slot__中列出的属性名会在内部映射到这个数组特定的索引上,但使用了这个也就不能为实例添加新的属性了。
根据我实际的测试结果来看,内存的差距不大,可能数据标本不够大,书中的要求,要百万级别的实例
讨论
__slot__一般用的比较少,除非实例很多。__slot__可以阻止给用户实例添加新的属性,但这是__slot__带来的副作用,主要还时用来优化用的
8.5 将名称封装到类中
问题:我们想将'私有'数据分装到类的实例上,但是又需要考虑到Python缺乏对属性的访问控制问题
解决方案
我们一般单下划线的名称被认为属于内部使用,双下划线的名称会被重复名,是为了继承这样的属性不能通过继承而覆盖。
讨论:
一般我们命名私有方法属性,只要_单下划线就可以了,但如果设计到子类化处理,而且有些内部属性应该对子类进行影藏,那么此时就应该使用双下划线开头。
单变量名与保留字冲突可以在名字后面加下划线
8.6 创建可管理的属性
在对实例属性的获取和设定上,我们希望增加一些额外的处理过程。
解决方案
通过propety把方法当做属性来用
这是通过调用property复制类属性创建特性。
讨论
property属性实际上就时把一系列方法绑定在一起
但当我们访问property属性时会自动触发这些方法的调用。
如果对属性的读取与复制没有处理任何任务,不要写成下面的方式。
有几处不好,第一让代码变的啰嗦,第二让程序变换很多。
特别是如果稍后要对莫个属性增加额外的处理步骤的时候,在不修改代码的情况下,把特性名修成与实例属性名一样,这样访问一个属性的语法并不会改改变(即,访问普通属性与访问property属性的代码一样)
简单的在一个方法上加上@property,都能够简单的通过属性的方法对方法进行调用。
不要重复大量的出现重复性的property,可以通过流畅的Python书中介绍的,通过描述符或者特性工厂函数完成同样的任务。
8.7 调用父类中的方法
我们想调用一个父类中的方法,这个方法在子类中已经被覆盖。
解决方案
调用父类(超类)中的方法,可以使用super()函数完成。
讨论
super函数我前面写过,书中也写了,多重继承的时候,super比较好的是,就只能父类一次,而且是根据__mro__表执行。
当使用super()函数时,Python会继续从MRO中的写一个类开始搜索。只要每一个重新定义过的方法(也就是覆盖方法)都使用了super(),并且只调用了它一次,那么控制流最终就可以遍历整个MRO列表,并且让每个方法只会被调用一次。
书中下面的有一个案例非常有意思。下面上代码
A与B类完全没有关系,但A中的super居然调用到了B的spam,这一切就是MRO列表能解释
由于super()可能会调用到我们不希望调用的方法,那么这里有一些基本准则。
首相,确保在继承体系中所有相同的方法都有可兼容 调用签名(参数数量想同,参数名称也想用)。
如果super()尝试去调用非直接父类的方法,那么这就可以确保不会遇到麻烦。其次,确保最顶层的类实现了这个方法通常是个好主意。这个沿着MRO列表展开的查寻链会因为最终找到了事件的方法而终止。
8.8在子类中扩展属性
我们想在子类中扩展莫个属性的功能,而这个属性在父类已经被特性或者描述符定义了。
解决方案
讨论
因为属性被定义为(getter,setter,delete)的集合,而不仅仅时单独的方法,需要搞清楚时定义所有的方法还时定义其中的一个方法。
上面的例子是定义了全部方法。为了调用setter函数之前的实现,唯一能调用到这个方法的方式就时以类变量而不是实例变量的方式去访问。
后面通过描述符的写法更加好理解。
如果只想扩展属性中的一个方法,可以用下面的方法,而且这么做,前面定义过 属性方法都会拷贝过来。
最后通过描述符的实例赋值类属性来看父类调用,就更加容易理解了。
再次强调本章的关键:为了调用setter函数之前的实现,唯一能调用到这个方法的方式就时以父类变量而不是实例变量的方式去访问。
8.9创建一种新形式的类属性或实例属性。
问题:
想创建一种新形式的实例属性,可以拥有一些额外的功能,比如类型检查
解决方案。用描述符实例添加类属性。
讨论
描述符都提供了底层的魔法,包括@classmethod @staticmethod @property __slot__
描述符是高级程序库和框架的作者们锁使用 的最为重要的工具之一。
下面一个大神写的通过装饰器工厂函数,批量给类上属性,属性为描述符实例。
高手就时高手,通过了带参数的装饰器,传入参数,然后给类属性赋值,很好的学习到了
8.10 让属性具有惰性求值的能力
问题
想将一个只读的属性定义为property属性方式,只有在访问它时才参与计算。但一旦访问了该属性,希望把值缓存到对象属性,
解决方案
这里其实用到了描述符只有__get__属性的时候,会被同名的自身属性覆盖,流畅的Python书中有更加详细的介绍。流畅的Python书中没有案例,当时我自己能力有限,没能想出实现方式,这里有了。
通过测试来看,这个很想官方的property去掉了__set__与__delete__版本,当我加上了__set__以后,就成为了不可覆盖的类型,每次读取该属性,都会调用描述符。
讨论
由于前面的属性计算出来以后,可以被修改,这样会非常的不安全,书中还有一种计算属性赋值以后,就不会被修改的装饰器,我没看懂,现在测试看看。
上面我已经对书中的一些代码做了解释,应该89不离十正确,
这样写也可以,就更加明显了,返回了一个特性,特性里面的fget去执行具体逻辑。
8.11 简化数据结果的初始化过程
问题
我们编写了很多类,把它们当做数据结构来用。但是我们厌倦了编写高度重复且样式想同的__init__()函数
解决方案
通过一个父类,继承以后初始化实例属性,这种写法在Django里面好像看到过。
加入实例化参数的时候,输入的时候,有关键字传参,书中也有了完美的解决的方案。
高手就是高手
还有另外一种可能,通过利用关键字传入额外的属性。
讨论
如果编写的程序中有大量的小型数据结构,那么定义一个通用性的__init__()方法会特别有用。
对于属性的赋值,书中说明了为什么不能
因为如果子类有property属性验证的化,这样不安全,如果子类有__slot__的属性的话,拿直接报错了。
这个技术一个潜在的缺点就是会影响到IDE(集成开发环境)的文档和帮助功能。
help(Stock)
需要编写一个功能函数,逻辑还是很简单的,就是通过读取初始化函数里面的参数。
8.12定义一个接口或抽象基类。
问题
定义一个抽象基类,可以执行类型检查或者确保子类实现特定方法。
解决方案
书中还讲到了虚拟子类,这个其实流畅的Python书中讲的更加仔细,但我差不多忘了很多了,晕死
讨论
Python的collections模块中定义了很多容器很迭代器相关的抽象基类,numbers库中定义了和数值相关的抽象基类。io库中定义了和I/O处理相关的抽象基类。
尽管抽象基类使得类型检查变得更容易,但不应该在程序中过度使用它。Python的核心在于它使一种动态语言,它带来了极大的灵活性。如果处处都强制实行类型约束,则会使代码变得更加复杂,而这是不应该的。我们应该拥抱Python的灵活性。
8.13实现一种数据模型或类型系统
我们想定义各种各样的数据结构,但是对于某些特定属性,我们想对允许赋给它们的值添加一些限制
解决方法
书中三种方法,一种通过描述符类的继承,类装饰器,和元类
三种方式如果描述符用的少的化,我觉的第一种对方便,如果用的多,用元类也可以,第二种的装饰器写法写的感觉不好,通过装饰器给类属性赋值描述符实例有点麻烦。
讨论
书中的方法说,类装饰器可以提供最大的灵活性和稳健性,第一,这种解决方案步依赖与任何高级的机制,比如说元类。第二,装饰器可以很容易地根据需要在类定义上添加或者删除。
最后书中用了类装饰器的解决方案取代mixin、多重继承以及super()函数的使用。
说实话,这个代码,我要是看到会骂人,明明继承都够用了,用了匿名函数,还要用装饰器工厂,这是装逼大法的最高进阶了。
但,这个类装饰器的方案要比采用mixin的方案快几乎一倍以上。
8.14实现自定义的容器
问题
我们想实现一个自定义的类,用来模仿普通的内建容器类型比较列表或者字典的行为,但是我们不知道需要定义什么方法实现。
解决方案
按照书上的例子,继承父类定义一个Sequence,序列
看来只要定义了__getitem__和__len__就够了
序列书中测试,其实包含了索引、迭代、len、in甚至分片
讨论
书中最后定义了一个可变序列,我按照书中的样式进行抄写,熟悉。
定义了可变序列,几乎支持可变列表所有的核心方法。
8.15委托属性访问
问题
我们想在访问实例的属性时能够将其委托到一个内部持有的对象上,这可以作为继承的代替方案或者是为了实现一种代理机制
解决方案
解决方案就是简单的情况下,将一个实例赋值给另一个类的初始化属性,复杂一点可以通过定义__getattr__实现
先上最简单的模式:
在上通过__getattr__,如果没有这个属性的时候,使用该方法
相对复杂一点点的操作,可以通过委托给对象赋值属性,删除属性,读取属性
讨论
委托有时候可以做诶继承的代替方案,但更多的时候,委托跟继承是不同的
有时候直接使用继承可能没多大意思,或者我们想跟多的控制对象之间的关系(列如只暴露特定的方法、实现接口等),此时使用委托会很有用.
同样需要强调的是__getattr__()方法通常不实用与大部分名称以双下划线开头和结果的特殊方法。
实际应该写成这样
8.16 在类中定一个多个构建函数
讨论
我们正在编写一个类,但是想让用户能够以多种方式创建实例,而不是局限与__init__()提供的这一种
解决方案
使用类方法,用类装饰器,因为里面第一个参数为类本身
讨论
类方法可以继承给子类,这是非常好用的
还有就是定义一个有着多个构建函数的类时,应该让__init__()函数尽可能的简单,除了给属性赋值之外什么都不做。如果有其他需求,可以在其他备选的构造函数中选择更高级的操作。
这个也可以实现同样的效果,但再运行中,表达不清楚,代码维护也会不清晰
8.17 不通过调用init来创建实例
我们需要创建一个实例,但是处于某写原因想绕过__init__()方法,用别的方式来创建。
解决方法
通过__new__方法来实现,__new__方法时不用加类方法装饰器,第一个参数为类的方法
讨论
这个可以反序列化,前面就是把JSON数据转换成模型了。
通过setattr比通过__dict__方式写去属性要好,因为__slots__、property情况下,通过__dict__写入属性会报错,第二个会忽悠筛选。
8.18 用Mixin技术来扩展类定义。
问题:
我们由一些十分有用的方法,希望用他们来扩展其他类的功能。但是,需要添加方法的这些类之间并不一定属于继承关系。因此,没法将这些方法直接关联到一个共同的基类上。
解决方法
主要提供一个基础类以及一些可选的定制化方法。
下面上书中的Minix类
下面是一些简单的运行结果。
上面就可以看到mixin类与其他已有的类的混合。
讨论:
mixin类绝对不是为了直接实例化而创建的,mixin没有__init__,说明一般是没有状态属性的。
如果一定要定义一个拥有__init__()方法以及实例变量的的mixin类,有极大的风险,最好用*args, 关键字参数,**kwargs传参。
书中实例。
最后书中用了类装饰器,原理也比较简单,进去一个类,处理一个类,然后修改一些类定义的方法。
其实有点问题,在初始化的时候,没有激活__setitem__
8.19实现带有状态的对象或状态机
问题:
我们想实现一个状态机,或者让对象可以在不同的状态中进行操作,但是我们并步希望代码里会因此出现大量的条件判断
解决方案:
其实这个解决方案,我是真心看的好累,用一个类定义一种状态是书中的方法。
下面是书中觉的使用if条件下的状态。
书中用了一种更加优雅的方式,将每一种状态定义成一个单独的类,然后在Connection类中使用这些状态类
代码是比较优雅,但逻辑量是多了不少,后面还有更加骚的操作。
讨论
书中尽然通过修改实例的__class__修改实例所在的类,但实例换了所属于的类,它的实例方法都将进行改变。
我下面写一个小测试。
经过这样的测试,所以在状态切换的时候,可以通过self.__class__的切换,让self拥有不同的类的实例方法。
8.20调用对象上的方法,方法名以字符串的形式输出
问题:
我们想调用对象上的莫个方法,现在这个方法名保存在字符串中,我们想过它来调用
解决方案:
getattr相对比较熟悉,取出属性,是方法的话,就是可调用的属性,执行方法,填入参数。
后面还有5个小结,我粗粗看了一下,感觉还是比较难的。
8.21 实现访问者模式
问题
我们需要编写代码来处理或遍历一个由许多不同类型的对象组成的复杂数据结构,每种类型的对象处理的方式都不同。列如遍历一个树结构,根据遇到的树节点的类型执行不同的操作。
解决方法
书中使用了一个案例,一个嵌套的数学运算程序
通过递归的方式,剥洋葱一样,一层一层剥开。书中还有一个堆栈机的代码,看书不是很理解,先上上来看看。
讨论
本节涵盖了两个核心思想。首先是设计策略,既把操作复杂数据结构的代码和数据结构本身进行了解耦。也就是说,本节中没有任何一个Node类的实现有对数据进行操作。
相反,所有对数据的处理都放在特定的NodeVisito类中实现。这种隔离使得代码变得非常通用。
第二核心思想在于对访问者类本身的实现。通过一些小技巧再类中定义各种方法,讲层层包裹中的各种情况都囊括了,如果没有匹配就报异常。
8.22实现非递归的访问者模式
本章节是我看到现在最累的一个章节,用了到生成器函数,并通过列表对数据进行压栈与弹栈,看了一天了,其实还是有一些不明白,准备放弃中。
问题:
我们使用访问者模式来遍历一个深度嵌套的树结构,但由于超出了Python的递归限制而奔溃。我们想要去掉递归,但依旧保持访问者模式的编程风格。
解决方案:
讨论:
这里代码展示了如何利用生成器和协程来控制程序的执行流。
首先,再有关遍历树结构的问题中,为了避免使用递归,常见的策略就是利用栈或者列队来实现算法。
第二个要点在于生成器中yield语句的行为。
最后一个需要考虑的是如何传递结果。
其实对于这种设计我真的看的很晕,虽然感觉很高大上,但就我现在的水平,确实让我很晕,有机会回头再看。
8.23 在环装数据结构中管理内存
问题
我们的程序创建了环装的数据结构,但是再内存管理上遇到了麻烦。
解决方案
这是一个我弱引用的测试,在测试中,经常发现,变量已经删除,但弱引用里面的对象还存在,并不是马上释放弱引用里面的对象。
讨论
8.24 让类支持比较操作
问题
我们想使用标准的比较操作符(>,<)等在类实例之间进行比较,但是又不想编写大量的特殊方法
解决问题
用了一个functools里面的total_ordering装饰器
一般我们要用大于大于什么的,需要在里面定义__eq__,__lt__,__le__,__ge__,__gt__等特殊方法。
用了装饰器方便多了。
讨论
其实这个装饰器就帮你写了这么几句话
__le__ = lambda self, other: self < other or self == other
__gt__ = lambda self, other: not(self < other or self == other)
__ge__ .....
__ne__.....
8.25创建缓存实例
问题
当创建类实例时,我们想返回一个缓存引用,让其指向上一个用同样参数(如果有的话)创建出的类实例。
解决方法
讨论:
创建一个工厂函数,方便的创建弱引用类,缓存。
整个类这一章节结束了,除了那个遍历的树结构通过yield与列表进行弹栈与压栈比较难,另外的相对还是比较容易理解的。
下一个章节,将是元编程,这是一个难度不小的骨头,希望能够再10天能够啃完。