Python与Java不同,Python可以多重继承,在设计类时,可以考虑MixIn
设计,一个类继承多个类,使其具有多个功能。
介绍了以下几种类的方法:
__str__()
, __iter__()
, __next__()
, __getitem__()
, __getattr__()
, __call__()
__str__
类似java中的toString()
方法,print
一个类调用的就是类的__str__
方法。
class Student:
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name: %s)' % self.name
print(Student('Michael'))
Student object (name: Michael)
直接显示变量调用的不是__str__()
,而是__repr__()
,__str__
返回用户看到的字符串,而__repr__()
返回程序开发者看到的字符串,用于调式服务。通常两个方法代码一样
class Student:
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name: %s)' % self.name
__repr__ = __str__
__iter__
类似于Java中的Iterator
。一个类想被用于for循环,就必须实现一个__iter__()
方法。该方法返回一个迭代对象,循环不断调用__next__
方法获取下一个值,直到遇到StopIteration
错误时退出循环。
class Fib:
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 > 20:
raise StopIteration()
return self.a
for n in Fib():
print(n)
1
1
2
3
5
8
13
__getitem__
可以使该类像list
一样取第几个元素
def getitem(self, n): # 定义getitem函数,也可以在函数定义内定义
a, b = 1, 1
for x in range(n):
a, b = b, a+b
return n
Fib.__getitem__ = getitem # 添加__getitem__ 方法。
f = Fib()
print(f[0])
print(f[5])
0
5
如果想要实现切片的方法,需要对__getitem__()
方法进一步改进
类似,如果把对象看成是dict
,__getitem__()
的参数是一个可以作为key
的object
,例如str
。与之对应有__setitem__()
,__delitem()__
方法。
这些方法可以让自己定义的类和Python自带的list
, tuple
, dict
没什么区别,这完全归功于动态语言的“鸭子类型”,不需要强制继承某个接口。
__getattr__()
方法,动态返回一个属性
class Student2:
def __init__(self):
self.name = 'Michael'
def __getattr__(self, attr):
if attr == 'score':
return 99
当调用的属性不存在时,Python解释器会试图调用__getattr__(self, 'score')
来尝试获得属性,这样就有机会返回score
的值
s = Student2()
print(s.name)
print(s.score)
print(s.zipcode)
Michael
99
None
__getattr__
也可以返回函数。
只有在没有找到属性的情况下,才会调用__getattr__
,如果是已有属性,不会在__getattr__
中查找。
没有定义的调用s.zipcode
会返回None
,因为__getattr__
默认返回就是None
。
为了避免这种情况,让类只响应几个特定属性,需要抛出AttributeError
错误。
class Student3:
def __getattr__(self, attr):
if attr == 'age':
return lambda: 25
raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)
s3 = Student3()
s3.abc
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
in
6
7 s3 = Student3()
----> 8 s3.abc
in __getattr__(self, attr)
3 if attr == 'age':
4 return lambda: 25
----> 5 raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)
6
7 s3 = Student3()
AttributeError: 'Student' object has no attribute 'abc'
把一个类的所有方法和属性动态处理了。可针对完全动态地情况作调用。
例如,利用完全动态地__getattr__
,写出一个链式调用
class Chain:
def __init__(self, path = ''):
self._path = path
def __getattr__(self, path):
return Chain('%s/%s' % (self._path, path))
def __str__(self):
return self._path
__repr__ = __str__
chain = Chain() # create a new instance
print(chain.status) # this will call __getattr__('/status)
print(chain.user) # this will call __getattr__('/user')
print(chain.status.user.timeline)
/status
/user
/status/user/timeline
解析:
__getattr__('/status')
will call Chain('/status)
, now, _path = '/status'
then __getattr__('user')
will call Chain('/status/user')
, now, _path = '/status/user'
continue this chain, _path
variable can be dynamically setted.
Chain().user.fan.documents.markdown_file # since __repr__ = __str__, we can directly print it without print() method.
/user/fan/documents/markdown_file
__call__
可以直接对实例进行调用
class Student4:
def __init__(self, name):
self.name = name
def __call__(self):
print('My name is %s' % self.name)
s4 = Student4('Fan')
s4()
My name is Fan
__call__
还可以定义参数,对实例进行直接调用就和对一个函数进行调用一样,所以我们完全可以把对象看成函数,把函数看成是对象,因为两者根本就没啥区别。
如果你把对象看成函数,那么函数本身其实也可以在运行期动态创建出来,因为类的实例都是运行期创建出来的,这么一来,我们就模糊了对象和函数的界限。
那么,怎么判断一个变量是对象还是函数呢?其实,更多的时候,我们需要判断一个对象是否能被调用,能被调用的对象就是一个Callable
对象,比如函数是Callable
的,它可以被调用,同样我们上面定义的带有__call__()的类实例也是Callable
的。
callable(max) # True, 函数是callable的
callable(Student4('Fan')) # True Student4的实例是callable的
callable('str') # False, 一个string不能被调用,不是callable
False