当前位置: 首页 > news >正文

Python-类-面向对象-继承-多继承-学习笔记

欠4前年的一份笔记 ,献给今后的自己。

面向对象

语言的分类

面向机器
抽象成机器指令,机器容易理解

代表:汇编语言
面向过程

做一件事情,排出个步骤,第一步干什么,第二步于什么,如果出现情况A,做什么处理,如果出现了情况B,做什么处理。

问题规模小,可以步骤化,按部就班处理。
代表:C语言
面向对象OOP

随着计算机需要解决的问题的规模扩大,情况越来越复杂。需要很多人、很多部门协作,面向过程编程不太适合了。
代表:C++、Java、Python等

面向对象

什么是面向对象呢?
一种认识世界、分析世界的方法论。将万事万物抽象为类。
类class

类是抽象的概念,是万事万物的抽象,是一类事物的共同特征的集合。用计算机语言来描述类,就是属性和方法的集合。

对象instance、object

对象是类的具象,是一个实体。
对于我们每个人这个个体,都是抽象概念人类的不同的实体。

举例:
你吃鱼

你,就是对象;鱼,也是对象;吃就是动作

你是具体的人,是具体的对象。你属于人类,人类是个抽象的概念,是无数具体的个体的抽象。鱼,也是具体的对象,就是你吃的这一条具体的鱼。这条鱼属于鱼类,是无数的鱼抽象出来的概念。

吃,是动作,也是操作,也是方法,这个吃是你的动作,也就是人类具有的方法。如果反过来,鱼吃人。吃就是鱼类的动作了。吃,这个动作,很多动物都具有的动作,人类和鱼类都属于动物类,而动物类是抽象的概念,是动物都有吃的动作,但是吃法不同而已。

你驾驶车,这个车也是车类的具体的对象(实例),驾驶这个动作是鱼类不具有的,是人类具有的方法。

属性,它是对象状态的抽象,用数据结构来描述。
操作,它是对象行为的抽象,用操作名和实现该操作的方法来描述。
每个人都有名字、身高、体重等信息,这些信息是个人的属性,但是,这些信息不能保存在人类中,因为它是抽象的概念,不能保留具体的值。
而人类的实例,是具体的人,他可以存储这些具体的属性,而且可以不同人有不同的属性。

哲学

一切皆对象

对象是数据和操作的封装
对象是独立的,但是对象之间可以相互作用
目前00P是最接近人类认知的编程范式

面向对象3要素

  1. 封装
  • 组装:将数据和操作组装到一起。
  • 隐藏数据:对外只暴露一些接口,通过接口访问对象。比如驾驶员使用汽车,不需要了解汽车的构造细节,只需要知道使用什么部件怎么驾驶就行,踩了油门就能跑,可以不了解后面的机动原理。
  1. 继承:
  • 多复用,继承来的就不用自己写了
  • 多继承少修改,OCP(Open-closed Principle ),使用继承来改变,来体现个性
  1. 多态:
  • 面向对象编程最灵活的地方,动态绑定
    人类就是封装;
    人类继承自动物类,孩子继承父母特征。分为单一继承、多继承;
    多态,继承自动物类的人类、猫类的操作”吃"不同。

Python的类

定义

class ClassName:

语句块
1、必须使用class关键字
2、类名必须是用大驼峰命名
3、类定义完成后,就产生了一个类对象,绑定到了标识符ClassName上
举例

class MyClass:"""A example class"""x = 'abc'  # 类属性def foo(self):  # 类属性foo,也是方法return 'My Class'print(MyClass.x)        # abc 
print(MyClass.foo)      # <function MyClass.foo at 0x101204040>
print(MyClass.__doc__)  # A example class

类对象及类属性

  • 类对象,类的定义就会生成一个类对象
  • 类的属性,类定义中的变量和类中定义的方法都是类的属性
  • 类变量,上例中x是类MyClass的变量

MyClass中,X、foo都是类的属性,__doc__ 也是类的属性
foo方法是类的属性,如同 吃 是人类的方法,但是每一个具体的人才能吃东西,也就是说 吃 是人的实例才能调用的方法。
foo是方法对象method ,不是普通的函数对象function了,它一般要求至少有一个参数。第一个参数可以是self( self只是个惯用标识符,可以换名字),这个参数位置就留给了self.

self 指代当前实例本身

问题

上例中,类是谁?实例是谁?

实例化

a = MyClass()  # 实例化

使用上面的语法,在类对象名称后面加上一个括号,就调用类的实例化方法,完成实例化。实例化就真正创建一个该类的对象(实例)。例如

tom = Person()
jerry = Person()

上面的tom、jerry都是Person类的实例,通过实例化生成了2个实例。
每次实例化后获得的实例,是不同的实例,即使是使用同样的参数实例化,也得到不一样的对象。

Python类实例化后,会自动调用_nit_方法。这个方法第一个参数必须留给self,其它参数随意。

__init__方法

MyClass()实际上调用的是 __init__(seLf)方法,可以不定义,如果没有定义会在实例化后隐式调用。
作用:对实例进行初始化


class MyClass:
def \_\_init\_\_(self):
print( 'init')
print(MyClass) # 不会调用
print(MyClass())# 调用_init_
a = MyClass()# 调用_init_

__init__ (self, name, age) 初始化函数可以多个参数,请注意第一个位置必须是self,例如

class Person:def __init__(self, name, age):self.name = nameself.age = agedef showage(self):print('{} is {}'.format(self.name, self.age))tom = Person('Tom', 20)  # 实例化jerry = Person('Je', 25)print(tom.name, jerry.age)  # Tom 25jerry.age += 1print(jerry.age)  # 26jerry.showage()  # Je is 26

init 注意:__init__()方法不能有返回值,也就是只能是None

实例对象instance

类实例化后一定会获得一个对象,就是实例对象。

上例中的tom、jerry就是Person类的实例。

__init__ 方法的第一参数 self 就是指代某一个实例。
类实例化后,得到一个实例对象,实例对象会绑定方法,调用方法时采用jerry.showage()的方式。

但是函数签名是showage(self),少传一个参数self吗?
这个self就是jerry,Python会把方法的调用者作为作为第一参数self的实参传入。
self.name就是jerry对象的name,name是保存在了jerry对象上,而不是Person类上。所以,称为实例变量。

class Myclass:def __init__(self):print('self in init = {}'.format(id(self)))  # self in init = 4377122128c = Myclass()  # 会调用_initprint('c = {}'.format(id(c)))  # c = 4369028432

上例说明,self就是调用者,就是c对应的实例对象。

self这个名字只是一个惯例,它可以修改,但是请不要修改,否则影响代码的可读性

看打印的结果,思考一下执行的顺序,为什么?

实例变量和类变量

class Person:age = 3def __init__(self, name):self.name = nametom = Person('Tom')  # 实例化、初始化   
jerry = Person('Jerry')
print(tom.name, tom.age)   # tom = Person ('Tom", 20) #
print(jerry.name, jerry.age)        # Jerry 3
print(Person.age)       # 3
# print(Person.name) #
Person.age = 30
print(Person.age, tom.age, jerry.age)       # 30 30 30

实例变量是每一个实例自己的变量,是自己独有的;类变量是类的变量,是类的所有实例共享的属性和方法

在这里插入图片描述

注意:

Python中每一种对象都拥有不同的属性。函数、类都是对象,类的实例也是对象。

举例

class Person:age = 3def __init__(self, name):self.name = nameprint('----class----')print(Person.__class__)     # <class 'type'>print(sorted(Person.__dict__.items()), end='\n\n')  # 属性字典 [('__dict__', <attribute '__dict__' of 'Person' objects>), ('__doc__', None), ('__firstlineno__', 1), ('__init__', <function Person.__init__ at 0x104390040>), ('__module__', '__main__'), ('__static_attributes__', ('name',)), ('__weakref__', <attribute '__weakref__' of 'Person' objects>), ('age', 3)]tom = Person('Tom')print('----instance tom----')print(tom.__class__)         # <class '__main__.Person'>print(sorted(tom.__dict__.items()), end='\n\n')     # [('name', 'Tom')]print("----tom's class----")
print(tom.__class__.__name__)   # Personprint(sorted(tom.__class__.__dict__.items()), end='\n\n')  # [('__dict__', <attribute '__dict__' of 'Person' objects>), ('__doc__', None), ('__firstlineno__', 1), ('__init__', <function Person.__init__ at 0x104390040>), ('__module__', '__main__'), ('__static_attributes__', ('name',)), ('__weakref__', <attribute '__weakref__' of 'Person' objects>), ('age', 3)]

