一. 继承
1. 继承的概念
1) 现实中的继承
在现实生活中,继承一般指的是子女继承父辈的财产,如下图
搞不好,结果如下..
2) 程序中的继承
- 在程序中,继承描述的是多个类之间的所属关系。
- 如果一个类A里面的属性和方法可以复用,则可以通过继承的方式,传递到类B里。
- 那么类A就是基类,也叫做父类;类B就是派生类,也叫做子类。
# 父类
class A(object):
def __init__(self):
self.num = 10
def print_num(self):
print(self.num + 10)
# 子类
class B(A):
pass
b = B()
print(b.num)
b.print_num()
计算结果:
10
20
2* 单继承
单继承:子类只继承一个父类
- 故事情节:煎饼果子老师傅在煎饼果子界摸爬滚打几十年,拥有一身精湛的煎饼果子技术,并总结了一套”古法煎饼果子配方”。
- 可是老师傅年迈已久,在嗝屁之前希望把自己的配方传承下去,于是老师傅把配方传给他的徒弟大猫…
# 定义一个Master类
class Master(object):
def __init__(self):
# 属性
self.kongfu = “古法煎饼果子配方”
# 实例方法
def make_cake(self):
print(“按照 <%s> 制作了一份煎饼果子…” % self.kongfu)
# 定义Prentice类,继承了 Master,则Prentice是子类,Master是父类。
class Prentice(Master):
#子类可以继承父类所有的属性和方法,哪怕子类没有自己的属性和方法,也可以使用父类的属性和方法。
pass
laoli = Master()
print(laoli.kongfu)
laoli.make_cake()
damao = Prentice() # 创建子类实例对象
print(damao.kongfu) # 子类对象可以直接使用父类的属性
damao.make_cake() # 子类对象可以直接使用父类的方法
说明:
- 虽然子类没有定义__init__方法初始化属性,也没有定义实例方法,但是父类有。所以只要创建子类的对象,就默认执行了那个继承过来的__init__方法
总结:
- 子类在继承的时候,在定义类时,小括号()中为父类的名字
- 父类的属性、方法,会被继承给子类
剧情发展:
大猫掌握了师傅的配方,可以制作古法煎饼果子。但是大猫是个爱学习的好孩子,他希望学到更多的煎饼果子的做法,于是通过百度搜索,找到了一家煎饼果子培训学校。(多继承)
3* 多继承
多继承:子类继承多个父类
class Master(object):
def __init__(self):
# 实例变量,属性
self.kongfu = “古法煎饼果子配方”
# 实例方法,方法
def make_cake(self):
print(“[古法] 按照 <%s> 制作了一份煎饼果子…” % self.kongfu)
def dayandai(self):
print(“师傅的大烟袋..”)
class School(object):
def __init__(self):
self.kongfu = “现代煎饼果子配方”
def make_cake(self):
print(“[现代] 按照 <%s> 制作了一份煎饼果子…” % self.kongfu)
def xiaoyandai(self):
print(“学校的小烟袋..”)
# 多继承,继承了多个父类(School在前)
class Prentice(School, Master):
pass
damao = Prentice()
print(damao.kongfu)
damao.make_cake()
damao.dayandai()
damao.xiaoyandai()
class Prentice(Master, School): # 多继承,继承了多个父类(Master在前)
pass
damao = Prentice()
# 执行Master的属性
print(damao.kongfu)
# 执行Master的实例方法
damao.make_cake()
# 子类的魔法属性__mro__决定了属性和方法的查找顺序
print(Prentice.__mro__)
# 不重名不受影响
damao.dayandai()
damao.xiaoyandai()
说明:
- 多继承可以继承多个父类,也继承了所有父类的属性和方法
- 注意:如果多个父类中有同名的 属性和方法,则默认使用第一个父类的属性和方法(根据类的魔法属性mro的顺序来查找)
- 多个父类中,不重名的属性和方法,不会有任何影响。
剧情发展:
大猫掌握了 师傅的配方 和 学校的配方,通过研究,大猫在两个配方的基础上,创建了一种全新的煎饼果子配方,称之为 “猫氏煎饼果子配方”。(子类重写父类同名属性和方法)
4* 子类重写父类的同名属性和方法
class Master(object):
def __init__(self):
self.kongfu = “古法煎饼果子配方”
def make_cake(self):
print(“[古法] 按照 <%s> 制作了一份煎饼果子…” % self.kongfu)
class School(object):
def __init__(self):
self.kongfu = “现代煎饼果子配方”
def make_cake(self):
print(“[现代] 按照 <%s> 制作了一份煎饼果子…” % self.kongfu)
class Prentice(School, Master): # 多继承,继承了多个父类
def __init__(self):
self.kongfu = “猫氏煎饼果子配方”
def make_cake(self):
print(“[猫氏] 按照 <%s> 制作了一份煎饼果子…” % self.kongfu)
# 如果子类和父类的方法名和属性名相同,则默认使用子类的
# 叫 子类重写父类的同名方法和属性
damao = Prentice()
print(damao.kongfu) # 子类和父类有同名属性,则默认使用子类的
damao.make_cake() # 子类和父类有同名方法,则默认使用子类的
# 子类的魔法属性__mro__决定了属性和方法的查找顺序
print(Prentice.__mro__)
剧情发展:
大猫的新配方大受欢迎,但是有些顾客希望也能吃到古法配方和 现代配方 的煎饼果子…
5* 子类调用父类同名属性和方法
class Master(object):
def make_cake(self):
print(“按照 [古法] 制作了一份煎饼果子…”)
class School(object):
def make_cake(self):
print(” 按照 [现代] 制作了一份煎饼果子…”)
# 自定义类
class Master(object):
# 实例方法
def make_cake(self):
print(“按照 [古法] 制作了一份煎饼果子…”)
# 自定义类
class School(object):
def make_cake(self):
print(“按照 [现代] 制作了一份煎饼果子…”)
# 多继承,继承了多个父类
class Prentice(School, Master):
# 实例方法
def make_cake(self):
print(“按照 [猫氏] 制作了一份煎饼果子…”)
# 调用父类方法格式:父类类名.父类方法(self)
def make_old_cake(self):
# 调用父类Master的实例方法
Master.make_cake(self)
def make_new_cake(self):
# 调用父类School的实例方法
School.make_cake(self)
# 实例化对象,自动执行子类的__init__方法
damao = Prentice()
damao.make_cake() # 调用子类的方法(默认重写了父类的同名方法)
print(“–” * 10)
damao.make_old_cake() # 进入实例方法去调用父类Master的方法
print(“–” * 10)
damao.make_new_cake() # 进入实例方法去调用父类School的方法
print(“–” * 10)
damao.make_cake() # 调用本类的实例方法
执行结果:
按照 [猫氏] 制作了一份煎饼果子…
——————–
按照 [古法] 制作了一份煎饼果子…
——————–
按照 [现代] 制作了一份煎饼果子…
——————–
按照 [猫氏] 制作了一份煎饼果子…
剧情发展:
大猫的煎饼果子店非常红火,终于有一天,他成了世界首富!!
但是他也老了,所以他希望把 师傅的配方 和 学校的配方 以及自己的配方 继续传承下去…(多层继承)
6. super()的使用
# 自定义类
class Master(object):
# 实例方法,方法
def make_cake(self):
print(“按照 [古法] 制作了一份煎饼果子…”)
# 父类是 Master类
class School(Master):
# 实例方法,方法
def make_cake(self):
print(“按照 [现代] 制作了一份煎饼果子…”)
# 执行父类的实例方法
super().make_cake()
# 父类是 School 和 Master
# 多继承,继承了多个父类
class Prentice(School, Master):
# 实例方法,方法
def make_cake(self):
print(“按照 [猫氏] 制作了一份煎饼果子…”)
def make_all_cake(self):
# 方式1. 指定执行父类的方法(代码臃肿)
# School.make_cake(self)
# Master.make_cake(self)
# 方法2. super() 带参数版本,只支持新式类
# super(Prentice, self).make_cake()
# self.make_cake()
# 方法3. super()的简化版,只支持新式类
# 执行父类的 实例方法
super().make_cake()
damao = Prentice()
damao.make_cake()
damao.make_all_cake()
# print(Prentice.__mro__)
知识点:
子类继承了多个父类,如果父类类名修改了,那么子类也要涉及多次修改。而且需要重复写多次调用,显得代码臃肿。
使用super() 可以逐一调用所有的父类方法,并且只执行一次。调用顺序遵循 mro 类属性的顺序。
注意:如果继承了多个父类,且父类都有同名方法,则默认只执行第一个父类的(同名方法只执行一次,目前super()不支持执行多个父类的同名方法)
super() 在Python2.3之后才有的机制,用于通常单继承的多层继承。
二. 面向对象
面向对象三大特性:封装、继承、多态
1. 私有权限 — 私有方法和私有属性
封装的意义:
- 将属性和方法放到一起做为一个整体,然后通过实例化对象来处理;
- 隐藏内部实现细节,只需要和对象及其属性和方法交互就可以了;
- 对类的属性和方法增加 访问权限控制。
私有权限:在属性名和方法名 前面 加上两个下划线 __
- 类的私有属性 和 私有方法,都不能通过对象直接访问,但是可以在本类内部访问;
- 类的私有属性 和 私有方法,都不会被子类继承,子类也无法访问;
- 私有属性 和 私有方法 往往用来处理类的内部事情,不通过对象处理,起到安全作用。
class Master(object):
def __init__(self):
self.kongfu = “古法煎饼果子配方”
def make_cake(self):
print(“[古法] 按照 <%s> 制作了一份煎饼果子…” % self.kongfu)
class School(object):
def __init__(self):
self.kongfu = “现代煎饼果子配方”
def make_cake(self):
print(“[现代] 按照 <%s> 制作了一份煎饼果子…” % self.kongfu)
class Prentice(School, Master):
def __init__(self):
self.kongfu = “猫氏煎饼果子配方”
# 私有属性,可以在类内部通过self调用,但不能通过对象访问
self.__money = 10000
# 私有方法,可以在类内部通过self调用,但不能通过对象访问
def __print_info(self):
print(self.kongfu)
print(self.__money)
def make_cake(self):
self.__init__()
print(“[猫氏] 按照 <%s> 制作了一份煎饼果子…” % self.kongfu)
def make_old_cake(self):
Master.__init__(self)
Master.make_cake(self)
def make_new_cake(self):
School.__init__(self)
School.make_cake(self)
class PrenticePrentice(Prentice):
pass
damao = Prentice()
# 对象不能访问私有权限的属性和方法
print(damao.__money)
damao.__print_info()
pp = PrenticePrentice()
# 子类不能继承父类私有权限的属性和方法
print(pp.__money)
pp.__print_info()
总结
- Python中没有像C++中 public 和 private 这些关键字来区别公有属性和私有属性。
- Python是以属性命名方式来区分,如果在属性和方法名前面加了2个下划线’__’,则表明该属性和方法是私有权限,否则为公有权限。
2. 修改私有属性的值
- 如果需要修改一个对象的属性值,通常有2种方法
- 对象名.属性名 = 数据 —-> 直接修改
- 对象名.方法名() —-> 间接修改
- 私有属性不能直接访问,所以无法通过第一种方式修改,一般的通过第二种方式修改私有属性的值:定义一个可以调用的公有方法,在这个公有方法内访问修改。
class Master(object):
def __init__(self):
self.kongfu = “古法煎饼果子配方”
def make_cake(self):
print(“[古法] 按照 <%s> 制作了一份煎饼果子…” % self.kongfu)
class School(object):
def __init__(self):
self.kongfu = “现代煎饼果子配方”
def make_cake(self):
print(“[现代] 按照 <%s> 制作了一份煎饼果子…” % self.kongfu)
class Prentice(School, Master):
def __init__(self):
self.kongfu = “猫氏煎饼果子配方”
# 私有属性,可以在类内部通过self调用,但不能通过对象访问
self.__money = 10000
# 现代软件开发中,通常会定义get_xxx()方法和set_xxx()方法来获取和修改私有属性值。
# 返回私有属性的值
def get_money(self):
return self.__money
# 接收参数,修改私有属性的值
def set_money(self, num):
self.__money = num
def make_cake(self):
self.__init__()
print(“[猫氏] 按照 <%s> 制作了一份煎饼果子…” % self.kongfu)
def make_old_cake(self):
Master.__init__(self)
Master.make_cake(self)
def make_new_cake(self):
School.__init__(self)
School.make_cake(self)
class PrenticePrentice(Prentice):
pass
damao = Prentice()
# 对象不能访问私有权限的属性和方法
print(damao.__money)
damao.__print_info()
# 可以通过访问公有方法set_money()来修改私有属性的值
damao.set_money(100)
# 可以通过访问公有方法get_money()来获取私有属性的值
print(damao.get_money())
3. 多态
所谓多态:定义时的类型和运行时的类型不一样,此时就成为多态 ,多态的概念是应用于Java和C#这一类强类型语言中,而Python崇尚“鸭子类型”。
鸭子类型:虽然我想要一只”鸭子”,但是你给了我一只鸟。 但是只要这只鸟走路像鸭子,叫起来像鸭子,游泳也像鸭子,我就认为这是鸭子。
Python的多态,就是弱化类型,重点在于对象参数是否有指定的属性和方法,如果有就认定合适,而不关心对象的类型是否正确。
- Python伪代码实现Java或C#的多态
class F1(object):
def show(self):
print(‘F1.show’)
class S1(F1):
def show(self):
print(‘S1.show’)
class S2(F1):
def show(self):
print(‘S2.show’)
# 由于在Java或C#中定义函数参数时,必须指定参数的类型
# 为了让Func函数既可以执行S1对象的show方法,又可以执行S2对象的show方法,
# 所以在def Func的形参中obj的类型是 S1和S2的父类即F1
#
# 而实际传入的参数是:S1对象和S2对象
def Func(F1 obj):
“””Func函数需要接收一个F1类型或者F1子类的类型”””
print(obj.show())
s1_obj = S1()
Func(s1_obj) # 在Func函数中传入S1类的对象 s1_obj,执行 S1 的show方法,结果:S1.show
s2_obj = S2()
Func(s2_obj) # 在Func函数中传入Ss类的对象 ss_obj,执行 Ss 的show方法,结果:S2.show
通俗点理解:定义obj这个变量是说的类型是:F1的类型,但是在真正调用Func函数时给其传递的不一定是F1类的实例对象,有可能是其子类的实例对象, 这种情况就是所谓的多态
- Python “鸭子类型”
class F1(object):
def show(self):
print(‘F1.show’)
class S1(F1):
def show(self):
print(‘S1.show’)
class S2(F1):
def show(self):
print(‘S2.show’)
def Func(obj):
# python是弱类型,即无论传递过来的是什么,obj变量都能够指向它,这也就没有所谓的多态了(弱化了这个概念)
print(obj.show())
s1_obj = S1()
Func(s1_obj)
s2_obj = S2()
Func(s2_obj)
4. 类属性和实例属性
在了解了类基本的东西之后,下面看一下python中这几个概念的区别
先来谈一下类属性和实例属性
在前面的例子中我们接触到的就是实例属性(对象属性),顾名思义,类属性就是类对象所拥有的属性,它被所有类对象的实例对象所共有,在内存中只存在一个副本,这个和C++中类的静态成员变量有点类似。对于公有的类属性,在类外可以通过类对象和实例对象访问
类属性
class People(object):
name = ‘Tom’ # 公有的类属性
__age = 12 # 私有的类属性
p = People()
print(p.name) # 正确
print(People.name) # 正确
print(p.__age) # 错误,不能在类外通过实例对象访问私有的类属性
print(People.__age) # 错误,不能在类外通过类对象访问私有的类属性
实例属性(对象属性)
class People(object):
address = ‘山东’ # 类属性
def __init__(self):
self.name = ‘xiaowang’ # 实例属性
self.age = 20 # 实例属性
p = People()
p.age = 12 # 实例属性
print(p.address) # 正确
print(p.name) # 正确
print(p.age) # 正确
print(People.address) # 正确
print(People.name) # 错误
print(People.age) # 错误
通过实例(对象)去修改类属性
class People(object):
country = ‘china’ #类属性
print(People.country)
p = People()
print(p.country)
p.country = ‘japan’
print(p.country) # 实例属性会屏蔽掉同名的类属性
print(People.country)
del p.country # 删除实例属性
print(p.country)
总结
- 如果需要在类外修改类属性,必须通过类对象去引用然后进行修改。如果通过实例对象去引用,会产生一个同名的实例属性,这种方式修改的是实例属性,不会影响到类属性,并且之后如果通过实例对象去引用该名称的属性,实例属性会强制屏蔽掉类属性,即引用的是实例属性,除非删除了该实例属性。
5. 静态方法和类方法
####1*) 类方法
是类对象所拥有的方法,需要用修饰器@classmethod来标识其为类方法,对于类方法,第一个参数必须是类对象,一般以cls作为第一个参数(当然可以用其他名称的变量作为其第一个参数,但是大部分人都习惯以’cls’作为第一个参数的名字,就最好用’cls’了),能够通过实例对象和类对象去访问。
class People(object):
country = ‘china’
#类方法,用classmethod来进行修饰
@classmethod
def get_country(cls):
return cls.country
p = People()
print(p.get_country()) #可以用过实例对象引用
print(People.get_country()) #可以通过类对象引用
类方法还有一个用途就是可以对类属性进行修改:
class People(object):
country = ‘china’
#类方法,用classmethod来进行修饰
@classmethod
def get_country(cls):
return cls.country
@classmethod
def set_country(cls,country):
cls.country = country
p = People()
print(p.get_country()) #可以用过实例对象访问
print(People.get_country()) #可以通过类访问
p.set_country(‘japan’)
print(p.get_country())
print(People.get_country())
结果显示在用类方法对类属性修改之后,通过类对象和实例对象访问都发生了改变
####2*) 静态方法
需要通过修饰器@staticmethod来进行修饰,静态方法不需要多定义参数,可以通过对象和类来访问。
class People(object):
country = ‘china’
@staticmethod
#静态方法
def get_country():
return People.country
p = People()
# 通过对象访问静态方法
p.get_contry()
# 通过类访问静态方法
print(People.get_country())
总结
- 从类方法和实例方法以及静态方法的定义形式就可以看出来,类方法的第一个参数是类对象cls,那么通过cls引用的必定是类对象的属性和方法;
- 实例方法的第一个参数是实例对象self,那么通过self引用的可能是类属性、也有可能是实例属性(这个需要具体分析),不过在存在相同名称的类属性和实例属性的情况下,实例属性优先级更高。
- 静态方法中不需要额外定义参数,因此在静态方法中引用类属性的话,必须通过类实例对象来引用
python中的方法总结:
1.实例方法(对象方法) –> 场景很多
调用格式: 对象名.实例方法名()
使用场景: 在方法中需要self
2.类方法 –> 对私有类属性取值或者赋值
定义格式: @classmethod
def 类方法名(cls):
调用格式: 类名.类方法名() 或者 对象名.类方法名()
使用场景: 在方法中需要cls类名
3.静态方法 –> 一般不用
定义格式: @staticmethod
def 静态方法名():
调用格式: 类名.类方法名() 或者 对象名.类方法名()
使用场景: 在方法中不需要self 也不需要cls
6. __new__方法
__new__和__init__的作用:
class A(object):
def __init__(self):
print(“这是 init 方法”)
def __new__(cls):
print(“这是 new 方法”)
return object.__new__(cls)
A()
总结
- __new__至少要有一个参数cls,代表要实例化的类,此参数在实例化时由Python解释器自动提供
- __new__必须要有返回值,返回实例化出来的实例,这点在自己实现__new__时要特别注意,可以return父类__new__出来的实例,或者直接是object的__new__出来的实例
- __init__有一个参数self,就是这个__new__返回的实例,__init__在__new__的基础上可以完成一些其它初始化的动作,__init__不需要返回值
- 我们可以将类比作制造商,__new__方法就是前期的原材料购买环节,__init__方法就是在有原材料的基础上,加工,初始化商品环节
补充:
*args: 表示 将位置参数中的剩余实参存放到 args 中, 且以元组的形式保存
def foo(x,*args):
print(x)
print(args)
foo(1,2,3,4,5)#其中的2,3,4,5都给了args
运行结果:
1
(2, 3, 4, 5)
def foo(x,y=1,*args):
print(x)
print(y)
print(args)
foo(1,2,3,4,5)#其中的x为1,y=1的值被2重置了,3,4,5都给了args
结果:
1
2
(3, 4, 5)
def foo(x,*args,y=1):
print(x)
print(args)
print(y)
foo(1,2,3,4,5)#其中的x为1,2,3,4,5都给了args,y按照默认参数依旧为1
结果:
1
(2, 3, 4, 5)
1
** kwargs: 表示 形参中按照关键字传值 把多余的值以字典呈现
def foo(x,**kwargs):
print(x)
print(kwargs)
foo(1,y=1,a=2,b=3,c=4)#将y=1,a=2,b=3,c=4以字典的方式给了kwargs
执行结果是:
1
{‘y’: 1, ‘a’: 2, ‘b’: 3, ‘c’: 4}
def foo(x,*args,**kwargs):
print(x)
print(args)
print(kwargs)
foo(1,2,3,4,y=1,a=2,b=3,c=4)#将1传给了x,将2,3,4以元组方式传给了args,y=1,a=2,b=3,c=4以字典的方式给了kwargs
结果:
1
(2, 3, 4)
{‘y’: 1, ‘a’: 2, ‘b’: 3, ‘c’: 4}
位置参数、默认参数、kwargs三者的顺序必须是位置参数、默认参数、kwargs,不然就会报错:
def foo(x,y=1,**kwargs):
print(x)
print(y)
print(kwargs)
foo(1,a=2,b=3,c=4)#将1按照位置传值给x,y按照默认参数为1,a=2,b=3,c=4以字典的方式给了kwargs
结果:
1
1
{‘a’: 2, ‘b’: 3, ‘c’: 4}
7* 单例模式
1. 单例是什么
举个常见的单例模式例子,我们日常使用的电脑上都有一个回收站,在整个操作系统中,回收站只能有一个实例,整个系统都使用这个唯一的实例,而且回收站自行提供自己的实例。因此回收站是单例模式的应用。
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,单例模式是一种对象创建型模式。
就像游戏中的尤里x
2. 创建单例-保证只有1个对象
# 单例模式: 在程序中这个类创建出来的对象 只有一个(也就是占用一分内存地址)
# 单例模式 也只会走一次__init__方法(保证这个单例对象的属性也是唯一的)(name=小明
# age = 20)
# 合理使用内存 避免浪费(避免浪费内存)
class Person(object):
# 定义一个私有类属性, 初始化是该类为None
__instance = None
def __new__(cls, *args, **kwargs):
# 如果不存在, 则进入if语句
if not cls.__instance:
# 不存在时进入 我们就创建一个
cls.__instance = object.__new__(cls)
# 创建出来之后, return返回
return cls.__instance
# 创建对象
xiaoming = Person(‘小明’, 20)
xiaohong = Person(‘小红’, 32)
xiaozhang = Person(‘小孙’, 31)
print(xiaoming)
print(xiaohong)
print(xiaozhang)
运行结果:
# 我们可以看到三个值都是一样的,代表对象没有创建多个,而是只有一个,达到我们的目的
<__main__.Person object at 0x00000000023CD2E8>
<__main__.Person object at 0x00000000023CD2E8>
<__main__.Person object at 0x00000000023CD2E8>
3. 创建单例-保证只执行1次init方法
class Person(object):
__instance = None
# 创建一个私有类属性, 记录是否是第一次进入
# 只有第一次进入的时候, 当前初始化的值(True)被使用
# 其他时候进入的都将该参数变为False
__isFirst = True
def __new__(cls, *args, **kwargs):
if not cls.__instance:
cls.__instance = object.__new__(cls)
return cls.__instance
# 对init方法加工, 使其只能被调用一次, 保证对象的属性只能赋值一次
def __init__(self, name, age):
if Person.__isFirst:
self.name = name
self.age = age
Person.__isFirst = False
# 创建对象
xiaoming = Person(‘小明’, 20)
xiaohong = Person(‘小红’, 32)
xiaozhang = Person(‘小孙’, 31)
print(xiaoming)
print(xiaohong)
print(xiaozhang)
print(xiaozhang.name, xiaoming.name, xiaohong.name)
运行结果:
<__main__.Person object at 0x00000000023CD2E8>
<__main__.Person object at 0x00000000023CD2E8>
<__main__.Person object at 0x00000000023CD2E8>
小明 小明 小明
总结:
- 单例: 单独的实例, 即只能有一个实例
- 这样可以节约内存, 例如针对公共的一些对象,我们只需要指定一个即可, 需要使用时, 直接拿去用.
- 内存有限, 请珍惜.
- 既然该类的对象都只有一个, 那么该对象的属性也只有一份, 不存在别人也有该对象属性的情况.
三 . 异常 & 模块(了解)
1. 异常
###1) 异常简介
看如下示例:
print ‘—–test–1—‘
open(‘123.txt’,’r’)
print ‘—–test–2—‘
运行结果:
—–test–1—
Traceback (most recent call last):
File “demo.py”, line 2, in <module>
open(‘123.txt’, ‘r’)
FileNotFoundError: [Errno 2] No such file or directory: ‘123.txt’
说明:
打开一个不存在的文件123.txt,当找不到123.txt 文件时,就会抛出给我们一个IOError类型的错误,No such file or directory:123.txt (没有123.txt这样的文件或目录)
异常:
当Python检测到一个错误时,解释器就无法继续执行了,反而出现了一些错误的提示,这就是所谓的”异常”
2. 捕获异常
1) 单个异常捕获(try…except…)
使用规则:
try:
可能发生异常的代码01
可能发生异常的代码02
…
except 要捕获的异常类型名:
如果发生异常,进行的后续处理01
如果发生异常,进行的后续处理02
…
看如下示例:
try:
print(num)
except NameError:
print(‘发生异常了’)
print(‘go on continue’)
运行结果:
发生异常了
go on continue
说明:
- 如果我们在可能发生异常的代码外, 添加 try….except…. 方法, 这样就可以捕获发生的异常,可以对异常进行实时跟踪, 而且程序不会崩溃, 可以继续运行后续的代码
- 我们通过运行结果可以看到, 虽然异常发生了, 但是对程序的破坏性不是很大, 有利于程序的继续执行.
- 其中, try: 里面的代码是有可能发生异常的内容
- except 后面紧跟的是发生异常的类型名称, 如果异常名称弄错, 那么当前异常照样捕获不到, 就相当于: except 鸭子类型异常: 但是发生的是天鹅类型异常, 这样的话except照样捕获不到该异常.
- 使用try…except…能够使我们在异常发生的情况下正常执行程序, 这一点非常非常好. 对于公司来说,这个功能非常有用.
2) 多种异常捕获
想一想:
上例程序,已经可以使用except捕获异常了, 但是如果try中的代码发生的异常类型有多种, 那么该如何解决呢?
答: 使用 except 捕获多种异常
使用规则:
try:
可能发生异常的代码01
可能发生异常的代码02
…
except (要捕获的异常类型名01,要捕获的异常类型名02, 要捕获的异常类型名03…):
如果发生异常,进行的后续处理01
如果发生异常,进行的后续处理02
…
例如:
try:
print(num)
open(‘hm.txt’)
except (FileNotFoundError, NameError):
print(‘捕获到异常了’)
运行结果:
捕获到异常了
说明:
- except后面可以跟 多种类型的异常名, 并且把这些异常名用元组包裹起来, 就可以同时捕获多种异常.
- 其他使用方式不变
3) 异常的描述信息获取
总结:
- except 后面可以用括号包含多种异常的名字
- except后面可以连接 as 关键字, as 关键字能够把异常信息赋给一个变量, 我们可以通过打印变量的形式来获取有关该异常的描述信息
- 我们也可以简单的理解为: as 关键字 能够把异常描述信息 保存在变量中.
4) 捕获所有异常
我们有两种方式可以捕获所有的异常:
第一种 :
使用规则:
try:
有可能产生异常的代码01
有可能产生异常的代码02
…
except:
捕获到异常后的操作01
捕获到异常后的操作02
…
说明: 虽然用这样的形式可以捕获异常,但是不建议大家使用, 原因是这样的方式获取的异常不能够查看 聚的异常信息.
第二种 :
使用规则:
try:
有可能产生异常的代码01
有可能产生异常的代码02
…
except Exception:
捕获到异常后的操作01
捕获到异常后的操作02
…
说明:
- 使用这样的形式可以捕获所有类型的异常, 原因是: Exception是所有异常类的父类.
- 这样的书写形式后面可以跟 as 关键字, 从而可以存储并打印输出异常的具体信息.
- 推荐使用这样的形式, 当然, 如果你不需要异常信息, 只想知道代码是否有异常, 可以使用上面的形式
举例:
try:
open(‘hm.txt’,’r’)
except:
print(‘获取异常…’)
运行结果:
获取异常…
try:
print(num)
open(‘hm.txt’)
except Exception as info:
print(‘捕获到异常了’, info)
运行结果:
捕获到异常了 name ‘num’ is not defined
注意:
上面代码中: 因为异常代码有两行, 所以捕获到的是先执行的第 2 行的异常.
总结:
- 捕获所有异常和正常的捕获行为很像, 只是捕获类名有所变化
- 此处学习捕获所有异常后, 在公司如无其他需求, 一般都是使用上面的两种方法, 获取代码的异常.
- 此处学习的两种方法均可使用, 希望大家牢记, 多多联系.
5) 与else的搭配使用
在 if 中,它的作用是当条件不满足时执行的代码;
同样在 try…except…else 中也是如此,即如果没有捕获到异常,那么就执行else中的事情
如果产生异常, 就不执行 else 中的语句.
try:
num = 100
print(num)
except NameError as errorMsg:
print(‘产生错误了:%s’%errorMsg)
else:
print(‘没有捕获到异常,真高兴’)
运行结果如下:
100
没有捕获到异常,真高兴
try:
# num = 100
print(num)
except NameError as errorMsg:
print(‘产生错误了:%s’%errorMsg)
else:
print(‘没有捕获到异常,真高兴’)
运行结果如下:
产生错误了:name ‘num’ is not defined
6) 与finally的搭配
finally语句用来表达这样的情况:
在程序中,如果一个段代码必须要执行,即无论异常是否产生都要执行,那么此时就需要使用finally。 比如文件关闭,释放锁,把数据库连接返还给连接池等
demo:
try:
num = 100
print(num)
except NameError as errorMsg:
print(‘产生错误了:%s’%errorMsg)
finally:
print(‘finally方法’)
结果:
100
finally方法
demo:
try:
# num = 100
print(num)
except NameError as errorMsg:
print(‘产生错误了:%s’%errorMsg)
finally:
print(‘finally方法’)
结果:
产生错误了:name ‘num’ is not defined
finally方法
总结:
- finally 如果添加了, 那么无论是否发生异常, finally里面的程序都会执行
- finally一般不写, 除非公司有这方面的需求.
- finally 和 try…. except…. 可以搭配使用, 组成: try…. except…. finally…..
- 其中, try 中出错, 那么执行 except 和 finally 的代码
- 如果, try 中没有错误, 那么执行 try 和 finally 中的代码
- finally 和 try …. except… else …. 可以搭配使用, 组成 try…..except….else…..finally….
- 其中, 如果 try 中出错, 那么执行 except 和 finally 的代码
- 如果, try 中如果没有错误, 那么执行 try 和 else 和 finally 的代码
- try:
num = 100
print(num)
except NameError as errorMsg:
print(‘产生错误了:%s’%errorMsg)
else:
print(‘else’)
finally:
print(‘finally方法’) - 结果:
- 100
else
finally方法 - 如果出错,则:
- try:
# num = 100
print(num)
except NameError as errorMsg:
print(‘产生错误了:%s’%errorMsg)
else:
print(‘else’)
finally:
print(‘finally方法’) - 结果:
- 产生错误了:name ‘num’ is not defined
finally方法
7) 异常的传递
1. try嵌套
两个try嵌套, 如果内部的异常没有捕获到, 则异常会往外部传递, 外部会进行捕获:
try:
num = 100
print(num)
try:
open(‘text.haha’)
except NameError as error:
print(‘嵌套内部的异常捕获:’, error)
except Exception as result:
print(‘嵌套外围的异常捕获: 产生错误了’, result)
运行结果:
100
嵌套外围的异常捕获: 产生错误了 [Errno 2] No such file or directory: ‘text.haha’
说明:
上例中,内部没有捕获到的原因是: open()方法出错的异常类型应该是IOName, 而内部我们捕获的类型是NameError.
两个try嵌套, 如果内部的异常, 内部已经捕获到, 则不会往外部传递, 外部不会捕获:
try:
num = 100
print(num)
try:
open(‘text.haha’)
except Exception as error:
print(‘嵌套内部的异常捕获:’, error)
except Exception as result:
print(‘嵌套外围的异常捕获: 产生错误了’, result)
运行结果:
100
嵌套内部的异常捕获: [Errno 2] No such file or directory: ‘text.haha’
说明:
这次内部能够捕获异常的原因是: 内部捕获的是所有的异常类型,使用的是: Exception.
2. 函数嵌套
def demo1():
print(“—-demo1-1—-“)
print(num)
print(“—-demo1-2—-“)
def demo2():
try:
print(“—-demo2-1—-“)
demo1()
print(“—-demo2-2—-“)
except Exception as result:
print(“捕获到了异常,信息是:%s” % result)
print(“—-demo2-3—-“)
demo2()
运行结果:
—-demo2-1—-
—-demo1-1—-
捕获到了异常,信息是:name ‘num’ is not defined
—-demo2-3—-
或者:
def demo1():
print(“—-demo1-1—-“)
try:
print(num)
print(“—-demo1-2—-“)
except Exception as result:
print(“捕获到了异常,信息是:%s” % result)
print(“—-demo1-3—-“)
def demo2():
print(“—-demo2-1—-“)
demo1()
print(“—-demo2-2—-“)
demo2()
结果:
—-demo2-1—-
—-demo1-1—-
捕获到了异常,信息是:name ‘num’ is not defined
—-demo1-3—-
—-demo2-2—-
总结:
- 如果try嵌套,那么如果里面的try没有捕获到这个异常,那么外面的try会接收到这个异常,然后进行处理,如果外边的try依然没有捕获到,那么再往外进行传递。。。
- 如果异常是在函数嵌套中产生的:
- 例如函数A—->函数B—->函数C,而异常是在函数C中产生的,那么如果函数C中没有对这个异常进行处理,那么这个异常会传递到函数B中,如果函数B有异常处理那么就会按照函数B的处理方式进行执行;如果函数B也没有异常处理,那么这个异常会继续传递,以此类推。。。如果所有的函数都没有处理,那么此时就会进行异常的默认处理,程序最终会崩溃.
- 第一个案例中: 当demo1中发生了异常, 此异常被传递到demo2函数中处理,当处理完成后, 并没有返回demo1中进行执行, 而是在demo2中继续执行.
3. 模块
####1. Python中的模块
在Python中有一个概念叫做模块(module),这个和C语言中的头文件以及Java中的包很类似,比如在Python中要调用randint()函数,必须用import关键字引入 random 这个模块,下面就来了解一下Python中的模块。
模块就好比是工具包,要想使用这个工具包中的工具(就好比函数),就需要导入这个模块
####2 import
在Python中, 一般用关键字import来导入模块
比如要引用模块 random,就可以在文件最开始的地方用 import random 来导入。
形如:
import module1,mudule2…
当解释器遇到 import 语句,我们添加的模块就会被导入。
在调用 random 模块中的函数时,必须这样引用:
模块名.函数名
例如:
random.randint()
想一想:
为什么必须加上模块名调用呢?
答:
因为可能存在这样一种情况:在多个模块中含有相同名称的函数,此时如果只是通过函数名来调用,解释器无法知道到底要调用哪个函数。所以如果像上述这样引入模块的时候,调用函数必须加上模块名
import math
# 这样会报错
# 求4的非负平方根
print sqrt(4)
#这样才能正确输出结果
print math.sqrt(4)
3 from…import
有时候我们只需要用到模块中的某些个函数,只需要引入该函数即可,此时可以用下面方法实现:
from 模块名 import 函数名1,函数名2….
不仅可以引入函数,还可以引入一些全局变量、类等
注意:
- 通过这种方式引入的时候,调用函数时只能给出函数名,不能给出模块名,但是当两个模块中含有相同名称函数的时候,后面一次引入会覆盖前一次引入。也就是说假如模块A中有函数function( ),在模块B中也有函数function( ),如果引入A中的function在先、B中的function在后,那么当调用function函数的时候,是去执行模块B中的function函数。
- 如果想一次性引入某个模块中所有的东西,还可以通过 from 模块名 import * 来实现
例如,要导入模块 random 的 randint 方法,使用如下语句:
from random import randint
注意:
- 这样的方式不会把整个模块的内容导入到当前文件中, 它只会将模块中的单个方法引入进来
####4. from … import *
把一个模块的所有内容全都导入到当前的文件中也是可行的,只需使用如下声明:
from 模块名 import *
注意:
- 这提供了一个简单的方法来导入一个模块中的所有项目。然而这种声明不该被过多地使用。
- 这个方法最好不要用的太多, 因为这样的方式是把模块中的内容一次性都引入, 会对内存造成很大的压力.
5. as
as 关键字可以给导入的模块起一个别名
使用格式:
import 模块名 as 别名
说明:
- 需要说明的是 一旦使用 as 给某个模块起别名以后, 原来模块的名字就不可以使用了, 用了会报错
import random as tt
tt.randint(0, 10)
random.randint(0, 10)
运行结果为:
Traceback (most recent call last):
File “demo.py”, line 3, in <module>
random.randint(0, 10)
NameError: name ‘random’ is not defined
6. 定位模块 (linux系统)
当你导入一个模块,Python解析器对模块位置的搜索顺序是:
- 当前目录
- 如果不在当前目录,Python则搜索在shell变量PYTHONPATH下的每个目录。
- 如果都找不到,Python会察看默认路径。UNIX下,默认路径一般为/usr/local/lib/python/
- 模块搜索路径存储在system模块的sys.path变量中。变量里包含当前目录,PYTHONPATH和由安装过程决定的默认目录。
7. 模块制作
#####1. 定义自己的模块
在Python中,每个Python文件都可以作为一个模块,模块的名字就是文件的名字。
比如有这样一个文件 demo.py,在 demo.py 中定义了函数add
demo.py文件中:
# demo.py文件中:
def add(a, b):
return a + b
2. 调用自己定义的模块
那么在其他文件中就可以先import demo,然后通过demo.add(a,b)来调用了,当然也可以通过from demo import add来引入
main.py文件中:
# main.py
import demo
result = demo.add(11,22)
print(result)
3. 测试模块
在实际开中,当一个开发人员编写完代码后,会检测这个模块是否达到了想要的效果, 往往会在当前项目中添加一些检测代码, 例如:
demo.py文件中:
# demo.py文件中
def add(a,b):
return a+b
# 用来进行自测:
ret = add(12,22)
print(‘demo: 12+22=%d’%ret)
如果此时,在其他py文件中引入了此文件的话,想想看,测试的那段代码是否也会执行呢!
main.py文件中:
import demo
result = demo.add(11,22)
print(result)
运行现象:
demo: 12+22=34
33
至此,可发现 demo.py 中的测试代码,应该是单独执行 demo.py 文件时才应该执行的,不应该是其他的文件中引用而执行
为了解决这个问题,python在执行一个文件时有个变量__name__
4. __name__:
demo.py文件中运行的结果为:
# demo.py文件中
def add(a,b):
return a+b
# 用来进行自测:
print(‘demo中__name__的值是: %s’ % __name__)
结果:
demo中__name__的值是: __main__
在别的文件中导入demo.py文件,然后运行:
在main.py文件中运行后:
import demo
运行结果:
demo中__name__的值是: demo
总结:
- 可以根据name变量的结果能够判断出,是直接执行的python脚本还是被引入执行的,从而能够有选择性的执行测试代码
- 就和我们经常看到的NBA一样, 分为 主场 和 客场 ,主场就是在本队的球场打, 客场就是在对方的球队打.
- 或者我们也可以理解为: 在家为主人, 在别人家为客人
####8.模块中的__all__
我们可以在模块中添加__all__属性. 这个属性的作用是:
如果一个模块中有__all__属性,则只有__all__内指定的属性, 方法, 类可被导入
但是这样做有一个限制: 必须用 from 模块名 import * 的形式导入
#####1. 没有__all__
demo01.py:
# 全局变量
name = ‘itcast’
def hello():
print(‘hello’)
class Person(object):
def eat(self):
print(‘eat’)
demo02.py:
from demo01 import *
print(name)
hello()
per = Person()
per.eat()
运行demo02.py , 可得结果:
itcast
hello
eat
2. 模块中有__all__
demo01.py:
__all__ = [‘name’, ‘hello’]
# 全局变量
name = ‘itcast’
def hello():
print(‘hello’)
class Person(object):
def eat(self):
print(‘eat’)
demo02.py:
from demo01 import *
print(name)
hello()
per = Person()
per.eat()
运行demo02.py, 可得:
itcast
hello
Traceback (most recent call last):
File “demo02.py”, line 5, in <module>
per = Person()
NameError: name ‘Person’ is not defined
上例中,如果我们把 Person 添加到__all__中,可得:
demo01.py:
__all__ = [‘name’, ‘hello’, ‘Person’]
# 全局变量
name = ‘itcast’
def hello():
print(‘hello’)
class Person(object):
def eat(self):
print(‘eat’)
demo02.py:
from demo01 import *
print(name)
hello()
per = Person()
per.eat()
运行demo02.py, 可得:
itcast
hello
eat
总结
- 如果一个模块中有__all__属性,则只有__all__内指定的属性, 方法, 类可被导入, 当然前提是使用from 模块名 import * 的导入形式
- 这样做有利于我们决定是否将模块中的所有内容都展示出去. 可以有选择性的进行保留
- 在程序中一般定义接口的时候, 可能用到. 这里知道即可, 不做过多讲解.
4. python中的包
包是什么? 其实: 包就是带有__init__.py文件的文件夹
1. 引入包
在程序中, 我们往往会把有联系的模块放在一个包中 (即把有联系的多个.py文件放在一个特殊文件夹下)
特殊在哪里呢? 这个文件夹下会有一个__init__.py的文件
例如:
如果这两个文件有联系,我们可以创建一个包(文件夹), 把这两个放在里面:
我们在创建一个新的模块(文件), 在新建的模块中, 导入包里面的两个文件:
这两个文件在包中, 所以我们导入的时候,也要把包名带上:
###2.包中的__init__.py文件
__init__.py 控制着包的导入行为
总结:
- 包将有联系的模块组织在一起,即放到同一个文件夹下,并且在这个文件夹创建一个名字为__init__.py 文件,那么这个文件夹就称之为包
- 有效避免模块名称冲突问题,让应用组织结构更加清晰
- __init__.py文件有什么用?
- __init__.py 控制着包的导入行为
- __init__.py如果为空:
- 仅仅是把这个包导入,不会导入包中的模块
__all__
-
- 在__init__.py文件中,定义一个__all__变量,它控制着 from 包名 import *时导入的模块