面向对象3

面向对象程序(Object Oriented Programming),简称OOP

面向对象VS面向过程

面向对象和面向过程的对比
名称 优缺点 使用场景
面向过程 以流程为核心,强调解决问题的过程。代码简单,对于代码量比较大的情况下维护相对比较麻烦,可扩展性比较差。 适合一些小的应用,如脚本
面向对象 具有相同属性和动作的结合体叫对象。代码比较多,复杂度高于面向过程。可维护性比较高,名称空间是独立的,可扩展性比较强。 适合大型和超大型应用

 

面向对象如何编写

面向对象编程方案:
1. 先构思
2. 写类
3. 创建对象

类是对事物的描述。相当于一张图纸,
对象:什么都是对象。python中万事万物皆为对象


类与对象的关系: 类是对事物的总结,抽象的概念。 类⽤用来描述对象的。对象是类的实例
化的结果。对象能执⾏哪些⽅法都由类来决定,类中定义了了什么. 对象就拥有什么。

  

面向对象的三大特性

  • 继承
  • 封装
  • 多台

封装

封装主要包括对属性和方法的封装
把很多数据封装到一个对象中。把固定的功能的代码封装到一个代码块中函数形成函数

class Person:

def __init__(self, name, age, gender):
    self.name = name
    self.age = age
    self.gender = gender # 对属性的封装

def play(self): # 对方法的封装
    print("%s play" % self.name)


p = Person('yn', 28, 'male')
p.play()
p.hooby = '旅游' # 对属性的封装,也可以在外面写
print(p.hooby)

  

继承

子类拥有父类除私有属性和私有方法以外的其他所有内容
作用:减少代码编写,优化代码。

 

多态

同一个对象多种形态。下面是一个多态的例子,不管是什么类都可以调用

class Butterfly:
    def fly(self):
        print("蝴蝶会飞")


class Mosquito:
    def fly(self):
        print("蚊子会飞")

class Eagle:
    def fly(self):
        print("老鹰会飞")


class Animal:
    def __init__(self, name):
        self.name = name

    def required_fly(self): # 不管是什么动物(类)我只要求会飞(方法)即可
        self.name.fly()


b = Butterfly()
m = Mosquito()
e = Eagle()

Animal(b).required_fly() # 蝴蝶会飞
Animal(m).required_fly() # 蚊子会飞
Animal(e).required_fly() # 老鹰会飞

多态性也叫鸭子模型,会嘎嘎叫就行。如上就是不管传进来的是Butterfly类型、Mosquito类型还是Eagle类型,只要有requeried_fly方法就行。

  

面向对象的成员

变量

 

在类中变量分为两大类:

  • 实例变量(普通字段)
  • 类变量(静态字段)

实例1:
实例变量属于对象的变量

