【Python之路Day8】基础篇之面向对象下篇
今日目录:
类成员
类成员修饰符
特殊的类成员
面向对象相关联的其他知识
异常捕获与处理
设计模式之单例模式
一. 类成员
类的成员有:字段、方法和属性
关系图如下:
1. 字段:
字段分:
- 静态字段
- 普通字段
两者在定义和使用上有所区别,如下代码:
class Province: contry = '中国' #静态字段,保存在类中 def __init__(self,name): self.name = name #普通字段,保存在对象中
在内存中的存储位置是不同的, 静态字段保存在类中, 而普通字段保存在对象中。调用字段:
class Province: contry = '中国' def __init__(self,name): self.name = name sx = Province('山西') #访问静态字段和普通字段 # 静态字段存储在类中, 使用类调用, 如 Province.contry # 普通字段存储在对象中, 由对象调用, 如 sx.name print('国家: %s\n省份: %s'%(Province.contry,sx.name)) #执行结果: 国家: 中国 省份: 山西
可以看出:
静态字段存储在类中, 只在内存中保存一份;
普通字段存储在每个对象中,需要在每个对象中都保存一份
所以,当类创建时,如果每个对象都具有相同的字段,就使用静态字段。
2. 方法
包括:
- 普通方法,由对象调用,至少需要一个self参数。执行普通方法时,self就是对象本身,自动将调用该方法的对象赋值给self
- 静态方法,属于类,由类来调用执行,无默认参数,等同于函数。创建方式: @staticmethod。
- 类方法,是静态方法的一种特殊形式。由类调用,至少要有一个参数cls,值为类名。创建方式: @classmethod
所有的方法都属于类,只不过调用方式不同。
class Province: #静态字段 country = '中国' def __init__(self,arg): self.arg = arg #普通方法 def show(self): print('普通方法,由对象调用,至少需要一个self参数。执行普通方法时,self就是对象本身,自动将调用该方法的对象赋值给self!') #静态方法 @staticmethod def static_show(): print('静态方法,属于类,由类来调用执行,无默认参数。创建方式: @staticmethod') @classmethod def class_show(cls): print('是静态方法的一种特殊形式。由类调用,至少要有一个参数cls,值为类名。创建方式: @classmethod') # print(cls) #调用静态方法,由类调用 Province.static_show() #调用类方法, 由类调用 Province.class_show() #调用普通方法,需要先实例化 obj = Province('山西') obj.show() #执行结果: 静态方法,属于类,由类来调用执行,无默认参数。创建方式: @staticmethod 是静态方法的一种特殊形式。由类调用,至少要有一个参数cls,值为类名。创建方式: @classmethod 普通方法,由对象调用,至少需要一个self参数。执行普通方法时,self就是对象本身,自动将调用该方法的对象赋值给self! #类方法的参数cls是类本身,也可以在类方法里操作, 如下: @classmethod def class_show(cls): # print('是静态方法的一种特殊形式。由类调用,至少要有一个参数cls,值为类名。创建方式: @classmethod') a = cls('核弹') a.static_show() #执行结果: 静态方法,属于类,由类来调用执行,无默认参数。创建方式: @staticmethod
相同点:由于所有的方法都属于类, 所以在内存中存储只保存一份。
不同点:由于各种方法的调用方式不同,调用方法时自动传入的参数不同。
但是有个比较刺激的, 继续上面的例子:
#直接在对象中调用静态方法和类方法 obj = Province('山西') obj.show() obj.static_show() obj.class_show() #执行结果: 普通方法,由对象调用,至少需要一个self参数。执行普通方法时,self就是对象本身,自动将调用该方法的对象赋值给self! 静态方法,属于类,由类来调用执行,无默认参数。创建方式: @staticmethod 是静态方法的一种特殊形式。由类调用,至少要有一个参数cls,值为类名。创建方式: @classmethod
so,也是可以被调用的,虽说可以调用,但是不到万不得已,不要用对象来直接调用原属于类该调用的东西。这就是要谈到的要遵循的编程规则了:
一般情况下,自己访问自己的字段,虽说Python没有做严格的限制,但是这是理应遵循的一种编程习惯。
3. 属性
访问属性时和访问字段时一样,不需要加()。 在Python中,属性其实就是普通方法的变种
(1) 基本使用
class C1: xxx = '静态字段' def func(self): print('普通方法') #定义一个属性,使用 @property装饰 @property def f1(self): print('属性') #实例化 obj = C1() #调用普通字段,用类调用 C1.xxx #调用普通方法, 需要() obj.func() #调用定义的属性,直接用调用字段的方式,不用加() obj.f1 #代码执行结果: 普通方法 属性
class C1: xxx = '静态字段' def func(self): print('普通方法') #定义一个属性,使用 @property装饰 @property def f1(self): return '属性' #实例化 obj = C1() #调用普通字段,用类调用 C1.xxx #调用普通方法, 需要() obj.func() #调用定义的属性,直接用调用字段的方式,不用加() result = obj.f1 #如果有return值,需要赋值给变量,而后调用 print(result)
属性的定义和调用要注意:
定义时,在普通方法上面加 @property 装饰器来装饰
定义时,属性中仅有一个self参数,不能有其他的
调用的时候, 不需要()
属性存在意义是: 访问属性时可以制造出和访问字段相同的假象
属性是普通方法变种而来,如果Python中没有属性,方法完全可以代替其功能。
实例: 对于主机列表页面,每次请求不可能把数据库中的所有的内容显示到页面上,而是通过分页的功能显示,所以,在向数据库请求数据时就需要显式指定获取从第m条到第n条的数据(limit m,n), 这个分页的功能包括:
- 根据用户请求的当前页和总数据条数计算出m和n
- 根据m和n去数据库中请求数据
class Pager: def __init__(self,curr_num,per_page): ''' 构造函数 :param curr_num: 当前请求的页码 :param per_page: 每页要显示的数据 :return: ''' self.curr_num = curr_num self.per_page = per_page #计算出开始位 @property def start(self): #-1就是找出上一页开始位置 value = (self.curr_num-1) * self.per_page return value #计算出结束位 @property def end(self): value = self.curr_num * self.per_page return value #实例化,传入请求页码和每页显示的数量 #要显示哪页,传入参数即可 P = Pager(1,10) #起始值 print(P.start) #结束值 print(P.end) #执行结果 0 10
(2) 属性的两种定义方式
a. 装饰器方式:
在普通方法的上面添加 @property装饰器的方式
Python中的类有经典类和新式类(python3中全是新式类),新式类的类型比经典类丰富。(如果类继object,那么该类是新式类)
经典类,有一种 @property装饰器
class Pager: def __init__(self,curr_num,per_page): ''' 构造函数 :param curr_num: 当前请求的页码 :param per_page: 每页要显示的数据 :return: ''' self.curr_num = curr_num self.per_page = per_page #计算出开始位 @property def start(self): #-1就是找出上一页开始位置 value = (self.curr_num-1) * self.per_page return value #计算出结束位 @property def end(self): value = self.curr_num * self.per_page return value #实例化,传入请求页码和每页显示的数量 P = Pager(1,10) #起始值 print(P.start) #自动执行 @property装饰的start方法 #结束值 print(P.end) #自动执行 @property装饰的end方法
新式类,有三种 @property装饰器,类似于在字典中我们的操作,如:
a = {'k1':'v1'} print(a.get('k1')) #获取值 a['k1'] = 'v2' #设置一个值 del a['k1'] #删除一个key print(a)
class Pager: def __init__(self,curr_num,per_page): ''' 构造函数 :param curr_num: 当前请求的页码 :param per_page: 每页要显示的数据 :return: ''' self.curr_num = curr_num self.per_page = per_page #计算出开始位 @property def start(self): #-1就是找出上一页开始位置 value = (self.curr_num-1) * self.per_page return value @start.setter def start(self,value): # value = self.curr_num * self.per_page print('你输入的参数是: %s ,这是新修改的值'%value) #return value @start.deleter def start(self): print('这里是删除dele动作,你触发了del操作') #实例化,传入请求页码和每页显示的数量 P = Pager(1,10) #起始值 result = P.start #自动执行 @property装饰的start方法 print(result) P.start = 'shayashi' #自动执行 @start.setter装饰的start方法 del P.start #自动执行 @start.deleter装饰的start方法 #执行结果: 0 你输入的参数是: shayashi ,这是新修改的值 这里是删除dele动作,你触发了del操作
经典类中的属性只有一种访问方式,对应的是被 @property装饰的方法
新式类中的属性有三种, @property, @方法名.setter, @方法名.deleter
b. 静态字段方式
创建值为 property对象的字段, 这种方式经典类和新式类无区别。
class Test: def __init__(self,name): self.name = name def show(self): return self.name xx = property(show) #注意方法这里不能加(),否则报错 obj1 = Test('daniel') result = obj1.xx #自动会调用 show方法,并获取返回值 print(result)
property构造方法的四个参数:
- 第一个是fget,值是方法名,调用 对象.属性 属性时自动触发执行
- 第二个是fset,值是方法名,调用 对象.属性=值 的时候自动触发执行
- 第三个是fdel,值是方法名,调用 del 对象.属性 的时候自动触发执行
- 第四个参数是doc, 调用 对象.属性.__doc__ 的时候自动打印,这是该属性的描述信息
以上四种方式中的参数都是可选,可以直接写方法名,但是注意的是,直接写方法名的时候,要对应好位置,比如要写del的一个属性,必须要加上前两个。
class Test: def __init__(self,name): self.name = name def show(self): return self.name def setter(self,value): print('setter') def deleter(self): print('deleter') # xx = property(show,setter,deleter,'这是一段描述信息') #注意方法这里不能加(),否则报错 xx = property(fget=show,fdel=deleter) #如果只定义一个get和del的属性,那么就需要使用fget和fdel显式指定了,用普通方法名已经不能满足需求了 obj1 = Test('daniel') result = obj1.xx #自动会调用 show方法,并获取返回值 # obj1.xx = 'xxoo' #自动会调用 setter方法,并获取返回值 del obj1.xx #自动会调用 deleter 方法,并获取返回值 print(result)
二. 类成员修饰符
上面介绍了类的成员,这会来看下类成员的修饰符,顾名思义,作用就是来修饰类成员的。 对于每一个类的成员,都有两种形式:
- 公有成员,在任何地方都可以访问
- 私有成员,只允许在类的内部访问
私有成员和共有成员的定义不同,私有成员命名时,前两个字符是下划线(特殊成员除外,如__init__, __call__等)。
class Foo: country = '中国' #公有静态字段 __xx = '不给你看!' #私有静态字段 def __init__(self,name): self.name = name #共有普通字段 self.__oo = '还不让你看!' #私有普通字段
私有成员和公有成员的访问限制不同:
1. 字段
静态字段:
- 共有静态字段: 类可以访问; 类内部可以访问; 子类中可以访问。
- 私有静态字段: 仅类内部可以访问class Foo:
country = '中国' #公有静态字段 __xx = '不给你看!' #私有静态字段 def __init__(self,name): self.name = name #共有普通字段 self.__oo = '还不让你看!' #私有普通字段 def test(self): print(self.__xx) def test1(self): print(self.__oo) #静态字段: print(Foo.country) #从类里访问静态公共字段,成功 #Foo.__xx #访问私有静态字段, 失败 obj = Foo('dbq') #在类里面访问静态字段, 成功 obj.test() obj.test1() #在类里面访问普通私有字段,成功
class Foo: country = '中国' #公有静态字段 __xx = '不给你看!' #私有静态字段 def __init__(self): self.__oo = '还不让你看!' #私有普通字段 def test(self): print(Foo.__xx) def test1(self): print(self.__oo) class Foo1(Foo): def test2(self): print(Foo.country) #子类访问父类公共静态字段, 成功 print(Foo.__xx) #子类访问父类私有静态字段, 错误 obj1 = Foo1() obj1.test2()
普通字段:
- 公共普通字段: 对象可以访问, 类内部可以访问,子类(派生类) 中可以访问;
- 私有普通字段: 仅能从类内部访问;
class Foo: country = '中国' #公有静态字段 __xx = '不给你看!' #私有静态字段 def __init__(self): self.__oo = '私有普通字段,不让你看!' #私有普通字段 self.name = 'DBQ' def test(self): print(self.name) def test1(self): print(self.__oo) class Foo1(Foo): def test2(self): print(self.name) obj1 = Foo() print(obj1.name) #通过对象访问, 成功 obj1.test() #类内部访问, 成功 obj2 = Foo1() obj2.test2() #子类中访问,成功 #执行结果: DBQ DBQ DBQ
class Foo: country = '中国' #公有静态字段 __xx = '不给你看!' #私有静态字段 def __init__(self): self.__oo = '私有普通字段,不让你看!' #私有普通字段 self.name = 'DBQ' def test(self): print(self.__oo) def test1(self): print(self.__oo) class Foo1(Foo): def test2(self): print(self.__oo) obj1 = Foo() print(obj1.__oo) #通过对象访问, 失败 obj1.test() #类内部访问, 成功 obj2 = Foo1() obj2.test2() #子类中访问, 失败
注意, 如果要强制访问私有字段,可以通过 [ 对象._类名__私有字段名 ] 来访问,但是强烈不建议强制访问私有成员!
class Foo: country = '中国' #公有静态字段 __xx = '不给你看!' #私有静态字段 def __init__(self): self.__oo = '私有普通字段,不让你看!' #私有普通字段 self.name = 'DBQ' def test(self): print(self.__oo) def test1(self): print(self.__oo) class Foo1(Foo): def test2(self): print(self.__oo) obj1 = Foo() print(obj1._Foo__oo) #通过 [对象名._类名__私有字段名] 强制访问成功 #虽说能成功,但是不建议这么访问私有字段!
2. 方法
普通类方法
- 公共普通类方法:对象可以访问, 子类可以访问
- 私有普通类方法:仅能从类内部访问
class Foo: def show(self): print('这是公共普通方法') class Son(Foo): def show1(self): print(self.show()) obj = Foo() obj.show() #对象访问, 成功 # Foo.show() #类调用,失败 obj_son = Son() obj_son.show1() #子类访问, 成功
class Foo: def __show(self): print('这是私有静态方法') def show2(self): self.__show() class Son(Foo): def show1(self): print(self.__show()) obj = Foo() #obj.__show() #外部对象访问, 失败 obj.show2() #类内部访问, 成功 #Foo.show2() #类调用,失败 obj_son = Son() obj_son.show1() #子类访问, 失败
静态类方法
- 公共静态类方法:对象可以访问, 类内部可以访问,子类(派生类) 中可以访问;
- 私有静态类方法:仅能从类内部访问
class Foo: @staticmethod def show(): print('这是共有静态类方法') def show2(self): self.show() class Son(Foo): def show1(self): print(self.show()) obj = Foo() obj.show() #外部对象访问, 成功 obj.show2() #类内部访问, 成功 obj_son = Son() obj_son.show1() #子类访问, 成功
class Foo: @staticmethod def __show(): print('这是私有静态类方法') def show2(self): self.__show() class Son(Foo): def show1(self): print(self.__show()) obj = Foo() obj.__show() #外部对象访问, 失败 obj.show2() #类内部访问, 成功 # obj_son = Son() obj_son.show1() #子类访问, 失败
类方法
- 公共类方法:对象可以访问, 类内部可以访问,子类(派生类) 中可以访问;
- 私有类方法:仅能从类内部访问
class Foo: @classmethod def show(cls): print('这是类方法') def show2(self): self.show() class Son(Foo): def show1(self): print(self.show()) obj = Foo() obj.show() #外部对象访问, 成功 obj.show2() #类内部访问, 成功 # # obj_son = Son() obj_son.show1() #子类访问, 成功
class Foo: @classmethod def __show(cls): print('这是私有类方法') def show2(self): self.__show() class Son(Foo): def show1(self): print(self.__show()) obj = Foo() obj.__show() #外部对象访问,失败 obj.show2() #类内部访问, 成功 # obj_son = Son() obj_son.show1() #子类访问, 失败
3. 属性
公共类属性:对象可以访问, 类内部可以访问,子类(派生类) 中可以访问
私有类属性:仅能从类内部访问
class Foo: @property def show(self): print('这是公共属性') def show2(self): self.show class Son(Foo): def show1(self): print(self.show) obj = Foo() obj.show #外部对象访问,成功 obj.show2() #类内部访问, 成功 # # obj_son = Son() obj_son.show1() #子类访问, 成功
class Foo: @property def __show(self): print('这是公共属性') def show2(self): self.__show class Son(Foo): def show1(self): print(self.__show) obj = Foo() obj.__show #外部对象访问, 失败 obj.show2() #类内部访问, 成功 # # # obj_son = Son() obj_son.show1() #子类访问, 失败
Ps: 无论方法、属性还是字段,想要强制性的访问私有的话,和上面提到的访问静态私有字段一样,通过: [ 对象._类名__私有字段名 ], 但是不建议这么操作!
三. 类的特殊成员
类的特殊成员泛指类中的那些两边都有__的成员, 如__init__, __doc__, __dict__等
1. __doc__ 类的文档描述信息
class F1: ''' 这是类的描述信息, 由特殊成员 __doc__调用 ''' def __init__(self): pass print(F1.__doc__) #代码执行结果: 这是类的描述信息, 由特殊成员 __doc__调用
2. __init__ 类的构造方法, 类实例化时自动触发执行。
class F1: ''' 这是类的描述信息, 由特殊成员 __doc__调用 ''' def __init__(self,name): self.name = name print(self.name) obj = F1('DBQ') #执行代码结果: DBQ
3. __module__ 和 __class__
__module__ 当前操作的对象在哪个模块
__class__ 当前操作的对象的类是什么?
#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Author: DBQ(Du Baoqiang) class F1: ''' 这是类的描述信息, 由特殊成员 __doc__调用 ''' def __init__(self,name): self.name = name # print(self.name) def show_info(self): print(self.name)
from lib import modules obj = modules.F1('DBQ') print(obj.__module__) # 查看当前操作的对象属于哪个模块? print(obj.__class__) #查看当前操作的对象的类是哪个? #执行结果: lib.modules <class 'lib.modules.F1'>
4. __del__ 析构方法, 当对象在内存中被释放时候,自动触发执行。
PS:一般无需定义,因为Python是一门高级语言,编程时程序员无需关注内存的分配和释放,Python解释器会自动执行垃圾回收。__del__也就是在垃圾回收机制要回收时,自动触发执行。
class F1: ''' 这是类的描述信息, 由特殊成员 __doc__调用 ''' def __del__(self): pass
5. __call__ 对象后面加括号,触发执行
我们如果要实例化一个类,应该使用 对象 = 类() , 如:
obj = F1()
而对于 __call__方法是 在对象后面加括号触发的,即 对象(), 如:
class F1: ''' 这是类的描述信息, 由特殊成员 __doc__调用 ''' def __init__(self,name): self.name = name # print(self.name) def __del__(self): pass def __call__(self, *args, **kwargs): print('你是在对象后面加()了,所以才能看到这个__call__定义的信息') obj = F1('DBQ') obj() #对象后加() , 自动触发__call__ #执行结果: 你是在对象后面加()了,所以才能看到这个__call__定义的信息
6. __dict__ 类或对象中的所有成员, 以字典的形式返回
class F1: ''' 这是类的描述信息, 由特殊成员 __doc__调用 ''' xx = 'oo' def __init__(self,name): self.name = name # print(self.name) def __del__(self): pass def __call__(self, *args, **kwargs): print('你是在对象后面加()了,所以才能看到这个__call__定义的信息') def show_info(self): print(self.name) #查看类的成员: print(F1.__dict__) #结果: {'__call__': <function F1.__call__ at 0x1012ef2f0>, 'xx': 'oo', '__dict__': <attribute '__dict__' of 'F1' objects>, '__weakref__': <attribute '__weakref__' of 'F1' objects>, '__doc__': '\n 这是类的描述信息, 由特殊成员 __doc__调用\n ', '__del__': <function F1.__del__ at 0x1012ef268>, 'show_info': <function F1.show_info at 0x1012ef378>, '__init__': <function F1.__init__ at 0x1012ef1e0>, '__module__': '__main__'} #查看对象成员: obj = F1('DBQ') print(obj.__dict__) #执行结果: {'name': 'DBQ'} #查看obj2对象的成员 obj2 = F1('Tom') print(obj2.__dict__) #执行结果; {'name': 'Tom'}
7. __str__ 如果一个类中定义了 __str__ 方法,那么打印对象时, 默认输出该方法的返回值。
class F1: ''' 这是类的描述信息, 由特殊成员 __doc__调用 ''' xx = 'oo' def __init__(self,name): self.name = name # print(self.name) def __str__(self): return '你默认是按不到这个信息的,只有定义了__str__你print对象的时候才能看到这个信息!' obj = F1('DBQ') print(obj) #执行代码结果: 你默认是按不到这个信息的,只有定义了__str__你print对象的时候才能看到这个信息!
8. __getitem__, __setitem__, __delitem__
用于索引操作, 如字典。 分别表示获取、设置、删除数据。
class F1: ''' 这是类的描述信息, 由特殊成员 __doc__调用 ''' def __getitem__(self, item): print('你执行了get 操作,所以看到了 __getitem__ 定义的print值') def __setitem__(self, key, value): print('你执行了set 操作,所以看到了 __setitem__ 定义的print值') def __delitem__(self, key): print('你执行了删除操作,所以看到了 __delitem__ 定义的print值') obj = F1() obj['k1'] #get操作, 自动触发__getitem__ obj['k1'] = [1,2,3,4,5] #set操作,自动触发__setitem__ del obj['k1'] #del操作,自动触发 __delitem__ #执行结果: 你执行了get 操作,所以看到了 __getitem__ 定义的print值 你执行了set 操作,所以看到了 __setitem__ 定义的print值 你执行了删除操作,所以看到了 __delitem__ 定义的print值
9. __getslice__ 、 __setslice__、 __delslice__
__getslice__ , __setslice__ 和 __delslice__ , 主要用于分片操作, 如列表的分片。
注意:python3.x中执行分片,执行的还是__getitem__ __setitem__ __delitem__
在2.7中:
#!/usr/bin/env python # -*- coding:utf-8 -*- class Foo(object): def __getslice__(self, i, j): print '__getslice__',i,j def __setslice__(self, i, j, sequence): print '__setslice__',i,j def __delslice__(self, i, j): print '__delslice__',i,j obj = Foo() obj[-1:1] # 自动触发执行 __getslice__ obj[0:1] = [11,22,33,44] # 自动触发执行 __setslice__ del obj[0:2] # 自动触发执行 __delslice__ #代码执行结果: __getslice__ -1 1 __setslice__ 0 1 __delslice__ 0 2
在Python3.x中
class F1: ''' 这是类的描述信息, 由特殊成员 __doc__调用 ''' def __getitem__(self, item): print('你执行了get 操作,所以看到了 __getitem__ 定义的print值') def __setitem__(self, key, value): print('你执行了set 操作,所以看到了 __setitem__ 定义的print值') def __delitem__(self, key): print('你执行了删除操作,所以看到了 __delitem__ 定义的print值') def __getslice__(self,i, j): print(i,j,'__getslice__') def __setslice__(self, i, j, sequence): print(i,j,sequence,'这是__setslice__') def __delslice__(self, i, j): print(i,j,'这是__delslice__') obj = F1() obj[0:1] # 将自动触发__getitem__操作 obj[0:1:3] = [1,2] #自动触发 __setitem__操作 del obj[0:1] #自动触发__delitem__操作 #代码执行结果: 你执行了get 操作,所以看到了 __getitem__ 定义的print值 你执行了set 操作,所以看到了 __setitem__ 定义的print值 你执行了删除操作,所以看到了 __delitem__ 定义的print值
10. __iter__
迭代器, 元组、字典、列表等之所以可以for循环,是因为内部定义了一个__iter__
class F1: pass obj = F1() for i in obj: print(i) #代码执行报错: TypeError: 'F1' object is not iterable
class F1: def __iter__(self): pass obj = F1() for i in obj: print(i) #代码执行,报错: TypeError: iter() returned non-iterator of type 'NoneType'
class F1: def __init__(self,num): self.num = num def __iter__(self): return iter(self.num) obj = F1((1,2,3,4,5)) for i in obj: print(i) #代码执行结果: 1 2 3 4 5
不难看出,for循环迭代的其实就是 iter((1,2,3,4,5)), 所以,代码可以变成:
obj = iter((1,2,3,4,5)) for i in obj: print(i) #执行结果: 1 2 3 4 5
11. __new__ 、 __metaclass__
class F1: def __init__(self,name): self.name = name def show_info(self): print(self.name) obj = F1('DBQ') print(type(obj)) print(type(F1)) #执行代码结果: <class '__main__.F1'> #表示 obj对象由 F1类实例化而来 <class 'type'> #表示 F1类由 type 类创建
在上述代码实例中,obj是通过 F1类实例化而来的对象。其实,不仅obj是对象,F1类也是个对象,因为,Python中一切事物皆对象!
如果按照这个理论,obj对象是通过执行F1类的构造方法创建而来, F1类对象应该也是通过执行某个类的构造方法创建而来的。
so, obj对象是F1类的一个实例, F1对象是type类的一个实例。即, F1类对象是 通过 type类的构造方法创建。
那么,创建类就可以有两种方式:
(1) 普通方式:
class F1: def __init__(self,name): self.name = name def show_info(self): print(self.name)
(2) 特殊方式 (type类的构造方法)
def show_info(self): print('DBQ') F1 = type('F1',(object,),{'show_info':show_info}) #第一个参数: 类名 #第二个参数: 当前类的基类 #第三个参数: 类成员 obj = F1() obj.show_info()
》》》》类是由 type类实例化而来
那么问题来了,类默认是有 type 类实例化而来,type类中是如何实现的创建类 ? 类又是如何创建对象?
答案: 类中有一个属性 __mataclass__, 其作用是 表示该类是由谁来实例化创建的。 所以, 我们可以为 __metaclas__ 设置一个type 类产生的派生类(子类), 从而查看类创建的过程:
四. 面向对象相关联的其他知识
1. isinstance(obj,cls)
查看某对象是否是某类的实例,返回bool, 继承的父类也为真
class F1: pass class F2(F1): pass obj = F2() print(isinstance(obj,F1)) #查看是否是父类的实例, 为真 print(isinstance(obj,F2)) #查看是否是F2类的实例, 为真
2. issubclass(sub,super)
查看是否是某类的子类
class F1: pass class F2(F1): pass obj = F2() print(issubclass(F2,F1)) #查看F2是否是F1的子类, 为真 print(issubclass(F1,F2)) #查看F1是否是F2的子类, 为假
3. 对别人的源码扩展
注意一个原则就是,尽量不在源码中修改。
class C1: def f1(self): print('C1.f1') class C2(C1): def f1(self): super(C2,self).f1() #在执行C2代码之前执行C1中的f1方法 print('C2.f1') obj = C2() obj.f1() #执行结果: C1.f1 C2.f1
五. 异常捕获与处理
1. 异常的基础
编程中,为了增加友好性, 在程序出现某些问题时,一般不会将错误信息显示给用户,而是提示一个友好的界面,比较直观的例子就是网页中的404请求,大都重定向到一个比较友好的界面。如下代码:
while True: num = input('请输入你一个或多个整数: ').strip() try: num = int(num) print('你输入的数字是: %d'%num) except Exception: print('%s, 你输入的不是一个整数格式!'%Exception) # 如果输入的是一个整数类型,将返回输入的号码 # 如果输入的是其他的类型,如字符串、浮点数等,会提示用户输入的不是一个整数格式! ####执行结果: 请输入你一个或多个整数: 123 你输入的数字是: 123 请输入你一个或多个整数: a <class 'Exception'>, 你输入的不是一个整数格式! 请输入你一个或多个整数: 1. <class 'Exception'>, 你输入
异常是一个事件,该事件会在程序执行过程中发生,影响了程序的正常执行。一般情况下,在Python无法正常处理程序时就会发生一个异常。异常也是Python对象,表示一个错误。
当Python发生异常时我们需要捕获并且处理它,否则程序会中止执行。
2.异常处理
捕获异常可以使用 try / except语句。try: 用来检测语句块中的错误,从而让 except中语句捕获的异常信息并处理。
语法:
try: <语句> #运行代码 except <名字>: <语句> #如果在try上面的语句出现了 <名字> 异常 except <名字> ,<数据>: <语句> #如果引发引发了 <名字> 异常,获得附加的<数据> else: <语句> #如果没有异常发生,有点类似于for,while中的else finally: <语句> #无论异常与否,最终执行该块
try的工作原理是,当开始一个try语句后,Python就在当前程序的上下文中做标记,当异常出现时就可以回到这里, try子句先执行,接下来的执行流程依赖于执行时是否出现异常?
- 如果try后的语句执行时发生了异常,python就跳回到try并执行第一个匹配该异常的except子句,异常处理完毕,控制流就通过整个try语句(除非在处理异常时又引发别的异常)
- 如果在try后的语句里发生了异常,却没有匹配的except子句,异常将被递交到上层的try,或者到程序的最上边(这样将结束程序,并且打印默认的报错信息).
- 如果在try子句执行时没有发生异常,python将执行else语句后的语句(前提是定义了else子句),然后控制流通过整个try语句
- 无论try子句执行有无异常发生,python将触发执行finally子句
实例:
打开一个文件,往文件中写入内容,并且没有发生异常:
try: f = open('test.txt','w') f.write('测试文件,用于测试异常捕获') except IOError: print('Error: 写入失败, 没有找到文件或者权限不足!') else: print('写入成功!') f.close() #执行结果: 写入成功! #文件内容: Daniel-Mac:blog daniel$ cat test.txt &&echo 测试文件,用于测试异常捕获
修改文件的权限没有写,而后在打开文件,往文件中写入内容,查看异常:
chmod -w test.txt try: f = open('test.txt','w') f.write('测试文件,用于测试异常捕获') except IOError: print('Error: 写入失败, 没有找到文件或者权限不足!') else: print('写入成功!') f.close() #再次执行代码: Error: 写入失败, 没有找到文件或者权限不足!
3. 异常种类:
Python标准异常
异常名称 | 描述 |
---|---|
BaseException | 所有异常的基类 |
SystemExit | 解释器请求退出 |
KeyboardInterrupt | 用户中断执行(通常是输入^C) |
Exception | 常规错误的基类 |
StopIteration | 迭代器没有更多的值 |
GeneratorExit | 生成器(generator)发生异常来通知退出 |
StandardError | 所有的内建标准异常的基类 |
ArithmeticError | 所有数值计算错误的基类 |
FloatingPointError | 浮点计算错误 |
OverflowError | 数值运算超出最大限制 |
ZeroDivisionError | 除(或取模)零 (所有数据类型) |
AssertionError | 断言语句失败 |
AttributeError | 对象没有这个属性 |
EOFError | 没有内建输入,到达EOF 标记 |
EnvironmentError | 操作系统错误的基类 |
IOError | 输入/输出操作失败 |
OSError | 操作系统错误 |
WindowsError | 系统调用失败 |
ImportError | 导入模块/对象失败 |
LookupError | 无效数据查询的基类 |
IndexError | 序列中没有此索引(index) |
KeyError | 映射中没有这个键 |
MemoryError | 内存溢出错误(对于Python 解释器不是致命的) |
NameError | 未声明/初始化对象 (没有属性) |
UnboundLocalError | 访问未初始化的本地变量 |
ReferenceError | 弱引用(Weak reference)试图访问已经垃圾回收了的对象 |
RuntimeError | 一般的运行时错误 |
NotImplementedError | 尚未实现的方法 |
SyntaxError | Python 语法错误 |
IndentationError | 缩进错误 |
TabError | Tab 和空格混用 |
SystemError | 一般的解释器系统错误 |
TypeError | 对类型无效的操作 |
ValueError | 传入无效的参数 |
UnicodeError | Unicode 相关的错误 |
UnicodeDecodeError | Unicode 解码时的错误 |
UnicodeEncodeError | Unicode 编码时错误 |
UnicodeTranslateError | Unicode 转换时错误 |
Warning | 警告的基类 |
DeprecationWarning | 关于被弃用的特征的警告 |
FutureWarning | 关于构造将来语义会有改变的警告 |
OverflowWarning | 旧的关于自动提升为长整型(long)的警告 |
PendingDeprecationWarning | 关于特性将会被废弃的警告 |
RuntimeWarning | 可疑的运行时行为(runtime behavior)的警告 |
SyntaxWarning | 可疑的语法的警告 |
UserWarning | 用户代码生成的警告 |
实例,索引错误:
dic1 = ['tom','jerry'] try: dic1[3] except IndexError: print('索引错误啦,哪里有这么多值') #代码执行结果: 索引错误啦,哪里有这么多值'
实例,key错误
dic1 = {'name':'Tom'} try: dic1['Tom'] except KeyError: print('Key错误了,你输入的key不存在!') #代码执行结果: Key错误了,你输入的key不存在!
实例,ValueError
s = 'hello world' try: float(s) except ValueError: print('值错误!') else: print('值为: %s'%s)
"万能异常", 可以捕获到所有的异常情况,Exception:
s = 'hello world' try: float(s) except Exception: #各种类型错误都能捕获! print('值错误!') else: print('值为: %s'%s)
但是,Exception语句捕获所有发生的异常,这并不是一个很好的方式,我们不能通过程序识别出具体的异常信息。
所以,我们对特殊处理或提醒异常需要先定义,而后再定义Exception来捕获其他的异常,确保程序能正常运行。
s1 = 'hello world' try: int(s1) except KeyError: print('Error: Key错误!') except IndexError: print('Error: 索引错误!') except ValueError: print('Error: 值错误!') except Exception: print('Error: 出错了!') else: print('你的值是: %s'%s1)
4. 异常的参数
一个异常可以带上参数,可作为输出的异常信息参数。可以通过except语句来捕获异常的参数(该参数在python3中被废弃掉了):
def convert(var): try: return int(var) except ValueError,Argument: print('参数没有包含数字\n',Argument) convert('abc')
5. 触发异常
可以使用raise来手动触发异常:
raise语法:
raise [Exception [, args [, traceback]]]
Exception是异常的类型, 如ValueError等,是一个异常参数值。可选,如果不提供,异常参数是 “None”。
最后一个参数是可选的,如果存在,是跟踪异常对象。
try: raise Exception('这是手动触发的异常!!') #触发异常后,后面的代码就不会在执行 except Exception: print('手动触发了异常!') #代码执行结果: 手动触发了异常!
6. 自定义异常:
用户创建一个新的异常类,程序可以命名他们的异常。异常应该是通过直接或间接的方式,继承Exception类。
class MyException(Exception): def __init__(self,msg): self.message = msg def __str__(self): return self.message try: raise MyException('错了!') except MyException: print('自己定义的一个异常,触发执行了!')
7. 断言:
断言,能确保代码的正确性。话说回来,如果你确定代码是正确的,那么也就没必要使用断言了,因为它们从来不会运行失败,所以可以直接移除这些断言。如果你确定代码会失败,那么如果不用断言,代码就会通过编译并忽略你的检查。
python 用assert来检查一个条件,如果为真就不做任何事情。如果为假,则会抛出AssertionError的异常信息。
使用语法:
assert 条件
如 :
t1 = ('a','b','c') try: target = input('请输入你一个小写字母:').strip() assert target in t1 print(target) except AssertionError: print(' 错误!') #执行代码后,如果输入的是a、b、c的话,会直接打印输入的字母,其他值的话,则会抛异常而后被except捕获,打印“错误”
六. 设计模式之单例模式
单例模式,顾名思义,也就是单个实例的意思。
模式特点:保证类仅有一个实例,并提供一个访问它的全局访问点。
# 方法1: __new__方法实现: # 将类的实例绑定到类变量 _instance , 如果_instance为None说明还没有实例化,那么实例化类,并返回; # 如果不为 None, 直接返回 cls._instance class Singleton(object): def __new__(cls, *args, **kwargs): if not hasattr(cls,'_instance'): orig = super(Singleton,cls) cls._instance = orig.__new__(cls,*args,**kwargs) return cls._instance class Myclass(Singleton): value = 10 a = Myclass() b = Myclass() print(a.value) #查看值 print(id(a)) #查看a的内存地址 print(b.value) print(id(b)) #查看b的内存地址,两个一致
class Singleton: __instance = None #定义一个私有静态字段为初始值 def __init__(self,name): self.name = name def show(self): print(self.name) return 'test_instance' @classmethod def get_instance(cls): if cls.__instance: #如果字段内有值,直接返回字段值 return cls.__instance else: obj = cls('DBQ') #实例化 cls.__instance = obj #将对象赋值给字段 return cls.__instance #返回对象 a = Singleton.get_instance() b = Singleton.get_instance() print(a) print(id(a)) #内存地址和b相同 print() print(b) print(id(b)) #内存地址和a相同 # 后面再来几个对象,也是一样的! #代码执行结果: <__main__.Singleton object at 0x101b769b0> 4323764656 <__main__.Singleton object at 0x101b769b0> 4323764656
单例模式的存在主要是保证当前内存中存在单个实例,避免内存资源浪费。
#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Author: DBQ(Du Baoqiang) from wsgiref.simple_server import make_server class DBHelper(object): def __init__(self): self.hostname = '10.1.1.1' self.port = '3306' self.username = 'tom' self.password = 'sdk23sdf#$_@' def select(self): ''' 连接数据库,拼接sql语句, 操作 :return: ''' return 'select' def create(self): ''' 连接数据库, 拼接sql语句, 操作 :return: ''' return 'create' def delete(self): ''' 连接数据库, 拼接sql语句, 操作 :return: ''' return 'delete' def update(self): ''' 连接数据库, 拼接sql语句, 操作 :return: ''' return 'update' class Handler(object): def index(self): ''' 创建对象 :return: ''' db = DBHelper() db.select() return 'index' def news(self): ''' :return: ''' return 'news' def RunServer(environ, start_response): start_response('200 OK',[('Content-Type', 'text/html')]) url = environ['PATH_INFO'] temp = url.split('/')[1] obj = Handler() is_exsit = hasattr(obj, temp) #反射 if is_exsit: func = getattr(obj,temp) result = func() return result else: return '404 not found' if __name__ == '__main__': httpd = make_server('',8080, RunServer) print('Start HTTP on port 8080...') httpd.serve_forever()
#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Author: DBQ(Du Baoqiang) from wsgiref.simple_server import make_server class DBHelper(object): __instance = None #先定义一个私有静态字段 def __init__(self): self.hostname = '10.1.1.1' self.port = '3306' self.username = 'tom' self.password = 'sdk23sdf#$_@' @staticmethod def singleton(): if DBHelper.__instance: return DBHelper.__instance else: DBHelper.__instance = DBHelper() return DBHelper.__instance def select(self): ''' 连接数据库,拼接sql语句, 操作 :return: ''' return 'select' def create(self): ''' 连接数据库, 拼接sql语句, 操作 :return: ''' return 'create' def delete(self): ''' 连接数据库, 拼接sql语句, 操作 :return: ''' return 'delete' def update(self): ''' 连接数据库, 拼接sql语句, 操作 :return: ''' return 'update' class Handler(object): def index(self): ''' 创建对象 :return: ''' db = DBHelper.singleton() print(id(db)) db.select() return 'index' def news(self): ''' :return: ''' return 'news' def RunServer(environ, start_response): start_response('200 OK',[('Content-Type', 'text/html')]) url = environ['PATH_INFO'] temp = url.split('/')[1] obj = Handler() is_exsit = hasattr(obj, temp) #反射 if is_exsit: func = getattr(obj,temp) result = func() return result else: return '404 not found' if __name__ == '__main__': httpd = make_server('',8080, RunServer) print('Start HTTP on port 8080...') httpd.serve_forever()