面向对象编程
概念
面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。
而面向对象的程序设计(oop)把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。
数据封装、继承和多态是面向对象的三大特点。
面向过程和面向对象区别
- 面向过程
假如我们要处理公司的工资单,为了表示一个员工的工资,面向过程的程序可以用一个dict表示:
staff1 = { 'name': 'wang', 'wages': 3000 }
staff2 = { 'name': 'shuo', 'wages': 4000 }
显示员工工资可以通过函数实现:
def print_wages(staff):
print('%s: %s' % (staff['name'], staff['wages']))
- 面向对象
采用oop思想,我们首选思考的不是程序的执行流程,而是Staff应该被视为一个对象,这个对象拥有name和wages这两个属性(Property)。
如果要打印一个员工的工资,首先必须创建出这个员工对应的对象,然后,给对象发一个print_wages消息,让对象自己把自己的数据打印出来。
class Staff(object):
def __init__(self, name, wages): # 创建实例的方法
self.name = name
self.wages = wages
def print_wages(self):
print('%s: %s' % (self.name, self.wages))
给对象发消息实际上就是调用对象对应的关联函数,我们称之为对象的方法(Method)。
wang = Staff('wang', 3000)
shuo = Staff('shuo', 4000)
wang.print_wages()
shuo.print_wages()
oop抽象出Class,再根据Class创建Instance,抽象程度比函数要高,因为一个Class既包含数据,又包含操作数据的方法。
数据封装
概念:在类中对数据的赋值,内部调用对外部是不可见的,这使类变成了一个容器
面向对象最重要的概念就是类(Class)和实例(Instance),类是抽象的模板,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。
- 定义
在Python中,定义类是通过class关键字:
class Staff(object):
pass
class后面紧接着是类名,即Staff,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的,通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。
定义好了类,就可以根据Staff类创建出Staff的实例
>>> wang = Staff()
>>> wang
<__main__.Staff object at 0x10a67a590>
>>> Staff
<class '__main__.Staff'>
可以看到,变量wang指向的就是一个Staff的实例,后面的0x10a67a590是内存地址,每个object的地址都不一样,而Staff本身则是一个类。
可以自由地给一个实例变量绑定属性,比如,给实例wang绑定一个name属性:
>>> wang.name = 'wang'
>>> wang.name
'wang'
由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的init方法,在创建实例的时候,就把name,wages等属性绑上去:
class Staff(object):
def __init__(self, name, wages):
self.name = name
self.wages = wages
注意到init方法的第一个参数永远是self,表示创建的实例本身,因此,在init方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。
有了init方法,在创建实例的时候,就不能传入空的参数了,必须传入与init方法匹配的参数,但self不需要传,Python解释器自己会把实例变量传进去:
>>> wang = Student('wang', 3000)
>>> wang.name
'wang'
>>> wang.wages
3000
和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数。除此之外,类的方法和普通函数没有什么区别,所以,你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。
- 数据封装
面向对象编程的一个重要特点就是数据封装。在上面的Staff类中,每个实例就拥有各自的name和wages这些数据。我们可以通过函数来访问这些数据:
>>> def print_wages(std):
... print('%s: %s' % (staff.name, staff.wages))
...
>>> print_wages(wang)
wang: 3000
访问这些数据,可以直接在Staff类的内部定义访问数据的函数,这样,就把“数据”给封装起来了。这些封装数据的函数是和Staff类本身是关联起来的,我们称之为类的方法:
class Staff(object):
def __init__(self, name, wages):
self.name = name
self.wages = wages
def print_wages(self):
print('%s: %s' % (self.name, self.wages))
要定义一个方法,除了第一个参数是self外,其他和普通函数一样。要调用一个方法,只需要在实例变量上直接调用,除了self不用传递,其他参数正常传入:
>>> wang.print_wages()
wang: 3000
这样一来,我们从外部看Staff类,就只需要知道,创建实例需要给出name和wages,而如何打印,都是在Staff类的内部定义的,这些数据和逻辑被“封装”起来了,调用很容易,但却不用知道内部实现的细节。
封装的另一个好处是可以给Staff类增加新的方法,比如get_tax:
class Staff(object):
...
def get_tax(self):
if self.wages <= 3000:
return '0.1'
elif self.wages <= 6000:
return '0.2'
else:
return '0.3'
同样的,get_tax方法可以直接在实例变量上调用,不需要知道内部实现细节
- 访问限制
在Class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑。
foo:一种约定,Python内部的名字,用来区别其他用户自定义的命名,以防冲突.
_foo:一种约定,用来指定变量私有.程序员用来指定私有变量的一种方式.
__foo:这个有真正的意义:解析器用_classname_foo来代替这个名字,以区别和其他类相同的命名.
class Staff(object):
def __init__(self, name, wages):
self.__name = name
self.__wages = wages
def print_wages(self):
print('%s: %s' % (self.__name, self.__score))
对于外部代码来说,没什么变动,但是已经无法从外部访问实例变量.name和实例变量.wages了:
>>> wang = Staff('wang', 3000)
>>> wang.__name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Staff' object has no attribute '__name'
这样就确保了外部代码不能随意修改对象内部的状态。
如果外部代码要获取name和wages,如果外部代码要获取name和wages
class Staff(object):
...
def get_name(self):
return self.__name
def get_wages(self):
return self.__wages
要允许外部代码修改wages,可以再给Staff类增加set_wages方法:
class Staff(object):
...
def set_wages(self, wages):
self.__wages = wages
在方法中,可以对参数做检查,避免传入无效的参数:
class Staff(object):
...
def set_wages(self, wages):
if 3000 <= wages <= 10000:
self.__wages = wages
else:
raise ValueError('bad wages')
在Python中,变量名类似xxx的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用name、score这样的变量名。
有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。
把下面的Staff对象的gender字段对外隐藏起来,用get_gender()和set_gender()代替,并检查参数有效性:
#!/usr/bin/python
# -*- coding: utf-8 -*-
class Staff(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def get_gender(self):
return self.gender
def set_gender(self,gender):
self.gender = gender
# 测试:
wang = Staff('wang', 'male')
if wang.get_gender() != 'male':
print('测试失败!')
else:
wang.set_gender('female')
if wang.get_gender() != 'female':
print('测试失败!')
else:
print('测试成功!')
- 使用slots
如果我们想要限制实例的属性,比如,只允许对Staff实例添加name和wages属性。
为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的slots变量,来限制该class实例能添加的属性:
class Staff(object):
__slots__ = ('name', 'wages') # 用tuple定义允许绑定的属性名称
然后,我们试试:
>>> s = Staff() # 创建新的实例
>>> s.name = 'wang' # 绑定属性'name'
>>> s.wages = 3000 # 绑定属性'wages'
>>> s.age = 2018 # 绑定属性'age'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Staff' object has no attribute 'age'
由于’age’没有被放到slots中,所以不能绑定age属性,试图绑定age将得到AttributeError的错误。
使用slots要注意,slots定义的属性仅对当前类实例起作用,对继承的子类是不起作用的:
>>> class Manager(Staff):
... pass
...
>>> g = Manager()
>>> g.age = 2018
除非在子类中也定义slots,这样,子类实例允许定义的属性就是自身的slots加上父类的slots。
对属性参数的限定有两种方法:
- 定义属性为私有,然后在设置参数是调用函数进行限定
通过一个set_wages()方法来设置成绩,再通过一个get_wages()来获取工资,这样,在set_score()方法里,就可以检查参数:
class Staff(object):
def get_wages(self):
return self._wages
def set_wages(self, wages):
if not isinstance(wages, int):
raise ValueError('wages must be an integer!')
if wages < 1000 or wages > 10000:
raise ValueError('wages must between 1000 ~ 10000!')
self._wages = wages
现在,对任意的Staff实例进行操作,就不能随心所欲地设置wages了:
>>> s = Staff()
>>> s.set_wages(3000)
>>> s.get_wages()
3000
>>> s.set_wages(10)
Traceback (most recent call last):
...
ValueError: wages must between 1000 ~ 10000!
对于类的方法,装饰器一样起作用。Python内置的@property装饰器就是负责把一个方法变成属性调用的:
class Staff(object):
@property
def wages(self):
return self._wages
@wages.setter
def wages(self, wages):
if not isinstance(wages, int):
raise ValueError('wages must be an integer!')
if wages < 1000 or wages > 10000:
raise ValueError('wages must between 1000 ~ 10000!')
self._wages = wages
@property的实现比较复杂。如果要获得数据,直接加property即可。但如果要设置数据,@property本身又创建了另一个装饰器@wages.setter:
>>> s = Staff()
>>> s.wages = 3000 # OK,实际转化为s.set_wages(3000)
>>> s.wages # OK,实际转化为s.get_wages()
3000
>>> s.wages = 60
Traceback (most recent call last):
...
ValueError: wages must between 1000 ~ 10000!
还可以定义只读属性,只定义getter方法,不定义setter方法就是一个只读属性:
class Staff(object):
@property
def birth(self):
return self._birth
@birth.setter
def birth(self, value):
self._birth = value
@property
def age(self):
return 2018 - self._birth
上面的birth是可读写属性,而age就是一个只读属性,因为age可以根据birth和当前时间计算出来。
利用@property给一个Screen对象加上width和height属性,以及一个只读属性resolution:
#!/usr/bin/python
# -*- coding: utf-8 -*-
class Screen(object):
@property
def width(self):
return self._width
@property
def height(self):
return self._height
@width.setter
def width(self, value):
self._width = value
@height.setter
def height(self, value):
self._height = value
@property
def resolution(self):
return self._width*self._height
# 测试:
s = Screen()
s.width = 1024
s.height = 768
print('resolution =', s.resolution)
if s.resolution == 786432:
print('测试通过!')
else:
print('测试失败!')
继承
概念:一个类派生出子类,子类自动继承父类的属性和方法,如果一种语言不支持继承,那么类就没什么意义
- 继承
在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。
比如,我们已经编写了一个名为Staff的class,有一个work()方法可以直接打印:
class Staff(object):
def work(self):
print('Staff is working...')
当我们需要编写Manager和Workers类时,就可以直接从Staff类继承:
class Manager(Staff):
pass
class Worker(Staff):
pass
对于Staff来说,Manager就是它的父类,对于Manager来说,Staff就是它的子类。Worker和Manager类似。
子类获得了父类的全部功能。由于Staff实现了work()方法,Manager和Worker作为它的子类,就自动拥有了work()方法:
manager = Manager()
manager.work()
worker = Worker()
worker.work()
运行结果如下:
Staff is working...
Staff is working...
当然,也可以对子类增加一些方法,比如Manager类:
class Manager(Staff):
def work(self):
print('Manager is working...')
def rest(self):
print('resting...')
继承的第二个好处需要我们对代码做一点改进。Manager和Worker在work()的时候,显示的都是Staff is working…,符合逻辑的做法是分别显示Manager is working…和Worker is working…,因此,对Manager和Worker改进如下:
class Manager(Staff):
def work(self):
print('Manager is working...')
class Worker(Staff):
def work(self):
print('Worker is working...')
再次运行,结果如下:
Manager is working...
Worker is working...
继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写。
- 多重继承
继承是面向对象编程的一个重要的方式,因为通过继承,子类就可以扩展父类的功能。
通过多重继承,一个子类就可以同时获得多个父类的所有功能。
class Staff(object):
pass
# 大类:
class Manage(Staff):
pass
class Work(Staff):
pass
# 各种职位:
class Manager(Manage):
pass
class Boss(Manage):
pass
class Worker(Work):
pass
class Driver(Work):
pass
现在,我们要给职位再加上Regular和Irregular的功能,只需要先定义好Regular和Irregular的类:
class Regular(object):
def position(self):
print('Guangzhou...')
class Irregular(object):
def job(self):
print('Drivering...')
对于Regular功能的职位,就多继承一个Regular,例如Manager:
class Manager(Manage, Regular):
pass
对于Irregular功能的职位,就多继承一个Irregular,例如Driver:
class Driver(Driver, Irregular):
pass
在设计类的继承关系时,通常,主线都是单一继承下来的,例如,Worker继承自Work。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让Worker除了继承自Work外,再同时继承Irregular。这种设计通常称之为MixIn。
为了更好地看出继承关系,我们把Regular和Irregular改为RegularMixIn和IrregularMixIn。类似的,还可以定义出人力资源HRMixIn和总监IspectorMixIn,让某个职位同时拥有好几个MixIn:
class Manager(Manage, RegularMixIn, IspectorMixIn):
pass
MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。
- super()
被继承的类一般称为“超类”或“父类”,继承的类称为“子类”。
super用来调用父类的方法,一般是用来保证在本身初始化的时候,父类也被正确的初始化。
class A:
def __init__(self):
self.x = 0
class B(A):
def __init__(self):
super().__init__()
self.y = 1
这个可以结合访问限制的 __ 来使用,因为之前说过,双下划线的命名方式的目的就是为了防止父类的属性被子类不小心覆盖了。我们可以在子类中找不到对应属性的时候,使用super()去父类中寻找。
class Test(Father):
def __init__(self, obj):
self._obj = obj
def __getattr__(self, name):
return getattr(self._obj, name)
def __setattr__(self, name, value):
if name.startswith('__'):
super().__setattr__(name, value)
else:
setattr(self._obj, name, value)
super() 用于多继承时,会避免重复调用某一个父类的初始化方法。而如果直接使用父类的类名来调用初始化方法,则会重复调用。当然super是需要慎用的。
class A:
def __init__(self):
print("Enter A")
print("Leave A")
class B(A):
def __init__(self):
print("Enter B")
super().__init__()
print("Leave B")
class C(A):
def __init__(self):
print("Enter C")
print(super())
super().__init__()
print("Leave C")
class D(B,C):
def __init__(self):
print("Enter D")
print(super())
super().__init__()
print("Leave D")
D()
print(D.mro())
输出结果:
Enter D
<super: <class 'D'>, <D object>>
Enter B
Enter C
Enter A
Leave A
Leave C
Leave B
Leave D
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
解析:
- 执行顺序:D->B->C->A->object
- 根据执行顺序,执行D类,输出Enter D,
第一次执行super()时,调用D的父类B的init(),输出Enter B,
第二次执行super()时,则调用D的父类C的init(),输出Enter C,
第三次执行super()时,则调用A类的init(),输出Enter A和Leave A
C类执行完super().init()后,继续执行下一句,输出Leave C
B类执行完super().init()后,继续执行下一句,输出Leave B
D类执行完super().init()后,继续执行下一句,输出Leave D
总结:
super()当出现多继承时,调用的顺序是根据类实例化时的执行顺序决定。
python3的继承寻址方式是广度优先,先从左到右,最后到基类
super的使用方法:
- super(type, obj)
当定义一个子类时: ``` class Manager(Manage, Regular)::
def init(self): super(Manager, self).init() print(“Manager init”)
- super(Manager, self).** init ** ():
- 获取Manager的mro(类继承体系中的成员解析顺序)
- 从mro中Manager右边的一个类开始,依次寻找**init**函数。这里是从Manage开始寻找
- 一旦找到,就把找到的**init**函数绑定到self对象,并返回
> 在这里如果我们想要调用Manage的**init**而不是Manager的**init**,就应该写成super(Manage,self).** init**()
- super(type, type2)<br />当定义一个子类时:
class Regular(Staff):
def new(Manager): obj = super(Manage, Manager).new(Manager) print(“Manager new”) return obj
- super(Manage, Manager).** new**(Manager):
- 获取Manager这个类的mro,即[Manager,Manage,Regular,Staff]
- 从mro中Manage右边的一个类开始,依次寻找**new**函数,即寻找Regular,Staff
- 一旦找到,就返回“ 非绑定 ”的**new**函数
由于返回的是非绑定的函数对象,因此调用时不能省略函数的第一个参数。这也是这里调用**new**时,需要传入参数Manager的原因,同样的,如果我们想从某个mro的某个位置开始查找,只需要修改super的第一个参数就行。
4. 钻石问题
多重继承的时候,如果我们要初始化父类,就会出现多个父类的**init**方法需要被初始化,并且它们要通过不同的参数来初始化,比如
class Staff(object): def init(self,name,wages): self.name = name self.wages = wages
class Manage(Staff): def init(self,name,wages,number): Staff.init(self,name,wages) self.number=number
class Regular(Staff): def init(self,name,wages,position): Staff.init(self,name,wages) self.position=position
class Manager(Manage, Regular): def init(self,name,wages,numbers,position,phone): Manage.init(self,name,wages,number) Regular.init(self,name,wages,position) self.phone = phone
在这里,我们直接调用了每一个超类的**init**方法并且显式地传递了self参数,但是存在一些问题:
- 忽略显式地调用初始化函数可能会导致一个超类未被初始化(比如试图将数据插到一个未连接的数据库中)
- 由于这个类的层次关系可能导致某个超类被调用多次,在例子中表现为Staff类被调用两次。由基类扩展了两个或多个类,然后再由第三方多重继承,这个称为钻石继承,因为这个类的视图是一个钻石的形状。
对于多重继承我们只想要调用下一个方法,而不是"父类"的方法。实际上,下一个方法可能不属于当前类或者当前类的父类或者祖先类。使用super可以让形式复杂的多重继承成为可能
class Staff(object): def init(self,name,wages): self.name = name self.wages = wages
class Manage(Staff): def init(self,number,kwargs): super().init(kwargs) # 把关键字参数传递给它的超类,即Regular self.number=number
class Regular(Staff): def init(self,position,kwargs): super().init(kwargs) # 把关键字参数传递给它的超类,即Staff self.position=position
class Manager(Manage, Regular): def init(self,phone,kwargs): super().init(kwargs) # 把关键字参数传递给它的超类,即Manage self.phone = phone
我们通过设置空字符串为参数默认值,把所有的参数变成了关键字参数。我们也保证包含了一个**kwargs参数,它可以捕获任何特殊方法不知道怎么处理的额外参数。通过调用super方法,它将这些参数传递给了下一个类。
5. 扩展内置类
大部分内置数据类型可以进行扩展,常见的可扩展内置数据类型是object、list、set、dict、file以及str。像整型int和浮点型float这些数值类型偶尔也会做继承。
比如继承list并添加一个方法用于搜索
class Stafflist(list): def search(self,name): staff_list=[] for staff in self: if name in staff.name: staff_list.append(staff) return staff_list
class Staff(object): staff_list = Stafflist()
def __init__(self,name,email):
self.name = name
self.email = email
self.staff_list.append(self)
c1 = Staff(“wang”,”123@123.com”) c2 = Staff(“shuo”,”456@456.com”) c3 = Staff(“wang shuo”,”123456@123456.com”)
print([c.name for c in Staff.staff_list.search(‘wang’)])
执行结果
[‘wang’, ‘wang shuo’]
在这里我们改变了内置列表的语法,使它成为可以继承的对象。使用[]来创建一个空的列表,实际上是用list()创建空列表的简化形式( [] == list() )
<a name="ZZ1G7"></a>
## 多态
概念:接口的多种实现即为多态
要理解什么是多态,我们首先要对数据类型再作一点说明。当我们定义一个class的时候,我们实际上就定义了一种数据类型。我们定义的数据类型和Python自带的数据类型,比如str、list、dict没什么两样:
a = list() # a是list类型 b = Staff() # b是Staff类型 c = Worker() # c是Worker类型
判断一个变量是否是某个类型可以用isinstance()判断:
isinstance(a, list) True isinstance(b, Staff) True isinstance(c, Worker) True ```
看来a、b、c确实对应着list、Staff、Worker这3种类型。
但是等等,试试:
>>> isinstance(c, Staff)
True
c不仅仅是Worker,c还是Staff,因为Worker是从Staff继承下来的。
所以,在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。但是,反过来就不行:
>>> b = Staff()
>>> isinstance(b, Worker)
False
Worker可以看成Staff,但Staff不可以看成Worker。
要理解多态的好处,我们还需要再编写一个函数,这个函数接受一个Animal类型的变量:
def work_twice(staff):
staff.work()
staff.work()
当我们传入Animal的实例时,work_twice()就打印出:
>>> work_twice(Staff())
Staff is working...
Staff is working...
当我们传入Worker的实例时,work_twice()就打印出:
>>> work_twice(Staff())
Worker is working...
Worker is working...
当我们传入Manager的实例时,work_twice()就打印出:
>>> work_twice(Manager())
Manager is working...
Manager is working...
看上去没啥意思,但是仔细想想,现在,如果我们再定义一个Boss类型,也从Staff派生:
class Boss(Staff):
def work(self):
print('Boss is working at home...')
当我们调用run_twice()时,传入Boss的实例:
>>> work_twice(Boss())
Boss is working at home...
Boss is working at home...
使用了同一种调用的方式,但是使用的对象可以千变万化
经典类与新式类
创建类的时候,如果我们继承了object类,该类就是新式类。两种类在多继承状态下查找的方法不同,经典类是深度查找,新式类是广度查找,即:
当定义A(object),B(A),C(A),D(B,C)时(这种是新式类,采用广度查找),mro是D-B-C-A-object
当定义A,B,C,D(B,C)时(这种是经典类,采用深度查找),mro是D-B-A-object
定制类
- 定制类str
我们先定义一个Staff类,打印一个实例:
>>> class Staff(object):
... def __init__(self, name):
... self.name = name
...
>>> print(Staff('wang'))
<__main__.Staff object at 0x109afb190>
只需要定义好str()方法,返回一个好看的字符串就可以了:
>>> class Staff(object):
... def __init__(self, name):
... self.name = name
... def __str__(self):
... return 'Staff object (name: %s)' % self.name
...
>>> print(Staff('wang'))
Staff object (name: wang)
直接显示变量调用的不是str(),而是repr(),两者的区别是str()返回用户看到的字符串,而repr()返回程序开发者看到的字符串,也就是说,repr()是为调试服务的。
class Staff(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Staff object (name=%s)' % self.name
__repr__ = __str__
- 定制类iter
如果一个类想被用于for … in循环,类似list或tuple那样,就必须实现一个iter()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的next()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。
我们以斐波那契数列为例,写一个Fib类,可以作用于for循环:
class Fib(object):
def __init__(self):
self.a, self.b = 0, 1 # 初始化两个计数器a,b
def __iter__(self):
return self # 实例本身就是迭代对象,故返回自己
def __next__(self):
self.a, self.b = self.b, self.a + self.b # 计算下一个值
if self.a > 100000: # 退出循环的条件
raise StopIteration()
return self.a # 返回下一个值
现在,试试把Fib实例作用于for循环:
>>> for n in Fib():
... print(n)
...
1
1
2
3
5
...
46368
75025
- 定制类getitem
Fib实例虽然能作用于for循环,看起来和list有点像,但是,把它当成list来使用还是不行,比如,取第5个元素:
>>> Fib()[5]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'Fib' object does not support indexing
要表现得像list那样按照下标取出元素,需要实现getitem()方法:
class Fib(object):
def __getitem__(self, n):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
现在,就可以按下标访问数列的任意一项了:
>>> f = Fib()
>>> f[0]
1
>>> f[1]
1
>>> f[2]
2
>>> f[3]
3
>>> f[10]
89
>>> f[100]
573147844013817084101
但是list有个神奇的切片方法:
>>> list(range(100))[5:10]
[5, 6, 7, 8, 9]
对于Fib却报错。原因是getitem()传入的参数可能是一个int,也可能是一个切片对象slice,所以要做判断:
class Fib(object):
def __getitem__(self, n):
if isinstance(n, int): # n是索引
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
if isinstance(n, slice): # n是切片
start = n.start
stop = n.stop
if start is None:
start = 0
a, b = 1, 1
L = []
for x in range(stop):
if x >= start:
L.append(a)
a, b = b, a + b
return L
现在试试Fib的切片:
>>> f = Fib()
>>> f[0:5]
[1, 1, 2, 3, 5]
>>> f[:10]
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
如果把对象看成dict,getitem()的参数也可能是一个可以作key的object,例如str。
与之对应的是setitem()方法,把对象视作list或dict来对集合赋值。最后,还有一个delitem()方法,用于删除某个元素。
- 定制类getattr
正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错。比如定义Student类:
class Staff(object):
def __init__(self):
self.name = 'wang'
调用name属性,没问题,但是,调用不存在的score属性,就有问题了:
>>> s = Staff()
>>> print(s.name)
wang
>>> print(s.wages)
Traceback (most recent call last):
...
AttributeError: 'Staff' object has no attribute 'wages'
错误信息很清楚地告诉我们,没有找到wages这个attribute。
要避免这个错误,除了可以加上一个wages属性外,Python还有另一个机制,那就是写一个getattr()方法,动态返回一个属性。修改如下:
class Staff(object):
def __init__(self):
self.name = 'wang'
def __getattr__(self, attr):
if attr=='wages':
return 3000
当调用不存在的属性时,比如score,Python解释器会试图调用getattr(self, ‘wages’)来尝试获得属性,这样,我们就有机会返回score的值:
>>> s = Student()
>>> s.name
'wang'
>>> s.score
3000
返回函数也是完全可以的:
class Staff(object):
def __getattr__(self, attr):
if attr=='age':
return lambda: 2018
只是调用方式要变为:
>>> s.age()
2018
注意,只有在没有找到属性的情况下,才调用getattr,已有的属性,比如name,不会在getattr中查找。
此外,注意到任意调用如s.abc都会返回None,这是因为我们定义的getattr默认返回就是None。要让class只响应特定的几个属性,我们就要按照约定,抛出AttributeError的错误:
class Staff(object):
def __getattr__(self, attr):
if attr=='age':
return lambda: 2018
raise AttributeError('\'Staff\' object has no attribute \'%s\'' % attr)
利用完全动态的getattr,我们可以写出一个链式调用:
class Chain(object):
def __init__(self, path=''):
self._path = path
def __getattr__(self, path):
return Chain('%s/%s' % (self._path, path))
def __str__(self):
return self._path
__repr__ = __str__
这时候无论API怎么变,SDK都可以根据URL实现完全动态的调用,而且,不随API的增加而改变!
>>> Chain().a.b.c.d
'/a/b/c/d'
还有些REST API会把参数放到URL中,比如GitHub的API:
GET /users/:user/repos
调用时,需要把:user替换为实际用户名。如果我们能写出这样的链式调用:
Chain().users('michael').repos
编写
class Chain(object):
def __init__(self, path=''):
self._path = path
def __getattr__(self, path):
return Chain('%s/%s' % (self._path, path))
def __call__(self,name):
return Chain('/: %s' % name)
def __str__(self):
return self._path
__repr__ = __str__
- 定制类call
一个对象实例可以有自己的属性和方法,当我们调用实例方法时,我们用instance.method()来调用。
在实例中,任何类,只需要定义一个call()方法,就可以直接对实例进行调用。
class Staff(object):
def __init__(self, name):
self.name = name
def __call__(self):
print('My name is %s.' % self.name)
调用方式如下:
>>> s = Student('wang')
>>> s()
My name is wang.
call()还可以定义参数。对实例进行直接调用就好比对一个函数进行调用一样,所以你完全可以把对象看成函数,把函数看成对象
能被调用的对象就是一个Callable对象,比如函数和我们上面定义的带有call()的类实例:
>>> callable(Staff())
True
>>> callable(max)
True
>>> callable([1, 2, 3])
False
>>> callable(None)
False
>>> callable('str')
False
枚举类
python为枚举类定义一个class类型,每个常量都是class的一个唯一实例。
from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
这样我们就获得了Month类型的枚举类,可以直接使用Month.Jan来引用一个常量,或者枚举它的所有成员:
for name, member in Month.__members__.items():
print(name, '=>', member, ',', member.value)
Jan => Month.Jan , 1
Feb => Month.Feb , 2
Mar => Month.Mar , 3
Apr => Month.Apr , 4
May => Month.May , 5
Jun => Month.Jun , 6
Jul => Month.Jul , 7
Aug => Month.Aug , 8
Sep => Month.Sep , 9
Oct => Month.Oct , 10
Nov => Month.Nov , 11
Dec => Month.Dec , 12
value属性则是自动赋给成员的int常量,默认从1开始计数。
如果需要更精确地控制枚举类型,可以从Enum派生出自定义类:
from enum import Enum, unique
@unique
class Weekday(Enum):
Sun = 0 # Sun的value被设定为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
@unique装饰器可以帮助我们检查保证没有重复值。
访问这些枚举类型可以有若干种方法:
>>> day1 = Weekday.Mon
>>> print(day1)
Weekday.Mon
>>> print(Weekday.Tue)
Weekday.Tue
>>> print(Weekday['Tue'])
Weekday.Tue
>>> print(Weekday.Tue.value)
2
>>> print(day1 == Weekday.Mon)
True
>>> print(day1 == Weekday.Tue)
False
>>> print(Weekday(1))
Weekday.Mon
>>> print(day1 == Weekday(1))
True
>>> Weekday(7)
Traceback (most recent call last):
...
ValueError: 7 is not a valid Weekday
>>> for name, member in Weekday.__members__.items():
... print(name, '=>', member)
...
Sun => Weekday.Sun
Mon => Weekday.Mon
Tue => Weekday.Tue
Wed => Weekday.Wed
Thu => Weekday.Thu
Fri => Weekday.Fri
Sat => Weekday.Sat
练习:把Student的gender属性改造为枚举类型,可以避免使用字符串:
#!/usr/bin/python
# -*- coding: utf-8 -*-
from enum import Enum, unique
class Gender(Enum):
Male = 0
Female = 1
class Student(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
# 测试:
bart = Student('Bart', Gender.Male)
if bart.gender == Gender.Male:
print('测试通过!')
else:
print('测试失败!')
上下文管理
让一个类支持with特性的话,需要自定义enter()和exit()方法(无论 with 中的代码块在执行的过程中发生任何情况,最终都会完成结束)
from socket import socket, AF_INET, SOCK_STREAM
class LazyConnection:
def __init__(self, address, family=AF_INET, type=SOCK_STREAM):
self.address = address
self.family = family
self.type = type
self.sock = None
def __enter__(self): # 开始
if self.sock is not None:
raise RuntimeError('Already connected')
self.sock = socket(self.family, self.type)
self.sock.connect(self.address)
return self.sock
def __exit__(self, exc_ty, exc_val, tb): # 异常值、异常类型、回溯信息
self.sock.close()
self.sock = None
这个类在实例化的时候并不会去创建连接,只有在使用with时,连接的连接和关闭都是自动完成的,所以调用完with之后,可以直接send或者recv进行数据交互了。
上下文管理在资源管理中用的非常广,比较文件、网络socket,还有上次说的线程锁。可以在 exit 中release来保证锁的释放。
对象销毁
Python 使用了引用计数这一简单技术来跟踪和回收垃圾。在 Python 内部记录着所有使用中的对象各有多少引用。一个内部跟踪变量,称为一个引用计数器。
当对象被创建时, 就创建了一个引用计数, 当这个对象不再需要时, 也就是说, 这个对象的引用计数变为0 时, 它被垃圾回收。但是回收不是”立即”的, 由解释器在适当的时机,将垃圾对象占用的内存空间回收
垃圾回收机制不仅针对引用计数为0的对象,同样也可以处理循环引用的情况。循环引用指的是,两个对象相互引用,但是没有其他变量引用他们。这种情况下,仅使用引用计数是不够的。Python 的垃圾收集器实际上是一个引用计数器和一个循环垃圾收集器。作为引用计数的补充, 垃圾收集器也会留心被分配的总量很大(及未通过引用计数销毁的那些)的对象。 在这种情况下, 解释器会暂停下来, 试图清理所有未引用的循环
析构函数 del ,del在对象销毁的时候被调用,当对象不再被使用时,del方法运行:
class Point:
def init( self, x=0, y=0):
self.x = x
self.y = y
def del(self):
class_name = self.class.name
print("%s销毁" % class_name)
获取对象信息
- 使用type()
我们来判断对象类型,使用type()函数:
基本类型都可以用type()判断:
>>> type(123)
<class 'int'>
>>> type('str')
<class 'str'>
>>> type(None)
<type(None) 'NoneType'>
如果一个变量指向函数或者类,也可以用type()判断:
>>> type(abs)
<class 'builtin_function_or_method'>
>>> type(a)
<class '__main__.Animal'>
但是type()函数返回的是什么类型呢?它返回对应的Class类型。如果我们要在if语句中判断,就需要比较两个变量的type类型是否相同:
>>> type(123)==type(456)
True
>>> type(123)==int
True
>>> type('abc')==type('123')
True
>>> type('abc')==str
True
>>> type('abc')==type(123)
False
判断基本数据类型可以直接写int,str等,但如果要判断一个对象是否是函数怎么办?可以使用types模块中定义的常量:
>>> import types
>>> def fn():
... pass
...
>>> type(fn)==types.FunctionType
True
>>> type(abs)==types.BuiltinFunctionType
True
>>> type(lambda x: x)==types.LambdaType
True
>>> type((x for x in range(10)))==types.GeneratorType
True
- 使用isinstance()
对于class的继承关系来说,使用type()就很不方便。我们要判断class的类型,可以使用isinstance()函数。
我们回顾上次的例子,如果继承关系是:
object -> Staff -> Work -> Worker
那么,isinstance()就可以告诉我们,一个对象是否是某种类型。先创建3种类型的对象:
>>> a = Staff()
>>> d = Work()
>>> h = Worker()
然后,判断:
>>> isinstance(h, Worker)
True
没有问题,因为h变量指向的就是Staff对象。
再判断:
>>> isinstance(h, Staff)
True
h虽然自身是Worker类型,但由于Worker是从Work继承下来的,所以,h也还是Work类型。换句话说,isinstance()判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上。
因此,我们可以确信,h还是Staff类型:
>>> isinstance(h, Staff)
True
同理,实际类型是Work的d也是Staff类型:
>>> isinstance(d, Work) and isinstance(d, Staff)
True
但是,d不是Worker类型:
>>> isinstance(d, Worker)
False
能用type()判断的基本类型也可以用isinstance()判断:
>>> isinstance('a', str)
True
>>> isinstance(123, int)
True
>>> isinstance(b'a', bytes)
True
并且还可以判断一个变量是否是某些类型中的一种,比如下面的代码就可以判断是否是list或者tuple:
>>> isinstance([1, 2, 3], (list, tuple))
True
>>> isinstance((1, 2, 3), (list, tuple))
True
- 使用dir()
如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法:
>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']
类似xxx的属性和方法在Python中都是有特殊用途的,比如len方法返回长度。在Python中,如果你调用len()函数试图获取一个对象的长度,实际上,在len()函数内部,它自动去调用该对象的len()方法,所以,下面的代码是等价的:
>>> len('ABC')
3
>>> 'ABC'.__len__()
3
我们自己写的类,如果也想用len(myObj)的话,就自己写一个len()方法:
>>> class MyStaff(object):
... def __len__(self):
... return 50
...
>>> staff = MyStaff()
>>> len(staff)
100
剩下的都是普通属性或方法,比如lower()返回小写的字符串:
>>> 'ABC'.lower()
'abc'
仅仅把属性和方法列出来是不够的,配合getattr()、setattr()以及hasattr(),我们可以直接操作一个对象的状态:
>>> class MyObject(object):
... def __init__(self):
... self.x = 9
... def power(self):
... return self.x * self.x
...
>>> obj = MyObject()
紧接着,可以测试该对象的属性:
>>> hasattr(obj, 'x') # 有属性'x'吗?
True
>>> obj.x
9
>>> hasattr(obj, 'y') # 有属性'y'吗?
False
>>> setattr(obj, 'y', 19) # 设置一个属性'y'
>>> hasattr(obj, 'y') # 有属性'y'吗?
True
>>> getattr(obj, 'y') # 获取属性'y'
19
>>> obj.y # 获取属性'y'
19
如果试图获取不存在的属性,会抛出AttributeError的错误:
>>> getattr(obj, 'z') # 获取属性'z'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'MyObject' object has no attribute 'z'
可以传入一个default参数,如果属性不存在,就返回默认值:
>>> getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404
404
也可以获得对象的方法:
>>> hasattr(obj, 'power') # 有属性'power'吗?
True
>>> getattr(obj, 'power') # 获取属性'power'
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn = getattr(obj, 'power') # 获取属性'power'并赋值到变量fn
>>> fn # fn指向obj.power
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn() # 调用fn()与调用obj.power()是一样的
81
- 四个重要的内置函数getattr、hasattr、delattr和setattr
- hasattr(object, name)用来判断一个对象里面,是否有name属性或者name方法,返回BOOL值
>>> class Staff(object):
... name="wangshuo"
... def work(self):
... return "working"
...
>>> a=Staff()
>>> hasattr(a, "name") #判断对象有name属性
True
>>> hasattr(t, "work") #判断对象有run方法
True
>>> hasattr(t, "play") #判断对象有play方法
False
通过hasattr的判断,可以防止非法输入错误,并将其统一定位到错误页面。
- getattr(object, name[,default])用来获取对象object的属性或者方法.如果存在打印出来,如果不存在,打印出默认值,默认值可选。
需要注意的是,如果是返回的对象的方法,返回的是方法的内存地址,如果需要运行这个方法,
可以在后面添加一对括号。
>>> class Staff(object):
... name="wangshuo"
... def work(self):
... return "working"
...
>>> a=Staff()
>>> getattr(a, "name") # 获取对象name属性
wangshuo
>>> getattr(a, "work") # 获取对象run方法
<bound method test.run of <__main__.test instance at 0x0269C878>>
>>> getattr(a, "play") # 获取对象play方法
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: Staff instance has no attribute 'play'
>>> getattr(a, "age","18") #若属性不存在,返回一个默认值。
'18'
- setattr(object, name, values)给对象的属性赋值,若属性不存在,先创建再赋值。
>>> class Staff(object):
... name="wangshuo"
... def work(self):
... return "working"
...
>>> a=Staff()
>>> hasattr(a, "age") # 判断对象有age属性
False
>>> setattr(a, "age", "18") # 为对age属性赋值
>>> hasattr(a, "age") # 判断对象有age属性
True
- delattr(object, name)删除对象的一个由name指定的属性。
>>> class Staff(object):
... name="wangshuo"
... def work(self):
... return "working"
...
>>> a=Staff()
>>> delattr(a, "name") # 判断对象有age属性
getattr,hasattr,setattr,delattr对模块的修改都在内存中进行,并不会影响文件中真实内容。
5.反射机制
通过字符串的形式,导入模块;通过字符串的形式,去模块寻找指定函数,并执行。利用字符串的形式去对象(模块)中操作(查找/获取/删除/添加)成员,一种基于字符串的事件驱动
实例:基于反射机制模拟web框架路由
需求:比如我们输入:www.xxx.com/commons/f1,返回f1的结果。
"""
动态导入模块,并执行其中函数
"""
url = input("url: ") # 输入网址
target_module, target_func , target_result= url.split('/') # 通过/分割字符串
m = __import__('lib.'+target_module, fromlist=True)
inp = url.split("/")[-1] # 分割url,并取出url最后一个字符串
if hasattr(m,target_result): # 判断在commons模块中是否存在inp这个字符串
target_result = getattr(m,target_result) # 获取inp的引用
target_result() # 执行
else:
print("404")
动态创建类
创建class的方法就是使用type()函数。
type()函数既可以返回一个对象的类型,又可以创建出新的类型,比如,我们可以通过type()函数创建出Name类,而无需通过class Name(object)…的定义:
>>> def func(self, name='shuo'): # 先定义函数
... print('wang %s.' % name)
...
>>> Name = type('Name', (object,), dict(name=func)) # 创建Name class,继承object,func绑定到方法name上
>>> h = Name()
>>> h.name()
wang shuo.
>>> print(type(Name))
<class 'type'>
>>> print(type(h))
<class '__main__.Name'>
要创建一个class对象,type()函数依次传入3个参数:
- class的名称;
- 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
- class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上。
函数方法
python类函数有三种方法:静态方法,类方法和实例方法
def test(x): # 它的工作跟任何东西(类,实例)无关
print("test(%s)" % x)
class A(object):
def test(self,x): # 实例方法的调用离不开实例,我们需要把实例自己传给函数,使用时是a.test(x).
print("test(%s)" % x)
@classmethod
def class_test(cls,x): # 类方法和实例方法一样,只不过它传递的是类而不是实例(A.class_test(x))
print ("class_test(%s,%s)" % (cls,x))
@staticmethod
def static_test(x): # 静态方法不需要对谁进行绑定,唯一区别是调用的时候需要使用a.static_test(x)或者A.static_test(x)来调用
print("static_test(%s)" % x)
test("wangshuo")
a=A()
a.test("wangshuo")
a.class_test("wangshuo")
A.class_test("wangshuo")
a.static_test("wangshuo")
A.static_test("wangshuo")
### 打印结果
test(wangshuo)
test(wangshuo)
class_test(<class '__main__.A'>,wangshuo)
class_test(<class '__main__.A'>,wangshuo)
static_test(wangshuo)
static_test(wangshuo)
python的自省机制
自省就是面向对象的语言所写的程序在运行时,所能知道对象的类型.简单一句就是运行时能够获得对象的类型
- 获取对象的属性
用来检测和访问对象属性的方法可以参考上方”获取对象信息”,其中有详细的解释
- 根据对象的类型的自省机制
- 模块(module)
在引用模块的时候,可以使用一些函数来获取模块信息:
>>> import os as a
>>> print(a.__doc__.splitlines()[0]) # 打印os文档字符串。如果模块没有文档,这个值是None。在这里是打印第一行
OS routines for NT or Posix depending on what system we're on.
>>> print(a.__name__) # 打印定义时的模块名;即使你使用import .. as 为它取了别名,或是赋值给了另一个变量名,它打印的仍然是定义时的模块名
os
>>> print(a.__file__) # 打印该模块的文件路径。需要注意的是内建的模块没有这个属性,访问它会抛出异常!
/usr/local/lib/python3.6/os.py
- 类(class)
在使用类的时候,同样也有一些函数来获取模块信息
__ doc__: 文档字符串。如果类没有文档,这个值是None。
__name__: 定义时的类名。
__module__: 包含该类的定义的模块名;需要注意,是字符串形式的模块名而不是模块对象
>>> class A(object):
... a=1
>>> print(A.__module__)
__main__
__bases__: 直接父类对象的元组;但不包含继承树更上层的其他类,比如父类的父类。
>>> print(A.__dict__)
{'__module__': '__main__', 'a': 1, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
- 实例(instance)
>>> class A(object):
... a=1
>>> a=A()
>>> print(a.__dict__ ) # 包含了可用的属性名-属性字典。
{}
>>> a.name="wangshuo"
>>> print(a.__dict__ )
{'name': 'wangshuo'}
>>> print(a.__class__==A) # 该实例的类对象
True
- 函数(function)和方法(method)
同样包含doc, name, module, dict
单例模式
单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在。当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场。
单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。
在 Python 中,我们可以用多种方法来实现单例模式:
- 使用模块
Python 的模块就是天然的单例模式。因为模块在第一次导入时,会生成 .pyc 文件,当第二次导入时,就会直接加载 .pyc 文件,而不会再次执行模块代码。因此,我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。
# test.py
class A(object):
def test(self):
print('A.test')
a=A()
# test1.py
from .test import a
a.test()
- 使用 new
为了使类只能出现一个实例,我们可以使用 new 来控制实例的创建过程
class A(object):
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance: # 将类的实例和一个类变量 _instance 关联起来,如果 cls._instance 为 None 则创建实例,否则直接返回 cls._instance。
cls._instance = super(A, cls).__new__(cls, *args, **kwargs)
return cls._instance
class AA(A):
a = 1
- 使用装饰器(decorator)
装饰器(decorator)可以动态地修改一个类或函数的功能。这里,我们也可以使用装饰器来装饰某个类,使其只能生成一个实例
def A(cls, *args, **kw):
instances = {}
def getinstance(): # 判断某个类是否在字典 instances 中,如果不存在,则会将 cls 作为 key,cls(*args, **kw) 作为 value 存到 instances 中,否则,直接返回
if cls not in instances:
instances[cls] = cls(*args, **kw)
return instances[cls]
return getinstance
@A
class AA:
- 使用元类(metaclass)
元类(metaclass)可以控制类的创建过程,它主要做三件事:拦截类的创建、修改类的定义、返回修改后的类
class A(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class AA(metaclass=A):
pass