文章目录
面向对象高级编程
使用__slots__
定义了一个class并创建了一个class的实例后,可以给该实例绑定任何属性和方法。如:
# 定义class
class Student(object):
pass
# 创建实例
s=Student()
# 给实例绑定属性
s.name = 'a'
# 给实例绑定方法
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--->5
- 给一个实例绑定的方法,对另一个实例不起作用
- 为了给所有实例都绑定方法,可以给 class绑定方法:
def set_score(self,score):
self.score = score
Student.set_score=set_score
# using
s.set_score(100)
>>>s.score--->100
- 通常上面的set_score方法可以直接定义在class中,但动态绑定允许在程序运行的过程中动态给class加上功能,这在静态语言中很难实现
使用__slots__
- 限制实例的属性,如只允许对Student实例添加name和age属性,此时在定义class时,定义一个特殊的__slots__变量,来限制该class实例能添加的属性
a. 可以用tuple定义允许绑定的属性名称
class Student(object):
__slots__ = ('name','age')
b. slot__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的,除非在子类中也定义__slots,这样子类实例允许定义的属性就是自身的__slots__加上父类的__slots__
使用@property
绑定属性时,限制其范围,如在Student中的score属性设置范围可以通过set_score()方法设置成绩,再通过一个get_score()来获取成绩:
class Student(object):
def get_score(self):
return self._score
def set_score(self,value):
if not isinstance(value,int):
raise ValueError('score must be an integer1')
if value < 0 or value >100:
raise ValueError('score must between 0~100!')
self._score=value
- 既可以检查函数,又可以给函数动态加上功能—使用python内置的@property装饰器将一个方法变成属性调用,
对实例属性操作时候,该属性是通过getter和setter方法来实现的:
class Student(object):
@property # 加上这个可以把一个getter方法变属性
def score(self):
return self._sore
@score.setter # @property本身又创建了零一装饰器@score.setter
def score(self,value):
if not isinstance(value,int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0~100!')
self._score = value
定义只读属性:只定义getter方法,不定义setter方法就是一个只读属性
class Student(object):
@property
def birth(self):
return self._birth
@birth.setter ## birth时可读可写属性
def birth(self,value):
self._birth = value
@property
def age(self): ## age就是一个只读属性,因为age可以根据birth和当前时间计算出来
return 2015 - self._birth
练习
请利用@property给一个Screen对象加上width和height属性,以及一个只读属性resolution:
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
多重继承
继承是面向对象编程的一个重要方式,通过继承,子类就可以扩展父类的功能
通过多重继承,一个子类就可以同时获得多个父类的所有功能,如:
class Animal(object):
pass
# 大类:class Mammal(Animal):
pass
class Bird(Animal):
pass
# 各种动物:class Dog(Mammal):
pass
class Bat(Mammal):
pass
class Parrot(Bird):
pass
class Ostrich(Bird):
pass
## 给动物加上Runnable类和Flyable
class Runnable(object):
def run(self):
print('Running...')
class Flyable(object):
def fly(self):
print('Flying...')
对于需要Runnable功能的动物,就多继承一个Runnable,例如Dog:
class Dog(Mammal,Runnable)
pass
对于需要Flyable功能的动物,就多继承一个Flyable,例如Bat:
class Bat(Mamal,Flyable):
pass
Mixln
增加额外的功能,可以通过多重继承实现,如class Dog(Mammal,Runnable)
,让Dog除了继承Mammal还要继承Runnable,这种设计叫做Mixln。
Mixln的目的就是给一个类增加多个功能
如:
## 哺乳动物,飞行,肉食
class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
pass
- python自带的很多库也是用了Mixln
- 设计时使用Mixln,我们不需要复杂而庞大的继承链,只要选择组合不同的类的功能,就可以快速构造出所需子类
小结
- 由于Python允许使用多重继承,因此,MixIn就是一种常见的设计。只允许单* 一继承的语言(如Java)不能使用MixIn的设计。
定制类
str
- 作用:打印实例返回一个好看的字符串
class Student(object):
def __init__(self,name):
self.name=name
def __str__(self):
return 'Student object (name: %s)' %self.name
__repr__ = __str__
- __repr__()是程序开发者看到的字符串,是为调试服务的, __str__()返回用户看到的字符串,所以可以加一句
__repr__ = __str__
使得print打印出来的实例 与 定义实例后直接敲变量后打印出来的实例一样
iter
__iter__()方法可以使得一个类被用于for...in
循环,该方法返回一个迭代对象,费波纳基数列:
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)
- 该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。
- 注意:Fib实例虽然能作用域for循环,看起来和list有点像,但是它不能提取其中元素,如
Fib()[5]
就不可以
getitem
- 将类被用于for循环像list和tuple那样,并且还可以提取元素可以使用__getitem__方法:
class Fib(object):
def __getitem__(self,n):
a,b = 1,1
for x in range(n)
a,b = b , a+b
return a
# using
f=Fib()
>>>f[0]--->1
>>>f[1]--->1
>>>f[2]--->2
...
- 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:
strat = 0
a,b = 1,1
L=[]
for x in range(stop):
if x >= start:
L.append(a)
a,b = b,a+b
return L
## using slice
f = Fib()
>>>f[0:5]-----[1,1,2,3,5]
- 此Fib的切片没有对step参数做处理,所以不可f[:10:2],也没有对负数做处理
- 若把对象堪称dict,getitem()的参数也可能是一个可以做key 的object,如str。
- setitem()方法,把对象视作list或dict来对集合赋值
- delitem()方法,用于删除某个元素
getattr
- getattr()方法可以动态返回一个属性:
class Student(object):
def __init__(self):
self.name = 'a'
def __getattr__(self,attr):
if attr=='score':
return 99
# using
s=Student()
s.score--->99
当调用不存在的属性时,python解释器会试图调用__getattr(self,‘score’)来尝试获得属性,这样就有机会返沪score的值。
- getattr()方法可以返回函数:
class Student(object):
def __getattr__(self,attr):
if attr=='age':
return lambda:25
raise AttributeError('\'Student\' object has no attribute \'%s\' % attr)
# using method
s.age()
- 其中__getattr__默认返回的就是None,要让class只响应特定的几个属性,就要按照约定抛出
AttributeError
的错误 - 这把一个类的所有属性和方法调用全部动态化处理了,不需要任何特殊手段,作用:可以针对完全动态的情况作调用,
- 利用完全动态的__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__
## using
>>>Chain().status.user.timeline.list---->
/status/user/timeline/list
这样,无论API怎么变,SDK都可以根据URL实现完全动态的调用,而且,不随API的增加而改变!
这里其实没太看懂。。。
call
一个对象实例可以有自己的属性和方法,调用实例方法时,用instance.method()
来调用。
- 定义__call__()方法,就可以直接对实例进行调用:
class Student(object):
def __init__(self,name):
self.name = name
def __call__(self):
print('My name is %s.' % self.name)
# using method
s=Student('a')
s() # self参数不要传入
--->My name is a
- call()还可以定义参数
- 使用
Callable()
函数,我们就可以判断一个对象是否是“可调用”对象
小结
Python的class允许定义许多定制方法,可以让我们非常方便地生成特定的类。
更多定制方法见<a href=“https://docs.python.org/3/reference/datamodel.html#special-method-names”>https://docs.python.org/3/reference/datamodel.html#special-method-names</a>
使用枚举类
Enum
类可以为枚举类型定义一个类型,然后每个常量都是class的一个唯一实例。
from enum import Enum
# 获取了Month类型的枚举类
Month = Enum(
'Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
# using 可以直接使用Month.Jan来禁用一个常量,或者枚举它的所有成员
for name,member in Month.__members__.items():
print(name,'=>',member,',',member.value)
这个里边最后显示出来的member是第一层括号里的Month.第二层括号里的月份
- 如果需要更精确地控制枚举类型,可以从Enum派生出自定义类
from enum import Enum,nuique
@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)
print(Weekday.Tue)
--->Weekday.Mon
print(Weekday['Tue'])
print(Weekday.Tue.value)
print(Weekday(1))
exercise
把Student的gender属性改造为枚举类型,可以避免使用字符串:
from enum import Enum,unique
class Gender(Enum): #相当于从Enum中派生出自定义类
Male = 0
Female = 1
class Student(object):
def __init__(self,name,gender):
self.name = name
self.gender = gender
# testing
>>> bart = Student('Bart', Gender.Male)
>>> if bart.gender == Gender.Male:
print('测试通过!')
else:
print('测试失败!')
测试通过!
>>> bart.gender
<Gender.Male: 0>
>>> Gender.Male
<Gender.Male: 0>
小结
- Enum可以把一组相关常量定义在一个class中,且class不可变,而且成员可以直接比较
使用元类
type()
- 动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。
- type()函数可以查看一个类型或变量的类型,一个class的类型是type,其实例的类是该class
- 使用type()函数可以创建class的方法,而无需通过class xx(object)的方法
def fn(self,name='world'): #先定义函数,这个函数就是类里边的方法
print('Hello,%s.' % name)
# using
Hello = type('Hello',(object,),dict(hello=fn)) #创建Hello,类是前面被赋值的那个Hello而不是括号里的,括号里那个我也不知道干嘛的
- 要创建一个class对象,type()函数依次传入三个参数:
- class的名称
- 继承的父类集合,注意python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法
- class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上
用法:type(classname,object,sttr_dict)
classname:要创建的class的名称
object:父类的元祖,就是要继承哪些父类,可以为空
sttr_dict:类的属性(名称和值)
如:
# method 1
class A(object):
name = 'john'
# method 2
A = type('A',(),{'name':'john'}) # 动态创建了一个A类
print(A()) #创建了该类的实例
print(A.name) #输出类A类的属性john
# 创建B类并继承A类
B=type('B',(A,),{})
print(B.name) #输出结果是John,因为继承A类
type()函数允许动态创建类====动态语言本身支持运行期动态创建类
metaclass
- 除了使用type()动态创建类以外,要控制类的创建行为,还可以使用metaclass。
- metaclass,直译为元类:
当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。但如果想要创建出类,那就先定义metaclass,然后创建类,最后创建实例 - new()方法接收到的参数依次是:
- 当前准备创建的类的对象;
- 类的名字;
- 类继承的父类集合;
- 类的方法集合