32、新式类与旧式类
新式类与旧式类:是否继承了object基类,python3中默认是新式类,class A() <==> class A(object) <==> class A
mro顺序:新式类是广度优先,从左到右;旧式类是深度优先,从左到右。
class Parent():
def __init__(self):
self.a = 1
class Son1(Parent):
pass
class Son2(Parent):
def __init__(self):
self.a = 2
class Grandson(Son1, Son2):
pass
class Grandson2(Son2, Son1):
pass
gs = Grandson()
gs2 = Grandson2()
print("MRO顺序:\n", Grandson.__mro__)
print("MRO顺序还与继承父类的顺序有关:\n", Grandson.__mro__)
print(gs.a) # 在新式类里是按照广度优先的搜索方式,按照mro顺序,Grandson中没有属性a,则去Son1中找,Son1中没有则去Son2中找,找到了,返回
MRO顺序:
(<class '__main__.Grandson'>, <class '__main__.Son1'>, <class '__main__.Son2'>, <class '__main__.Parent'>, <class 'object'>)
MRO顺序还与继承父类的顺序有关:
(<class '__main__.Grandson'>, <class '__main__.Son1'>, <class '__main__.Son2'>, <class '__main__.Parent'>, <class 'object'>)
2
在旧式类中,mro顺序为:Grandson --> Son1 --> Parent --> Son2,所以gs.a返回1
33、多类继承
多类继承的方式有两种方式:一种是直接 父类名.父类方法 ,一种是super()方法 ,这两种方式各有优势,第一种比较直观简介,但是会出现父类执行多次的情况。第二种方式,虽然不够直观,但是可以保证每个父类的方法之执行一次。
class Parent():
def foo(self):
print("I am Parent")
class Son1(Parent):
def foo(self):
print("I am Son1")
Parent.foo(self)
class Son2(Parent):
def foo(self):
print("I am Son2")
Parent.foo(self)
class Grandson(Son1, Son2):
def foo(self):
print("I am Grandson")
Son1.foo(self)
Son2.foo(self)
gs = Grandson()
gs.foo()
I am Grandson
I am Son1
I am Parent
I am Son2
I am Parent
发现Parent父类的foo函数被调用了两次
class Parent():
def foo(self):
print("I am Parent")
class Son1(Parent):
def foo(self):
print("I am Son1")
# super().foo()
super(Son1, self).foo()
class Son2(Parent):
def foo(self):
print("I am Son2")
super().foo()
class Grandson(Son1, Son2):
def foo(self):
print("I am Grandson")
super().foo()
gs = Grandson()
gs.foo()
I am Grandson
I am Son1
I am Son2
I am Parent
super方法是根据mro顺序执行的,从gs.foo()开始,先执行Grandson里面的foo函数,当执行到super().foo()时,执行mro顺序表中的Grandson的下一个,即Son1中的foo函数,碰到super函数,再接着执行Son1的下一个中的foo,…,直到最后的父类,这样保证了每个父类的函数只会执行一次。
super()函数继承的形式有两种,super().foo(xx, xxx) super(子类名, self).foo(xx, xxx) ,似乎第一种更好记一些哈。
class Parent():
def __init__(self, name, *args, **kwargs): #
print("Parent的init函数开始调用")
self.name = name
print("Parent的init函数调用结束")
class Son(Parent):
def __init__(self, name, age, *args, **kwargs):
print("Son的init函数开始调用")
self.age = age
super().__init__(name, *args, **kwargs)
print("Son的init函数调用结束")
class Son2(Parent):
def __init__(self, name, addr, *args, **kwargs):
print("Son2的init函数开始调用")
self.addr = addr
super().__init__(name, *args, **kwargs)
print("Son2的init函数调用结束")
class Grandson2(Son, Son2):
def __init__(self, name, age, addr):
print("Grandson2的init函数开始调用")
super().__init__(name, age, addr) # 为了避免多继承报错,使用不定长参数,接收参数
print("Grandson2的init函数调用结束")
gs2 = Grandson2("zhangsan", 18, "南京")
print("姓名:", gs2.name)
print("年龄:", gs2.age)
print("地址:", gs2.addr)
Grandson2的init函数开始调用
Son的init函数开始调用
Son2的init函数开始调用
Parent的init函数开始调用
Parent的init函数调用结束
Son2的init函数调用结束
Son的init函数调用结束
Grandson2的init函数调用结束
姓名: zhangsan
年龄: 18
地址: 南京
34、单继承中的super
class Parent():
def __init__(self, name):
print("Parent的init函数开始调用")
self.name = name
print("Parent的init函数调用结束")
class Son(Parent):
def __init__(self, name, age):
print("Son的init函数开始调用")
self.age = age
super().__init__(name)
print("Son的init函数调用结束")
class Grandson(Son):
def __init__(self, name, age, gender):
print("Grandson的init函数开始调用")
self.gender = gender
super().__init__(name, age) # 单继承使用super方法对父类传参时不需要传递全部参数,只需要传递父类方法所需的参数
print("Grandson的init函数调用结束")
gs = Grandson("zhangsan", 18, "男")
print("姓名:", gs.name)
print("年龄:", gs.age)
print("性别:", gs.gender)
Grandson的init函数开始调用
Son的init函数开始调用
Parent的init函数开始调用
Parent的init函数调用结束
Son的init函数调用结束
Grandson的init函数调用结束
姓名: zhangsan
年龄: 18
性别: 男
# 面试题:
class Parent():
x = 1
class Son1(Parent):
pass
class Son2(Parent):
pass
print(Parent.x, Son1.x, Son2.x)
Son1.x = 2
print(Parent.x, Son1.x, Son2.x)
Parent.x = 3
print(Parent.x, Son1.x, Son2.x)
1 1 1
1 2 1
3 2 3
35、with与上下文管理器
对于系统资源如文件,数据库连接、socket而言,应用程序打开这些资源后并执行完业务逻辑之后,必须关闭这些系统资源。比如python中打开一个文件,让里面写内容,写完之后,就要关闭该文件,否则极端情况下会出现“too many open files”的错误,因为系统允许你打开的最大文件数量是有限的。同样,对于数据库,如果连接过多而没有及时关闭的话,就会出现“can not collect to MySQL server Too many connection”,因为数据库的连接是非常昂贵的资源,不可能无限制的被创建。
1、如何正确的关闭一个文件?
# 普通版
def f():
f = open("./log.txt", "r")
content = f.read()
print(content)
f.close()
f()
2019-05-23 19:42:22,366-test.py[line:27]-INFO:这是logging info message
2019-05-23 19:42:22,366-test.py[line:29]-WARNING:这是logging warning message
2019-05-23 19:42:22,366-test.py[line:30]-ERROR:这是logging error message
2019-05-23 19:42:22,366-test.py[line:31]-CRITICAL:这是logging critical message
2019-05-23 19:43:06,568-<ipython-input-12-b21ac2ceac6e>[line:25]-INFO:这是logging info message
2019-05-23 19:43:06,569-<ipython-input-12-b21ac2ceac6e>[line:27]-WARNING:这是logging warning message
2019-05-23 19:43:06,571-<ipython-input-12-b21ac2ceac6e>[line:28]-ERROR:这是logging error message
2019-05-23 19:43:06,572-<ipython-input-12-b21ac2ceac6e>[line:29]-CRITICAL:这是logging critical message
这样存在一个潜在的问题,如果调用read的过程中出现了异常,导致后续代码无法继续执行,close方法将无法被正常使用,因此资源就会一直被该程序占用。
# 进阶版
def f2():
try:
f = open("./log.txt", "r")
content = f.read()
print(content)
except IOError:
print("oops error")
finally:
print("不管有没有异常都会执行到这里")
f.close()
f2()
2019-05-23 19:42:22,366-test.py[line:27]-INFO:这是logging info message
2019-05-23 19:42:22,366-test.py[line:29]-WARNING:这是logging warning message
2019-05-23 19:42:22,366-test.py[line:30]-ERROR:这是logging error message
2019-05-23 19:42:22,366-test.py[line:31]-CRITICAL:这是logging critical message
2019-05-23 19:43:06,568-<ipython-input-12-b21ac2ceac6e>[line:25]-INFO:这是logging info message
2019-05-23 19:43:06,569-<ipython-input-12-b21ac2ceac6e>[line:27]-WARNING:这是logging warning message
2019-05-23 19:43:06,571-<ipython-input-12-b21ac2ceac6e>[line:28]-ERROR:这是logging error message
2019-05-23 19:43:06,572-<ipython-input-12-b21ac2ceac6e>[line:29]-CRITICAL:这是logging critical message
不管有没有异常都会执行到这里
## 高级版
with open("./log.txt", "r") as f:
content = f.read()
print(content)
2019-05-23 19:42:22,366-test.py[line:27]-INFO:这是logging info message
2019-05-23 19:42:22,366-test.py[line:29]-WARNING:这是logging warning message
2019-05-23 19:42:22,366-test.py[line:30]-ERROR:这是logging error message
2019-05-23 19:42:22,366-test.py[line:31]-CRITICAL:这是logging critical message
2019-05-23 19:43:06,568-<ipython-input-12-b21ac2ceac6e>[line:25]-INFO:这是logging info message
2019-05-23 19:43:06,569-<ipython-input-12-b21ac2ceac6e>[line:27]-WARNING:这是logging warning message
2019-05-23 19:43:06,571-<ipython-input-12-b21ac2ceac6e>[line:28]-ERROR:这是logging error message
2019-05-23 19:43:06,572-<ipython-input-12-b21ac2ceac6e>[line:29]-CRITICAL:这是logging critical message
with的作用和使用try / finally语句是一样的。
2、上下文管理器
上下文: 在不同的地方表示不同的含义,其实说白了跟文章的上下文是一个意思,可以理解为环境。举个例子,APP点击一个按钮进入一个新的界面,要保存你是在那个屏幕跳过来的等等信息,以便你点击返回时能正确跳转。
上下文管理器: 任何实现了__enter__()和__exit__()方法的对象称为上下文管理器,上下文管理器对象可以使用with关键字。
class File():
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
def __enter__(self): # 返回资源对象
print("entering")
self.f = open(self.filename, self.mode)
return self.f
def __exit__(self, *args): # 处理一些清除操作
print("will exit")
self.f.close()
with File("./log.txt", "r") as ff: # 1、判断File("./log.txt", "r")是不是上下文管理器; 2、执行__enter__方法返回self.f给ff
print("reading")
content = ff.read() # 3、执行读操作
print(content) # 4、执行完成或出现异常会自动调用__exit__方法
entering
reading
2019-05-23 19:42:22,366-test.py[line:27]-INFO:这是logging info message
2019-05-23 19:42:22,366-test.py[line:29]-WARNING:这是logging warning message
2019-05-23 19:42:22,366-test.py[line:30]-ERROR:这是logging error message
2019-05-23 19:42:22,366-test.py[line:31]-CRITICAL:这是logging critical message
2019-05-23 19:43:06,568-<ipython-input-12-b21ac2ceac6e>[line:25]-INFO:这是logging info message
2019-05-23 19:43:06,569-<ipython-input-12-b21ac2ceac6e>[line:27]-WARNING:这是logging warning message
2019-05-23 19:43:06,571-<ipython-input-12-b21ac2ceac6e>[line:28]-ERROR:这是logging error message
2019-05-23 19:43:06,572-<ipython-input-12-b21ac2ceac6e>[line:29]-CRITICAL:这是logging critical message
will exit
实现上下文管理器的另一种方式,contextmanager装饰器,进一步简化了上下文管理器的实现方式。通过yield将函数分割成两部分,yield之前的语句在__enter__方法中执行,yield后面的语句在__exit__()方法中执行。紧跟yield后面的值是函数的返回值。
from contextlib import contextmanager
@contextmanager
def open_(path, mode):
f = open(path, mode) # __enter__方法中执行
yield f # 返回资源对象
f.close # __exit__方法执行
with open_("./log.txt", "r") as ff:
content = ff.read()
print(content)
2019-05-23 19:42:22,366-test.py[line:27]-INFO:这是logging info message
2019-05-23 19:42:22,366-test.py[line:29]-WARNING:这是logging warning message
2019-05-23 19:42:22,366-test.py[line:30]-ERROR:这是logging error message
2019-05-23 19:42:22,366-test.py[line:31]-CRITICAL:这是logging critical message
2019-05-23 19:43:06,568-<ipython-input-12-b21ac2ceac6e>[line:25]-INFO:这是logging info message
2019-05-23 19:43:06,569-<ipython-input-12-b21ac2ceac6e>[line:27]-WARNING:这是logging warning message
2019-05-23 19:43:06,571-<ipython-input-12-b21ac2ceac6e>[line:28]-ERROR:这是logging error message
2019-05-23 19:43:06,572-<ipython-input-12-b21ac2ceac6e>[line:29]-CRITICAL:这是logging critical message
36、封装、继承、多态
1、封装
# 面向过程编程
global_val_1 = 1
global_val_2 = 2
global_val_3 = 3
global_val_4 = 4
def f1():
pass
def f2():
pass
def f3():
pass
# 在实际开发过程中,为了完成较为复杂的任务,往往需要多个函数的配合,当一个函数中收到了
# 数据,为了让其他函数中也能够直接使用,很多人想到使用全局变量实现传递功能,这样很方便,
# 但是存在一个问题,并发程序会出现对同一个全局变量操作的问题。
# 面向对象编程
class Function1():
class_val_1 = 1
class_val_2 = 2
class_val_3 = 3
def f1(self):
pass
def f2(self):
pass
def f3(self):
pass
class Function2():
class_val_4 = 1
def f4(self):
pass
def f5(self):
pass
# 面向对象编程,将函数和变量制成一个模板,当需要一个功能时,就提供给需求者。
# 那么需求者就会得到函数+变量(数据),为了使每个需求的人之间不产生误会,因
# 此每个人一份函数(功能)+变量(数据)。
好处:
- 1、在使用面向过程编程时,当需要对数据进行处理时,需要考虑用哪个模板的哪个函数进行操作,但是当使用面向对象编程时,因为已将数据存储到了这个独立的空间中,这个独立的空间(即对象)中通过一个特殊的变量(__class__)能够获取到类(模板),而这个类中是有一定数量的方法,与此类无关的方法是不会出现在这个类中,一次需要对数据处理时,可以很快的定位到需要的方法,这样更方便。
- 2、全局变量只能有一份,多个函数就需要多个备份,往往需要利用其它变量进行存储;而通过封装会将用来存储数据的这个变量变为对象中的一个“全局”变量,只要对象不一样那么这个变量就可以再有一份,所以更方便。
- 3、代码划分更清晰。
2、继承
优点:
- 1、能够提高代码的重用率,及开发一个类,可以在多个功能中直接使用
- 2、继承可以有效的进行代码的管理,当某个类有问题只要修改这个类就行,而继承这个类的子类不需要修改
3、多态
子类继承父类,调用某一方法时,如果子类没有对父类的方法进行重写,就调用父类的,如果重写了,就调用子类重写的方法。