一.实例绑定方法和给类绑定方法
由于python动态语言的特性,在创建类之后,可以给实例或类再绑定方法。给实例绑定方法,只对该实例有用,其它实例则没有该方法;给对象绑定方法,则所有实例都会存在该方法
使用 MethodType 给实例绑定方法
class Student(object):
pass
s1 = Student()
# 定义一个实例方法
def set_age(self, age):
self.age = age
# 从 'types' 模块中 引入MethodType方法
from types import MethodType
s1.set_age = MethodType(set_age, s1) # 给实例绑定方法
s.set_age(25)
s.age
25
# 其它实例需要再绑定一次才会有set_age方法
>>> s2 = Student()
>>> s2.set_age(25)
AttributeError: 'Student' object has no attribute 'set_age'
给所有实例都绑定方法,则直接给类绑定方法:
>>> Student.set_age = set_age
>>> s1.set_age(20)
20
>>> s2.set_age(20)
20
__slots__
限制实例属性
为了限制实例属性,我们可以使用 __slots__
变量:
比如只允许实例添加 'name', 'age' 属性
class Student(object):
__slots__ = ('name', 'age') # 使用tuple定义允许绑定的属性名称
>>> s = Student()
>>> s.name = 'james' # OK
>>> s.age = 27 # OK
>>> s.gender = 'M' # AttributeError: 'Student' object has no attribute 'gender'
值得注意的是,__slots__
只对当前类实例起限制作用,对其子类不起作用
二.@property
这个就是装饰器(decorator),主要用于对类中属性 getter, setter 访问器属性进行设置
如果我们要对一个属性进行一定控制,可以设置其get,set方法,比如对 'age' 属性进行一定的限制:
class Student(object):
def get_age(self):
return self._age
def set_age(self, value):
if not isinstance(value, int):
raise ValueError('age must be an integer')
if value < 0 or value > 120:
raise ValueError('age must between 0 - 120')
self._age = value
# 使用
>>> s = Student()
>>> s.set_age(60) # OK
>>> s.get_age()
60
>>> s.set_age(9999)
age must between 0 - 120
但是通过 set_age(), get_age() 方法实在是不够简洁,于是python中 @property 属性就可以很好的解决这个问题,我们可以直接的通过 's.age = 100', 's.age' 这样的方式来进行设置和读取
上面的例子可以写为:
class Student(object):
@property
def age(self): # 定义getter
return self._age
@age.setter # 定义setter, 如果不定义,则属性为只读
def age(self, age):
if not isinstance(value, int):
raise ValueError('age must be an integer')
if value < 0 or value > 120:
raise ValueError('age must between 0 - 120')
self._age = value
>>> s = Student()
>>> s.age = 190 # ERROR
>>> s.age = 62 #OK
>>> s.age
62
上面的例子可以看出,@property
只是python的一种简便写法,另外设置只读,只需要将setter省略即可:
class Student(object):
@property
def birth(self):
return self._birth
@birth.setter
def birth(self, value):
self._birth = value
# age为只读属性
@property
def age(self):
return 2017 - self._birth
三.多重继承 和 MixIn
这个特性还是很方便的,在C#中,只支持单一继承,想要继承某些功能,需要通过接口的方式,而python直接可以通过多重继承来完成。
当然python在设计继承关系时,主线都是单一继承下来的,额外的功能继承使用类似接口的方式,只不过python中称之为 MixIn
比如狗属于动物(Aniaml),狗还能跑(Runable),则可以让狗继承这2个大类,主线为Animal,MixIn是Runable(命名规范,一般命名为RunableMixIn)
class Animal(object):
pass
class RunnableMixIn(object):
def run(self):
print('I can run')
class Dog(Animal, RunnableMixIn):
pass
MixIn的目的就是给一个类添加多个功能, 这样,在设计类的时候,我们应优先考虑通过多重继承来组合多个MixIn功能,而不是层次结构复杂的继承关系
python 内置的MixIn
Python自带的 TCPServer
和 UDPServer
两类服务网络,而要同时服务多个用户就必须使用多线程或多进程模型,这2种模型由 ThreadingMixIn
和 ForkingMixIn
提供,通过组合可以创建适合的服务
例如,编写 多进程模式的TCP服务:
class MyTCPServer(TCPServer, ForkingMixIn):
pass
编写 多线程模式的UDP服务:
class MyUDPServer(UDPServer, ThreadingMixIn):
pass
编写 协程模型:
class MyTCPServer(TCPServer, CoroutineMixIn):
pass
四.python内部属性方法
python以 __xxx__
的形式表示特殊变量,很多内部属性方法名称都是以这种形式命名的,下面介绍几种python内部的函数,对类的行为进行拦截修改:
-
__str__
&&__repr__
-
__iter__
&&__next__
__getitem__
__getattr__
__call__
__str__()
&&__repr__()
如果打印一个实例的类型,会得到下面结果:
class Student(object):
def __init__(self, name):
self.name = name
>>> print(Student('James'))
<__main__.Student object at 0x109afb190>
可以通过 __str__()
来自定义输出字符串:
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name, %s)' % self.name
>>> print(Student('James'))
Student object (name: Michael)
但是如果不调用print函数,打印的依旧不好看:
>>> s = Student('James')
>>> s
<__main__.Student object at 0x109afb31>
这是因为直接显示变量调用的是repr(),这个函数是程序开发者看到的字符串, 所以还需要将这个函数改写,可以直接将上面改写的 __str__()
函数赋值给这个函数即可
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name, %s)' % self.name
__repr__ = __str__
>>> s = Student('James')
>>> s
Student object (name: Michael)
__iter__()
&&__next__()
如果想使一个类能够使用 for...in
循环,可以在类中添加 __iter__()
方法,这个方法返回一个可迭代的对象,此处为实例本身,然后python的for循环会不停的调用该迭代对象的 __next__()
方法, 直到遇到 StopIteration
异常退出循环
class Fib(object):
def __init__(self):
self.a, self.b = 0, 1
def __iter__(self):
return self # 实例本身就是迭代对象,返回本身
def __next__(self):
self.a, self.b = self.b, self.a + self.b
if self.a > 1000: # 退出循环的条件
raise StopIteration()
return self.a # 返回下一个值
# 调用
>>> for n in Fib():
... print(n)
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
__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[2]
1
>>> f[2]
2
>>> f[3]
3
为了list中实现slice功能,可以对 __getitem__(self, n)
中的 n 进行判断,有可能是 int 类型,也可能是 slice 类型
class Fib(object):
def __getitem__(self, n):
if isinstance(n, int):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
if isinstance(n, slice): # n是切片
start = n.start # slice的属性 start, 不能换成别的名字
stop = n.stop # slice的属性 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
>>> f = Fib()
>>> f[:5]
[1, 1, 2, 3, 5]
这只是模拟了部分slice的功能,比如step,负数还没有进行处理
其它的一些内部函数有 __setitem__()
__delitem__()
等,这些函数可以对对象属性进行拦截,模拟list, tuple, dict等数据类型的功能
__getattr__()
当python编译器在调用不存在的属性时,会尝试调用 __getattr__(self, attr)
这个函数来获取该属性,该方法默认返回None
class Student(object):
def __init__(self):
self.name = 'James'
def __getattr__(self, attr):
if attr == 'age':
return lambda: 25 # 返回一个函数
raise AttributeError('对象没有该属性')
>>> s = Student()
>>> s.name
'James'
>>> f = s.age # 返回一个函数
>>> f()
25
# 如果访问未定义的属性, 会直接报错
>>> print(s.gender)
对象没有该属性
可以根据这个特性来写出一个链式调用的类:
class Chain(object):
def __init__(self, path=''):
self._path = path
# 默认path为attr,范围Chain调用自身构造器
def __getattr__(self, path):
return Chain('%s/%s' % (self._path, path))
def __str__(self):
return self._path
__repr__ = __str__
>>> s = Chain().status.user
'/status/user'
这样,无论API怎么变,SDK都可以根据URL实现完全动态的调用,而且,不随API的增加而改变
__call__()
实例自己调用,一般的实例方法为 instance.method() 这种调用形式,使用 __call__()
, 我们可以实现 instance() 这种形式的调用
class Student(object):
def __init__(self, name):
self.name = name
def __call__(self):
print('my name is %s' % self.name)
>>> s = Student('James')
>>> s()
my name is James
五.枚举类
在js中我们想使用枚举,一般通过对象的形式进行模拟,比如:
var Weekday = {
'Sun': 0,
'Mon': 1
// ...
}
在python中可以通过 Enum() 构造器, 或者 类继承Enum 的方式来实现枚举类
from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
>>> Month.Jan
Month.Jan
# 通过 value 属性获取对应int值, 从1开始
>>> Month.Jan.value
1
# 通过 '__members__' 来访问枚举类中的成员
for name, member in Month.__members__.items():
print(name, '=>', member, ',', member.value)
使用类继承的方式:
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
>>> 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
其中 @unique
装饰器可以帮助我们检查保证没有重复值。
六.元类编程
python中的 type()
函数有2个功能
- 判断对象类型
- 创建类
判断对象类型
>>> type(123) is int
True
创建类:使用class创建类,实质上是通过type()来创建的, 语法:
type(ClassName:string, ClassesToExtend:tuple, attrs:dict)
比如创建一个 'Hello' 类:
def fn(self, name='world'):
print('Hello, %s' % name)
Hello = type('Hello', (object,), dict(hell0=fn)) # 注意tuple只有1个元素时,别忘记了逗号
# 将函数fn绑定到方法名hello上
>>> h = Hello()
>>> h.hello()
Hello World
metaclass
type() 除了动态的创建类之外,还可以作为 类的类 用作继承
创建类的实质过程是: 先定义metaclass(元类), 然后创建类, 再创建实例, 也就是说metaclass 可以创建类
按照命名习惯metaclass通常以 'Metaclass' 结尾,一般在调用 inti() 之前,先调用 __new__()
方法。
__new__()
的签名为:
- 当前准备创建的类的对象
- 类的名字
- 类继承的父类集合
- 类的方法集合
比如下面一个元类:
# metaclass为类的模版, 所以必须从 'type' 类型派生
class ListMetaclass(type):
def __new__(cls, name, bases, attrs):
attrs['add'] = lambda self, value: self.append(value)
return type.__new__(cls, name, bases, attrs)
派生类使用metaclass 关键词参数:
class MyList(list, metaclass=ListMetaclass):
pass
>>> ml = MyList()
>>> ml.add(1)
>>> ml
[1]
元类编程主要用于需要动态生成类的情况下,元类编程较为复杂,待进一步学习
总结
本章主要介绍面向对象高级编程,主要是python的一些内部属性和方法的使用,这和js中的proxy,reflection很想,元类编程则是python作为动态语言的另一大特点,可以动态的创建类和方法
-
__slots__
: 可以用于限制实例动态添加属性的属性名 -
@property
: 对于属性的getter, setter 进行简便的表示 - 多重继承和
MixIn
, 对类的功能进行扩展 -
__str__()
|__repr__()
: 用于自定义输出字符串 -
__getitem__(self, n)
: 使类拥有索引,slice, (list数据类型的一些特性) -
__getattr__()
: 当实例上不存在该属性时,实例会从这个方法中搜索该属性 -
__call__()
: 使实例能自己调用,这种形式 instance() - 枚举类的创建, 主要引入 from enum import Enum, unique 等模块, 使用
@unique
装饰器来确保没有重复值, 枚举类中的内部属性__members__
- 元类编程,利用
type
的动态创建类的能力, 内部属性__new__()
总的来说,本章内容比较难,需要日后更多的实际操作了解,待了解
2017年3月9日 19:43:20