Effective python(三):类与继承

1,尽量使用辅助类来维护程序状态

  1. 在数据结构嵌套层数多了一层时(字典嵌套字典再嵌套元组的情况),应该考虑使用辅助类来拆解原有数据结构,提供更明确的接口,而不是继续使用字典或元组
  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)])
    
  1. 多个辅助类的拆解实现,代码量虽然是普通字典嵌套的两倍以上,但是这种程序理解更容易,也比普通嵌套字典的代码更清晰更容易扩展
    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形式的多态去构造对象

  1. @classmethod修饰的函数第一个参数为这个类,由系统自动传入,命名为cls,它一般用于构建实例并返回return cls(*arg),可以更加通用的在类方法内处理参数和数据,从而摆脱直接经过__init__方法。可以创建多个参数不同的@classmethod方法从而经过不同的调用构建出不同的实例
  2. 与@staicmethod区分,@staicmethod是可以从类上直接调用的方法

5,Super初始化父类

  1. 初始化父类可以在子类中使用父类.__init(self,value)的形式初始化,但复杂的继承会出现不可预料的问题,比如钻石继承模型可能会多次调用顶层父类导致出现问题
  1. super函数可以避免这一系列问题,它的解析顺序(深度优先,从左至右),保证顶层公共基类只会执行一次,注意:初始化运行顺序遵守mro,超类的初始化顺序与超类在class语句中出现的顺序相同
  1. mro执行顺序可以通过from pprint import pprint #换行 pprint(子类.mro())打印出初始化顺序,顺序是按照从底向上的方向执行的
  1. Python3的super简化写法super().__init__(value)

6,用mix-in工具类代替多重继承

  1. 类的__dict__属性存放全局变量,类方法等内容,而对象的__dict__只存放自身的属性如self.X
  2. 用mix-in组件创作多种工具,然后通过继承他们获得他们的功能,从而不必进行多重继承
  3. 能用mix-in组件实现的效果,就不要通过多重继承来做
  4. 将各种功能实现为可插拔的mix-in组件
  5. 将各种简单的行为封装到mix-in组件,然后通过组合就可以写出复杂的行为了,mix-in组件之间可以互相依赖

7,public and private属性

  1. 在类外直接访问私有字段会引发异常,如:ins.__private,但其实是Python对其名称做了变换,如上的私有属性python会变换成_ins__private,因此实际上还是可以访问的
  1. 类内的方法可以访问当前类的私有字段,但不能访问父类的私有字段
  1. 在继承的时候,使用get方法获取私有属性就会失效,原来的return self.__value在子类中就必须改成return self._父类名__value,但一旦更换继承,则父类名又需要重写,所以尽量不要使用private属性
  1. 只有子类不受自己控制(公共API),避免子类属性名和超类冲突时,可以在超类中合理使用private,避免公共API与子类属性名重复
  1. 可以多用protected属性即单下划线开头_p

8,继承collections.abc实现自定义容器类型

  1. 制定一个自定义的统计元素频率的列表类型,简单容器可以直接继承
    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
    
  1. 用下标访问序列中的元素时bar[0],python会把代码转译为bar.__getitem__(0)
  1. 为了避免新建一个类的时候需要实现count,index等一系列方法,可以从collections.abc中继承基类from collections.abc import Sequence,只需要实现几个基本的方法如__len__,剩余的方法会自动实现
posted @ 2020-03-24 21:10  石天放  阅读(210)  评论(0编辑  收藏  举报