# 这是学习廖雪峰老师python教程的学习笔记
1、概览
看到类似__slots__这种形如__xxx__的变量或者函数名就要注意,这些在Python中是有特殊用途的。
__slots__我们已经知道怎么用了,__len__()方法我们也知道是为了能让class作用于len()函数。
除此之外,Python的class中还有许多这样有特殊用途的函数,可以帮助我们定制类。
1.1、__str__() 和 __repr__()
1、__str__()
修改print(instance) 显示的值
# 正常打印instance
>>> print (s)
<__main__.Student object at 0x00000005AD1EAAC8>
# 在类中加入__str__() 方法
... def __str__(self):
... return 'Student object (name: %s)' % self.name
# 再次打印
>>> print(s)
Student object (name: Bob)
2、__repr__()
修改 instance 显示的值
# 正常instance 显示
>>> s
<__main__.Student object at 0x00000005AD1EAAC8>
# 在类中加入__str__() 方法
... def __repr__(self):
... return 'Student object (name: %s)' % self.name
# 再次打印
>>>s
Student object (name: Bob)
__str__() 和 __repr__() 的区别
__str__()返回用户看到的字符串,而__repr__()返回程序开发者看到的字符串
通常__str__()和__repr__()代码都是一样的。所以可以这样写
def __str__(self):
return 'Student object (name=%s)' % self.name
__repr__ = __str__
1.2、__iter__()
我们知道,只有 iterable对象可以进行 for…in… 循环。
如果想让一个类被用于for循环,就需要__iter__() 方法。该方法返回一个iterable 对象。然后,Python的for循环会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。
以斐波那契数列为例,写一个Fib类,可以作用于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 # 计算下一个值
if self.a > 100000: # 退出循环的条件
raise StopIteration()
return self.a # 返回斐波那契数列
# 调用
>>> for n in Fib(): # for 循环打印 Fib() 实例
... print(n)
1.3、__getitem__()
1、__getitem__()方法,可以实现 实例像list 一样的下标取值
# 类的编写
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] # 0 就是传入的参数 n,返回的值是a的值
1
注:
虽然 f 可以像list一样,进行下标取值,但不能进行切片。
原因是__getitem__()传入的参数可能是一个int,也可能是一个切片对象slice,所以要做判断:
2、__getitem__()方法,实现 实例的切片
# 类的编写
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: # 判断a值 是否应该加入L
L.append(a)
a, b = b, a + b # a的值一直再变。只是上面if 要判断是否保存a值
return L
# 调用
>>> f = Fib()
>>> f[0:5]
[1, 1, 2, 3, 5]
总结
Fib(),现在可以下标取值 或s切片取值,但切片的处理没有步长和负数,所以,要正确实现一个__getitem__()还是有很多工作要做的。
此外,如果把对象看成dict,__getitem__()的参数也可能是一个可以作key的object,例如str。
与之对应的是__setitem__()方法,把对象视作list或dict来对集合赋值。最后,还有一个__delitem__()方法,用于删除某个元素。
1.3、关于切片对象slice的思考
slice一般是跟在list后面的。所以不能通过判断切片对list的操作,如L[0:5] 这样的表达式,来判断slice的数据类型。
在Python中,有一个slice对象,它的类型就是 slice。所以猜想:
上面的代码中的f[0:5],n==[0:5]。__getitem__(),会把[0:5],解释成 slice(0,5,none)。从而判断传入的参数是 slice。
n是slice的实例,有start,stop属性。所以通过 start = n.start ,stop = n.stop,来获取 slice 开始和结束的范围
1.4、__getattr__()
正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错。那么,我们可以通过__getattr__()方法,动态返回一个属性。
__getattr__() 方法返回属性的前提是,在__getattr__()的函数体里,这个属性符合你设置的条件
1、动态返回属性
class Student(object):
def __init__(self):
self.name = 'Michael'
def __getattr__(self, attr):
if attr=='score':
return 99
# 我们只定义了name属性,如果尝试获取score属性,就会交由__getattr__() 方法处理
>>> s.score
99
# 如果请求的属性不符合__getattr__()方法的判断条件呢
>>> s.gender # 无显示。正常会报错
>>> print(s.gender) # 显示None,正常会报错
None
2、动态返回函数
class Student(object):
def __getattr__(self, attr):
if attr=='age':
return lambda: 25
# 调用
>>> s.age()
25
3、抛出错误
使用__getattr__()方法后,如果 某属性 class里没定义,__getattr__()里也没有,是不会抛出错误的。针对这些属性,如果要正常抛出错误,需要在__getattr__() 方法里定义
class Student(object):
def __getattr__(self, attr):
if attr=='age':
return lambda: 25
raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)
1.5、__call__()
我们调用实例方法时,我们用instance.method()来调用。而定义一个__call__()方法,就可以直接对实例进行调用
class Student(object):
def __init__(self, name):
self.name = name
def __call__(self):
print('My name is %s.' % self.name)
# 调用
>>> s() # self参数不要传入
My name is Michael.
__call__()还可以定义参数。对实例进行直接调用就好比对一个函数进行调用一样,所以你完全可以把对象看成函数,把函数看成对象,因为这两者之间本来就没啥根本的区别。
callable()函数,可以判断一个对象是否是“可调用”对象。
>>> callable(max)
True
>>> callable([1, 2, 3])
False
2、例题
1、利用完全动态的__getattr__,写出一个链式调用。完全动态的生成URL
class Chain(object):
def __init__(self,path=""): # 初始化实例,Chain().path 为空
self._path=path
def __getattr__(self,path): # 使用类没有定义的属性,就调用
return Chain("%s/%s"%(self._path,path))
def __call__(self,path): # 直接对实例进行调用,将实例当作类似函数一样调用
return Chain("%s/%s"%(self._path,path))
def __str__(self): # 实例显示的值
return self._path
__repr__=__str__
# 调用
print(Chain().a.b.user("ChenTian").c.d)
/a/b/user/ChenTian/c/d
调用解析:
创建了一个实例Chain()。
Chain().a,类没有a属性,调用__getattr__() 方法,将 实例名和属性名传进去,返回一个Chain(/a)实例
Chain(/a).b,操作同上,返回一个Chain(/a/b)实例
Chain(/a/b).user("ChenTian"),先会执行getattr返回Chain实例,Chain(/a/b/user("ChenTian"))
然后由于有__call__方法,可以直接对实例调用。此时就会调用__call__方法。传入的path="ChenTian"。
然后返回Chain(/a/b/user/ChenTian)
Chain(/a/b/user/ChenTian).c.d 操作同第2步