定制类
class中有许多前后双下划线的特殊用途函数,比如__slots__
限制属性范围,__len__()
让 class作用域len()
函数。
__str__
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'student object(name:%s)' % self.name
print(Student('Michael'))
打印结果 :
本来应该打印一串地址什么的,现在这样就更好看了 。
但如果调用 方式 是原来那样
>>> s = Student('Michael')
>>> print(s)
#输出结果:
<__main__.Student object at 0x109afb310>
这就 又不好看了。这是因为直接显示变量调用的不是__str__()
,而是__repr__()
,两者的 区别是__str__()
返回用户看到的字符串,而__repr__()
返回 程序开发 者看到的字符串,__repr__()
是为调试 服务的。
解决办法是再定义一个__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__
print(Student('Michael'))
s = Student('zz')
print(s)
#输出结果:
student object(name:Michael)
student object(name:zz)
__iter__
一个类如果想要for。。。in循环,就必须写一个__iter__()方法,该方法返回一个迭代对象,然后Python的for循环就不断调用迭代 对象的__next__()方法 拿到循环 的下一个值,直到遇到StopIteration错误退出循环。
例:写一个Fib类,作用于for 循环
class Fib(object):
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 > 100000:
raise StopIteration()
return self.a
for n in Fib():
print(n)
__getitem__
Fib实例能作用于for 循环 ,但不能以下标访问,想实现就得用到__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
>>> f[100]
573147844013817084101
但是还不能切片,想切片就得这样写
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 #注意这里隐含next,每次都有next()
if isinstance(n, slice): #还能判断是否切片的,惊了
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
但是还是不能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__
正常情况下,调用类的方法和属性时,如果不存在就会报错,要避免这个错误,除了可以加上一个 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)
返回函数也可以:
class Student(object):
def __getattr__(self, attr):
if attr == 'age':
return lambda :25
s = Student()
print(s.age())
只有在没有找到属性的情况下,才调用__getattr__
,已有的属性不会 从__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)
s = Student( )
print( s.name())
抛出错误 :
AttributeError: ’ student’ object has no attribute ’ name ’
如果要写SDK,给每个URL对应的API都写一个方法,那得累死,而且,API一旦改动,SDK也要改。
__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 = Student('Michael')
s()
输出:
My name is Michael
判断一个对象是对象还是函数,通过判断否可调用,可调用的就是一个Callable对象,比如函数和我们上面定义的带有__call__()的类实例 。
>>> callable(Student()) #能被调用,函数
True
>>> callable(max) #能被调用,函数
True
>>> callable([1, 2, 3]) #不能被调用,对象
False
>>> callable(None) #不能被调用,对象
False
>>> callable('str') #不能被调用,对象
False
当我们需要定义常量时,一个办法是用大写变量通过整数来定义,例如月份