Python的class中还有许多这样有特殊用途的函数,可以帮助我们定制类
_str_
class Student(object):
def __init__(self, name):
self.name = name
print(Student('Michael'))
<__main__.Student object at 0x10d9a16a0>
打印一串 <__main__.Student object at 0x10d9a16a0>
,怎么才能打印更好看呢?只需要重写__str__()
方法,就可以返回指定的内容了
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name: %s)' % self.name
print(Student('Michael'))
Student("cody")
Student object (name: Michael)
<__main__.Student at 0x10d9a1c88>
如果不用print 打印出的实例依然是 <__main__.Student at 0x10d9a1b38>
。
__str__()
返回用户看到的字符串--> 需要通过print()调用显示__repr__()
返回程序开发者看到的字符串 -->直接在终端显示
通常__str__()
方法和__repr__()
方法是一样的偷懒的写法:
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name: %s)' % self.name
__repr__ = __str__
_iter_
如果一个类想被for... in ...
循环,必须实现一个__iter__()
方法,该方法返回一个迭代对象,然后,Python的for循环不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇StopIteration错误时退出循环。
斐波那契额数列 用于for循环:
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 # 计算下一个值
# 等价上面的语句
# self.a = self.b
# self.b = self.a + self.b
if self.a > 100000: # 退出循环的条件
raise StopIteration()
return self.a
for n in Fib():
print(n)
# TypeError: 'Fib' object does not support indexing
Fib()[5]
# Fib虽然可以用作for,但是不能当做list来使用,比如取索引值
1
1
2
3
5
8
...
10946
17711
28657
46368
75025
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-12-ef3892c3fd8e> in <module>()
15 for n in Fib():
16 print(n)
---> 17 Fib()[5]
TypeError: 'Fib' object does not support indexing
_getitem_
如果要表现得跟list一样,可以通过索引取值,就需要实现__getitem__()
方法
class Fib(object):
def __getitem__(self, n = 1000):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
f = Fib()
# for i in f:
# print(i)
f[0]
f[5]
8
list的神奇切片方法
list(range(100))[5:10]
[5, 6, 7, 8, 9]
但是该方法对于Fib报错,原因是__getitem__()
传入的参数可能是一个int
,也可能是一个切片对象slice,所以要做判断
class Fib(object):
def __getitem__(self, n):
if isinstance(n, int): # n是索引
a, b = 1, 1
for i 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 i in range(stop):
if i >= start:
L.append(a)
a, b = b, a + b
return L
# Fib的切片
f = Fib()
f[0:5]
[1, 1, 2, 3, 5]
_getattr_
当调用类的方法或属性时,如果不存在,就会报错
class Student(object):
def __init__(self):
self.name = 'Michael'
s = Student()
print(s.name)
# AttributeError: 'Student' object has no attribute 'score'
print(s.score)
Michael
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-16-09213cfcfc0a> in <module>()
6 print(s.name)
7 # AttributeError: 'Student' object has no attribute 'score'
----> 8 print(s.score)
AttributeError: 'Student' object has no attribute 'score'
如果要避免调用一个不存在的属性时不保存,python提供 __getattr__()
方法,动态返回一个属性。
class Student(object):
def __init__(self):
self.name = 'Michael'
def __getattr__(self, attr):
if attr == 'score':
return 99
s = Student()
print(s.name)
print(s.score)
Michael
99
返回函数也可以实现
class Student(object):
def __getattr__(self, attr):
if attr == 'age':
return lambda: 25
s = Student()
# 改变调用方法
s.age()
25
需要注意的是,只有没有找到属性的情况下,才调用__getattr__
,已有的属性不会再__getattr__
里面找,这种情况下,如果调用s.abcdef __getattr__
就返回None
,所以为了其他的方法或属性 直接抛出异常AttributeError
class Student(object):
def __getattr__(self, attr):
if attr == "age":
return lambda: 25
raise AttributeError(f"Student object has no attribute {attr}")
s = Student()
print(s.age())
print(s.aaa)
print(s.abc())
25
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-28-14a64c261ef9> in <module>()
6 s = Student()
7 print(s.age())
----> 8 print(s.aaa)
9 print(s.abc())
<ipython-input-28-14a64c261ef9> in __getattr__(self, attr)
3 if attr == "age":
4 return lambda: 25
----> 5 raise AttributeError(f"Student object has no attribute {attr}")
6 s = Student()
7 print(s.age())
AttributeError: Student object has no attribute aaa
__getattr__()
的应用场景
现在很多网站都搞REST API,比如新浪微博、豆瓣啥的,调用API的URL类似:
- http://api.server/user/friends
- http://api.server/user/timeline/list
如果要写SDK,给每个URL对应的API都写一个方法,那得累死,而且,API一旦改动,SDK也要改。
利用完全动态的__getattr__
,我们可以写出一个链式调用:
class Chain(object):
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().status.user.timeline.list
/status/user/timeline/list
_call_
一个实例可以有自己的属性和方法,通过instance.method()
来调用. call实现直接在实例本身上调用,任何类只需要定义一个__call__()
方法就可以直接对实例进行调用
class Student(object):
def __init__(self, name):
self.name = name
def __call__(self):
print('My name is %s' % self.name)
s = Student('Michael')
s() # self参数不传
My name is Michael
函数和方法没有本质上的区别
怎么判断一个变量是对象还是函数呢? 判断一个对象是否能被调用,能被调用的对象其实就是一个Callable
对象,比如定义了带__call__()
的类实例
print(callable(Student('cody')))
print(callable(max))
print(callable([1, 2, 3]))
print(callable(None))
print(callable('str'))
True
True
False
False
False