Python学习笔记(七)--面向对象高级编程
创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。
定义Class:
classStudent(object):
pass
s= Student()
比如尝试给实例s绑定一个方法:
>>>def set_age(self, age): # 定义一个函数作为实例方法
... self.age = age
>>> from types import MethodType
>>> s.set_age =MethodType(set_age, s) # 给实例绑定一个方法
>>>s.set_age(25) # 调用实例方法
>>>s.age # 测试结果
25
但是,给一个实例绑定的方法,对另一个实例是不起作用的。
为了给所有实例都绑定方法,可以给class绑定方法:
>>>def set_score(self, score):
... self.score = score
...
>>>Student.set_score = set_score
给class绑定方法后,所有实例均可调用:
>>>s.set_score(100)
>>>s.score
100
>>>s2.set_score(99)
>>>s2.score
99
动态绑定允许我们在程序运行的过程中动态给class加上功能,这在静态语言中很难实现。
Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性:
class Student(object):
def __init__(self,name,score):
self.name = name
self.score = score
__slots__ = ('name', 'score')
def print_score(self):
print(self.score)
s = Student('aaa aa',80)
s.print_score()
print(s.name)
s.age = 20 # AttributeError: 'Student'object has no attribute 'age'
注意:__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的:
ClassGraduate(Student):
Pass
>>>m= Graduate()
>>>m.age= 20 # right
除非在子类中也定义__slots__,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__。
第一,slots只能限制添加属性,不能限制通过添加方法来添加属性:
defset_city(self, city):
self.city=city
classStudent(object):
__slots__ = ('name', 'age', 'set_city')
pass
Student.set_city= MethodType(set_city, Student)
a= Student()
a.set_city(Beijing)
a.city
上段代码中,Student类限制两个属性name 和 age,但可以通过添加方法添加一个city属性(甚至可以添加很多属性,只要set_city方法里有包括)
第二,属性分实例属性和类属性,多个实例同时更改类属性,值是最后更改的一个
defset_age(self, age):
self.age=age
classStu(object):
pass
s=Stu()
a=Stu()
fromtypes import MethodType
Stu.set_age=MethodType(set_age,Stu)
a.set_age(15) \\通过set_age方法,设置的类属性age的值
s.set_age(11) \\也是设置类属性age的值,并把上个值覆盖掉
print(s.age,a.age) \\由于a和s自身没有age属性,所以打印的是类属性age的值
a.age= 10 \\给实例a添加一个属性age并赋值为10
s.age= 20 \\给实例b添加一个属性age并赋值为20
\\这两个分别是实例a和s自身的属性,仅仅是与类属性age同名,并没有任何关系·
print(s.age,a.age) \\打印的是a和s自身的age属性值,不是类age属性值
所以, 1,slots并不能严格限制属性的添加,可通过在方法里定义限制之外的属性来添加本不能添加的属性(当然,前提是方法没有被限制) 2,类属性是公共属性,所有实例都可以引用的,前提是实例自身没有同名的属性,因此类属性不能随意更改(别的实例可能在引用类属性的值),就是说不能随便用a.set_age()更改age的值(因为调用此方法更改的是类属性age的值,不是实例a自身的age属性值)
@property装饰器:负责把一个方法变成属性调用。
在绑定属性时,如果我们直接把属性暴露出去,虽然写起来很简单,但是,没办法检查参数,导致可以把成绩随便改:
s = Student()
s.score = 9999
为了限制score的范围,可以通过一个set_score()
方法来设置成绩,再通过一个get_score()
来获取成绩,这样,在set_score()
方法里,就可以检查参数:
class Student(object):
def get_score(self):
return self._score
def set_score(self, value):
if not isinstance(value,int):
raise ValueError('score mustbe an integer!')
if value < 0 or value> 100:
raise ValueError('score mustbetween 0 ~ 100!')
self._score = value
对于类的方法,装饰器decorator一样起作用。Python内置的@property装饰器就是负责把一个方法变成属性调用的:
class Student(object):
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value,int):
raise ValueError('scoremust be an integer!')
if value < 0 or value> 100:
raise ValueError('scoremust be 0~100!')
self._score = value
s = Student()
s.score = 999
print(s.score)
>>> s = Student()
>>> s.score = 60 # OK,实际转化为s.set_score(60)
>>> s.score # OK,实际转化为s.get_score()
60
>>> s.score = 9999
Traceback (most recent calllast):
...
ValueError: score must between 0~ 100!
@property的实现比较复杂,我们先考察如何使用。把一个getter方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作:
注意到这个神奇的@property,我们在对实例属性操作的时候,就知道该属性很可能不是直接暴露的,而是通过getter和setter方法来实现的。
还可以定义只读属性,只定义getter方法,不定义setter方法就是一个只读属性:
class Student(object):
@property
def birth(self): #getter
return self._birth
@birth.setter #setter
def birth(self, value):
self._birth = value
@property #getter
def age(self):
return 2015 - self._birth
上面的birth是可读写属性,而age就是一个只读属性,因为age可以根据birth和当前时间计算出来。
# coding: utf-8
class Screen(object):
@property
def width(self):
return self._width
@width.setter
def width(self, value):
self._width = value
@property
def height(self):
return self._height
@height.setter
def height(self, value):
self._height = value
@property
def resolution(self):
return self._width *self._height
# test:
s = Screen()
s.width = 1024
s.height = 768
print(s.resolution)
assert s.resolution == 786432, '1024 * 768 = %d ?' % s.resolution #当assert后面的表达式为false的时候才会返回预设的字符串。并且还会给你标注出错的行。
通过多重继承,一个子类就可以同时获得多个父类的所有功能。
classBat(Mammal, Flyable):
pass
MixIn
目的:给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。
classDog(Mammal, RunnableMixIn, CarnivorousMixIn):
pass
举个例子,Python自带了TCPServer和UDPServer这两类网络服务,而要同时服务多个用户就必须使用多进程或多线程模型,这两种模型由ForkingMixIn和ThreadingMixIn提供。通过组合,我们就可以创造出合适的服务来。
比如,编写一个多进程模式的TCP服务,定义如下:
classMyTCPServer(TCPServer, ForkingMixIn):
pass
编写一个多线程模式的UDP服务,定义如下:
classMyUDPServer(UDPServer, ThreadingMixIn):
pass
如果你打算搞一个更先进的协程模型,可以编写一个CoroutineMixIn:
classMyTCPServer(TCPServer, CoroutineMixIn):
pass
这样一来,我们不需要复杂而庞大的继承链,只要选择组合不同的类的功能,就可以快速构造出所需的子类。
由于Python允许使用多重继承,因此,MixIn就是一种常见的设计。
只允许单一继承的语言(如Java)不能使用MixIn的设计。
这种形如__xxx__的变量或者函数名就要注意,这些在Python中是有特殊用途的。
1、 __slots__
2、 __len__:能让class作用于len()
函数。
3、 __str__:
>>>class Student(object):
... def__init__(self, name):
... self.name = name
>>>print(Student('Michael'))
<__main__.Studentobject at 0x109afb190>
打印出一堆<__main__.Studentobject at 0x109afb190>,不好看。
怎么才能打印得好看呢?只需要定义好__str__()方法,返回一个好看的字符串就可以了:
>>> classStudent(object):
... def __init__(self, name):
... self.name = name
... def __str__(self):
... return 'Student object (name:%s)' % self.name
>>>print(Student('Michael'))
Student object (name: Michael)
但是细心的朋友会发现直接敲变量不用print,打印出来的实例还是不好看:
>>> s =Student('Michael')
>>> s
<__main__.Student object at0x109afb310>
这是因为直接显示变量调用的不是__str__(),而是__repr__(),两者的区别是__str__():返回用户看到的字符串,
__repr__():返回程序开发者看到的字符串,也就是说,__repr__()是为调试服务的。
解决办法是再定义一个__repr__()。但是通常__str__()和__repr__()代码都是一样的,所以,有个偷懒的写法:
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object(name=%s)' % self.name
__repr__ = __str__
4、 __iter_ _:
如果一个类想被用于for... in循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。
# eg斐波那契数列
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 # 返回下一个值
5、 __getitem_ _
Fib实例虽然能作用于for循环,看起来和list有点像,但是,把它当成list来使用还是不行,比如,取第5个元素:
>>> Fib()[5]
Traceback (most recent calllast):
File "
TypeError: 'Fib' object does notsupport 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
但是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]
但是没有对step参数作处理:
>>> f[:10:2]
[1, 1, 2, 3, 5, 8, 13, 21, 34,55, 89]
也没有对负数作处理,所以,要正确实现一个__getitem__()还是有很多工作要做的。
此外,如果把对象看成dict,__getitem__()的参数也可能是一个可以作key的object,例如str。
与之对应的是__setitem__()方法,把对象视作list或dict来对集合赋值。最后,还有一个__delitem__()方法,用于删除某个元素。
总之,通过上面的方法,我们自己定义的类表现得和Python自带的list、tuple、dict没什么区别,这完全归功于动态语言的“鸭子类型”,不需要强制继承某个接口。
6、 __getattr_ _
正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错。要避免这个错误,除了可以加上一个score属性外,Python还有另一个机制,那就是写一个__getattr__()方法,动态返回一个属性。
class Student(object):
def __init__(self):
self.name = 'Michael'
def __getattr__(self, attr):
if attr=='score':
return 99
>>> s = Student()
>>> s.name
'Michael'
>>> s.score
99
当调用不存在的属性时,比如score,Python解释器会试图调用__getattr__(self,'score')来尝试获得属性,这样,我们就有机会返回score的值。
返回函数也是完全可以的:
def__getattr__(self, attr):
if attr=='age':
return lambda: 25
只是调用方式要变为:
>>> s.age()
25
注意:只有在没有找到属性的情况下,才调用__getattr__。
注意到任意调用其他属性如s.abc都会返回None,这是因为我们定义的__getattr__默认返回就是None。要让class只响应特定的几个属性,我们就要按照约定,抛出AttributeError的错误:
class Student(object):
def __getattr__(self, attr):
if attr=='age':
return lambda: 25
raise AttributeError('\'Student\' objecthas no attribute \'%s\'' % attr)
这实际上可以把一个类的所有属性和方法调用全部动态化处理了,不需要任何特殊手段。作用就是,可以针对完全动态的情况作调用。
7、 Call
任何类,只需要定义一个__call__()方法,就可以直接在实例本身上进行调用
class Student(object):
def __init__(self, name):
self.name = name
def __call__(self):
print('My name is %s.' % self.name)
调用方式如下:
>>> s =Student('Michael')
>>> s() # self参数不要传入
My name is Michael.
__call__()还可以定义参数。对实例进行直接调用就好比对一个函数进行调用一样,所以你完全可以把对象看成函数,把函数看成对象,因为这两者之间本来就没啥根本的区别
通过callable()函数,我们就可以判断一个对象是否是“可调用”对象。
from enum importEnum
Month = Enum('Month', ('Jan','Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
得到Month类型的枚举类,可以直接使用Month.Jan来引用一个常量,或者枚举它的所有成员
value属性则是自动赋给成员的int常量,默认从1开始计数。
如果需要更精确地控制枚举类型,可以从Enum派生出自定义类:
fromenum 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 calllast):
...
ValueError: 7 is nota 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
可见,既可以用成员名称引用枚举常量,又可以直接根据value的值获得枚举常量。
1. 枚举的定义
示例代码:
from enum import Enum
class Color(Enum):
red = 1
orange = 2
yellow = 3
green = 4
blue = 5
indigo = 6
purple = 7
代码分析:
1.1 定义枚举时,成员名称不允许重复
from enum import Enum
class Color(Enum):
red = 1
red = 2
上面的代码,就无法执行。提示错误:TypeError:Attempted to reuse key: 'red'
1.2 默认情况下,不同的成员值允许相同。但是两个相同值的成员,第二个成员的名称被视作第一个成员的别名
from enum import Enum
class Color(Enum):
red = 1
red_alias = 1
成员Color.red和Color.red_alias具有相同的值,那么成员Color.red_alias的名称red_alias就被视作成员Color.red名称red的别名。
1.3 如果枚举中存在相同值的成员,在通过值获取枚举成员时,只能获取到第一个成员
from enum import Enum
class Color(Enum):
red = 1
red_alias = 1
print(Color(1))
输出结果为:Color.red
1.4如果要限制定义枚举时,不能定义相同值的成员。可以使用装饰器@unique【要导入unique模块】
from enum import Enum, unique
@unique
class Color(Enum):
red = 1
red_alias = 1
再执行就会提示错误:ValueError:duplicate values found in
2. 枚举取值
2.1 通过成员的名称来获取成员
Color['red']
2.2 通过成员值来获取成员
Color(2)
2.3 通过成员,来获取它的名称和值
red_member = Color.red
red_member.name
red_member.value
3. 迭代器
3.1枚举支持迭代器,可以遍历枚举成员
for color in Color:
print(color)
输出结果是,枚举的所有成员。Color.red、Color.orange、Color.yellow、Color.green、Color.blue、Color.indigo、Color.purple。
3.2 如果枚举有值重复的成员,循环遍历枚举时只获取值重复成员的第一个成员
from enum import Enum
class Color(Enum):
red = 1
orange = 2
yellow = 3
green = 4
blue = 5
indigo = 6
purple = 7
red_alias = 1
for color in Color:
print(color)
输出结果是:Color.red、Color.orange、Color.yellow、Color.green、Color.blue、Color.indigo、Color.purple。但是Color.red_alias并没有出现在输出结果中。
3.3 如果想把值重复的成员也遍历出来,要用枚举的一个特殊属性__members__
from enum import Enum
class Color(Enum):
red = 1
orange = 2
yellow = 3
green = 4
blue = 5
indigo = 6
purple = 7
red_alias = 1
for color inColor.__members__.items():
print(color)
输出结果:('red',
('indigo',
4. 枚举比较
4.1 枚举成员可进行同一性比较
Color.red is Color.red
输出结果是:True
Color.red is not Color.blue
输出结果是:True
4.2 枚举成员可进等值比较
Color.blue == Color.red
输出结果是:False
Color.blue != Color.red
输出结果是:True
4.3 枚举成员不能进行大小比较
Color.red < Color.blue
输出结果出错:TypeError:unorderable types: Color() < Color()
+
type()函数:1、可以查看一个类型或变量的类型;2、又可以创建出新的类型(如类class):
>>> def fn(self, name='world'): # 先定义函数
print('Hello, %s.' % name)
>>> Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class(名称,继承,函数绑定)
>>> h = Hello()
>>> h.hello()
Hello, world.
>>> print(type(Hello))
>>> print(type(h))
Hello = type('Hello', (object,),dict(hello=fn))
要创建一个class对象,type()函数依次传入3个参数:
通过type()函数创建的类和直接写class是完全一样的,因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。
metaclass
控制类的创建行为,可以使用metaclass元类
过程:先定义metaclass,就可以创建类,最后创建实例。
# metaclass是类的模板,所以必须从`type`类型派生:
class ListMetaclass(type):
def __new__(cls,name, bases, attrs):
attrs['add'] = lambdaself, value: self.append(value)
returntype.__new__(cls, name,bases, attrs)
有了ListMetaclass,我们在定义类的时候还要指示使用ListMetaclass来定制类,传入关键字参数metaclass:
class MyList(list, metaclass=ListMetaclass):
pass
当我们传入关键字参数metaclass时,魔术就生效了,它指示Python解释器在创建MyList时,要通过ListMetaclass.__new__()来创建,在此,我们可以修改类的定义,比如,加上新的方法,然后,返回修改后的定义。
returntype.__new__(cls, name, bases, attrs)
__new__()方法接收到的参数依次是:
测试一下MyList是否可以调用add()方法:
>>> L = MyList()
>>> L.add(1)
>> L
[1]
而普通的list没有add()方法:
>>> L2 = list()
>>> L2.add(1)
Traceback (most recent call last):
File "
AttributeError: 'list' object has noattribute 'add'