目录
引言
3. 面向对象高级和应用
3.1. 继承【补充】
3.1.1. mro和c3算法
c3算法
一句话搞定继承关系
3.1.2. py2和py3区别
3.3. 异常处理
3.3.1. 异常细分
3.3.2. 自定义异常&抛出异常
3.3.3. 特殊的finally
3.4. 反射
3.4.1. 一些皆对象
3.4.2. import_module + 反射
结尾
引言
本篇文章主要是有关面向对象更加进阶一些的内容,主要是讲解了mro和C3算法,明确了Python中的继承关系,并且介绍了如何做异常处理和如何通过字符串去对象中拿元素
3. 面向对象高级和应用
3.1. 继承【补充】
对于Python面向对象中的继承,我们已学过:
- 继承存在意义:将公共的方法提取到父类中,有利于增加代码重用性。
- 继承的编写方式:
# 继承
class Base(object):passclass Foo(Base):pass
调用类中的成员时,遵循:
- 优先在自己所在类中找,没有的话则去父类中找。
- 如果类存在多继承(多个父类),则先找左边再找右边。
3.1.1. mro和c3算法
如果类中存在继承关系,可以通过mro()
获取当前类的继承关系(找成员的顺序)。
class A:def method(self):print("A")class B(A):def method(self):print("B")class C(A):def method(self):print("C")class D(B, C):pass# 输出方法解析顺序
print(D.__mro__)# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
c3算法
C3算法基于一些原则来确定MRO:
-
子类优先: 在继承链中,子类的方法优先于父类的方法。
-
从左到右: 在同一层级的继承关系中,按照从左到右的顺序查找方法。
-
深度优先: 在多重继承中,首先深度优先搜索,然后从左到右搜索。
我们可以先来看一个例子:
这是继承结构图:
class D(object):passclass C(D):passclass B(D):passclass A(B, C):passprint(A.mro()) # [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>]
C3算法的核心是通过合并(merge)原则来计算MRO
所以我们首先得知道什么是merge,其实这就是一个函数
这个函数接受合并参数,主要根据以下规则来进行计算:
- 从所有候选的列表头中选择第一个,如果该头在其它列表的除第一个以外里面,就排除掉这个头,继续选择下一个头。
- 重复这个过程,直到所有的列表都为空或者没有可以选择的头。
下面演示了C3算法中的merge函数和合并过程:
mro(A) = [A] + merge( mro(B), mro(C), [B,C]) # A代表当前类,然后继续找B,C的父类,最后一个参数就代表当前类的本层继承父类# 然后开始拆分:
# B,C都继承了D类,所以如下(当然这里没有考虑object,因为这肯定是最后的基类):
mro(A) = [A] + merge( [B,D], [C,D], [B,C])
# 然后从第一个[B,D]的B开始找,找后面除头部以外的其他的,看有没有B,如果没有B,就删除,并且加入队列(也就是最后A的mro继承顺序)mro(A) = [A] + [B,C,D]
# 最后直接合并
mro(A) = [A,B,C,D]
当然,以上分析起来十分麻烦,如果遇到很多的继承关系,分析起来就难上加难了,比如:
当然,没理解也没关系,我下面会继续讲解一个更好的方法:
一句话搞定继承关系
如果用正经的C3算法规则去分析一个类继承关系有点繁琐
所以,这里总结了一句话:从左到右,深度优先,大小钻石,留住顶端,基于这句话可以更快的找到继承关系。
大概意思就是:
-
从左到右: 先从左往右开始找
-
深度优先: 继续往当前子类,往下面的深度扩展
-
大小钻石: 如果遇到菱形的,就要特殊处理
-
留住顶端: 先跳过父类,等到右边的时候,再往父类去找
过程:
- 先找A,A的子类是B,C,P,所以先往B找
- 到达B,然后深度搜索,往B的下面找,也就是D
- D继续往下面找,也就是G,H,K,然后返回
- 发现E,B,C,A构成了菱形,所以先跳过E,找到C
- 然后C又开始往下面找,这个时候可以找E了,然后就是E
- 到达E,发现又是菱形,所以先找F
- F之后就是M,N,最后返回就是P
- 最后就是基类
简写为:A -> B -> D -> G -> H -> K -> C -> E -> F -> M -> N -> P -> object
通过这种方法,可以很快解析继承关系,可以在网上找找视频结合本篇文章,食用更佳。
3.1.2. py2和py3区别
概述:
- 在python2.2之前,只支持经典类【从左到右,深度优先,大小钻石,不留顶端】
- 后来,Python想让类默认继承object(其他语言的面向对象基本上也都是默认都继承object),此时发现原来的经典类不能直接集成集成这个功能,有Bug。
- 所以,Python决定不再原来的经典类上进行修改了,而是再创建一个新式类来支持这个功能。【从左到右,深度优先,大小钻石,留住顶端。】
-
- 经典类,不继承object类型
class Foo:pass
-
- 新式类,直接或间接继承object
class Base(object):passclass Foo(Base):pass
- 这样,python2.2之后 中就出现了经典类和新式类共存。(正式支持是2.3)
- 最终,python3中丢弃经典类,只保留新式类。
Py2:
- 经典类,未继承object类型。【从左到右,深度优先,大小钻石,不留顶端】
class Foo:pass
- 新式类,直接获取间接继承object类型。【从左到右,深度优先,大小钻石,留住顶端 -- C3算法】
class Base(object):passclass Foo(Base):pass
Py3
- 新式类,丢弃了经典类只保留了新式类。【从左到右,深度优先,大小钻石,留住顶端 -- C3算法】
class Foo:passclass Bar(object):pass
3.3. 异常处理
在程序开发中如果遇到一些 不可预知
的错误 或 你懒得做一些判断 时,可以选择用异常处理来做。
import requestswhile True:url = input("请输入要下载网页地址:")res = requests.get(url=url)with open('content.txt', mode='wb') as f:f.write(res.content)
上述下载视频的代码在正常情况下可以运行,但如果遇到网络出问题,那么此时程序就会报错无法正常执行。
如果使用异常处理,那么程序就不会报错,继续往下面执行
try:res = requests.get(url=url)
except Exception as e:代码块,上述代码出异常待执行。
print("结束")
异常处理的基本格式:
try:# 逻辑代码
except Exception as e:# try中的代码如果有异常,则此代码块中的代码会执行。
try:# 逻辑代码
except Exception as e:# try中的代码如果有异常,则此代码块中的代码会执行。
finally:# try中的代码无论是否报错,finally中的代码都会执行,一般用于释放资源。print("end")"""
try:file_object = open("xxx.log")# ....
except Exception as e:# 异常处理
finally:file_object.close() # try中没异常,最后执行finally关闭文件;try有异常,执行except中的逻辑,最后再执行finally关闭文件。
"""
3.3.1. 异常细分
import requestswhile True:url = input("请输入要下载网页地址:")try:res = requests.get(url=url)except Exception as e:print("请求失败,原因:{}".format(str(e)))continuewith open('content.txt', mode='wb') as f:f.write(res.content)
之前只是简单的捕获了异常,出现异常则统一提示信息即可。如果想要对异常进行更加细致的异常处理,则可以这样来做:
import requests
from requests import exceptionswhile True:url = input("请输入要下载网页地址:")try:res = requests.get(url=url)print(res) except exceptions.MissingSchema as e:print("URL架构不存在")except exceptions.InvalidSchema as e:print("URL架构错误")except exceptions.InvalidURL as e:print("URL地址格式错误")except exceptions.ConnectionError as e:print("网络连接错误")except Exception as e:print("代码出现错误", e)# 提示:如果想要写的简单一点,其实只写一个Exception捕获错误就可以了。
对错误进行细分的处理,例如:发生Key错误和发生Value错误分开处理。
基本格式:
try:# 逻辑代码passexcept KeyError as e:# 只捕获try代码中发现了键不存在的异常,例如:去字典 info_dict["n1"] 中获取数据时,键不存在。print("KeyError")except ValueError as e:# 只捕获try代码中发现了值相关错误,例如:把字符串转整型 int("诶器")print("ValueError")except Exception as e:# 处理上面except捕获不了的错误(可以捕获所有的错误)。print("Exception")
Python中内置了很多细分的错误,供你选择。
常见异常:
"""
AttributeError 试图访问一个对象没有的树形,比如foo.x,但是foo没有属性x
IOError 输入/输出异常;基本上是无法打开文件
ImportError 无法引入模块或包;基本上是路径问题或名称错误
IndentationError 语法错误(的子类) ;代码没有正确对齐
IndexError 下标索引超出序列边界,比如当x只有三个元素,却试图访问n x[5]
KeyError 试图访问字典里不存在的键 inf['xx']
KeyboardInterrupt Ctrl+C被按下
NameError 使用一个还未被赋予对象的变量
SyntaxError Python代码非法,代码不能编译(个人认为这是语法错误,写错了)
TypeError 传入对象类型与要求的不符合
UnboundLocalError 试图访问一个还未被设置的局部变量,基本上是由于另有一个同名的全局变量,
导致你以为正在访问它
ValueError 传入一个调用者不期望的值,即使值的类型是正确的
"""
更多异常:
"""
ArithmeticError
AssertionError
AttributeError
BaseException
BufferError
BytesWarning
DeprecationWarning
EnvironmentError
EOFError
Exception
FloatingPointError
FutureWarning
GeneratorExit
ImportError
ImportWarning
IndentationError
IndexError
IOError
KeyboardInterrupt
KeyError
LookupError
MemoryError
NameError
NotImplementedError
OSError
OverflowError
PendingDeprecationWarning
ReferenceError
RuntimeError
RuntimeWarning
StandardError
StopIteration
SyntaxError
SyntaxWarning
SystemError
SystemExit
TabError
TypeError
UnboundLocalError
UnicodeDecodeError
UnicodeEncodeError
UnicodeError
UnicodeTranslateError
UnicodeWarning
UserWarning
ValueError
Warning
ZeroDivisionError
"""
3.3.2. 自定义异常&抛出异常
上面都是Python内置的异常,只有遇到特定的错误之后才会抛出相应的异常。
其实,在开发中也可以自定义异常。
class MyException(Exception):pass
try:pass
except MyException as e:print("MyException异常被触发了", e)
except Exception as e:print("Exception", e)
上述代码在except中定义了捕获MyException异常,但他永远不会被触发。因为默认的那些异常都有特定的触发条件,例如:索引不存在、键不存在会触发IndexError和KeyError异常。
对于我们自定义的异常,如果想要触发,则需要使用:raise MyException()
类实现。
class MyException(Exception):passtry:# 。。。raise MyException()# 。。。
except MyException as e:print("MyException异常被触发了", e)
except Exception as e:print("Exception", e)
class MyException(Exception):def __init__(self, msg, *args, **kwargs):super().__init__(*args, **kwargs)self.msg = msgtry:raise MyException("xxx失败了")
except MyException as e:print("MyException异常被触发了", e.msg)
except Exception as e:print("Exception", e)
class MyException(Exception):title = "请求错误"try:raise MyException()
except MyException as e:print("MyException异常被触发了", e.title)
except Exception as e:print("Exception", e)
3.3.3. 特殊的finally
try:# 逻辑代码
except Exception as e:# try中的代码如果有异常,则此代码块中的代码会执行。
finally:# try中的代码无论是否报错,finally中的代码都会执行,一般用于释放资源。print("end")
当在函数或方法中定义异常处理的代码时,要特别注意finally和return。
def func():try:return 123except Exception as e:passfinally:print(666)func()
在try或except中即使定义了return,也会执行最后的finally块中的代码。
3.4. 反射
反射,提供了一种更加灵活的方式让你可以实现去 对象 中操作成员(以字符串的形式去 对象
中进行成员的操作)。
Python中提供了4个内置函数来支持反射:
- getattr,去对象中获取成员
v1 = getattr(对象,"成员名称")
v2 = getattr(对象,"成员名称", 不存在时的默认值)
- setattr,去对象中设置成员
setattr(对象,"成员名称",值)
- hasattr,对象中是否包含成员
v1 = hasattr(对象,"成员名称") # True/False
- delattr,删除对象中的成员
delattr(对象,"成员名称")
以后如果再遇到 对象.成员 这种编写方式时,均可以基于反射来实现。
案例:
class Account(object):def login(self):passdef register(self):passdef index(self):passdef run(self):name = input("请输入要执行的方法名称:") # index register login xx run ..account_object = Account()method = getattr(account_object, name,None) # index = getattr(account_object,"index")if not method:print("输入错误")return method()
3.4.1. 一些皆对象
在Python中有这么句话:万事万物,一切皆对象
。 每个对象的内部都有自己维护的成员。
- 对象是对象
class Person(object):def __init__(self,name,wx):self.name = nameself.wx = wxdef show(self):message = "姓名{},微信:{}".format(self.name,self.wx)user_object = Person("jiaoxingk","jiaoxingk666")
user_object.name
- 类是对象
class Person(object):title = "jiaoxingk"Person.title
# Person类也是一个对象(平时不这么称呼)
- 模块是对象
import rere.match
# re模块也是一个对象(平时不这么称呼)。
由于反射支持以字符串的形式去对象中操作成员【等价于 对象.成员 】,所以,基于反射也可以对类、模块中的成员进行操作。
简单粗暴:只要看到 xx.oo 都可以用反射实现。
class Person(object):title = "jiaoxingk"v1 = Person.title
print(v1)
v2 = getattr(Person,"title")
print(v2)
import rev1 = re.match("\w+","dfjksdufjksd")
print(v1)func = getattr(re,"match")
v2 = func("\w+","dfjksdufjksd")
print(v2)
3.4.2. import_module + 反射
在Python中如果想要导入一个模块,可以通过import语法导入;企业也可以通过字符串的形式导入。
示例一:
# 导入模块
import randomv1 = random.randint(1,100)
# 导入模块
from importlib import import_modulem = import_module("random")v1 = m.randint(1,100)
示例二:
# 导入模块exceptions
from requests import exceptions as m
# 导入模块exceptions
from importlib import import_module
m = import_module("requests.exceptions")
在很多项目的源码中都会有 import_module
和 getattr
配合实现根据字符串的形式导入模块并获取成员
我们在开发中也可以基于这个来进行开发,提高代码的可扩展性
结尾
至此,面向对象篇就完结啦,虽然有很多小细节没有写的很到位,后续也会慢慢补充。学完面向对象,就可以自己动手写一个小项目咯。