Python作为动态语言,灵活性之一就是支持类的动态扩展,可以动态给类对象或者实例添加属性或者方法。这一特性给Python语言带来了很大的灵活性。
0x01 属性和方法扩展
举个例子:
class Person(object):
def __init__(self,name):
self.__name=name
@property
def name(self):
return self.__name
上面定义了一个Person类,含有一个name的属性,但是我们可能需要给他添加Age、Sex等新的属性或者函数怎么办呢?可以这么来做:
a=Person('Andy')
#添加
a.age=18
print dir(a)
运行结果:
['_Person__name', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'name']
Process finished with exit code 0
动态添加了一个属性age。这是在实例中添加的,对其他实例是无效的,比如:
b=Person('kk')
print b.age
运行结果:
AttributeError: 'Person' object has no attribute 'age'
提示b中没有这样的属性。要想对类所有实例均有效,可以直接对类本身添加动态属性:
Person.age=18
b=Person('kk')
print b.age
同样的,也可以动态添加方法(函数)。需要导入模块types. MethodType,如下:
from types import MethodType
def eat(self):
print 'eat'
b=Person('kk')
b.eat=MethodType(eat,b)
b.eat()
类似的,如果要求对所有类的实例有效,就需要在类本身的对象上进行动态扩展:
Person.eat=MethodType(eat,Person)
b=Person('kk')
b.eat()
需要注意一点的是,动态扩展不要涉及“私有变量”,比如:
def getName(self):
return self.__name
Person.getName=MethodType(getName,Person)
b=Person('kk')
print b.getName()
运行结果:
AttributeError: type object 'Person' has no attribute '__name'
提示找不到属性__name,那么读取公开的属性name呢:
def getName(self):
return self.name
Person.getName=MethodType(getName,Person)
b=Person('kk')
name=b.getName()
print name
运行结果:
<property object at 0x0128D540>
虽然没有报错,但是只是打印出了获取的属性的信息,并没有获取其真实的值。
同样的给“私有变量”赋值也是无效的:
def setName(self,name):
self.__name=name
Person.setName=MethodType(setName,Person)
b=Person('kk')
b.setName('kkb7')
print b.name
打印出来的name仍然是kk。所以采用动态扩展方式时,不要对类中原有的属性和方法进行操作(当然静态变量除外)。
0x02 限制扩展
上面讲了类的扩展,如果我们需要显示随意扩展怎么办呢?比如,只能给Person扩展age和height。可以通过顶一个特殊的slots来实现。
class Person(object):
def __init__(self,name):
self.__name=name
__slots__=('__name','name','age','height','getAge')
@property
def name(self):
return self.__name
如果试图添加其他的属性和方法,将会报错:
def getAge(self):
return 18
def getHeight(self):
return 182
b=Person('kk')
#正常
b.age=18
b.height=182
b.getAge=MethodType(getAge,b)
#异常
b.width=130
b.getHeight=MethodType(getHeight,b)
设置断点调试:
前面几个扩展一切正常,断点前进即报错:
AttributeError: 'Person' object has no attribute 'width'
0x03 类扩展
上面讲了slots可以用来限制类的属性和方法定义。类似*这样的变量在Python中非常特殊。还有一些变量可以用来扩展类的功能。下面就讲几个常见的变量
(1) len
Python中很多对象都可以用len()来计算对象的“长度”,其实是在类里面实现了函数len。比如:
class Person(object):
def __init__(self,name):
self.__name=name
__slots__=('__name','name','age','height','getAge')
@property
def name(self):
return self.__name
#定义__len__
def __len__(self):
return len(self.__name)
b=Person('kk')
print 'b\'length is:',len(b)
c=Person('kikay')
print 'c\'length is:',len(c)
需要注意的是len函数的返回值必须是整数,否则将会报错:
class Person(object):
def __init__(self,name):
self.__name=name
__slots__=('__name','name','age','height','getAge')
@property
def name(self):
return self.__name
#定义__len__
def __len__(self):
return 'error'
b=Person('kk')
print 'b\'length is:',len(b)
运行结果:
TypeError: an integer is required
(2)str
在类中实现str函数,就可以执行str()函数:
class Person(object):
def __init__(self,name):
self.__name=name
__slots__=('__name','name','age','height','getAge')
@property
def name(self):
return self.__name
#定义__len__
def __len__(self):
return len(self.__name)
#定义__str__
def __str__(self):
return 'Person object (name:%s)'%self.__name
b=Person('kk')
print str(b)
运行结果:
Person object (name:kk)
(3)iter
如果类的对象希望具有迭代的效果(for…in…),那么就需要实现iter,该方法返回一个迭代对象,结合next函数,可以循环取值。用著名的Fibonacci数列的实现为例:
class Fibonacci(object):
def __init__(self):
self.__v1,self.__v2=0,1
def __iter__(self):
return self
#限制运行的值上限
__MaxVaue__=1000
def next(self):
self.__v1,self.__v2=self.__v2,self.__v2+self.__v1
if self.__v1>=self.__MaxVaue__:
#停止迭代
raise StopIteration()
return self.__v1
f=Fibonacci()
#迭代
for i in f:
print i
(4)getitem
如果先实现索引访问,需要实现getitem:
class Fibonacci(object):
def __init__(self):
self.__v1,self.__v2=0,1
def __iter__(self):
return self
#限制运行的值上限
__MaxVaue__=1000
def next(self):
self.__v1,self.__v2=self.__v2,self.__v2+self.__v1
if self.__v1>=self.__MaxVaue__:
#停止迭代
raise StopIteration()
return self.__v1
def __getitem__(self, item):
if isinstance(item,int):
temp1,temp2=1,1
for i in range(item):
temp1,temp2=temp2,temp1+temp2
return temp1
else:
raise TypeError('item must be integer')
f=Fibonacci()
print f[3]
上面是利用索引来访问,如果要支持切片功能,可以这样来实现:
class Fibonacci(object):
def __init__(self):
self.__v1,self.__v2=0,1
def __iter__(self):
return self
#限制运行的值上限
__MaxVaue__=1000
def next(self):
self.__v1,self.__v2=self.__v2,self.__v2+self.__v1
if self.__v1>=self.__MaxVaue__:
#停止迭代
raise StopIteration()
return self.__v1
def __getitem__(self, item):
if isinstance(item,int):
temp1,temp2=1,1
for i in range(item):
temp1,temp2=temp2,temp1+temp2
return temp1
elif isinstance(item,slice):
L=[]
temp1,temp2=1,1
start=item.start
stop=item.stop
if start is None:
start=0
for i in range(start,stop):
if i>=start:
L.append(temp1)
temp1,temp2=temp2,temp1+temp2
return L
else:
raise TypeError('item must be integer')
f=Fibonacci()
print f[3]
print f[2:8]
当然上面的切片实现是单步的,如果step不为1,就得修正代码实现逻辑了。这里就不再赘述这一过程,只做下getitem的使用总结。