```
class Person:
    country = "中国" # 叫类变量, 当一个字段为类中共有的字段的时候那么可以写成类的类变量

    def __init__(self, name, age, gender):
        self.name = name # 注意这个前面的name和后面的name完全不是一个概念,可以不相同,这只是大家管用的写法(前面name表示对象的变量,后面的为用户具体传入的值)
        self.age = age
        self.gender = gender # 叫实例变量

    def play(self):
        print("%s play" % self.name)


p = Person('yn', 28, 'male')
p.play()
print(p.country) # 通过对象去调用类变量
print(Person.country) # 直接通过类去调用,强烈建议通过类去调用类变量,省去了类的实例话的过程。

  

实例2:

class Person:
    country = "中国" # 静态字段, 当一个字段为类中共有的字段的时候那么可以写成类的静态字段

    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender # 普通字段

    def play(self):
        print("%s play" % self.name)


p2 = Person('lily', 18, 'female')
print(p2.country) # 中国
p2.country = "美国" # 修改实例变量,把实例变脸中的country改为美国
print(p2.country) # 美国 实例变量已经改过来了
print(Person.country) # 中国 ,啊,为啥类变量还没有改变过来呢?

p3 = Person('lucy', 18, 'female')
print(p3.country) # 中国, 新的实例化中country变量依然没有改变?为什么?

# 注意:通过实例化的对象去修改类变量只能修改当前实例的country变量,如果重新实例一个Person(xx,xx,xx),我们发现country的值并没有发生
# 类变量中的country也没有改过来,所以说类变量属于类的,无法通过实例变量对其进行修改,只能通过类变量对其进行修改


Person.country = "南非"
p4 = Person('Tom', 28, 'male')
print(p4.country) # 南非,已经变过来了


总结:类的变量只能通过类进行修改,类的变量调用强烈建议使用类进行修改    

 

方法

方法分为三种:

  • 实例(成员方法)方法
  • 类方法
  • 静态方法

1. 实例方法

实例方法从属于对象,实例方法第一参数是self,自动的把对象传给self

class Car:
# 实例方法第一参数是self, 自动的把对象传给self
    def run(self):
        print("车能跑")

obj = Car()
# 第一种调用方法
obj.run() # 车能跑

# 第二种调用方法
Car.run(obj) # 车能跑,强烈不建议这么做,除非万不得已
实例方法最为常见,使用的频率最高    

  

2. 类方法 

@classmethod 属于类的. 一般使用类名去访问. 对象也可以访问.

class Car:
    @classmethod# 第一个参数是规定死的,必须写cls表示当前的类
    def lease(cls):
        print(cls) # <class '__main__.Car'>


print(Car) # <class '__main__.Car'>
Car.lease() # # <class '__main__.Car'>
总结: 对于类方法,一般只会在使用当前类的使用才会使用。强烈建议使用类进行访问。

  

3. 静态方法

@staticmethod

class Car:
    def run(self): # 实例方法第一参数是self, 自动的把对象传给self
        print("车能跑")


    @staticmethod
        def work():  # 除了不能把self传进入,和普通的函数没有任何区别。
            print("拉客人")

Car.work() # 拉客人

总结:静态方法可以用类或者对象进行访问,建议用类进行调用。


使用场景:就是不想实例化某个类的时候还想用类里面的方法就可以使用静态方法

  

属性

属性存在的意义就是把方法变成一个变量, 只能传一个参数self

class Person:
    def __init__(self,name, birthday, gender):
        # 实例变量
        self.name = name 
        self.birthday = birthday
        self.gender = gender

        # 把方法变成属性值
    @property # age = 2019 - self.birthday
    def age(self):
        return 2019 - self.birthday

p = Person("佩奇", 1989, "男")
print(p.age) # 29

# 属性在调用的时候后面不需要加小括号,本质上也相当于在调用一个函数

  

私有

特点:私有的(包括方法,变量等)需要在前面加上双下划线(且后面没有双下划线)。不能被继承,不能在外部调用,只能在内部进行调用。

class Car:
    __product = "中国" # 定义了一个私有类变量

    def __init__(self, brand, displacement): # 注意这种不算,因为他的前后段都有下划线
        self.__brand = brand
        self.displacement = displacement

    def __run(self):
        print("汽车的排量为: %s" % self.displacement)

    @classmethod
    def call(cls):
        print(cls.__product)

    def test(self):
        self.__run()

class Jeep(Car):pass

obj = Car('大众', '2.0T')


1. 调用私有的类变量
# Car.__product # 错误,无法在外面调用。 AttributeError: type object 'Car' has no attribute '__product'
Car.call() # 正确, 通过类内部定义的call属性间接调用私有类变量__product


2. 调用私有的实例变量
# print(obj.__brand) # 错误, 无法在外面调用。AttributeError: 'Car' object has no attribute '__brand'


3. 调用私有的方法
# obj.__run() #错误, 无法在外面调用。 AttributeError: 'Car' object has no attribute '__run'
obj.test() # 汽车的排量为: 2.0T 正确,可以通过test方法间接的调用内部的私有__run方法


4. 通过继承调用私有方法
obj1 = Jeep('吉普车', '3.0T')
# obj1.__run() # 错误 无法继承私有方法。AttributeError: 'Jeep' object has no attribute '__run'

例外:
# 对象._类名+私有属性(对象.但下划线加私有属性),但是强烈建议这么去用
print(obj._Car__brand)
obj._Car__run()

总结:私有的无法在外面调用,也不能继承。


5. 私有方法的实现原理:
class Stu:
    __COUNTRY = "中国"


print(Stu.__dict__) # 注意私有变量的变化
{'__module__': '__main__', '_Stu__COUNTRY': '中国', '__dict__': <attribute '__dict__' of 'Stu' objects>, '__weakref__': <attribute '__weakref__' of 'Stu' objects>, '__doc__': None}

所以如果真想在外面调用私有方法也是可以:_类名__私有方法,但是及其不推荐!

  

特殊成员


1. 类名()会自动执行__init__()
2. 对象()会自动执行__call__()
3. 对象[key]会⾃动执行__getitem__()
4. 对象[key] = value 会⾃动执行__setitem__()
5. del对象[key]会自动执行__delitem__()
6. 对象+对象会自动执行__add__()
7. with对象as变量会⾃动执行__enter__和__exit__
8. 当对象为字符串时,打印对象的时候会自动执行__str__
9. 当对象为list时,打印对象会自动执行__repr__
10. 干掉可哈希 __hash__ == None 对象就不可哈希了了

 

类与类之间的关系

依赖关系

依赖(Dependency)描述的是一个类的引用用作另一个类的方法的参数

# 需求:假如你最近很累想去旅游,怎么办?

class Travel:
"""旅行"""
    def diary(self):
        print("写游记")

    def photo(self):
        print("拍照")

    def food(self):
        print("品美食")


class Person:
    def __init__(self):
        self.travel = Travel() # 旅游
        print(self.travel.__doc__)

obj = Person() # 旅行,这样就可以旅行了

试想一下如果某天旅行玩腻了,想去健身房怎么办,还想去干其他的事情等等。每次都需要需修改Person中的__init__()代码,
比较麻烦。怎么办呢?
我们可以这样去写?

class GymWorkOut:
"""健身房锻炼"""
    def run(self):
        print("跑步")

    def swim(self):
        print("游泳")

class Person:
    def __init__(self, obj):
        self.do = obj() # 把原来的Travel改obj,当要做的事情的类当作一个变量传进入,这种方式就是类与类的依赖关系,就很好的实现了解偶
        print(self.do.__doc__)


Person(Travel) # 去旅行
Person(GymWorkOut) # 去健身房

这种方式就很好的实现了解耦合。


解耦合的其他实现方式 设计模式中的观察者模式https://www.jianshu.com/p/730d7c5bd596
```

  

