Python-类-面向对象-继承-多继承-学习笔记
序
欠4前年的一份笔记 ,献给今后的自己。
面向对象
语言的分类
面向机器
抽象成机器指令,机器容易理解
代表:汇编语言
面向过程
做一件事情,排出个步骤,第一步干什么,第二步于什么,如果出现情况A,做什么处理,如果出现了情况B,做什么处理。
问题规模小,可以步骤化,按部就班处理。
代表:C语言
面向对象OOP
随着计算机需要解决的问题的规模扩大,情况越来越复杂。需要很多人、很多部门协作,面向过程编程不太适合了。
代表:C++、Java、Python等
面向对象
什么是面向对象呢?
一种认识世界、分析世界的方法论。将万事万物抽象为类。
类class
类是抽象的概念,是万事万物的抽象,是一类事物的共同特征的集合。用计算机语言来描述类,就是属性和方法的集合。
对象instance、object
对象是类的具象,是一个实体。
对于我们每个人这个个体,都是抽象概念人类的不同的实体。
举例:
你吃鱼
你,就是对象;鱼,也是对象;吃就是动作
你是具体的人,是具体的对象。你属于人类,人类是个抽象的概念,是无数具体的个体的抽象。鱼,也是具体的对象,就是你吃的这一条具体的鱼。这条鱼属于鱼类,是无数的鱼抽象出来的概念。
吃,是动作,也是操作,也是方法,这个吃是你的动作,也就是人类具有的方法。如果反过来,鱼吃人。吃就是鱼类的动作了。吃,这个动作,很多动物都具有的动作,人类和鱼类都属于动物类,而动物类是抽象的概念,是动物都有吃的动作,但是吃法不同而已。
你驾驶车,这个车也是车类的具体的对象(实例),驾驶这个动作是鱼类不具有的,是人类具有的方法。
属性,它是对象状态的抽象,用数据结构来描述。
操作,它是对象行为的抽象,用操作名和实现该操作的方法来描述。
每个人都有名字、身高、体重等信息,这些信息是个人的属性,但是,这些信息不能保存在人类中,因为它是抽象的概念,不能保留具体的值。
而人类的实例,是具体的人,他可以存储这些具体的属性,而且可以不同人有不同的属性。
哲学
一切皆对象
对象是数据和操作的封装
对象是独立的,但是对象之间可以相互作用
目前00P是最接近人类认知的编程范式
面向对象3要素
- 封装
- 组装:将数据和操作组装到一起。
- 隐藏数据:对外只暴露一些接口,通过接口访问对象。比如驾驶员使用汽车,不需要了解汽车的构造细节,只需要知道使用什么部件怎么驾驶就行,踩了油门就能跑,可以不了解后面的机动原理。
- 继承:
- 多复用,继承来的就不用自己写了
- 多继承少修改,OCP(Open-closed Principle ),使用继承来改变,来体现个性
- 多态:
- 面向对象编程最灵活的地方,动态绑定
人类就是封装;
人类继承自动物类,孩子继承父母特征。分为单一继承、多继承;
多态,继承自动物类的人类、猫类的操作”吃"不同。
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}
类方法
- 在类定义中,使用@classmethod装饰器修饰的方法
- 必须至少有一个参数,且第一个参数留给了cls,cls指代调用者即类对象自身
- cls这个标识符可以是任意合法名称,但是为了易读,请不要修改
- 通过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)