Python之面向对象详解(一篇足矣)_python 面向对象
目录
一、初阶面向对象
1. 初识面向对象
1.1 对象和self
1.2 常见成员
1.3 应用示例
将数据封装到一个对象,便于以后使用。
将数据封装到对象中,在方法中对原始数据进行加工处理。
根据类创建多个对象,在方法中对对象中的数据进行修改。
2. 三大特性
2.1 封装
2.2 继承
2.3 多态
3. 扩展:再看数据类型
二、进阶面向对象
1.成员
1.1 变量
1.2 方法
1.3 属性
2.成员修饰符
3.对象嵌套
4.特殊成员
三、补充
1. 继承【补充】
1.1 mro和c3算法
1.2 py2和py3区别(了解)
2. 内置函数补充
3.异常处理
3.1 异常细分
3.2 自定义异常和抛出异常
3.3 特殊的finally
许久未更新,我们将进入系列课程第3个模块的的学习,此模块包含如下三大部分知识:面向对象,Python中支持两种编程方式来写代码,分别是:函数式编程
、面向对象式编程
。
一、初阶面向对象
-
函数式,推荐初学者使用。理由:上手快且逻辑简单清晰。
# 定义函数,在函数中实现功能def func():print(\"一个NB的功能\")# 执行函数func()
-
面向对象,推荐有一些代码经验后使用。理由:面向对象的思想需要有一定的项目积累之后(写多了并且看的多)才能真正理解其精髓,基于面向对象可以编写出扩展性更强的代码(在一定程序上也可以简化代码)。
# 定义类class Foo(object): # 在类中定义方法 def func(self): print(\"一个NB的功能\") # 实例化类的对象obj = Foo()# 执行类中的方法obj.func()
补充:除此之外,在后面我们还会介绍到网络编程和并发编程。
网络编程,学习网络知识后,可以让我们的程序通过网络来进行数据交互和传输 并 掌握其本质。
例如:下载10个抖音视频,每个需要2分钟。- 按照以前的思路,逐一下载就需要20分钟。- 按照并发的思路,创建10个线程/进程来实现,大概需要2分钟就可以完成。
并发编程,一个程序想要执行的速度更快是必须要掌握并发编程的相关知识。
Python支持两种编程方式(其他很多语言只支持一种),所以初学者在刚开始学习往往不知道应如何选择,并且行业内对于 函数式编程 vs 面向对象编程 之间谁更好的讨论也是难分胜负,其实在开发中无论要实现什么样的功能,两种编程模式都能实现,那种让我们能更好实现就选择谁?不必非纠结于那种方式更好,编程语言支持工具,最重要的是把功能实现。初学者在选择编程方式时候,可以遵循如下规则:掌握相关知识点 并且 读懂源码 以及 编写简单的基于面向对象的程序。
1. 初识面向对象
想要通过面向对象去实现某个或某些功能时需要2步:
- 定义类,在类中定义方法,在方法中去实现具体的功能。
- 实例化类并的个一个对象,通过对象去调用并执行方法。
注意:
- 类名称首字母大写以及驼峰式命名;
- py3之后默认类都继承object;
- 在类种编写的函数称为方法;
- 每个方法的第一个参数是self;
- 类中可以定义多个方法。
你会发现,用面向对象编程写的类有点像归类的意思:将某些相似的函数划分到一个类中。但这种编写方式让人感觉有些鸡肋,直接用 函数 写多好呀(函数+模块也可实现)。对吧?别着急,记者往下看。
1.1 对象和self
在每个类中都可以定义个特殊的:__init__ 初始化方法
,在实例化类创建对象时自动执行,即:对象=类名()
。
class Message: #定义类 #定义类方法 def __init__(self, content): self.data = content def send_email(self, email): data = \"给{}发邮件,内容是:{}\".format(email, self.data) print(data) def send_wechat(self, vid): data = \"给{}发微信,内容是:{}\".format(vid, self.data) print(data)# 对象 = 类名() # 自动执行类中的 __init__ 方法。# 1. 根据类型创建一个对象,内存的一块区域 。# 2. 执行__init__方法,模块会将创建的那块区域的内存地址当self参数传递进去。往区域中(data=\"注册成功\")msg_object = Message(\"注册成功\") #实例化对象(根据类创建一个对象)#调用类方法msg_object.send_email(\"xiaofeng@live.com\") # 给xiaofeng@live.com发邮件,内容是:注册成功msg_object.send_wechat(\"小峰\") # 给小峰发微信,内容是:注册成功
通过上述的示例,你会发现:
- 对象,让我们可以在它的内部先封装一部分数据,以后想要使用时,再去里面获取。对象其实就是基于类实例化出来”一块内存“,默认里面没有数据;经过类的
__init__
方法,可以在内存中初始化一些数据。 - self,类中的方法需要由这个类的对象来触发并执行( 对象.方法名 ),且在执行时会自动将对象当做参数传递给self,以供方法中获取对象中已封装的值。self,本质上就是一个参数。这个参数是Python内部会提供,其实本质上就是调用当前方法的那个对象。
- 面向对象的思想:将一些数据封装到对象中,在执行方法时,再去对象中获取。函数式的思想:函数内部需要的数据均通过参数的形式传递。
注意:除了self默认参数以外,方法中的参数的定义和执行与函数是相同。当然,根据类也可以创建多个对象并执行其中的方法。
1.2 常见成员
在编写面向对象相关代码时,最常见成员有:
- 实例变量,属于对象,只能通过对象调用。
- 绑定方法,属于类,通过对象调用 或 通过类调用。
注意:还有很多其他的成员,后续再来逐一介绍。
类和对象总结:
- 仅做数据封装。
- 封装数据 + 方法再对数据进行加工处理。
- 创建同一类的数据且同类数据可以具有相同的功能(方法)。
class Person: def __init__(self, n1, n2): # 实例变量 self.name = n1 self.age = n2 # 绑定方法 def show(self): msg = \"我叫{},今年{}岁。\".format(self.name, self.age) print(msg) def all_message(self): msg = \"我是{}人,我叫{},今年{}岁。\".format(Person.country, self.name, self.age) print(msg) def total_message(self): msg = \"我是{}人,我叫{},今年{}岁。\".format(self.country, self.name, self.age) print(msg)# 执行绑定方法p1 = Person(\"小峰\",20) #初始化,实例化了Person类的对象叫p1p1.show()# 或# p1 = Person(\"小峰\",20)# Person.show(p1)p1 = Person(\"root\",99) p1.show()
1.3 应用示例
-
将数据封装到一个对象,便于以后使用。
class UserInfo: def __init__(self, name, pwd,age): self.name = name self.password = pwd self.age = agedef run(): user_object_list = [] # 用户注册 while True: user = input(\"用户名:\") if user.upper() == \"Q\": break pwd = input(\"密码\") # user_object对象中有:name/password user_object = UserInfo(user, pwd,19) # user_dict = {\"name\":user,\"password\":pwd} user_object_list.append(user_object) # user_object_list.append(user_dict) # 展示用户信息 for obj in user_object_list: print(obj.name, obj.password) 总结:- 数据封装到对象,以后再去获取。 - 规范数据(约束)
注意:用字典也可以实现做封装,只不过字典在操作值时还需要自己写key,面向对象只需要
.
即可获取对象中封装的数据。 -
将数据封装到对象中,在方法中对原始数据进行加工处理。
user_list = [\"用户-{}\".format(i) for i in range(1,3000)]# 分页显示,每页显示10条while True: page = int(input(\"请输入页码:\")) start_index = (page - 1) * 10 end_index = page * 10 page_data_list = user_list[start_index:end_index] for item in page_data_list: print(item)
class Pagination: def __init__(self, current_page, per_page_num=10): self.per_page_num = per_page_num if not current_page.isdecimal(): self.current_page = 1 return current_page = int(current_page) if current_page < 1: self.current_page = 1 return self.current_page = current_page def start(self): return (self.current_page - 1) * self.per_page_num def end(self): return self.current_page * self.per_page_numuser_list = [\"用户-{}\".format(i) for i in range(1, 3000)]# 分页显示,每页显示10条while True: page = input(\"请输入页码:\") # page,当前访问的页码 # 10,每页显示10条数据# 内部执行Pagination类的init方法。 pg_object = Pagination(page, 20) page_data_list = user_list[ pg_object.start() : pg_object.end() ] for item in page_data_list: print(item)
-
根据类创建多个对象,在方法中对对象中的数据进行修改。
class Police: \"\"\"警察\"\"\" def __init__(self, name, role): self.name = name self.role = role if role == \"队员\": self.hit_points = 200 else: self.hit_points = 500 def show_status(self): \"\"\" 查看警察状态 \"\"\" message = \"警察{}的生命值为:{}\".format(self.name, self.hit_points) print(message) def bomb(self, terrorist_list): \"\"\" 投炸弹,炸掉恐怖分子 \"\"\" for terrorist in terrorist_list: terrorist.blood -= 200 terrorist.show_status()class Terrorist: \"\"\" 恐怖分子 \"\"\" def __init__(self, name, blood=300): self.name = name self.blood = blood def shoot(self, police_object): \"\"\" 开枪射击某个警察 \"\"\" police_object.hit_points -= 5 police_object.show_status() self.blood -= 2 def strafe(self, police_object_list): \"\"\" 扫射某些警察 \"\"\" for police_object in police_object_list: police_object.hit_points -= 8 police_object.show_status() def show_status(self): \"\"\" 查看恐怖分子状态 \"\"\" message = \"恐怖分子{}的血量值为:{}\".format(self.name, self.blood) print(message)def run(): # 1.创建3个警察 p1 = Police(\"小峰\", \"队员\") p2 = Police(\"苑昊\", \"队员\") p3 = Police(\"于超\", \"队长\") # 2.创建2个匪徒 t1 = Terrorist(\"alex\") t2 = Terrorist(\"eric\") # alex匪徒射击于超警察 t1.shoot(p3) # alex扫射 t1.strafe([p1, p2, p3]) # eric射击苑昊 t2.shoot(p2) # 小峰炸了那群匪徒王八蛋 p1.bomb([t1, t2]) # 小峰又炸了一次alex p1.bomb([t1])if __name__ == \'__main__\': run()
2. 三大特性
面向对象编程在很多语言中都存在,这种编程方式有三大特性:封装、继承、多态。
2.1 封装
封装主要体现在两个方面:
- 将同一类方法封装到了一个类中,例如上述示例中:匪徒的相关方法都写在Terrorist类中;警察的相关方法都写在Police类中。
- 将数据封装到了对象中,在实例化一个对象时,可以通过
__init__
初始化方法在对象中封装一些数据,便于以后使用。
2.2 继承
传统的理念中有:儿子可以继承父亲的财产。在面向对象中也有这样的理念,即:子类可以继承父类中的方法和类变量(不是拷贝一份,父类的还是属于父类,子类可以继承而已)。
class Base: def func(self): print(\"Base.func\")class Son(Base): def show(self): print(\"Son.show\") s1 = Son()s1.show()s1.func() # 优先在自己的类中找,自己没有才去父类。对象到底是谁?优先就会先去谁里面找。s2 = Base()s2.func()
小结:
- 执行对象.方法时,优先去当前对象所关联的类中找,没有的话才去她的父类中查找。
- Python支持多继承:先继承左边、再继承右边的。
- 当前self到底是谁?去self对应的那个类中去获取成员,没有就按照继承关系向上查找 。
2.3 多态
多态,按字面翻译其实就是多种形态。其他编程语言中,是不允许这样类编写的,例如:Java
class Cat{ public void eat() { System.out.println(\"吃鱼\"); } }class Dog { public void eat() { System.out.println(\"吃骨头\"); } public void work() { System.out.println(\"看家\"); } }public class Test { public static void main(String[] args) { obj1 = Cat() obj2 = Cat() show(obj1) show(obj2) obj3 = Dog() show(obj3) } public static void show(Cat a) { a.eat() } }
java中正确的写法:
abstract class Animal { abstract void eat(); } class Cat extends Animal { public void eat() { System.out.println(\"吃鱼\"); } }class Dog extends Animal { public void eat() { System.out.println(\"吃骨头\"); } public void work() { System.out.println(\"看家\"); } }public class Test { public static void main(String[] args) { obj1 = Cat() show(obj1) obj2 = Dog() show(obj2) } public static void show(Animal a) { a.eat() } }
Tips:
在java或其他语言中的多态是基于:接口 或 抽象类和抽象方法 来实现,让数据可以以多种形态存在。在Python中则不一样,由于Python对数据类型没有任何限制,所以他天生支持多态。
class Email(object): def send(self): print(\"发邮件\") class Message(object): def send(self): print(\"发短信\") def func(arg): v1 = arg.send() # 浅拷贝v1 = Email()func(v1)v2 = Message()func(v2)
在程序设计中,鸭子类型(duck typing)是动态类型的一种风格。在鸭子类型中,关注点在于对象的行为,能作什么;而不是关注对象所属的类型。例如:一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟可以被称为鸭子。
三大特性的小结:
封装,将方法封装到类中 或 将数据封装到对象中,便于以后使用。
继承,将类中的公共的方法提取到基类中去实现。
多态,Python默认支持多态(这种方式称之为鸭子类型),如上面的代码即可。
3. 扩展:再看数据类型
在初步了解面向对象之后,再来看看我们之前学习的:str、list、dict等数据类型,他们其实都一个类,根据类可以创建不同类的对象。
补充:在Python3中编写类时,默认都会继承object(即使不写也会自动继承)。在Python2是不同的:继承object,新式类;不继承object,经典类。
二、进阶面向对象
1.成员
1.1 变量
- 实例变量,属于对象,每个对象中各自维护自己的数据。
- 类变量,属于类,可以被所有对象共享,一般用于给对象提供公共数据(类似于全局变量)。
class Person(object): country = \"中国\" #类变量 def __init__(self, name, age): self.name = name self.age = age #实例变量
提示:当把每个对象中都存在的相同的实例变量时,可以选择把它放在类变量中,这样就可以避免对象中维护多个相同数据。
- 注意读和写的区别。
class Person(object): country = \"中国\" def __init__(self, name, age): self.name = name self.age = ageprint(Person.country) # 中国p1 = Person(\"小峰\",20)print(p1.name) # 小峰print(p1.age) # 20print(p1.country) # 中国p1.name = \"root\" # 在对象p1中讲name重置为rootp1.num = 19 # 在对象p1中新增实例变量 num=19p1.country = \"china\" # 在对象p1中新增实例变量 country=\"china\"print(p1.country) # chinaprint(Person.country) # 中国Person.country=\"china\"print(Person.country) #china
- 继承关系中的读写
class Base(object): country = \"中国\"class Person(Base): def __init__(self, name, age): self.name = name self.age = age# 读print(Base.country) # 中国print(Person.country) # 中国obj = Person(\"小峰\",19)print(obj.country) # 中国# 写Base.country = \"china\"Person.country = \"泰国\"obj.country = \"日本\"print(obj.country) #日本print(Person.country) #泰国print(Base.country) #china
- 例题:
class Parent(object): x = 1class Child1(Parent): passclass Child2(Parent): passprint(Parent.x, Child1.x, Child2.x) # 1 1 1Child1.x = 2print(Parent.x, Child1.x, Child2.x) # 1 2 1Parent.x = 3print(Parent.x, Child1.x, Child2.x) # 3 2 3
1.2 方法
- 绑定方法,默认有一个self参数,由对象进行调用(此时self就等于调用方法的这个对象)【对象&类均可调用】
- 类方法,默认有一个cls参数,用类或对象都可以调用(此时cls就等于调用方法的这个类)【对象&类均可调用】
- 静态方法,无默认参数,用类和对象都可以调用。【对象&类均可调用】
class Foo(object): def __init__(self, name, age):#初始化方法 self.name = name self.age = age def f1(self): print(\"绑定方法\", self.name) @classmethod def f2(cls): print(\"类方法\", cls) @staticmethod def f3(): print(\"静态方法\")# 绑定方法(对象)obj = Foo(\"小峰\", 20)obj.f1() Foo.f1(obj)# 类方法Foo.f2() # cls就是当前调用这个方法的类。(类)obj.f2() # cls就是当前调用这个方法的对象的类。# 静态方法Foo.f3() # 类执行执行方法(类)obj.f3() # 对象执行执行方法
Tips: 在Python中比较灵活,方法都可以通过对象和类进行调用;而在java、c#等语言中,绑定方法只能由对象调用;类方法或静态方法只能由类调用。
思考题:在类中 @classmethod 和 @staticmethod 的作用是什么?
1.3 属性
属性其实就是由绑定方法 + 特殊装饰器 组合创造出来的,属性的出现让我们以后在调用方法时可以不加括号,例如:
class Foo(object): def __init__(self, name): self.name = name def f1(self): return 123 @property def f2(self): return 123obj = Foo(\"小峰\")v1 = obj.f1()print(v1)v2 = obj.f2 #v2 = obj.f2()print(v2)
其实,除了咱们写的示例意外,在很多模块和框架的源码中也有porperty的身影,例如:requests模块。
import requests# 内部下载视频,并将下载好的数据分装到Response对象中。res = requests.get( url=\"https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f240000buuer5aa4tij4gv6ajqg\", headers={ \"user-agent\": \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 FS\" })# 去对象中获取text,其实需要读取原始文本字节并转换为字符串res.text
关于属性的编写有两种方式:
方式一,基于装饰器
class C(object): def __init__(self): self.value=0 @property def x(self): return self.value @x.setter def x(self, value): self.value = value @x.deleter def x(self): self.value=0obj = C()obj.xobj.x = 123print(obj.x) #123del obj.xprint(obj.x) #0
方式二,基于定义变量
class C(object): def __init__(self): self.value=0 def getx(self): return self.value def setx(self, value): self.value = value def delx(self): self.value=0 x = property(getx, setx, delx, \"I\'m the \'x\' property.\")obj = C()obj.xobj.x = 123print(obj.x) #123del obj.xprint(obj.x) #0
最后,对属性进行一个补充:由于属性和实例变量的调用方式相同,所以在编写时需要注意——属性名称 不要 实例变量 重名。一旦重名,可能就会有报错。如果真的想要在名称上创建一些关系,可以让实例变量加上一个下划线。
class Foo(object): def __init__(self, name, age): self._name = name self.age = age @property def name(self): return \"{}-{}\".format(self._name, self.age)obj = Foo(\"小峰\", 123)print(obj._name)print(obj.name)
2.成员修饰符
Python中成员的修饰符就是指的是:公有、私有。
- 公有,在任何地方都可以调用这个成员。
- 私有,只有在类的内部才可以调用该成员(成员是以两个下划线开头,则表示该成员为私有)。
示例:
class Foo(object): def __init__(self, name, age): self.__name = name self.age = age def get_data(self): return self.__name def get_age(self): return self.age def get_age1(self): print(\"公有的get_age1\") def __get_data1(self): print(\"私有的__get_data1方法\") def proxy(self): print(\"公有的proxy\") self.__get_data1()#成员的访问obj = Foo(\"小峰\", 123)# 公有成员print(obj.age)v1 = obj.get_age()print(v1)# 私有成员# print(obj.__name) # 错误,由于是私有成员,只能在类中进行使用。v2 = obj.get_data()print(v2)#方法的访问o1 = Foo(\"xiaofeng\",10)o1.get_age1()# o1.__get_data1() #私有方法只能在类内部进行访问o1.proxy()
示例:
class Foo(object): def __init__(self, name, age): self.__name = name self.age = age @property def __get_age(self): print(\"公有的get_age\") return self.age @property def proxy(self): print(\"公有的proxy\") return self.__nameobj = Foo(\"xiaowu\", 18)v1 = obj.proxyprint(v1)# v2=obj.__get_age #私有属性,只能在类内部进行访问# print(v2)
特别提醒:父类中的私有成员,子类无法继承。
class Base(object): def __data(self): print(\"base.__data\") def num(self): print(\"base.num\")class Foo(Base): def func1(self): self.num() def func2(self): self.__data() # 不允许执行父类中的私有方法obj = Foo()obj.func1()#obj.func2() #会报错,子类不继承父类的私有成员(变量、方法、属性)
Tips:
写在最后,按理说私有成员是无法被外部调用,但如果用一些特殊的语法也可以(Flask源码中有这种写法,大家写代码不推荐这样写 【_类名__变量名/方法名/属性名】)。
成员是否可以作为独立的功能暴露给外部,让外部调用并使用。可以——公有使用;不可以,内部其他方法做辅助,私有使用。
class Foo(object): def __init__(self): self.__num = 123 self.age = 19 def __msg(self): print(1234)obj = Foo()#访问私有变量print(obj.age)#print(obj.__num()) #会报错print(obj._Foo__num)#访问私有方法# print(obj.__msg()) #会报错obj._Foo__msg()
3.对象嵌套
在基于面向对象进行编程时,对象之间可以存在各种各样的关系,例如:组合、关联、依赖等(Java中的称呼),用大白话来说就是各种嵌套。
下面我们就用示例来学习常见的嵌套的情景:
示例一:
class Student(object): \"\"\" 学生类 \"\"\" def __init__(self, name, age): self.name = name self.age = age def message(self): data = \"我是一名学生,我叫:{},我今年{}岁\".format(self.name, self.age) print(data)class Classes(object): \"\"\" 班级类 \"\"\" def __init__(self, title): self.title = title self.student_list = [] def add_student(self, stu_object): self.student_list.append(stu_object) def add_students(self, stu_object_list): for stu in stu_object_list: self.add_student(stu)s1 = Student(\"小峰\", 19)s2 = Student(\"小张\", 19)s3 = Student(\"日天\", 19)c1 = Classes(\"三年二班\")c1.add_student(s1)c1.add_students([s2, s3])print(c1.title)print(c1.student_list) #这样打印出来的只是学生对应对象的存储地址for i in range(len(c1.student_list)): #正确的遍历班级成员列表,并打印出学生的相关信息 print(c1.student_list[i].name, c1.student_list[i].age)
示例二:
class Student(object): \"\"\" 学生类 \"\"\" def __init__(self, name, age, class_object): self.name = name self.age = age self.class_object = class_object def message(self): data = \"我是一名{}班的学生,我叫:{},我今年{}岁\".format(self.class_object.title, self.name, self.age) print(data)class Classes(object): \"\"\" 班级类 \"\"\" def __init__(self, title, school_object): self.title = title self.school_object = school_objectclass School(object): \"\"\" 学校类 \"\"\" def __init__(self, name): self.name = name#实例化学校类s1 = School(\"北京校区\")s2 = School(\"上海校区\")#实例化班级类c1 = Classes(\"Python全栈\", s1)c2 = Classes(\"Linux云计算\", s2)user_object_list = [ Student(\"小峰\", 19, c1), Student(\"小王\", 19, c1), Student(\"小白\", 19, c2)]for obj in user_object_list: print(obj.name, obj.class_object.title, obj.class_object.school_object.name)
4.特殊成员
在Python的类中存在一些特殊的方法,这些方法都是 __方法__
格式,这种方法在内部均有特殊的含义,接下来我们来讲一些常见的特殊成员:
__init__
,初始化方法class Foo(object): def __init__(self, name): self.name = nameobj = Foo(\"小峰\")
__new__
,构造方法class Foo(object): def __init__(self, name): print(\"第二步:初始化对象,在空对象中创建数据\") self.name = name def __new__(cls, *args, **kwargs): print(\"第一步:先创建空对象并返回\") return object.__new__(cls)obj = Foo(\"小峰\")
__call__,对象()的时候自动执行
class Foo(object): def __call__(self, *args, **kwargs): print(\"执行call方法\")obj = Foo() obj() # 当遇见 对象名() 的时候就会自动执行call方法
__str__,该方法中必须返回一个字符串
class Foo(object): def __str__(self): return \"必须返回字符串\"obj = Foo(\"xiaowang\")#下面两个语句打印的结果是一样的,第一个是对第二个语句内部做了优化而已print(obj)print(str(obj))
__dict__,字典对象的支持【对象[\'\'] 、 对象[]=xxxx 、del 对象[]】可以和下面的三种方法搭配使用
class Foo(object): def __init__(self, name, age): self.name = name self.age = ageobj = Foo(\"小峰\",19)print(obj.__dict__)
__getitem__
、__setitem__
、__delitem__
class Foo(object): def __setitem__(self, key, value): self.value = value # print(key, value) def __getitem__(self, value): return self.value def __delitem__(self,key): self.value = None return keyobj = Foo()obj[\"feng\"]=19print(obj.__dict__)print(obj[\"feng\"])obj[\'feng\'] = 21print(obj[\"feng\"])del obj[\"feng\"]print(obj[\"feng\"])
__enter__
、__exit__,上下文管理和文件操作那块所讲的类似
class Foo(object): def __enter__(self): print(\"进入了\") return 666 def __exit__(self, exc_type, exc_val, exc_tb): print(\"出去了\")obj = Foo()with obj as data: print(data)
__add__
等操作函数class Foo(object): def __init__(self, name): self.name = name def __add__(self, other): return \"{}-{}\".format(self.name, other.name)v1 = Foo(\"I\")v2 = Foo(\"Love\")# 对象+值,内部会去执行 对象.__add__方法,并将+后面的值当做参数传递过去。v3 = v1 + v2print(v3)v4 = Foo(\"you\")v5=Foo(v3)+v4print(v5)
__iter__
迭代器迭代器类型的定义:
1.当类中定义了 __iter__ 和 __next__ 两个方法。
2.__iter__ 方法需要返回对象本身,即:self
3. __next__ 方法,返回下一个数据,如果没有数据了,则需要抛出一个StopIteration的异常。迭代器对象支持通过next取值,如果取值结束则自动抛出StopIteration。for循环内部在循环时,先执行__iter__方法,获取一个迭代器对象,然后不断执行的next取值(有异常StopIteration则终止循环)。
官方文档:https://docs.python.org/3/library/stdtypes.html#iterator-typesclass IT(object): def __init__(self): self.counter = 0 def __iter__(self): return self def __next__(self): self.counter += 1 if self.counter == 3: raise StopIteration() return self.counter# 根据类实例化创建一个迭代器对象:obj1 = IT()v1 = next(obj1) # obj1.__next__()print(v1)v2 = next(obj1)print(v2)v3 = next(obj1) # 抛出异常,异常会在后面的内容中讲到print(v3)############################obj2 = IT()for item in obj2: # 首先会执行迭代器对象的__iter__方法并获取返回值,一直去反复的执行 next(对象) print(item)
生成器,创建生成器对象(内部是根据生成器类generator创建的对象),生成器类的内部也声明了:__iter__、__next__ 方法。如果按照迭代器的规定来看,其实生成器类也是一种特殊的迭代器类(生成器也是一个中特殊的迭代器)。
def func(): yield 1 yield 2obj2 = func()for item in obj2: print(item)
可迭代对象。如果一个类中有__iter__方法且返回一个迭代器对象 ,则我们称以这个类创建的对象为可迭代对象。可迭代对象,是可以使用for来进行循环,在循环的内部其实是先执行 __iter__ 方法,获取其迭代器对象,然后再在内部执行这个迭代器对象的next功能,逐步取值。
class Foo1(object): def __iter__(self): return 迭代器对象(生成器对象)obj1 = Foo1() # obj是 可迭代对象。for item in obj1: passclass Foo2(object): def __iter__(self): yield 1 yield 2obj2 = Foo2()for item in obj2: print(item)
class IT(object): def __init__(self): self.counter = 0 def __iter__(self): return self def __next__(self): self.counter += 1 if self.counter == 3: raise StopIteration() return self.counter # 可迭代对象class Foo(object): def __iter__(self): return IT()obj = Foo() for item in obj: # 循环可迭代对象时,内部先执行obj.__iter__并获取迭代器对象;不断地执行迭代器对象的next方法。 print(item)
基于可迭代对象和迭代器实现:自定义range 。
class IterRange(object): def __init__(self, num): self.num = num self.counter = -1 def __iter__(self): return self def __next__(self): self.counter += 1 if self.counter == self.num: raise StopIteration() return self.counterclass Xrange(object): def __init__(self, max_num): self.max_num = max_num def __iter__(self): return IterRange(self.max_num) # 可迭代对象obj = Xrange(100)for item in obj: print(item)
基于可迭代对象和生成器实现:自定义range。
class Xrange(object): def __init__(self, max_num): self.max_num = max_num def __iter__(self): counter = 0 while counter > self.max_num: yield counter counter += 1obj = Xrange(100)for item in obj: print(item)
补充:
- Iterator检查对象是否是迭代器;Iterable,检查对象是否可以被迭代。
- 所有的迭代器都是可迭代的,但并非所有的可迭代对象都是迭代器。
from collections.abc import Iterator, Iterablev1 = [11, 22, 33]print( isinstance(v1, Iterator) ) # false,判断是否是迭代器;判断依据是__iter__ 和 __next__。v2 = v1.__iter__()print( isinstance(v2, Iterator) ) # Truev1 = [11, 22, 33]print( isinstance(v1, Iterable) ) # True,判断依据是是否有 __iter__且返回迭代器对象。v2 = v1.__iter__()print( isinstance(v2, Iterable) ) # True,判断依据是是否有 __iter__且返回迭代器对象。
三、补充
1. 继承【补充】
对于Python面向对象中的继承,我们已学过:
继承存在意义:将公共的方法提取到父类中,有利于增加代码重用性。
继承的编写方式:
# 继承class Base(object): passclass Foo(Base): pass# 多继承class Base1(object): passclass Bar1(object): passclass Foo1(Base1,Bar1): pass
调用类中的成员时,遵循:
- 优先在自己所在类中找,没有的话则去父类中找。
- 如果类存在多继承(多个父类),则先找左边再找右边。
上述的知识点掌握之后,其实就可以解决继承相关的大部分问题。
1.1 mro和c3算法
如果类中存在继承关系,在可以通过mro()
获取当前类的继承关系(找成员的顺序)。merge,与除了当前元素的其他元素比较是存在,若不存在就进行剔除.
示例1:
#############################################mro(A) = [A] + [B,C]mro(A) = [A,B,C]#############################################mro(A) = [A] + merge( mro(B), mro(C), [B,C] )mro(A) = [A] + merge( [object], [object], [] )mro(A) = [A] + [B,C,object]mro(A) = [A,B,C,object]
class C(object): passclass B(object): passclass A(B, C): passprint( A.mro() ) # [,, , ]print( A.__mro__ ) # (, , , )
示例2:
mro(A) = [A] + merge( mro(B), mro(C), [B,C] )mro(A) = [A] + merge( [], [D], [] )mro(A) = [A] + [B,C,D]mro(A) = [A,B,C,D]
class D(object): passclass C(D): passclass B(object): passclass A(B, C): passprint( A.mro() ) # [, , , , ]
示例3:
简写为:A -> B -> D -> G -> H -> K -> C -> E -> F -> M -> N -> P -> object
mro(A) = [A] + merge( mro(B), mro(C), mro(P), [B,C,P]) [] [N] [P] [P]mro(A) = [A,B,D,G,H,K,C,E,F,M,N,P]-----------------------------------------------------mro(B) = [B] + merge( mro(D), mro(E), [D,E])mro(D) = [D] + merge(mro(G),mro(H), [G,H])mro(G) = [G]mro(H) = [H,K]mro(B) = [B] + merge( [], [E,M], [E])mro(B) = [B,D,G,H,K,E,M]-----------------------------------------------------mro(C) = [C] + merge(mro(E),mro(F),[E,F])mro(E) = [E,M]mro(F) = [F,M,N] mro(C) = [C] + merge([M],[M,N] ,[])mro(C) = [C,E,F,M,N]
class M: passclass N: passclass E(M): passclass G: passclass K: passclass H(K): passclass D(G, H): passclass F(M, N): passclass P: passclass C(E, F): passclass B(D, E): passclass A(B, C, P): passprint(A.mro()) # 简写为:A -> B -> D -> G -> H -> K -> C -> E -> F -> M -> N -> P -> object
特别补充:一句话搞定继承关系
不知道你是否发现,如果用正经的C3算法规则去分析一个类继承关系有点繁琐,尤其是遇到一个复杂的类也要分析很久。所以,我自己根据经验总结了一句话赠送给大家:从左到右,深度优先,大小钻石,留住顶端,基于这句话可以更快的找到继承关系。
1.2 py2和py3区别(了解)
在python2.2之前,只支持经典类【从左到右,深度优先,大小钻石,不留顶端】未继承object类型。
后来,Python想让类默认继承object(其他语言的面向对象基本上也都是默认都继承object),此时发现原来的经典类不能直接集成集成这个功能,有Bug。
所以,Python决定不再原来的经典类上进行修改了,而是再创建一个新式类来支持这个功能。【从左到右,深度优先,大小钻石,留住顶端。-----C3算法】
经典类,不继承object类型
class Foo: pass
新式类,直接或间接继承object
class Base(object): passclass Foo(Base): pass
这样,python2.2之后 中就出现了经典类和新式类共存。(正式支持是2.3)
最终,python3中丢弃经典类,只保留新式类。
2. 内置函数补充
本次要给讲解的内置函数共8个,他们都跟面向对象的知识相关。
classmethod、staticmethod、property 。
callable,是否可在后面加括号执行。所以当你以后在见到下面的情况时,首先就要想到handler可以是:函数、类、具有call方法的对象 这三种,到底具体是什么,需要根据代码的调用关系才能分析出来。
super,按照mro继承关系向上找成员。
type,获取一个对象的类型。
isinstance,判断对象是否是某个类或其子类的实例。
issubclass,判断类是否是某个类的子孙类。
3.异常处理
在程序中如果遇到一些 不可预知
的错误,可以选择用异常处理来做。
import requestswhile True: url = input(\"请输入要下载网页地址:\") res = requests.get(url=url) with open(\'content.txt\', mode=\'wb\') as f: f.write(res.content)
上述下载视频的代码在正常情况下可以运行,但如果遇到网络出问题,那么此时程序就会报错无法正常执行。
异常处理的基本格式:
try: # 逻辑代码except Exception as e: # try中的代码如果有异常,则此代码块中的代码会执行。
try: # 逻辑代码except Exception as e: # try中的代码如果有异常,则此代码块中的代码会执行。finally: # try中的代码无论是否报错,finally中的代码都会执行,一般用于释放资源。\"\"\"try: file_object = open(\"xxx.log\") # ...except Exception as e: # 异常处理finally: file_object.close() \"\"\"
try中没异常,最后执行finally关闭文件;try有异常,执行except中的逻辑,最后再执行finally关闭文件。
3.1 异常细分
如果想要对错误进行更新的处理,例如:发生Key错误和发生Value错误分开处理。Python中内置了很多细分的错误,供你选择。
常见异常:\"\"\"AttributeError 试图访问一个对象没有的树形,比如foo.x,但是foo没有属性xIOError 输入/输出异常;基本上是无法打开文件ImportError 无法引入模块或包;基本上是路径问题或名称错误IndentationError 语法错误(的子类) ;代码没有正确对齐IndexError 下标索引超出序列边界,比如当x只有三个元素,却试图访问x[5]KeyError 试图访问字典里不存在的键KeyboardInterrupt Ctrl+C被按下NameError 使用一个还未被赋予对象的变量SyntaxError Python代码非法,代码不能编译(个人认为这是语法错误,写错了)TypeError 传入对象类型与要求的不符合UnboundLocalError 试图访问一个还未被设置的局部变量,基本上是由于另有一个同名的全局变量,导致你以为正在访问它ValueError 传入一个调用者不期望的值,即使值的类型是正确的\"\"\"更多异常:\"\"\"ArithmeticErrorAssertionErrorAttributeErrorBaseExceptionBufferErrorBytesWarningDeprecationWarningEnvironmentErrorEOFErrorExceptionFloatingPointErrorFutureWarningGeneratorExitImportErrorImportWarningIndentationErrorIndexErrorIOErrorKeyboardInterruptKeyErrorLookupErrorMemoryErrorNameErrorNotImplementedErrorOSErrorOverflowErrorPendingDeprecationWarningReferenceErrorRuntimeErrorRuntimeWarningStandardErrorStopIterationSyntaxErrorSyntaxWarningSystemErrorSystemExitTabErrorTypeErrorUnboundLocalErrorUnicodeDecodeErrorUnicodeEncodeErrorUnicodeErrorUnicodeTranslateErrorUnicodeWarningUserWarningValueErrorWarningZeroDivisionError\"\"\"
3.2 自定义异常和抛出异常
上面都是Python内置的异常,只有遇到特定的错误之后才会抛出相应的异常。其实,在开发中也可以自定义异常。
class MyException(Exception): passtry: passexcept MyException as e: print(\"MyException异常被触发了\", e)except Exception as e: print(\"Exception\", e)
上述代码在except中定义了捕获MyException异常,但他永远不会被触发。因为默认的那些异常都有特定的触发条件,例如:索引不存在、键不存在会触发IndexError和KeyError异常。对于我们自定义的异常,如果想要触发,则需要使用:
raise MyException()
类实现。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)
示例:
class IT(object): def __init__(self): self.count = 0 def __iter__(self): return self def __next__(self): self.count += 1 if self.count == 3: raise StopIteration return self.countobj=IT()while True: try: i=next(obj) except StopIteration as e: print(\"数据获取完毕\") break print(i)
3.3 特殊的finally
当在函数或方法中定义异常处理的代码时,要特别注意finally和return。在try或except中即使定义了return,也会执行最后的finally块中的代码。
def func(): try: return 123 except Exception as e: pass finally: print(666)print(func()) #先执行finally,再执行return
本 篇 完 结 … …
持 续 更 新 中 … …