关联关系

关联(association)描述两个类之间行为的一般二元关系

关联关系
1. 一对一
一个人有一个手机号码

class Person:
    def __init__(self, name, age, gender,phone_num):
        self.name = name
        self.age = age
        self.gender = gender
        self.phone_num = phone_num


class PhoneNumber:
    def __init__(self, number):
        self.number = number

num = PhoneNumber(137431234132341234) # PhoneNumber实例化之后当作一个变量传入Person
p = Person('yn', 30, '男', num)

print(p.phone_num.number) 
# p.phone_num 获取的是 PhoneNumber 实例化之后的对象,对象中封装的有实例变量number,在通过.num即可取到手机号码


2. 一对多

# 一个人一般都会在多家公司工作过

class Person:
    """个人的类"""
    def __init__(self, name, age, gender, company_lst=[]):
        self.name = name
        self.age = age
        self.gender = gender
        self.company_lst = company_lst # 这个列表主要放的是实例化的Company()信息, [company1, company2,...]

    def display(self):
        """打印每个公司名称"""
        for company in self.company_lst:
            print(company.name) # 获取每个对象的name属性


class Company:
    """公司的类"""
    def __init__(self, name):
        self.name = name


person = Person("yn", 28, "男")

c1 = Company("百度")
c2 = Company("阿里")
c3 = Company("腾讯")
c4 = Company("网易")

