Effective python(三):类与继承
1,尽量使用辅助类来维护程序状态
- 在数据结构嵌套层数多了一层时(字典嵌套字典再嵌套元组的情况),应该考虑使用辅助类来拆解原有数据结构,提供更明确的接口,而不是继续使用字典或元组
- 一旦元组过长,意味着代码越来越复杂难以理解,可以使用collections中的namedtuple创建命名元组,定义格式:
collections.namedtuple(typename,field_names,verbose=False, rename=False)
命名元组的特有属性:
类属性 _fields:包含这个类所有字段名的元组 类方法 _make(iterable):接受一个可迭代对象来生产这个类的实例 实例方法 _asdict():把具名元组以 collections.OrdereDict 的形式返回,可以利用它来把元组里的信息友好的展示出来from collections import namedtuple # 定义一个namedtuple类型User,并包含name,sex和age属性。 User = namedtuple('User', ['name', 'sex', 'age']) # 创建一个User对象 user = User(name='Runoob', sex='male', age=12) # 获取所有字段名 print( user._fields ) #('name', 'sex', 'age') # 也可以通过一个list来创建一个User对象,这里注意需要使用"_make"方法 user = User._make(['Runoob', 'male', 12]) print( user ) # User(name='user1', sex='male', age=12) # 获取用户的属性 print( user.name ) # Runoob print( user.sex ) # male print( user.age ) # 12 # 修改对象属性,注意要使用"_replace"方法 user = user._replace(age=22) print( user ) # User(name='user1', sex='male', age=22) # 将User对象转换成字典,注意要使用"_asdict" print( user._asdict() ) # OrderedDict([('name', 'Runoob'), ('sex', 'male'), ('age', 22)])
- 多个辅助类的拆解实现,代码量虽然是普通字典嵌套的两倍以上,但是这种程序理解更容易,也比普通嵌套字典的代码更清晰更容易扩展
import collections Grade=collections.namedtuple('Grade',('score','weight')) class Subject(object): def __init__(self): self._grades=[] def report_grad(self,score,weight): self._grades.append(Grade(score,weight)) def average_grade(self): total,total_weight=0,0 for grade in self._grades: total+=grade.score*grade.weight total_weight+=grade.weight return total/total_weight class Student(object): def __init(self): self._subject={} def subject(self,name): if name not in self._subject: self._subject[name]=Subject() return self._subject[name] def average_grade(self): pass class Gradebook(object): def __init__(self): self._students={} def student(self,name): if name not in self._students: self._students[name]=Student() return self._students[name]
2,对于简单接口尽量,通常传入函数
3,对于带状态的闭包(即记录次数或者其它记录变量),也可以定义类用__call__然后再对函数参数传入实例实现会更清晰
4,以@classmethod形式的多态去构造对象
- @classmethod修饰的函数第一个参数为这个类,由系统自动传入,命名为cls,它一般用于构建实例并返回
return cls(*arg)
,可以更加通用的在类方法内处理参数和数据,从而摆脱直接经过__init__
方法。可以创建多个参数不同的@classmethod方法从而经过不同的调用构建出不同的实例 - 与@staicmethod区分,@staicmethod是可以从类上直接调用的方法
5,Super初始化父类
- 初始化父类可以在子类中使用
父类.__init(self,value)
的形式初始化,但复杂的继承会出现不可预料的问题,比如钻石继承模型可能会多次调用顶层父类导致出现问题
- super函数可以避免这一系列问题,它的解析顺序(深度优先,从左至右),保证顶层公共基类只会执行一次,注意:初始化运行顺序遵守mro,超类的初始化顺序与超类在class语句中出现的顺序相同
- mro执行顺序可以通过
from pprint import pprint #换行 pprint(子类.mro())
打印出初始化顺序,顺序是按照从底向上的方向执行的
- Python3的super简化写法
super().__init__(value)
6,用mix-in工具类代替多重继承
- 类的__dict__属性存放全局变量,类方法等内容,而对象的__dict__只存放自身的属性如
self.X
- 用mix-in组件创作多种工具,然后通过继承他们获得他们的功能,从而不必进行多重继承
- 能用mix-in组件实现的效果,就不要通过多重继承来做
- 将各种功能实现为可插拔的mix-in组件
- 将各种简单的行为封装到mix-in组件,然后通过组合就可以写出复杂的行为了,mix-in组件之间可以互相依赖
7,public and private属性
- 在类外直接访问私有字段会引发异常,如:
ins.__private
,但其实是Python对其名称做了变换,如上的私有属性python会变换成_ins__private
,因此实际上还是可以访问的
- 类内的方法可以访问当前类的私有字段,但不能访问父类的私有字段
- 在继承的时候,使用get方法获取私有属性就会失效,原来的
return self.__value
在子类中就必须改成return self._父类名__value
,但一旦更换继承,则父类名又需要重写,所以尽量不要使用private属性
- 只有子类不受自己控制(公共API),避免子类属性名和超类冲突时,可以在超类中合理使用private,避免公共API与子类属性名重复
- 可以多用protected属性即单下划线开头
_p
8,继承collections.abc实现自定义容器类型
- 制定一个自定义的统计元素频率的列表类型,简单容器可以直接继承
class Flist(list): def __init__(self,members): super().__init__(members) def frequency(self): counts={} for item in self: counts.setdefault(item,0) counts[item]+=1 return counts
- 用下标访问序列中的元素时
bar[0]
,python会把代码转译为bar.__getitem__(0)
- 为了避免新建一个类的时候需要实现count,index等一系列方法,可以从collections.abc中继承基类
from collections.abc import Sequence
,只需要实现几个基本的方法如__len__,剩余的方法会自动实现
可以直接留言交流问题或想法,每天都会看