《Effective Python 编写高质量Python代码的59个有效方法》学习笔记4

文章介绍了如何在Python中使用类和命名元组(namedtuple)来更好地管理程序状态,避免过度依赖字典和元组。提倡使用辅助类来维护复杂状态,利用collections.namedtuple创建精简且不可变的数据类。此外,文章讨论了类的多态性、初始化父类的最佳实践以及使用描述符改进property方法,强调了在设计类时考虑扩展性和代码可读性的重要性。
摘要由CSDN通过智能技术生成

尽量用辅助类维护程序状态而不是字典或元组

#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__的方法具体运作细节
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

python收藏家

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值