上例中,可以看到类属性保存在类的__dict__中,实例属性保存在实例的__dict__中,如果
从实例访问类的属性,就需要借助 __class__找到所属的类。
有了上面知识,再看下面的代码

class Person:age = 3height = 170def __init__(self, name, age=18):self.name = nameself.age = agetom = Person('Tom')  # 实例化、初始化jerry = Person('Jerry', 20)Person.age = 30print(Person.age, tom.age, jerry.age)  # 输出什么结果         30 18 20print(Person.height, tom.height, jerry.height)  # 输出什么结果                170 170 170jerry.height = 175print(Person.height, tom.height, jerry.height)  # 输出什么结果                    170 170 175
tom.height += 10print(Person.height, tom.height, jerry.height)  # 输出什么结果            170 180 175Person.height += 15print(Person.height, tom.height, jerry.height)  # 输出什么结果            185 180 175Person.weight = 70print(Person.weight, tom.weight, jerry.weight)  # 输出什么结果            70 70 70print(tom.__dict__['height'])  # 180# Traceback (most recent call last):
#   File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 37, in <module>
#     print(tom.__dict__['weight'])  # 可以吗
#           ~~~~~~~~~~~~^^^^^^^^^^
# KeyError: 'weight'
print(tom.__dict__['weight'])  # 可以吗

总结
是类的,也是这个类所有实例的,其实例都可以访问到;是实例的,就是这个实例自己的,通过类访问不到。

类变量是属于类的变量,这个类的所有实例可以共享这个变量。

