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>

虽然没有报错,但是只是打印出了获取的属性的信息,并没有获取其真实的值。

python可扩展性高 python的扩展性_python可扩展性高

同样的给“私有变量”赋值也是无效的:

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)

设置断点调试:

python可扩展性高 python的扩展性_扩展_02

前面几个扩展一切正常,断点前进即报错:

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的使用总结。