person.company_lst.append(c1) # 把实例话的公司对象添加到company_lst列表中取
person.company_lst.append(c2)
person.company_lst.append(c3)
person.company_lst.append(c4)

person.display()
# 结果
百度
阿里
腾讯
网易

3.多对多
一个班级有多位老师,一个老师又可以带多个班级

思路上面类似

class Teacher:
    def __init__(self, name, class_lst=[]):
        """
        :param name: 老师的名字
        :param class_lst: 老师所带的班级列表
        """
        self.name = name
        self.class_lst = class_lst

    def display(self):
        for clas in self.class_lst:
            print(clas.name)


class ClassName:
    def __init__(self, name, teacher_lst=[]):
        """
        :param name: 班级的名称
        :param teacher_lst: 班级的老师列表
        """
        self.name = name
        self.teacher_lst = teacher_lst

    def display(self):
        for teacher in self.teacher_lst:
            print(teacher.name)

t1 = Teacher("张老师")
t2 = Teacher("王老师")
t3 = Teacher("李老师")

c1 = ClassName("C语言开发班")
c2 = ClassName("go语言开发班")


c1.teacher_lst.append(t1)
c1.teacher_lst.append(t2)
c1.teacher_lst.append(t3)
c1.display() # C语言开发班有几位老师

t1.class_lst.append(c1)
t1.class_lst.append(c2)
t1.display() # 张老师带了几个班

  

聚合关系