实例可以动态的给自己增加一个属性。实例.__dict__[变量名〕和 实例.变量名 都可以访问到。

实例的同名变量会隐藏这类变量,或者说是覆盖了这个类变量。

实例属性的查找顺序
指的是实例使用.来访问属性,会先找自己的__dict__,如果没有,然后通过属性__class__
找到自己的类,再去类的__dict__ 中找
注意,如果实例使用__dict__[变量名]访问变量,将不会按照上面的查找顺序找变量了,这是指明使用字典的key查找,不是属性查找。

一般来说,类变量使用全大写来命名。

装饰一个类

回顾,什么是高阶函数?什么是装饰器函数?
思考,如何装饰一个类?
需求,为一个类通过装饰,增加一些类属性

#  增加类变量
def add_name(name, cls):cls.NAME = name  # 动态增加类属性# 改进成装饰器
def add_name(name):def wrapper(cls):cls.NAME = namereturn clsreturn wrapper@add_name('Tom')
class Person:AGE = 3print(Person.NAME)		# Tom

之所以能够装饰,本质上是为类对象动态的添加了一个属性,而Person这个标识符指向这个类对兔

类方法和静态方法

前面的例子中定义的 __init__ 等方法,这些方法本身都是类的属性,第一个参数必须是self,而self必须指向一个对象,也就是类必须实例化之后,由实例来调用这个方法。

普通函数

class Person:def normal_method():  # 可以吗?print('normal')# 如何调用
Person.normal_method()  # 可以吗?Person().normal_method()  # 可以吗?
print(Person.__dict__);

Person.normal_method()

可以,因为这个方法只是被Person这个名词空间管理的一个普通的方法,normal_method这是Person的一个属性而已。

由于normal_method在定义的时候没有指定self,所以不能完成实例对象的绑定,不能用Person().normal_method()调用。

注意:虽然语法是对的,但是,没有人这么用,也就是说禁止这么写

类方法

class Person:@classmethoddef class_method(cls):  # cls 是什么print('class ={0.__name__}({0})'.format(cls))cls.HEIGHT = 170Person.class_method()   # class =Person(<class '__main__.Person'>)print(Person.__dict__)  # {'__module__': '__main__', '__firstlineno__': 1, 'class_method': <classmethod(<function Person.class_method at 0x104acc040>)>, '__static_attributes__': (), '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None, 'HEIGHT': 170}

类方法

  1. 在类定义中,使用@classmethod装饰器修饰的方法
  2. 必须至少有一个参数,且第一个参数留给了cls,cls指代调用者即类对象自身
  3. cls这个标识符可以是任意合法名称,但是为了易读,请不要修改
  4. 通过cls可以直接操作类的属性

注意:无法通过cls操作类的实例。为什么?

类方法,类似于C++、Java中的静态方法

静态方法

class Person:@classmethoddef class_method(cls):  # cIs是什么print('class = {0.__name__} ({0})'.format(cls))cls.HEIGHT = 170@staticmethoddef static_methd():print(Person.HEIGHT)        # 170 Person.class_method()        # class = Person (<class '__main__.Person'>)
Person.static_methd()
print(Person.__dict__)   # {'__module__': '__main__', '__firstlineno__': 1, 'class_method': <classmethod(<function Person.class_method at 0x102860040>)>, 'static_methd': <staticmethod(<function Person.static_methd at 0x1027a72e0>)>, '__static_attributes__': (), '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None, 'HEIGHT': 170}

静态方法

1.在类定义中,使用@staticmethod装饰器修饰的方法
2. 调用时,不会隐式的传入参数
静态方法,只是表明这个方法属于这个名词空间。函数归在一起,方便组织管理。

方法的调用

类可以定义这么多种方法,究竟如何调用它们?

class Person:def normal_method():print('normal')def method(self):print("{}'s method".format(self))@classmethoddef class_method(cls):  # cls是什么print('class = {0.__name__} ({0})'.format(cls))     # class = Person (<class '__main__.Person'>)cls.HEIGHT = 170@staticmethoddef static_methd():print(Person.HEIGHT)   # 170print('~~类访问')print(1, Person.normal_method())  # 可以吗                 1 None# Traceback (most recent call last):
#   File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 22, in <module>
#     print(2, Person.method())  # 可以吗
#              ~~~~~~~~~~~~~^^
# TypeError: Person.method() missing 1 required positional argument: 'self'
# print(2, Person.method())  # 可以吗print(3, Person.class_method())  # 可以吗   3 None
print(4, Person.static_methd())  # 可以吗   4 None
print(Person.__dict__)      # {'__module__': '__main__', '__firstlineno__': 1, 'normal_method': <function Person.normal_method at 0x102f45440>, 'method': <function Person.method at 0x103050040>, 'class_method': <classmethod(<function Person.class_method at 0x1031139c0>)>, 'static_methd': <staticmethod(<function Person.static_methd at 0x103195080>)>, '__static_attributes__': (), '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None, 'HEIGHT': 170}
print('~~实例访问')
print('tom----')tom = Person()# Traceback (most recent call last):
#   File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 36, in <module>
#     print(1, tom.normal_method())  # 可以吗
#              ~~~~~~~~~~~~~~~~~^^
# TypeError: Person.normal_method() takes 0 positional arguments but 1 was given
# print(1, tom.normal_method())  # 可以吗# <__main__.Person object at 0x10542d550>'s method
# 2 None
print(2, tom.method())  # 可以吗# class = Person (<class '__main__.Person'>)
# 3 None
print(3, tom.class_method())  # 可以吗?# 170
# 4 None
print(4, tom.static_methd())  # 可以吗print('jerry----')jerry = Person()  ## Traceback (most recent call last):
#   File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 51, in <module>
#     print(1, jerry.normal_method())  # 可以吗
#              ~~~~~~~~~~~~~~~~~~~^^
# TypeError: Person.normal_method() takes 0 positional arguments but 1 was given
# print(1, jerry.normal_method())  # 可以吗# <__main__.Person object at 0x10147d1d0>'s method
# 2 None
print(2, jerry.method())  # 可以吗# class = Person (<class '__main__.Person'>)
# 3 None
print(3, jerry.class_method())  # 可以吗?# 170
# 4 None
print(4, jerry.static_methd())  # 可以吗

类几乎可以调用所有内部定义的方法,但是调用 普通的方法 时会报错,原因是第一参数必须是

类的实例。
实例也几乎可以调用所有的方法,普通的函数 的调用一般不可能出现,因为不允许这么定义。

总结:

类除了普通方法都可以调用,普通方法需要对象的实例作为第一参数。
实例可以调用所有类中定义的方法(包括类方法、静态方法),普通方法传入实例自身,静态方法和类方法需要找到实例的类。

访问控制

私有(Private)属性

class Person:def __init__(self, name, age=18):self.name = nameself.age = agedef growup(self, i=1):if i > 0 and i < 150:  # 控制逻辑self.age += ip1 = Person('tom')
p1.growup(20)  # 正常的范围
p1.age = 160  # 超过了范围,并绕过了控制逻辑
print(p1.age)  # 160

上例,本来是想通过方法控制属性,但是由于属性在外部可以访问,或者说可见,就可以直接绕过方法,直接修改这个属性。

Python提供了私有属性可以解决这个问题。
私有属性
使用双下划线开头的属性名,就是私有属性

class Person:def __init__(self, name, age=18):self.name = nameself.__age = agedef growup(self, i=1):if i > 0 and i < 150:  # 控制逻辑self.__age += ip1 = Person('tom')
p1.growup(20)  # 正常的范围# Traceback (most recent call last):
#   File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 13, in <module>
#     print(p1.__age)  # 可以吗
#           ^^^^^^^^
# AttributeError: 'Person' object has no attribute '__age'
print(p1.__age)  # 可以吗

通过实验可以看出,外部已经访问不到__age了,age根本就没有定义,更是访问不到。
那么,如何访问这个私有变量 __age呢?
使用方法来访问

class Person:def __init__(self, name, age=18):self.name = nameself.__age = agedef growup(self, i=1):if i > 0 and i < 150:  # 控制逻辑self.__age += idef getage(self):return self.__ageprint(Person('tom').getage())   # 18 

私有变量的本质

外部访问不到,能够动态增加一个 __码age呢?

class Person:def __init__(self, name, age=18):self.name = nameself.__age = agedef growup(self, i=1):if i > 0 and i < 150:  # 控制逻辑self.__age += idef getage(self):return self.__agep1 = Person('tom')
p1.growup(20)  # 正常的范围
# print(P1._age)# 访问不到
p1.__age = 28print(p1.__age)  # 28 print(p1.getage())  # 38 # 为什么年龄不一样?__age没有覆盖吗?
print(p1.__dict__)  # {'name': 'tom', '_Person__age': 38, '__age': 28}

秘密都在 __dict__中,里面是{age’:28,‘_Person_age’: 38, ‘name’: tom}

私有变量的本质:
类定义的时候,如果声明一个实例变量的时候,使用双下划线;Python解释器会将其改名,转换名称为_类名_变量名的名称,所以用原来的名字访问不到了。知道了这个名字,能否直接修改呢?

class Person:def __init__(self, name, age=18):self.name = nameself.__age = agedef growup(self, i=1):if i > 0 and i < 150:  # 控制逻辑self.__age += idef getage(self):return self.__agep1 = Person('tom')
p1.growup(20)  # 正常的范围
# print(P1._age)# 访问不到
p1.__age = 28print(p1.__age)     # 28 print(p1.getage())  # 38 
# 为什么年龄不一样?__age没有覆盖吗?
print(p1.__dict__)  # {'name': 'tom', '_Person__age': 38, '__age': 28}
#  直接修改私有变量p1._Person__age = 15print(p1.getage())   # 15 print(p1.__dict__)  # {'name': 'tom', '_Person__age': 15, '__age': 28}

从上例可以看出,知道了私有变量的新名称,就可以直接从外部访问到,并可以修改它。

保护变量

在变量名前使用一个下划线,称为保护变量。

class Person:def __init__(self, name, age=18):self.name = nameself._age = agetom = Person ('Tom')    
print (tom._age) # 18 
print (tom.__dict__) # {'name': 'Tom', '_age': 18}

可以看出,这个_age属性根本就没有改变名称,和普通的属性一样,解释器不做任何特殊处理。这只是开发者共同的约定,看见这种变量,就如同私有变量,不要直接使用。

私有方法

参照保护变量、私有变量,使用单下划线、双下划线命名方法。

class Person:def __init__(self, name, age=18):self.name = nameself._age = agedef _getname(self):return self.namedef __getage(self):return self._agetom = Person('Tom')
print(tom._getname())  # 没改名            Tom
# Traceback (most recent call last):
#   File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 15, in <module>
#     print(tom.__getage())  # 无此属性
#           ^^^^^^^^^^^^
# AttributeError: 'Person' object has no attribute '__getage'. Did you mean: '_getname'?
# print(tom.__getage())  # 无此属性print(tom.__dict__)     # {'name': 'Tom', '_age': 18}
print(tom.__class__.__dict__)   # {'__module__': '__main__', '__firstlineno__': 1, '__init__': <function Person.__init__ at 0x101178040>, '_getname': <function Person._getname at 0x1010bf2e0>, '_Person__getage': <function Person.__getage at 0x1013439c0>, '__static_attributes__': ('_age', 'name'), '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
print(tom._Person__getage())  # 改名了  18 

私有方法的本质

单下划线的方法只是开发者之间的约定,解释器不做任何改变。
双下划线的方法,是私有方法,解释器会改名,改名策略和私有变量相同,_类名_方法名。
方法变量都在类的 __dict__ 中可以找到。

私有成员的总结

在Python中使用_单下划线或者__双下划线来标识一个成员被保护或者被私有化隐藏起来。

但是,不管使用什么样的访问控制,都不能真正的阻止用户修改类的成员。Python中没有绝对的

安全的保护成员或者私有成员。

因此,前导的下划线只是一种警告或者提醒,请遵守这个约定。除非真必要,不要修改或者使

用保护成员或者私有成员,更不要修改它们。

补丁

可以通过修改或者替换类的成员。使用者调用的方式没有改变,但是,类提供的功能可能已经改变了.

猴子补丁(Monkey Patch):

在运行时,对属性、方法、函数等进行动态替换。

其目的往往是为了通过替换、修改来增强、扩展原有代码的能力。黑魔法,慎用。

test2.py
class Person:def get_score(self):# connect to mysqlret = {'English': 78, 'Chinese': 86, 'History': 82}return ret
test3.py
def get_score(self):return dict(name=self._class__name_, English=88, Chinese=90, History=85)

测试

# test1.py
from test2 import Person
from test3 import get_scoredef monkeypatch4Person():Person.get_score = get_scoremonkeypatch4Person()  # 打补丁if __name__ == '__main__':print(Person().get_score()) # {'name': 'Person', 'English': 88, 'Chinese': 90, 'History': 85}

上例中,假设Person类get_score方法是从数据库拿数据,但是测试的时候,不方便。使用猴子补丁,替换了get_score方法,返回模拟的数据。

属性装饰器

一般好的设计是:把实例的属性保护起来,不让外部直接访问,外部使用getter读取属性和setter方法设置属性。

class Person:def __init__(self, name, age=18):self.name = nameself._age = agedef age(self):return self._agedef set_age(self, age):self._age = agetom = Person('Tom')
print(tom.age())        # 18 
tom.set_age(20)
print(tom.age())  # 20 

通过age和set_age方法操作属性。
有没有简单的方式呢?

class Person:def __init__(self, name, age=18):self.name = nameself._age = age@propertydef age(self):return self._age@age.setterdef age(self, age):self._age = age@age.deleterdef age(self):# del self._ageprint('del')tom = Person('Tom')
print(tom.age)  # 18 
tom.age = 20
print(tom.age)  # 20del tom.age  # del

特别注意:使用property装饰器的时候这三个方法同名

property装饰器

后面跟的函数名就是以后的属性名。它就是getter。这个必须有,有了它至少是只读属性

setter装饰器

与属性名同名,且接收2个参数,第一个是self,第二个是将要赋值的值。有了它,属性可写

deleter装饰器
可以控制是否删除属性。很少用

property装饰器必须在前,setter、deleter装饰器在后。
property装饰器能通过简单的方式,把对方法的操作变成对属性的访问,并起到了一定隐藏效果

其它的写法
class Person:def __init__(self, name, age=18):self.name = nameself._age = agedef getage(self):return self._agedef setage(self, age):self._age = agedef delage(self):# del self._ _ageprint('del')age = property(getage, setage, delage, 'age property')tom = Person('Tom')print(tom.age)      # 18 tom.age = 20print(tom.age)      # 20 del tom.age   # del 
还可以如下
class Person:def __init__(self, name, age=18):self.name = nameself._age = ageage = property(lambda self: self._age)tom = Person('Tom')
print(tom.age)  # 18 
对象的销毁

类中可以定义 __del__方法,称为析构函数(方法)。

作用:销毁类的实例的时候调用,以释放占用的资源。其中就放些清理资源的代码,比如释放连接。

注意这个方法不能引起对象的真正销毁,只是对象销毁的时候会自动调用它。

使用del语句删除实例,引用计数減1。当引用计数为0时,会自动调用 __del__方法。

由于Python实现了垃圾回收机制,不能确定对象何时执行垃圾回收。

import timeclass Person:def __init__(self, name, age=18):self.name = nameself._age = agedef __del__(self):print('delete {}'.format(self.name))def test():tom = Person('tom')tom.__del__()       tom.__del__()tom.__del__()tom.__del__()print('======start======')tom2 = tomtom3 = tom2print(1, 'del')del tomtime.sleep(3)print(2, 'del')del tom2time.sleep(3)del tom3  # 注释一下看看效果time.sleep(3)print('=======end')test()
输出:
delete tom
delete tom
delete tom
delete tom
======start======
1 del
2 del
delete tom
=======end

由于垃圾回收对象销毁时,才会真正清理对象,还会再之前自动调用_del_方法,除非你明确知道自己的目的,建议不要手动调用这个方法。

方法重载(overload)

在其他面向对象的高级语言中,都有重载的概念。
所谓重载,就是同一个方法名,但是参数数量、类型不一样,就是同一个方法的重载。
Python没有重载!
Python不需要重载!

Python中,方法(函数)定义中,形参非常灵活,不需要指定类型(就算指定了也只是一个说明而非约束),参数个数也不固定(可变参数)。一个函数的定义可以实现很多种不同形式实参的调用。所以Python不需要方法的重载。

或者说Python本身就实现了其它语言的重载。

封装

面向对象的三要素之一,封装Encapsulation

封装

将数据和操作组织到类中,即属性和方法

将数据隐藏起来,给使用者提供操作(方法)。使用者通过操作就可以获取或者修改数据。

getter和setter.

通过访问控制,暴露适当的数据和操作给用户,该隐藏的隐藏起来,例如保护成员或私有成员。

练习

1、随机整数生成类
可以指定一批生成的个数,可以指定数值的范围,可以调整每批生成数字的个数

可以指定一批生成的个数,可以指定数值的范围
常规实现如下

import random# 1 普通类实现
class RandomGen:def __init__(self, start=1, stop=100, count=10):self.start = startself.stop = stopself.count = countdef generate(self, start=1, stop=100, count=10):return [random.randint(self.start, self.stop) for x in range(self.count)]# 2 作为工具类来实现,提供类方法
class RandomGen:@classmethoddef generate(cls, start=1, stop=100, count=10):return [random.randint(start, stop) for x in range(count)]

随机整数生成类,可以指定一批生成的个数,可以指定数值的范围,可以调整每批生成数字的个数。使用生成器实现,如下:

# 使用生成器实现1
import randomclass RandomGenerator:def __init__(self, start=1, stop=100, patch=10):self.start = startself.stop = stopself.patch = patchself._gen = self._generate()def _generate(self):while True:yield random.randint(self.start, self.stop)def generate(self, count=0):if count <= 0:return [next(self._gen) for _ in range(self.patch)]else:return [next(self._gen) for _ in range(count)]a = RandomGenerator()print(a.generate())         # [61, 80, 12, 44, 3, 38, 91, 82, 100, 10]
print(a.generate(5))            # [79, 23, 59, 99, 100]
另外一种实现方式
import randomclass RandomGenerator:def __init__(self, start=1, stop=100, patch=10):self.start = startself.stop = stopself.patch = patchself._gen = self._generate()def _generate(self):while True:yield [random.randint(self.start, self.stop) for _ in range(self.patch)]def generate(self, count=0):if count > 0:self.patch = countreturn next(self._gen)a = RandomGenerator()
print(a.generate())     # [32, 2, 76, 13, 21, 47, 11, 77, 96, 34]
print(a.generate(5))    # [38, 77, 95, 16, 51]
使用property
import randomclass RandomGenerator:def __init__(self, start=1, stop=100, patch=10):self.start = startself.stop = stopself._patch = patchself._gen = self._generate()def _generate(self):while True:yield [random.randint(self.start, self.stop) for _ in range(self.patch)]def generate(self):return next(self._gen)@propertydef patch(self):return self._patch@patch.setterdef patch(self, value):self._patch = valuea = RandomGenerator()
print(a.generate())     # [77, 66, 33, 82, 48, 85, 60, 37, 79, 85]
a.patch = 5
print(a.generate())     # [3, 78, 10, 80, 15]

2、打印坐标
使用上题中的类,随机生成20个数字,两两配对形成二维坐标系的坐标,把这些坐标组织起来,并打印输出

import randomclass RandomGenerator:def __init__(self, start=1, stop=100, patch=10):self.start = startself.stop = stopself._patch = patchself._gen = self._generate()def _generate(self):while True:yield [random.randint(self.start, self.stop) for _ in range(self.patch)]def generate(self):return next(self._gen)@propertydef patch(self):return self._patch@patch.setterdef patch(self, value):self._patch = valueclass Point:def __init__(self, x, y):self.x = xself.y = ypoints = [Point(x, y) for x, y in zip(RandomGenerator(10).generate(), RandomGenerator(10).generate())]for p in points:print('{}:{}'.format(p.x, p.y))
输出:
91:15
94:66
80:10
29:78
41:72
100:34
100:99
68:12
76:81
48:20

3、车辆信息
记录车的品牌mark、颜色color、价格price、速度speed等特征,并实现增加车辆信息、显示全部车辆信息的功能

class Car:  # 记录单一车辆def __init__(self, mark, speed, color, price):self.mark = markself.speed = speedself.color = colorself.price = priceclass CarInfo:def __init__(self):self.info = []def addcar(self, car: Car):self.info.append(car)def getall(self):return self.infoci = CarInfo()
car = Car('audi', 400, 'red', 100)
ci.addcar(car)# 返回所有数据,此时在实现格式打印for item in ci.getall():print(item.mark, item.speed, item.color, item.price)    # audi 400 red 100

4、实现温度的处理
在这里插入图片描述

思路
假定一般情况下,使用摄氏度为单位,传入温度值。
如果不给定摄氏度,一定会把温度值转换到摄氏度。
温度转换方法可以使用实例的方法,也可以使用类方法,使用类方法的原因是,为了不创建对
象,就可以直接进行温度转换计算,这个类设计像个温度工具类。

class Temperature:def __init__(self, t, unit='c'):self._c = Noneself._f = Noneself._k = None# # 都要先转换到摄氏度,以后访问再计算其它单位的温度值if unit == 'k':self._k = tself._c = self.k2c(t)elif unit == 'f':self._f = tself._c = self.f2c(t)else:self._c = t@propertydef c(self):  # 摄氏度return self._c@propertydef k(self):  # 开氏温度if self._k is None:self._k = self.c2k(self._c)return self._k@propertydef f(self):  # 华氏温度if self._f is None:self._f = self.c2f(self._c)return self._f# 温度转换@classmethoddef c2f(cls, c):return 9 * c / 5 + 32@classmethoddef f2c(cls, f):return 5 * (f - 32) / 9@classmethoddef c2k(cls, c):return c + 273.15@classmethoddef k2c(cls, k):return k - 273.15@classmethoddef f2k(cls, f):return cls.c2k(cls.f2c(f))@classmethoddef k2f(cls, k):return cls.c2f(cls.k2c(k))print(Temperature.c2f(40))  # 104.0
print(Temperature.c2k(40))  # 313.15
print(Temperature.f2c(104.0))  # 40.0
print(Temperature.k2c(313.15))  # 40.0
print(Temperature.k2f(313.15))  # 104.0
print(Temperature.f2k(104))  # 313.15
t = Temperature(37)
print(t.c, t.k, t.f)  # 37 310.15 98.6
t = Temperature(300, 'k')
print(t.c, t.k, t.f)  # 26.850000000000023 300 80.33000000000004

5、模拟购物车购物

思路
购物车购物,分解得到两个对象 购物车、 物品,一个操作 购买。
购买不是购物车的行为,其实是人的行为,但是对于购物车来说就是 增加add。
商品有很多种类,商品的属性多种多样,怎么解决?
购物车可以加入很多不同的商品,如何实现?

class Color:RED = 0BLUE = 1GREEN = 2GOLDEN = 3BLACK = 4OTHER = 1000class Item:def __init__(self, **kwargs):self.__spec = kwargsdef __repr__(self):return str(sorted(self.__spec.items()))class Cart:def __init__(self):self.items = []def additem(self, item: Item):self.items.append(item)def getallitems(self):return self.itemsmycart = Cart()myphone = Item(mark='Huawei', color=Color.GOLDEN, memory='4G')mycart.additem(myphone)mycar = Item(mark='Red Flag', color=Color.BLACK, year=2017)mycart.additem(mycar)print(mycart.getallitems()) #[[('color', 3), ('mark', 'Huawei'), ('memory', '4G')], [('color', 4), ('mark', 'Red Flag'), ('year', 2017)]]

注意,以上代码只是一个非常简单的一个实现,生产环境实现购物车的增删改查,要考虑很多。

类的继承

基本概念
面向对象三要素之一,继承Inheritance

人类和猫类都继承自动物类。

个体继承自父母,继承了父母的一部分特征,但也可以有自己的个性。
在面向对象的世界中,从父类继承,就可以直接拥有父类的属性和方法,这样可以减少代码、多复用。子类可以定义自己的属性和方法。

看一个不用继承的例子

class Animal:def shout(self):print('Animal shouts')a = Animal()a.shout()   # Animal shoutsclass Cat:def shout(self):print('Cat shouts')c = Cat()
c.shout()   # Cat shouts

上面的2个类虽然有关系,但是定义时并没有建立这种关系,而是各自完成定义。
动物类和猫类都有吃,但是它们的吃有区别,所以分别定义。

class Animal:def __init__(self, name):self._name = namedef shout(self):  # 一个通用的吃方法print('{} shouts '.format(self.__class__.__name__))@propertydef name(self):return self._namea = Animal('monster')
a.shout()       # Animal shouts class Cat(Animal):passcat = Cat('garfield')
cat.shout()     #Cat shouts print(cat.name)     # garfieldclass Dog(Animal):passdog = Dog('ahuang')
dog.shout() #Dog shouts 
print(dog.name)     # ahuang

上例可以看出,通过继承,猫类,狗类不用写代码,直接继承了父类的属性和方法。

继承
class Cat(Animal) 这种形式就是从父类继承,括号中写上继承的类的列表。

继承可以让子类从父类获取特征(属性和方法)
父类

Animal就是Cat的父类,也称为基类、超类。
子类

Cat就是Animal的子类,也称为派生类。

定义
class 子类名(基类1[,基类2...]):
语句块

如果类定义时,没有基类列表,等同于继承自object。在Python3中,object类是所有对象的根基类。

class A:
pass
# 等价于
class A(object):
pass

注意,上例在Python2中,两种写法是不同的。
Python支持多继承,继承也可以多级。
查看继承的特殊属性和方法有

在这里插入图片描述

继承中的访问控制

class Animal:__COUNT = 100HEIGHT = 0def __init__(self, age, weight, height):self.__COUNT += 1self.age = ageself.__weight = weightself.HEIGHT = heightdef eat(self):print('{} eat'.format(self.__class__.__name__))def __getweight(self):print(self.__weight)@classmethoddef showcount1(cls):print(cls.__COUNT)@classmethoddef _showcount2(cls):print(cls.__COUNT)def showcount3(self):print(self.__COUNT)class Cat(Animal):NAME = 'CAT'__COUNT = 200# c= Cat()# _init_函数参数错误
c = Cat(3, 5, 15)c.eat()     # Cat eat
print(c.HEIGHT)     # 15  
# print(C.__COUNT)#私有的不可访问
# c•showweight()#私有的不可访问
c.showcount1()      # 100
# c. showcount2()#私有的不可访问
c.showcount3()      # 101print(c.NAME)  # CATprint("{}".format(Animal.__dict__)) # {'__module__': '__main__', '__firstlineno__': 1, '_Animal__COUNT': 100, 'HEIGHT': 0, '__init__': <function Animal.__init__ at 0x1044a1440>, 'eat': <function Animal.eat at 0x1045b0040>, '_Animal__getweight': <function Animal.__getweight at 0x1046779c0>, 'showcount1': <classmethod(<function Animal.showcount1 at 0x104805080>)>, '_showcount2': <classmethod(<function Animal._showcount2 at 0x104805300>)>, 'showcount3': <function Animal.showcount3 at 0x104ba1580>, '__static_attributes__': ('HEIGHT', '__weight', 'age'), '__dict__': <attribute '__dict__' of 'Animal' objects>, '__weakref__': <attribute '__weakref__' of 'Animal' objects>, '__doc__': None}
print("{}".format(Cat.__dict__))    # {'__module__': '__main__', '__firstlineno__': 29, 'NAME': 'CAT', '_Cat__COUNT': 200, '__static_attributes__': (), '__doc__': None}
print(c.__dict__)                   # {'_Animal__COUNT': 101, 'age': 3, '_Animal__weight': 5, 'HEIGHT': 15}
print(c.__class__.mro())            # [<class '__main__.Cat'>, <class '__main__.Animal'>, <class 'object'>]

从父类继承,自己没有的,就可以到父类中找。
私有的都是不可以访问的,但是本质上依然是改了名称放在这个属性所在类的了__dict__中。知道
这个新名称就可以直接找到这个隐藏的变量,这是个黑魔法技巧,慎用。
总结
继承时,公有的,子类和实例都可以随意访问;私有成员被隐藏,子类和实例不可直接访问,当
私有变量所在的类内的方法中可以访问这个私有变量。
Python通过自己一套实现,实现和其它语言一样的面向对象的继承机制。

属性查找顺序
实例的__dict__》类_dict_如果有继承==》父类_dict
如果搜索这些地方后没有找到就会抛异常,先找到就立即返回了。

方法的重写、覆盖override

class Animal:def shout(self):print('Animal shouts')class Cat(Animal):#  覆盖了父类方法def shout(self):print('miao')a = Animal()
a.shout()       # Animal shouts
c = Cat()
c.shout()       # miao
print(a.__dict__)       # {}
print(c.__dict__)       # {}
print(Animal.__dict__)  # {'__module__': '__main__', '__firstlineno__': 1, 'shout': <function Animal.shout at 0x102c70040>, '__static_attributes__': (), '__dict__': <attribute '__dict__' of 'Animal' objects>, '__weakref__': <attribute '__weakref__' of 'Animal' objects>, '__doc__': None}print(Cat.__dict__) # {'__module__': '__main__', '__firstlineno__': 6, 'shout': <function Cat.shout at 0x102bb72e0>, '__static_attributes__': (), '__doc__': None}
# Animal shouts
# miao

Cat中能否覆盖自己的方法吗?

class Animal:def shout(self):print('Animal shout')class Cat(Animal):#  覆盖了父类方法def shout(self):print('miao')# 覆盖了自身的方法,显式调用了父类的方法def shout(self):print(super())print(super(Cat, self))super().shout()super(Cat, self).shout()  # 等价于super()self.__class__.__base__.shout(self)  # 不推荐a = Animal()a.shout()        # Animal shoutc = Cat()
c.shout()print(a.__dict__)
print(c.__dict__)
print(Animal.__dict__)
print(Cat.__dict__)输出:
Animal shout
<super: <class 'Cat'>, <Cat object>>
<super: <class 'Cat'>, <Cat object>>
Animal shout
Animal shout
Animal shout
{}
{}
{'__module__': '__main__', '__firstlineno__': 1, 'shout': <function Animal.shout at 0x100afd440>, '__static_attributes__': (), '__dict__': <attribute '__dict__' of 'Animal' objects>, '__weakref__': <attribute '__weakref__' of 'Animal' objects>, '__doc__': None}
{'__module__': '__main__', '__firstlineno__': 7, 'shout': <function Cat.shout at 0x100d339c0>, '__static_attributes__': (), '__doc__': None}

super()可以访问到父类的属性,其具体原理后面说。
那对于类方法和静态方法呢?

class Animal:@classmethoddef class_method(cls):print('class_method _animal')@staticmethoddef static_method():print('static _method _animal')class Cat(Animal):@classmethoddef class_method(cls):print('class _method_cat')@staticmethoddef static_method():print('static_method_cat')c = Cat()
c.class_method()     # class _method_cat
c.static_method() # static_method_cat

这些方法都可以覆盖,原理都一样,属性字典的搜索顺序。

继承中的初始化

先看下面一段代码,有没有问题

class A:def __init__(self, a):self.a = aclass B(A):def __init__(self, b, c):self.b = bself.c = cdef printv(self):print(self.b)print(self.a)  # 出错吗?f = B(200, 300)print(f.__dict__)print(f.__class__.__bases__)f.printv()
输出:
{'b': 200, 'c': 300}
(<class '__main__.A'>,)
200
Traceback (most recent call last):File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 20, in <module>f.printv()~~~~~~~~^^File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 13, in printvprint(self.a)  # 出错吗?^^^^^^
AttributeError: 'B' object has no attribute 'a'

上例代码可知:
如果类B定义时声明继承自类A,则在类B中__bases__中是可以看到类A。
但是这和是否调用类A的构造方法是两回事。
如果B中调用了A的构造方法,就可以拥有父类的属性了。如何理解这一句话呢?
观察B的实例f的__dict__中的属性。

class A:def __init__(self, a, d=10):self.a = aself.__d = dclass B(A):def __init__(self, b, c):A.__init__(self, b + c, b - c)self.b = bself.c = cdef printv(self):print(self.b)   # 200print(self.a)  #500f = B(200, 300)
print(f.__dict__)   # {'a': 500, '_A__d': -100, 'b': 200, 'c': 300}
print(f.__class__.__bases__)    #(<class '__main__.A'>,)
f.printv()

作为好的习惯,如果父类定义了__init__方法,你就该在子类的_init_中调用它。
那子类什么时候自动调用父类的__init__方法呢?示例1

B实例的初始化会自动调用基类A的__init__方法
示例2

class A:def __init__(self):self.a1 = 'a1'self.__a2 = 'a2'print('A init')class B(A):def __init__(self):self.b1 = 'b1'print('B init')b = B() #
print(b.__dict__) # {'b1': 'b1'}

B实例的初始化__init__方法不会自动调用父类的初始化__init__方法,需要手动调用。

class A:def __init__(self):self.a1 = 'al'self._a2 = 'a2'print('A init')class B(A):def __init__(self):self.b1 = 'bl'print('B init')A.__init__(self)b = B()
print(b.__dict__)   # {'b1': 'bl', 'a1': 'al', '_a2': 'a2'}输出:
B init
A init
{'b1': 'bl', 'a1': 'al', '_a2': 'a2'}

如何正确初始化

class Animal:def __init__ (self, age):print( 'Animal init')self.age = agedef show(self):print (self.age)class Cat(Animal):def __init__ (self, age, weight):print( 'Cat init')self.age = age + 1self.weight = weightc = Cat(10, 5)
c. show()
输出:
Cat init
11

上例我们前面都分析过,不会调用父类的__init__方法的,这就会导致没有实现继承效果。所以在
子类的__init__方法中,应该显式调用父类的__init__方法。

class Animal:def __init__(self, age):print('Animal init')self.age = agedef show(self):print(self.age)class Cat(Animal):def __init__(self, age, weight):# 调用父类的_init__方法的顺序决定着show方法的结果super().__init__(age)print('Cat init')self.age = age + 1self.weight = weight# super() ._init_(age)c = Cat(10, 5)
c.show()
输出:
Animal init
Cat init
11

注意,调用父类的__init__方法,出现在不同的位置,可能导致出现不同的结果。那么,直接将上例中所有的实例属性改成私有变量呢?

class Animal:def __init__(self, age):print('Animal init')self.__age = agedef show(self):print(self.__age)class Cat(Animal):def __init__(self, age, weight):# 调用父类的_init_方法的顺序决定着show方法的结果super().__init__(age)print('Cat init')self.__age = age + 1self.__weight = weight# super().__init_(age)c = Cat(10, 5)
c.show()
print(c.__dict__)
输出:
Animal init
Cat init
10
{'_Animal__age': 10, '_Cat__age': 11, '_Cat__weight': 5}

上例中打印10,原因看__dict__就知道了。因为父类Animal的show方法中_age会被解释为
_Animal__age,因此显示的是10,而不是11。
这样的设计不好,Cat的实例c应该显示自己的属性值更好。
解决的办法:一个原则,自己的私有属性,就该自己的方法读取和修改,不要借助其他类的方法,即使是父类或者派生类的方法。

Python不同版本的类

Python2.2之前类是没有共同的祖先的,之后,引入object类,它是所有类的共同祖先类object。
Python2中为了兼容,分为古典类(旧式类)和新式类。
Python3中全部都是新式类。
新式类都是继承自object的,新式类可以使用super。

# # 以下代码在Python2.x中运行
# 古典类(旧式类)
class A: pass# 新式类class B(object): passprint(dir(A))
print(dir(B))print(A.__bases__)
print(B.__bases__)
# 古典类
a = A()
print(a.__class__)print(type(a))  # ‹type 'instance' ›
#  新式类
b = B()print(b.__class__)print(type(b))输出:
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__firstlineno__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__static_attributes__', '__str__', '__subclasshook__', '__weakref__']
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__firstlineno__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__static_attributes__', '__str__', '__subclasshook__', '__weakref__']
(<class 'object'>,)
(<class 'object'>,)
<class '__main__.A'>
<class '__main__.A'>
<class '__main__.B'>
<class '__main__.B'>

多继承

OCP原则:多用“继承”、少修改

继承的用途:增强基类、实现多态

多态

在面向对象中,父类、子类通过继承联系在一起,如果可以通过一套方法,就可以实现不同表
现,就是多态。
一个类继承自多个类就是多继承,它将具有多个类的特征。

多继承弊端

多继承很好的模拟了世界,因为事物很少是单一继承,但是舍弃简单,必然引入复杂性,带来了冲突。

如同一个孩子继承了来自父母双方的特征。那么到底眼睛像爸爸还是妈妈呢?孩子究竟该像谁多一点呢?

多继承的实现会导致编译器设计的复杂度增加,所以现在很多语言也舍弃了类的多继承。
C++支持多继承;Java舍弃了多继承。
Java中,一个类可以实现多个接口,一个接口也可以继承多个接口。Java的接口很纯粹,只是方法的声明,继承者必须实现这些方法,就具有了这些能力,就能干什么。

多继承可能会带来二义性,例如,猫和狗都继承自动物类,现在如果一个类多继承了猫和狗类,

猫和狗都有shout方法,子类究竟继承谁的shout呢?
解决方案

实现多继承的语言,要解决二义性,深度优先或者广度优先。

Python多继承实现

class ClassName(基类列表):类体

在这里插入图片描述

左图是多继承,右图是单一继承多继承带来路径选择问题,究竟继承哪个父类的特征呢

Python使用MRO( method resolution order ) 解决基类搜索顺序问题。

  • 历史原因,MRO有三个搜索算法:
    1、 经典算法,按照定义从左到右,深度优先策略。2.2之前
    左图的MRO是MyClass,D,B,A,C,A
    2、 新式类算法,经典算法的升级,重复的只保留最后一个。2.2
    左图的MRO是MyClass,D,B,C,A,object
    3、 C3算法,在类被创建出来的时候,就计算出一个MRO有序列表。2.3之后,Python3唯一支持的算法

左图中的MRO是MyClass, D,B,C,A,object的列表

C3算法解決多继承的二义性

多继承的缺点
当类很多,继承复杂的情况下,继承路径太多,很难说清什么样的继承路径。
Python语法是允许多继承,但Python代码是解释执行,只有执行到的时候,才发现错误。

团队协作开发,如果引入多继承,那代码将不可控。

不管编程语言是否支持多继承,都应当避免多继承。

Python的面向对象,我们看到的太灵活了,太开放了,所以要团队守规矩。

Mixin

类有下面的继承关系
在这里插入图片描述

文档Document类是其他所有文档类的抽象基类;Word、Pdf类是Document的子类。
需求:为Document子类提供打印能力
思路:
1、在Document中提供print方法

class Document:def __init__(self, content):self.content = contentdef print(self):raise NotImplementedError()class Word(Document): passclass Pdf(Document): pass

基类提供的方法不应该具体实现,因为它未必适合子类的打印,子类中需要覆盖重写。

print算是一种能力—-打印功能,不是所有的Document的子类都需要的,所有,从这个角度出发,有点问题。

2、需要打印的子类上增加

如果在现有子类上直接增加,违反了OCP的原则,所以应该继承后增加。因此有下图

在这里插入图片描述

class Printable:def print(self):print(self.content)class Document:  # 第三方库,不允许修改def __init__(self, content):self.content = contentclass Word(Document): passclass Pdf(Document): pass  # 第三方库,不允许修改class PrintableWord(Printable, Word): passprint(PrintableWord.__dict__)print(PrintableWord.mro())pw = PrintableWord('test string')pw.print()输出:
{'__module__': '__main__', '__firstlineno__': 17, '__static_attributes__': (), '__doc__': None}
[<class '__main__.PrintableWord'>, <class '__main__.Printable'>, <class '__main__.Word'>, <class '__main__.Document'>, <class 'object'>]
test string

看似不错,如果需要还要提供其他能力,如何继承?
应用于网络,文档应该具备序列化的能力,类上就应该实现序列化。
可序列化还可能分为使用pickle、json、messagepack等。
这个时候发现,类可能太多了,继承的方式不是很好了。
功能太多,A类需要某几样功能,B类需要另几样功能,很繁琐。
3、装饰器
用装饰器增强一个类,把功能给类附加上去,那个类需要,就装饰它

def printable(cls):def _print(self):print(self.content, '装饰器')cls.print = _printreturn clsclass Document:  # 第三方库,不允许修改def __init__(self, content):self.content = contentclass Word(Document): pass  # 第三方库,不允许修改class Pdf(Document): pass  # 第三方库,不允许修改@printable  # # 先继承,后装饰
class PrintableWord(Word): passprint(PrintableWord.__dict__)print(PrintableWord.mro())pw = PrintableWord('test string')pw.print()@printable
class PrintablePdf(Word): pass
输出:
{'__module__': '__main__', '__firstlineno__': 21, '__static_attributes__': (), '__doc__': None, 'print': <function printable.<locals>._print at 0x10484d760>}
[<class '__main__.PrintableWord'>, <class '__main__.Word'>, <class '__main__.Document'>, <class 'object'>]
test string 装饰器

优点:
简单方便,在需要的地方动态增加,直接使用装饰器
4, Mixin

先看代码

class Document:  # 第三方库,不允许修改def __init__(self, content):self.content = contentclass Word(Document): pass  # 第三方库,不允许修改class Pdf(Document): pass  # 第三方库,不允许修改class PrintableMixin:def print(self):print(self.content, 'Mixin')class PrintableWord(PrintableMixin, Word): passprint(PrintableWord.__dict__)print(PrintableWord.mro())def printable(cls):def _print(self):print(self.content, '装饰器')cls.print = printreturn cls@printable
class PrintablePdf(Word): passprint(PrintablePdf.__dict__)print(PrintablePdf.mro())输出:
{'__module__': '__main__', '__firstlineno__': 17, '__static_attributes__': (), '__doc__': None}
[<class '__main__.PrintableWord'>, <class '__main__.PrintableMixin'>, <class '__main__.Word'>, <class '__main__.Document'>, <class 'object'>]
{'__module__': '__main__', '__firstlineno__': 33, '__static_attributes__': (), '__doc__': None, 'print': <built-in function print>}
[<class '__main__.PrintablePdf'>, <class '__main__.Word'>, <class '__main__.Document'>, <class 'object'>]

Mixin就是其它类混合进来,同时带来了类的属性和方法。
这里看来Mixin类和装饰器效果一样,也没有什么特别的。但是Mixin是类,就可以继承。

class Document:  # 第三方库,不允许修改def __init__(self, content):self.content = contentclass Word(Document): pass  # 第二方库,不允许修改class Pdf(Document): pass  # 第三方库,不允许修改class PrintableMixin:def print(self):print(self.content, 'Mixin')class PrintableWord(PrintableMixin, Word): passprint(PrintableWord.__dict__)
print(PrintableWord.mro())pw = PrintableWord('test string')pw.print()class SuperPrintableMixin(PrintableMixin):def print(self):print('~' * 20)  # 打印增强super().print()print('~' * 20)  # 打印增强# PrintableMixin类的继承
class SuperPrintablePdf(SuperPrintableMixin, Pdf): passprint(SuperPrintablePdf.__dict__)print(SuperPrintablePdf.mro())spp = SuperPrintablePdf('super print pdf')spp.print()输出:
{'__module__': '__main__', '__firstlineno__': 17, '__static_attributes__': (), '__doc__': None}
[<class '__main__.PrintableWord'>, <class '__main__.PrintableMixin'>, <class '__main__.Word'>, <class '__main__.Document'>, <class 'object'>]
test string Mixin
{'__module__': '__main__', '__firstlineno__': 36, '__static_attributes__': (), '__doc__': None}
[<class '__main__.SuperPrintablePdf'>, <class '__main__.SuperPrintableMixin'>, <class '__main__.PrintableMixin'>, <class '__main__.Pdf'>, <class '__main__.Document'>, <class 'object'>]
~~~~~~~~~~~~~~~~~~~~
super print pdf Mixin
~~~~~~~~~~~~~~~~~~~~

Mixin类

Mixin本质上就是多继承实现的。
Mixin体现的是一种组合的设计模式。

在面向对象的设计中,一个复杂的类,往往需要很多功能,而这些功能有来自不同的类提供,这就需要很多的类组合在一起。
从设计模式的角度来说,多组合,少继承。
Mixin类的使用原则 昌教育 高薪职业学院

  • Mixin类中不应该显式的出现_init_初始化方法
  • Mixin类通常不能独立工作,因为它是准备混入别的类中的部分功能实现
  • Mixin类的祖先类也应是Mixin类
    使用时,Mixin类通常在继承列表的第一个位置,例如class PrintableWord (Printableixin, Word): pass

Mixin类和装饰器

这两种方式都可以使用,看个人喜好。

如果还需要继承就得使用Mixin类的方式。

练习
1、Shape基类,要求所有子类都必须提供面积的计算,子类有三角形、矩形、圆。

import mathclass Shape:@propertydef area(self):raise NotImplementedError('基类未实现')class Triangle(Shape):def __init__(self, a, b, c):self.a = aself.b = bself.c = c@propertydef area(self):р = (self.a + self.b + self.c) / 2return math.sqrt(р * (р - self.a) * (р - self.b) * (р - self.c))class Rectangle(Shape):def __init__(self, width, height):self.width = widthself.height = height@propertydef area(self):return self.width * self.heightclass Circle(Shape):def __init__(self, radius):self.d = radius * 2@propertydef area(self):return math.pi * self.d * self.d * 0.25shapes = [Triangle(3, 4, 5), Rectangle(3, 4), Circle(4)]for s in shapes:print('The area of {} = {}'.format(s.__class__.__name__, s.area))
输出:
The area of Triangle = 6.0
The area of Rectangle = 12
The area of Circle = 50.26548245743669

2、上题圆类的数据可序列化

import json
import msgpackimport mathclass Shape:@propertydef area(self):raise NotImplementedError('基类未实现')class Rectangle(Shape):def __init__(self, width, height):self.width = widthself.height = height@propertydef area(self):return self.width * self.heightclass Circle(Shape):def __init__(self, radius):self.d = radius * 2@propertydef area(self):return math.pi * self.d * self.d * 0.25class SerializableMixin:def dumps(self, t='json'):if t == 'json':return json.dumps(self.__dict__)elif t == 'msgpack':return msgpack.packb(self.__dict__)else:raise NotImplementedError('没有实现的序列化')class SerializableCircleMixin(SerializableMixin, Circle): passscm = SerializableCircleMixin(4)print(scm.area)s = scm.dumps('msgpack')print(s)输出:
50.26548245743669
b'\x81\xa1d\x08'

作业
用面向对象实现LinkedList链表

单向链表实现append、iternodes方法

双向链表实现append、pop、insert、remove、iternodes方法

在这里插入图片描述

参考
实现LinkedList链表
链表有双向单向链表

在这里插入图片描述

单向链表1

class SingleNode:  # 节点保存内容和下一跳def __init__(self, item, next=None):self.item = itemself.next = nextdef __repr__(self):return repr(self.item)class LinkedList:def __init__(self):self.head = Noneself.tail = None  # 思考tai1属性的作用def append(self, item):node = SingleNode(item)if self.head is None:  #self.head = node  # 设置开头结点,以后不变,学你else:self.tail.next = node  # 当前最后一个结点关联下self.tail = node  # 更新结尾结点return selfdef iternodes(self):current = self.headwhile current:yield currentcurrent = current.nextll = LinkedList()
ll.append('abc')
ll.append(1).append(2)
ll.append('def')print(ll.head, ll.tail)for item in ll.iternodes():print(item)
输出:
'abc' 'def'
'abc'
1
2
'def'

单向链表2

借助列表实现

class SingleNode:def __init__(self, item, next=None):self.item = itemself.next = nextdef __repr__(self):return repr(self.item)class SingleNode:  # 节点保存内容和下一跳def __init__(self, item, next=None):self.item = itemself.next = nextdef __repr__(self):return repr(self.item)class LinkedList:def __init__(self):self.head = Noneself.tail = None  # 思考tail属性的作用self.items = []  # 为什么在单向链表中使用list?def append(self, item):node = SingleNode(item)if self.head is None:  #self.head = node  # 设置开头结点,以后不变else:self.tail.next = node  # 当前最后一个结点关联下一跳self.tail = node  # 更新结尾结点self.items.append(node)return selfdef iternodes(self):current = self.headwhile current:yield currentcurrent = current.nextdef getitem(self, index):return self.items[index]ll = LinkedList()
ll.append('abc')
ll.append(1).append(2)ll.append('def')print(ll.head, ll.tail)for item in ll.iternodes():print(item)for i in range(len(ll.items)):print(ll.getitem(1))
输出:
'abc' 'def'
'abc'
1
2
'def'
1
1
1
1

为什么在单向链表中使用list?
因为只有结点自己知道下一跳是谁,想直接访问某一个结点只能遍历。
借助列表就可以方便的随机访问某一个结点了。

双向链表

实现单向链表没有实现的pop、remove、insert方法

class SingleNode:  # 节点保存内容和下一跳def __init__(self, item, prev=None, next=None):self.item = itemself.next = nextself.prev = prev  # 增加上一跳def __repr__(self):# return repr(self.item)return "({} <=={}==>{})".format(self.prev.item if self.prev else None,self.item,self.next.item if self.next else None)class LinkedList:def __init__(self):self.head = Noneself.tail = None  # 思考tai1属性的作用self.size = 0  # 以后实现def append(self, item):node = SingleNode(item)if self.head is None:  #self.head = node  # 设置开头结点,以后不变else:self.tail.next = node  # 当前最后一个结点关联下一跳node.prev = self.tail  # 前后关联self.tail = node  # 更新结尾结点return selfdef insert(self, index, item):if index < 0:  # 不接受负数raise IndexError('Not negative index {}'.format(index))current = Nonefor i, node in enumerate(self.iternodes()):if i == index:  # 找到了current = nodebreakelse:  # 没有break,尾部追加self.append(item)return# break,找到了node = SingleNode(item)prev = current.prevnext = currentif prev is None:  # FriSself.head = nodeelse:  # 不是首元素prev.next = nodenode.prev = prevnode.next = nextnext.prev = nodedef pop(self):if self.tail is None:  # 空raise Exception('Empty')node = self.tailitem = node.itemprev = node.previf prev is None:  # only one nodeself.head = Noneself.tail = Noneelse:prev.next = Noneself.tail = prevreturn itemdef remove(self, index):if self.tail is None:  # 空raise Exception('Empty')if index < 0:  # 不接受负数raise IndexError('Not negative index {}'.format(index))current = Nonefor i, node in enumerate(self.iternodes()):if i == index:current = nodebreakelse:  # Not Foundraise IndexError('Wrong index (}.'.format(index))prev = current.prevnext = current.next# 4种情况if prev is None and next is None:  # only one nodeself.head = Noneself.tail = Noneelif prev is None:  # 头部self.head = nextnext.prev = Noneelif next is None:  # 尾部self.tail = prevprev.next = Noneelse:  # 在中间prev.next = nextnext.prev = prevdel currentdef iternodes(self, reverse=False):current = self.tail if reverse else self.headwhile current:yield currentcurrent = current.prev if reverse else current.nextll = LinkedList()
ll.append('abc')
ll.append(1)
ll.append(2)
ll.append(3)
ll.append(4)
ll.append(5)
ll.append('def')
print(ll.head, ll.tail)
for x in ll.iternodes(True):print(x)print('==EEEEEEEEEEEE=')
ll.remove(6)
ll.remove(5)
ll.remove(0)
ll.remove(1)
for x in ll.iternodes():print(x)print('uunnnnnannn')ll.insert(3, 5)ll.insert(20, 'def')ll.insert(1, 2)ll.insert(0, 'abc')for x in ll.iternodes():print(x)输出:(None <==abc==>1) (5 <==def==>None)
(5 <==def==>None)
(4 <==5==>def)
(3 <==4==>5)
(2 <==3==>4)
(1 <==2==>3)
(abc <==1==>2)
(None <==abc==>1)
==EEEEEEEEEEEE=
(None <==1==>3)
uunnnnnannn
(1 <==2==>3)
uunnnnnannn
(2 <==5==>3)
uunnnnnannn
(5 <==3==>4)
uunnnnnannn
(3 <==4==>5)
uunnnnnannn
(4 <==5==>def)
uunnnnnannn
(5 <==def==>def)
uunnnnnannn
(def <==def==>def)
uunnnnnannn
(def <==def==>def)
uunnnnnannn
(def <==def==>def)
uunnnnnannn
(def <==def==>def)
uunnnnnannn
(def <==def==>None)
uunnnnnannn
(None <==abc==>abc)
(abc <==abc==>2)
(abc <==2==>abc)
(2 <==abc==>2)
(abc <==2==>5)
(2 <==5==>abc)
(5 <==abc==>2)
(abc <==2==>5)
(2 <==5==>abc)
(5 <==abc==>2)
(abc <==2==>5)
(2 <==5==>abc)
(5 <==abc==>2)
(abc <==2==>5)
(2 <==5==>abc)
(5 <==abc==>2)
(abc <==2==>5)
(2 <==5==>abc)
(5 <==abc==>2)
(abc <==2==>5)
(2 <==5==>abc)
(5 <==abc==>def)
(abc <==def==>2)
(def <==2==>5)
(2 <==5==>abc)
(5 <==abc==>def)
(abc <==def==>2)
(def <==2==>5)
(2 <==5==>abc)
(5 <==abc==>def)
(abc <==def==>2)
(def <==2==>5)
(2 <==5==>abc)
(5 <==abc==>def)
(abc <==def==>2)
(def <==2==>5)
(2 <==5==>1)
(5 <==1==>def)
(1 <==def==>2)
(def <==2==>5)
(2 <==5==>3)
(5 <==3==>def)
(3 <==def==>4)
(def <==4==>5)
(4 <==5==>def)
(5 <==def==>def)
(def <==def==>def)
(def <==def==>def)
(def <==def==>def)
(def <==def==>def)
(def <==def==>None)
http://www.lryc.cn/news/585877.html

相关文章:

  • RISC-V:开源芯浪潮下的技术突围与职业新赛道 (四) 产业应用全景扫描
  • CSS选择器进行定位
  • 开源 python 应用 开发(五)python opencv之目标检测
  • Android音视频探索之旅 | C++层使用OpenGL ES实现音频渲染
  • 10. 垃圾回收的算法
  • 【字符串移位包含问题】2022-8-7
  • 【飞算JavaAI】一站式智能开发,驱动Java开发全流程革新
  • 缺陷特征粘贴增强流程
  • 13. G1垃圾回收器
  • git版本发布
  • Kotlin基础学习记录
  • 基于定制开发开源AI智能名片S2B2C商城小程序的社群游戏定制策略研究
  • 云计算三大服务模式深度解析:IaaS、PaaS、SaaS
  • AI:机器人行业发展现状
  • GoC之汉诺塔绘制
  • Leaflet面试题及答案(41-60)
  • 电商广告市场惊现“合规黑洞”,企业如何避免亿元罚单
  • 11. JVM中的分代回收
  • JVM的垃圾回收算法和多种GC算法
  • 9. JVM垃圾回收
  • Opencv---深度学习开发
  • 初阶数据结构易错点整理
  • leetcode:HJ18 识别有效的IP地址和掩码并进行分类统计[华为机考][字符串]
  • 华为IPD(集成产品开发)流程是其研发管理的核心体系
  • Edge浏览器:报告不安全的站点的解决方案
  • 用YOLOv5系列教程(1)-用YOLOv5轻松实现设备状态智能监控!工业级教程来了
  • (C++)STL标准库(vector动态数组)(list列表)(set集合)(map键值对)相关对比,基础教程
  • 【Lucene/Elasticsearch】**Query Rewrite** 机制
  • U盘直接拔出不在电脑上弹出有何影响
  • 张量拼接操作