尽量用辅助类维护程序状态而不是字典或元组
#python字典可以很好的保存某个对象在其生命周期里的动态内部状态
class SimpleGradebook:
"""docstring for SimpleGradebook"""
def __init__(self): #双下划线是系统定义的,一般用户可以自己重写
self._grades = {} #单下划线是Python程序员使用类时的约定,表明程序员不希望类的用户直接访问属性。仅仅是一种约定!实际上,实例._变量,可以被访问。
def add_student(self,name):
self._grades[name] = [] #用个空列表
def report_grade(self,name,score):
self._grades[name].append(score)
def average_grade(self,name):
grades = self._grades[name]
return sum(grades) / len(grades)
book0 = SimpleGradebook()
book0.add_student('Jack')
book0.report_grade('Jack',90)
book0.average_grade('Jack')
#当有更多功能需要添加时就过于复杂了
#############【把嵌套结构重构为类】#################
#collections模块中的namedtuple(具名元组)类型非常适合实现,它能很容易定义出精简而又不可改变的数据类
import collections
Grade = collections.namedtuple('Grade',('score','weight'))#构建时,可以按位置指定或者关键字指定各项,这些字段都可通过属性名访问。注意引号
#科目的类,包含考试成绩
class Subject:
def __init__(self):
self._grades = []
def report_grade(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._subjects = {}
def subject(self,name):
if name not in self._subjects:
self._subjects[name] = Subject() #每个键值对中的名字对应一个课程类
return self._subjects[name]
def average_grade(self):
total,count = 0,0
for subject in self._subjects.values(): #dict.values()可以遍历所有的值,dict.keys()遍历所有的键。记住一定要加括号!
total += subject.average_grade()
count += 1
return total / count
#包含所有学生考试成绩的容器类,以学生名字为键,可动态添加学生
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]
#虽然上面这些类的代码量是原来那种的几倍,但是理解容易,且范例代码写起来也更清晰,更易扩展
book = Gradebook()
albert = book.student('Albert Einstein')
math = albert.subject('Math')
math.report_grade(80,0.10)
albert.average_grade()
'''
namedtuple的局限:
1.无法指定各参数的默认值。
2.其实例的各项属性依然可以通过下标及迭代访问。可能导致其他人以不符合设计者意图的方式使用这些元组,从而使以后很难把它迁移为真正的类。
'''
'''
不要使用包含其他字典的字典,也不要使用过长的元组
若容器中包含简单而又不可变的数据,则可先使用namedtuple来表示,待稍后有需要时再修改为完整的类
保存内部状态的字典如果变得比较复杂,那就应该把这些代码拆解为多个辅助类
'''
简单的接口应接受函数而不是类的实例
对于连接各种python组件的简单接口来说,通常应该给其直接传入函数,而不是先定义某个类,然后再传入该类的实例。
pyhton中的函数和方法都可以像一级类那样引用。所以也能放在表达式里。
通过名为__call__的特殊方法,可以使类的实例能够像普通的python函数那样得到调用
如果要用函数保存状态,那就应该定义新的类,并令其实现__call__方法,而不要定义带状态的闭包。
以@classmethod形式的多态通用地构建对象
#python中对象支持多态,类也支持多态
class GenericInputData(object):
def read(self):
raise NotImplementedError
@classmethod #这种形式的多态针对整个类
def generate_inputs(cls,config):
raise NotImplementedError
'''
Python程序中,每个类只能有一个构造器,即__init__方法。
通过@classmethod机制,可以用一种与构造器相仿的方式来构造类的对象。
通过类方法多态机制,我们能够以更加通用的方式来构建并拼接具体的子类。
'''
用super初始化父类
#初始化父类的传统方式,是在子类里用子类实例直接调用父类的__init__方法。
class MyBaseClass(object):
def __init__(self,value):
self.value = value
class MyChildClass(MyBaseClass):
def __init__(self):
def __init__(self):
MyBaseClass.__init__(self,5)
#这种办法对于简单的继承体系可行,但在许多情况下会出问题。
#使用内置的super函数
'''
python采用标准的方法解析顺序来解决超类初始化次序及钻石继承问题
总是应该使用内置的super函数来初始化父类
'''
mix-in组件&多重继承
能用mix-in组件实现的效果,就不要用多重继承来做。
将各功能实现为可插拔的mix-in组件,然后令相关的类继承自己需要的那些组件,即可定制该类实例所应具备的行为。
把简单的行为封装到mix-in组件里,然后就可以用多个mix-in组合出复杂的行为了。
多用public属性,少用private属性
python编译器无法严格保证private字段的私密性。
不要盲目地将属性设为private,而是应该一开始就做好规划,并允许子类更多的访问超类的内部API
应该多用protected属性,并在文档中把这些字段的合理用法告诉子类的开发者,而不要试图用private属性来限制子类访问这些字段。
只有当子类不受控制时,才可以考虑用private属性来避免名称冲突。
继承collections.abs以实现自定义的容器类型
#大部分python编程工作都是在定义类
#python中每一个类,某种程度上来说都是容器,都封装了属性与功能
#比如设计简单的序列,可以继承内置的list
#自定义列表统计各元素出现频率的方法
class FrequencyList(list):
"""docstring for FrequencyList"""
def __init__(self, members):
super(FrequencyList,self).__init__(members)
def frequency(self):
counts={}
for item in self:
counts.setdefault(item,0)
counts[item] += 1
return counts
foo = FrequencyList(['a','b','a','c','b','a','d'])
print('Length is', len(foo))
foo.pop()
print('After pop:',repr(foo))
#repr()将任意值转为字符串 / 函数得到的字符串通常可以用来重新获得该对象,repr()的输入对python比较友好。
#函数str() 用于将值转化为适于人阅读的形式,而repr() 转化为供解释器读取的形式。
#试了下,直接输出foo也是可以的
print('Frequency:',foo.frequency())
'''
输出:
Length is 7
After pop: ['a', 'b', 'a', 'c', 'b', 'a']
Frequency: {'a': 3, 'c': 1, 'b': 2}
'''
'''
如果要定制的子类很简单,就可以直接从python的容器类型(如list或dict)中继承。
想正确实现自定义的容器类型,可能需要编写大量的特殊方法。
编写自制的容器类型时,可以从collections.abc模块的抽象基类中继承,那些基类能够确保我们的子类具备适当的接口及行为。
'''
用纯属性取代get和set方法
#使用python强大的特性时,遵循最小惊讶原则(rule of le)
#即 这些机制只适合用来实现那些广为人知的python编程范式
'''
编写新类时,应该用简单的public属性来定义其接口,而不要手工实现set和get方法
如果访问对象的某个属性时,需要表现出特殊的行为,那就用@property来定义这种行为
@property方法应该遵循最小惊讶原则,而不应产生奇怪的副作用
@property方法需要执行得迅速一些,缓慢或复杂的工作,应该放在普通的方法里面
'''
考虑用@property代替属性重构
#实时计算当前所剩配额
@property
def quato(self):
return self.max_quota - self.quota_consumed
'''
python内置的@property修饰器,使开发者可以把类设计的很灵巧,令调用者能轻松访问该类的实例属性。
@property还有一种高级用法,可以把简单的数值属性迁移为实时计算(on-the-fly calculation,按需计算、动态计算)
我们只需要给本类添加新的功能即可,不需要修改原有代码。
在持续完善接口的过程中,也是一种重要的缓冲手段
'''
'''
@property可以为现有的实例属性添加新的功能
可以用@property来逐步完善数据模型
如果@property用的太频繁,那就应该考虑彻底重构该类并修改相关的调用代码
'''
用描述符改写需要复用的property方法
如果想复用@property方法及其验证机制,那么可以自己定义描述符类
WeakKeyDictionary可以保证描述符类不会泄露内存
通过描述符协议来实现属性的获取和设置操作时,不要纠结于__getattribute__的方法具体运作细节