聚合(Aggregation)是一种特殊的强关联(Association)形式,表示两个对象之间的所属(has-a)关系。所有者对象称为聚合对象,它的类称为聚合类;从属对象称为被聚合对象,它的类称为被聚合类。聚合是整体和个体之间的关系
```
举例就像公司和个人的关系,公司有很多员工,员工从属于公司,但是公司不存在了也不会影响到个人

class Company:
    def __init__(self):
        self.__employees = []

    def add_employee(self, people):
        self.__employees.append(people)

    def remove_employee(self, people):
        self.__employees.remove(people)


class People:
    pass


p1 = People()
p2 = People()
p3 = People()

company = Company()
company.add_employee(p1)
company.add_employee(p2)
company.add_employee(p3)

  

组合关系

组合Composition) 关系表示整体由部分构成,但是当整体不存在时部分也不存在,是一种强依赖关系。

比如一个池塘中有鱼有虾,当把池塘不存在了,鱼和虾自然也就不存在了。

class Shrimp:
    """虾"""
    def __init__(self, name):
        self.name = name


class Fish:
    """鱼"""
    def __init__(self, name):
        self.name = name


class Pond:
    """池塘"""
    def __init__(self, shrimp_name, fish_name):
        self.shrimp = Shrimp(shrimp_name)
        self.fish = Fish(fish_name)


pond = Pond("小龙虾", "银龙")
print(pond.shrimp.name) # 小龙虾
print(pond.fish.name) # 银龙

del pond

print(pond.shrimp.name) # NameError: name 'pond' is not defined

  

继承关系


被继承的类成为基类、父类、超类;继承的类成为子类,一个子类可以继承父类的任何属性和方法。

1. 单继承 
class 类名(被继承的类)

class Fitness:
    """健身"""
    def __init__(self, place):
        self.__place = place

    def basketball(self):
        print("在%s打篮球" % self.__place)
        self.__maintain()

    def football(self):
        print("在%s踢足球" % self.__place)

    def __maintain(self):
        print("%s暂停维护" % self.__place)


class Engineer(Fitness):
    """工程师需要健身"""
    pass


Engineer("海淀").football() # 在海淀踢足球
# Engineer("西城区").__maintain() # 无法继承私有方法
Engineer("朝阳").basketball() # 可以借助实例的普通方法调用

# 执行结果
在海淀踢足球
在朝阳打篮球
朝阳暂停维护

2. 多继承
class 类名(被继承的类1, 被继承的类2, 被继承的类3,...)

class Fitness:
    """健身"""
    def basketball(self):
        print("打篮球健身")


class Study:
    """学习"""
    def basketball(self):
        print("学习打篮球")


class Engineer(Fitness, Study): # 继承时按照从左到右的顺序继承
    """工程师需要健身"""
    pass

e = Engineer()
e.basketball() # 优先执行Fitness
print(Engineer.mro()) # 查询类的继承顺序
# 顺序如下
[<class '__main__.Engineer'>, <class '__main__.Fitness'>, <class '__main__.Study'>, <class 'object'>]

注意:类的继承采用的是MRO(Method Resolution Order)算法。对于多层复杂的计划,建议查看mro算法,同时官方不建议设计非常复杂的继承,像菱形,
至于MRO算法详情见 https://www.python.org/download/releases/2.3/mro/

  

约束


约束的两种方式


假如你是产品经理,需要做个一个平台的登录接口。
平台登录接口分为:管理员登录,普通用户登录,游客登录。他们的登录方法是不一样的,
要求:类中必须包含login方法
只凭口头是不行的,必须要在代码中进行限制

class Normal:
    def login(self):
        print("普通用户登录")

class Guest:
    def login(self):
        print("游客登录")

class Admin:
    def admin_login(self):
        print("管理员登录")


def user_login(login_obj):
    login_obj.login()

 

normal = Normal()
guest = Guest()
admin = Admin()

user_login(normal)
user_login(guest)
user_login(admin) # AttributeError: 'Admin' object has no attribute 'login'

试想一下,如果在进行线上测试的时候出这个问题,多让人头疼。如何在开发的过程中解决这个问题呢?

  

第一种方式

class CommonLogin:
    def login(self):
        raise NotImplementedError("你还没有写login方法哦")

class Normal(CommonLogin):
    def login(self):
        print("普通用户登录")

class Guest(CommonLogin):
    def login(self):
        print("游客登录")

class Admin(CommonLogin):
    def admin_login(self):
        print("管理员登录")


def user_login(login_obj):
    login_obj.login()


normal = Normal()
guest = Guest()
admin = Admin()

user_login(normal)
user_login(guest)
user_login(admin) # NotImplementedError: 你还没有写login方法哦 直接就提示给开发者了
注意: 这里就是提前写一个CommonLogin,强制让程序们继承这个公共的类。在这个公共的类定义好一个login方法,
如果用户没有定义自己的login方法,那么在其调试的过程中应该就报错了,而不是在上线的时候才发现。

  

第二种方式

from abc import ABCMeta, abstractmethod

class CommonLogin(metaclass=ABCMeta):
    @abstractmethod
    def login(self):
        pass

class Normal(CommonLogin):
    def login(self):
        print("普通用户登录")

class Guest(CommonLogin):
    def login(self):
        print("游客登录")

class Admin(CommonLogin):
    def admin_login(self):
        print("管理员登录")


def user_login(login_obj):
    login_obj.login()



normal = Normal()
guest = Guest()
admin = Admin() # TypeError: Can't instantiate abstract class Admin with abstract methods login

user_login(normal)
user_login(guest)
user_login(admin)

总结:两种方式都可以实现对类的约束。
区别是当用户没有按照要求在类中定义login方法时,第一种方式是在实例化类之后调用用户登录的方法的时候报的错,第二方式则强制要求用户定义login方法,否则类根本就无法进行实例化。

 

subclass, isinstance, type

class Foo:pass

class Bar(Foo):pass

b = Bar()

type 精确的判断对象的类型
print(type(b)) # <class '__main__.Bar'> # 精确的返回一个对象的类型


issubclass(A, B) 判断A类是否为B类的子类
print(issubclass(Bar, Foo)) # True Bar是Foo的子类
print(issubclass(Foo, Bar)) # False Foo不是Bar的子类


isinstance(obj, cls) 判断对象是否为类的实例
print(isinstance(b, Bar)) # True b为Bar的实例
print(isinstance(b, Foo)) # True b也为Foo的实例, 可以向上找

  

self的详解

 

class Car:
    def __init__(self):
        print('init->', self) # self


obj = Car()
print('obj->', obj) # obj为当前的对象

# 执行结果
init-> <__main__.Car object at 0x1041a16d8>
obj-> <__main__.Car object at 0x1041a16d8>

# 可见self就是当前访问方法的那个对象
self为是什么?

 


实例1: 判断代码的执行结果

class Base:
    def __init__(self, num):
        self.num = num

    def func1(self):
        print(self.num)
        self.func2()

    def func2(self):
        print("Base.func2", self.num)


class Foo(Base):
    def func2(self):
        print("Foo.func2", self.num)

lst = [Base(1), Base(2), Foo(3)]
for obj in lst:
    obj.func1()

# 执行结果
# Base(1)
1
Base.func2, 1

# Base(2)
2
Base.func2, 2

# Foo(3)
3 
Foo.func2, 3 # 此时的self为Foo实例化的对象,当调用self.func2()时,依然优先执行自己的func2方法

  

新式类VS经典类


经典类和新式类

python2.x: 
  在python2.2之前默认使用经典类,典型类在基类的根如果蛇么都不写,表示继承xxx,python2.2之后出现了新式类,新式类的特点是基类的根是object。
  默认使用的经典类,只有在基类中写上object才会使用新式类。

 

python3.x:

  全部使用新式类,即使基类什么都不写,默认也会继承object

  经典类在多继承时采用MRO采用的深度优先原则,什么是深度优先?就是一条路走到头,然后再回头继续找写一个。

  新式类的MRO算法采用的是C3算法完成的(共同的类一般放最后找)。

 


                          6
                         ---
Level 3                 | O |                  (more general)
                      /  ---  \
                     /    |    \                      |
                    /     |     \                     |
                   /      |      \                    |
                  ---    ---    ---                   |
Level 2        3 | D | 4| E |  | F | 5                |
                  ---    ---    ---                   |
                   \  \ _ /       |                   |
                    \    / \ _    |                   |
                     \  /      \  |                   |
                      ---      ---                    |
Level 1            1 | B |    | C | 2                 |
                      ---      ---                    |
                        \      /                      |
                         \    /                      \ /
                           ---
Level 0                 0 | A |                (more specialized)
                           ---
O = object
class F(O): pass
class E(O): pass
class D(O): pass
class C(D,F): pass
class B(D,E): pass
class A(B,C): pass
print(A.mro())  # 对于复杂的继承可以通过 类.mro() 来进行查看
# [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.E'>, <class '__main__.F'>, <class 'object'>]
# A->B->C->D->E->F->O
示例1

把B类中继承的D和E调换一下顺序看看

                           6
                          ---
Level 3                  | O |
                       /  ---  \
                      /    |    \
                     /     |     \
                    /      |      \
                  ---     ---    ---
Level 2        2 | E | 4 | D |  | F | 5
                  ---     ---    ---
                   \      / \     /
                    \    /   \   /
                     \  /     \ /
                      ---     ---
Level 1            1 | B |   | C | 3
                      ---     ---
                       \       /
                        \     /
                          ---
Level 0                0 | A |
                          ---

 1 O = object
 2 class F(O): pass
 3 class E(O): pass
 4 class D(O): pass
 5 class C(D,F): pass
 6 class B(E,D): pass
 7 class A(B,C): pass
 8 
 9 print(A.mro()) # 
10 # [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.F'>, <class 'object'>]
11 # A->B->E->C->D->F->O
12 
13 具体的计算有自己的一套公式就是C3算法,详细请查看官网https://www.python.org/download/releases/2.3/mro/
14 
15 总结:新式类和经典类
16 经典类:多继承唯一的规则是深度优先,没有MRO,默认不继承object
17 新式类:广度优先,MRO搜索路径(C3算法),默认继承object
示例2

 

 

super

super()可以帮助我们执行MRO中下一个父类的方法,通常super()有两个地方使用:
1. 在单继承中可以直接访问父类的方法,而不用"父类.方法名" 进行访问,使代码易于维护。
2. 在动态执行环境中支持协作多重继承。

以下实例都是借用https://rhettinger.wordpress.com/2011/05/26/super-considered-super/,所以强烈建议阅读官方文档。

注意super()在python不同版本的写法
python2 super(类名, self)
python3 super() # python2的写法在python中依然是可以的

 

示例1.

假如我们想在用户每次更新字典的时候,记录一下日志。

import logging
import collections

logging.basicConfig(level='INFO')

class LoggingDict(dict): # 继承python内置字典
# Simple example of extending a builtin class
    def __setitem__(self, key, value):
        logging.info('Setting %r to %r' % (key, value)) # 
        super().__setitem__(key, value)
        # dict.__setitem__(key, value)

ld = LoggingDict([('red', 1), ('green', 2), ('blue', 3)])
print(ld)
ld['red'] = 10 # 默认日志输出到屏幕 INFO:root:Setting 'red' to 10

引入super的好处:
1. 隔离型,我们以后可以完全按照LoggingDict对需要这个功能的dict类型进行操作,和原生字典的功能一摸一样。

2.就是我们不必按照 dict.__setitem__(key, value)方式去写,因为这样写扩展性,非常差。
引入super之后的扩展性就非常强了,我们以后还可以用来引入其他的映射类型


class LoggingDict(SomeOtherMapping): # new base class
    def __setitem__(self, key, value):
        logging.info('Settingto %r' % (key, value))
        super().__setitem__(key, value) # no change needed

我们想让输出的字典有序该怎么做呢?
让我们利用这个来构建一个日志记录有序字典而不修改我们现有的类:

class LoggingOD(LoggingDict, collections.OrderedDict):
# Build new functionality by reordering the MRO
    pass

# 查看 LoggingOD 的MRO顺序
print(" => ".join(list(map(lambda x: x.__name__, LoggingOD.__mro__))))
# LoggingOD => LoggingDict => OrderedDict => dict => object
ld = LoggingOD([('red', 1), ('green', 2), ('blue', 3)])
ld['red'] = 10
print(ld) # LoggingOD([('red', 10), ('green', 2), ('blue', 3)]) 成功了

我们新类的MRO顺序是:LoggingOD,LoggingDict,OrderedDict,dict,object。对于我们来说,最重要的结果是,OrderedDict被插入后oggingDict之后和dict之前!
这意味着LoggingDict .__ setitem__中的super()调用现在将key/value更新调度到OrderedDict而不是dict。
想一想我们没有改变LoggingDict的源代码,我们只是构建了一个子类,其唯一的逻辑是组合两个现有的类并控制他们MRO搜索循序。是不是很强!

 

关于super的实用性的建议

1. 确保super()调用的方法存在

2. 调用者和被调用者需要具有匹配的参数签名
 详细了解inspect.signature, 如 def foo(a, *, b:int, **kwargs):pass  inspect.signature(foo) 返回为(a, *, b:int, **kwargs)

3.每次出现的方法都需要使用super()


可能的解决方案
1.对于位置参数签名的匹配
     a. 我们可以坚持使用固定的签名,这是用于像__setitem__这样的方法,他们具有两个参数的固定签名,一个key一个和value。LoggingDict
 示例中显示了此技术,其中__setitem__在LoggingDict和dict中具有相同的签名
      b. 一种更加灵活的方案是让父类树中的每个方式协同设置接受关键字的参数和关键字参数字典,删除它需要的任何参数,并使用**kwds转发剩余的
参数,最终将字典留空给最后一次调用
      每个级别剥离它需要的关键字参数,以便最终的空dict可以发送到一个根本不需要参数的方法(例如,object.__ init__期望零参数:
class Shape:
    def __init__(self, shapename, **kwds):
        self.shapename = shapename
        super().__init__(**kwds)

class ColoredShape(Shape):
    def __init__(self, color, **kwds):
        self.color = color
        super().__init__(**kwds)

cs = ColoredShape(color='red', shapename='circle')  # 每个类剥离一个参数

2. 对于如何确保super()调用的方法存在
上面的例子显示了最简单的情况。我们知道对象有一个__init__方法,并且该object始终是MRO链中的最后一个类,因此对super().__init__的任何调用
# 序列都保证以对__init__方法的调用结束。换句话说,我们保证super()调用的目标保证存在,并且不会因AttributeError而失败。
# 对于object没有感兴趣的方法(例如draw()方法)的情况,我们需要编写一个保证在object之前调用的根类。根类的责任就是在不使用super()进行
# 转发调用的情况下使用方法调用。
#
#Root.draw还可以使用 断言来使用 防御性编程,以确保它不会在链中稍后屏蔽某些其他draw()方法。如果子类错误地合并了一个具有draw()方法但
# 不从 Root继承的类,则可能会发生这种情况 。
class Root:
    def draw(self):
        # the delegation chain stops here
        assert not hasattr(super(), 'draw')

class Shape(Root):
    def __init__(self, shapename, **kwds):
        self.shapename = shapename
        super().__init__(**kwds)
    def draw(self):
        print('Drawing.  Setting shape to:', self.shapename)
        super().draw()

class ColoredShape(Shape):
    def __init__(self, color, **kwds):
        self.color = color
        super().__init__(**kwds)
    def draw(self):
        print('Drawing.  Setting color to:', self.color)
        super().draw()

cs = ColoredShape(color='blue', shapename='square')
cs.draw()

# 如果子类想要将其他类注入MRO,那么其他类也需要从Root继承,这样调用draw()的路径就不会在没有被Root.draw停止的情况下到达对象。
# 这应该清楚地记录下来,以便编写新的合作类的人知道从Root继承。这个限制与Python自己的要求没有太大的不同,
# 即所有新异常都必须从BaseException继承

3.上面显示的技术确保super()调用已知存在的方法并且签名是正确的; 但是,我们仍然依赖于在每一步调用super(),以便授权链继续不间断。
# 如果我们合作设计类,这很容易实现 - 只需对链中的每个方法添加一个super()调用。

# 上面列出的三种技术提供了设计可由子类组合或重新排序的协作类的方法。

 

 

Mixin

通过以上我们直到了多继承是有风险的,很容易造成换乱,如何解决这个问题呢。就是使用Mixin。Mixin也是利用python多继承的特性,实现了一种叫做Mixin的类。

1. Mixin类是单一职责的,无法单独使用
2. Mixin类对调用它的类(宿主类)一无所知

为什么叫Mixin类呢?
因为每个Mixin类都是一个独立的功能单元,无法单独使用,必须与其他类进行混合使用。

 

class Displayer(): # 单独的类
    def display(self, message):
        """展示要记录的日志"""
        print(message)


class LoggerMixin(): # 单独的Mixin类,无法单独使用
    """记录日志使用"""
    def log(self, message, filename='logfile.txt'):
        with open(filename, 'a') as fh:
        fh.write(message)

    def display(self, message):
        super().display(message)
        self.log(message)


class MySubClass(LoggerMixin, Displayer):
    def log(self, message):
    super().log(message, filename='subclasslog.txt')


subclass = MySubClass()
subclass.display("This string will be shown and logged in subclasslog.txt")

注意代码是如何运行的

  

来个难点的,关于super以及MRO的题,看你是否可以写出正确的执行结果

 

class Init(object):
    def __init__(self, v):
        print("init")
        self.val = v

class Add2(Init):
    def __init__(self, val):
        print("Add2")
        super(Add2, self).__init__(val)
        print(self.val)
        self.val += 2

class Mult(Init):
    def __init__(self, val):
        print("Mult")
        super(Mult, self).__init__(val)
        self.val *= 5

class HaHa(Init):
    def __init__(self, val):
        print("哈哈")
        super(HaHa, self).__init__(val)
        self.val /= 5

class Pro(Add2,Mult,HaHa): #
pass

class Incr(Pro):
    def __init__(self, val):
        super(Incr, self).__init__(val)
        self.val+= 1

# # Incr Pro Add2 Mult HaHa Init
# p = Incr(5)
# print(p.val)

c = Add2(2)
print(c.val)

写出运算结果

  

posted @ 2019-03-03 09:14  早晨我在雨中采花  阅读(189)  评论(0编辑  收藏  举报