目录
面向对象编程
__str__( )
__repr__( )
__iter__( )与__next__( )
__getitem__( )
__setitem__( )
__delitem__( )
__getattr__( )
__call__( )
面向对象编程
之前已经介绍过形似__xxx__的是特殊变量或函数,如__init__、__slots__等。这一节将介绍更多的特殊方法,来帮助我们定制自己定义到class。
__str__( )
__str__( )方法可以根据定义的字符串,在打印实例时返回所定义的字符串。
在没有使用__str__( )方法时:
>>>class Stu(object): #定义一个class... def __init__(self, name): ... self.name = name ...>>>print(Stu('Ming')) #打印一个实例<__main__.Stu object at 0x00000000020E8550>
使用了__str__( )方法后:
>>>class Stu(object):... def __init__(self, name): ... self.name = name ... def __str__(self): ... return 'Stu object (name: %s)' % self.name ...>>>print(Stu('MIng')) Stu object (name: MIng)
这样可以清楚地看到实例内部的重要数据,需要注意的是,__str__( )方法里的return后面跟的必须是字符串。
__repr__( )
__repr__( )的用途与__str__( )很像,两者的区别是__str__( )返回的是用户看到的字符串,而__repr__( )返回的是程序开发者看到的字符串。
也就是说,如按上面的定义,在交互模式中直接调用实例,还是不会返回想要的字符串的。
>>>Stu('Ming') <__main__.Stu object at 0x00000000020E8550>
我们把上面的__str__( )改为__repr__( ),再试试。
>>>class Stu(object):... def __init__(self, name): ... self.name = name ... def __repr__(self): ... return 'Stu object (name: %s)' % self.name ...>>>Stu('MIng') Stu object (name: MIng)
成功返回。此时如果使用print,还是能够返回想要的字符串的。
>>>print(Stu('Ming')) Stu object (name: MIng)
如果同时定义__str__( )和__repr__( )呢?
class Stu(object): def __init__(self, name): self.name = name def __repr__(self): return 'Stu object (name: %s)' % self.name def __str__(self): return 'str'
>>>Stu('MIng') Stu object (name: MIng) >>>print(Stu('Ming')) str
可见在这种情况下,print是优先考虑__str__( )的。
总而言之,__str__( )面向用户,而__repr__( )面向程序员。所以如果想要在所有环境下统一显示的话,直接用__repr__即可。若要区分,则定义两个。一般情况下显示的内容时相同的,可用__repr__( ) = __str__( )来简化定义。
__iter__( )与__next__( )
for...in循环是作用于可迭代对象,那想要让自己定义的类可用for...in循环,就必须把自己变成可迭代对象。
__iter__( )方法的用途就是返回一个可迭代对象Iterable。
__next__( )方法的用途是定义一个循环的逻辑而返回下一个循环值(用了之后就是一个迭代器对象了Iterator)。
那么结合两种方法,for..in循环就会不断调用该可迭代对象作用于__next__( )方法得出下一个循环值。直到遇到StopIteration错误时退出循环。
举个例子,如生成斐波那契数列,写个Fib类。
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 > 1000: # 退出循环的条件 raise StopIteration() return self.a # 返回下一个值
用for..in作用于Fib的实例
>>>for n in Fib():... print(n)1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
实际上,不用__iter__( )方法也可以得到斐波那契数列,只不过需要不断调用next( )来拿取下一个值,而用了__iter__( )方法之后变成可迭代对象,就可以用for...in循环来轻松拿取了。
__getitem__( )
__getitem__( )方法的用途是让实例像list那样按照下标取元素。__getitem__( )方法需要传入两个参数,一个是实例本身,另一个是下标。当检测到有[ ]时就会运行__getitem__( )。
>>>class A(object):... def __getitem__(self, key): # 定义一个__getitem__方法,key参数是下标... print('Call function __getitem__') ... print(key + 1)>>>a = A()>>>a[1] # 在这里会把a传给self,而把1传给keyCall function __getitem__ # 当检测到有[]时,就会执行__getitem__方法2 # 返回的是key+1,即1+1=2
利用__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[1]1>>> f[2]2>>> f[3]3>>> f[10]89
若还想要想list那样具有切片功能,就需要判断传入的参数是整数还是切片,然后进一步做输出处理。像这样(以下代码转自廖雪峰的官方网站)
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: 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[0:5] [1, 1, 2, 3, 5]
当然还有地方需要改进,如对step参数的处理,对负数的处理等等,这些都可以在后面继续完善,这里值简单介绍__getitem__( )的用法。
__setitem__( )
__setitem__( )同样可以让实例像list那样按照下标取元素,同时还可以给key赋值value,所以需要传入三个参数,分别是实例本身,键key,值value。当检测到有形如[ ] = 的赋值语句时就会执行__setitem__( )方法。
>>>class A(object):... def __setitem__(self, key, value): # 定义一个__setitem__方法,key参数是键,value是值... print('Call function __setitem__') ... print(value)>>>a = A()>>>a[1] = 10 # 在这里会把a传给self,而把1传给key,把10传给valueCall function __getitem__ # 当检测到有[] = 时,就会执行__setitem__方法10 # 打印value
__delitem__( )
__delitem__( )用来删除指定key的元素,需要传入self和key两个参数。当检测到del时就会执行__delitem__( )方法。
结合__getitem__( )、__setitem__( )和__delitem__( ),方法,给个简单的例子再帮助理解。
class A(object): def __init__(self, start = 0): print('Call function __init__') self.start = start self.dict = {} # 定义一个dict def __getitem__(self, key): # 定义获取值的方法 print('Call function __getitem__') try: return self.dict[key] # 如果有对key赋值,则返回key对应的value except KeyError: return key # 如果没有对key赋值,则返回key本身 def __setitem__(self, key, value): # 定义赋值方法 print('Call function __setitem__') self.dict[key] = value def __delitem__(self, key): # 定义删除元素的方法 print('Call function __delitem__') del self.dict[key]
a = A() # 创建A的一个实例aCall function __init__ a[1] = 10 # 执行赋值方法__setitem__ Call function __setitem__ a[2] = 20 # 执行赋值方法__setitem__Call function __setitem__ a[1] # 执行取值方法__getitem__,[1]有对应的值10Call function __getitem__ 10 a.dict #dict属性中已有的值{1: 10, 2: 20} del a[1] #删除dict属性中,key为[1]的值Call function __delitem__ a.dict {2: 20}
__getattr__( )
当我们尝试调用一个没有被定义的属性或方法时,会出现错误。而__getattr__( )的用途是当调用不存在的属性时,就会执行__getattr__( )尝试返回另外的值。
未定义__getattr__( )时
class Stu(object): def __init__(self, name): self.name = name a = Stu('Ming')
>>>a.name Ming>>>a.ageAttributeError: 'Stu' object has no attribute 'age'
定义了__getattr__( )之后
class Stu(object): def __init__(self, name): self.name = name def __getattr__(self, attr): return 1
>>>a.name Ming>>>a.age # 这里的age将会传给__getattr__()的第二个参数attr1
也能返回一个函数
class Stu(object): def __init__(self, name): self.name = name def __getattr__(self, attr): return lambda: 10
当然,我们能根据__getattr__( )的参数,去完善定义,像这样:
class Stu(object): def __getattr__(self, attr): if attr=='age': return lambda: 10 raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)
__call__( )
__call__( )方法可以使实例变为可调用对象,当尝试直接调用实例时,会执行__call__( )方法。
class Stu(object): def __init__(self, name): self.name = name def __call__(self): print('I am %s' % self.name)
>>>a = Stu('Ming')>>>a() # 注意需要加()I am Ming
__call__( )还能定义参数,这样的话就可以实例对象看成一个函数了。这就模糊了对象和函数的区别,其实很多时候我们需要区分的是对象是否可调用,可以用callable( )函数来判断。
>>> callable(Stu())True>>> callable(max)True>>> callable([1, 2])False>>> callable(None)False
作者:三贝_
链接:https://www.jianshu.com/p/2e7bcb8e9493