Bootstrap

Python — 定制类

看到类似这种形如__xxx__的变量或者函数名就要注意,这些在Python中是有特殊用途的。
Python的class中有许多这样有特殊用途的函数,可以帮助我们定制类从而拥有丰富的功能。

str

定义__str__()方法可以帮助我们输出方便阅读的字符串。


>>class Student(object):
>>...    def __init__(self,name):
>>...        self.name = name
>>...
>>aptech = Student('lisi')
>>aptech
<__main__.Student object at 0x10c981d60>
>>aptech.name
'lisi'

正常情况下直接调用是返回一堆字符串的必须带上要查看的实例属性才可以,而__str__( )方法可以解决这个问题。


>>class Student(object):
>>...    def __iter__(self,name):
>>...        self.name = name
>>...    def __str__(self):
>>...        return 'Student object (name = %s)' % self.name
>>...    __repr__ = __str__
>>...
>>name = Student('lisi')
>>name
Student object (name = lisi)
>>print (name)
Student object (name = lisi)

现在就可以直接调用了,直接显示变量调用的不是__str__(),而是__repr__(),为了能够不用print直接调用,加上_repr__ = __str__就可以解决了。

iter

如果一个类想被用于for … in 循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。

class Fib(object):
    def __init__(self):
        self.a , self.b = 1, 2 # 初始化两个计数器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 n in Fib():
...    print(n)
...    
2
2
4
8
32
256
8192
getitem

如果在类中定义了这个__getitem__ 方法,那么它的实例对象(假定为p),可以像这样p[key] 取值,当实例对象做p[key] 运算时,会调用类中的方法__getitem__。

一般如果想使用索引访问元素时,就可以在类中定义这个方法(getitem(self, key) )。

class Abc(object):
    def __getitem__(self, n): 
        a,b = 1,1
        for x in range(n):
            a,b = b, b + 1
        return a

这样就可以像list那样按照下标取出元素。

>>Abc()[234]
234
>>Abc()[2342424]
2342424

那么如何实现切片功能呢

getitem()传入的参数可能是一个int,也可能是一个切片对象slice,写判断就可以了:

class Abc(object):

    def __getitem__(self, n):
        if isinstance(n,int):
            b = 1
            for x in range(n):
                a,b = b, 1 + b
            return a
        if isinstance(n,slice): #如果n是切片
            start = n.start   # 把切片开始值赋值给start
            stop  = n.stop    #把切片结束值赋值给stop
            if start is None:
                start = 0
            b = 1
            L = []
            for x in range(stop): #stop 值决定了循环的次数
                if x >= start:
                    L.append(a)
                a,b = b, 1 + b
            return L

>>f = Abc()
>>f [1234]
1234
>>f [1:4]
[1, 2, 3]
>>f [13:4]
[]
>>f [13:1523]
[13, 14, 15, 16,....1521, 1522]

但是没有对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没什么区别。

getattr

在正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错。
要避免这个错误,除了可以加上这个属性外,Python还有另一个机制,那就是写一个__getattr__()方法,动态返回一个属性。

class Student(object):

    def __init__(self):
        self.name = 'Michael'

    def __getattr__(self, attr):
        if attr=='score':
            return 99

当调用不存在的属性时,比如score,Python解释器会试图调用__getattr__(self, ‘score’)来尝试获得属性,这样,我们就有机会返回score的值:

>>> s = Student()
>>> s.name
'Michael'
>>> s.score
99

返回函数也是完全可以的:

class Student(object):

    def __getattr__(self, attr):
        if attr=='age':
            return lambda: 25

只是调用方式要变为:

>>> s.age()
25

注意,只有在没有找到属性的情况下,才调用__getattr__,已有的属性,比如name,不会在__getattr__中查找。

此外,注意到任意调用如s.abc都会返回None,这是因为我们定义的__getattr__默认返回就是None。要让class只响应特定的几个属性,我们就要按照约定,抛出AttributeError的错误:

class Student(object):

    def __getattr__(self, attr):
        if attr=='age':
            return lambda: 25
        raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)
call

任何类,只需要定义一个__call__()方法,就可以直接对实例进行调用。

class Student(object):
    def __init__(self, name):
        self.name = name

    def __call__(self):
        print('My name is %s.' % self.name)
;