文章目录
- 面向对象编程--python 类
- 类特殊的系统变量
- `__slots__`
- `@property`
- `__str__`
- `__iter__`
- `__getitem__`
- `__getattr__`
- `__call__`
面向对象编程–python 类
本文学习自廖雪峰老师的教程,如果想要系统学习请点廖雪峰的python教程
类特殊的系统变量
__slots__
_slots_ = (‘name’, ‘age’) # 用tuple定义允许绑定的属性名称,注意此限制对子类不影响
class Student():
__slots__ = ('score','name')
@property
为了方便的调用属性,python提供了@property装饰器
class Student():
@property # property修饰的函数可以使用属性,并且property会创建@score.setter进行属性赋值,如果不适用@score.setter属性则表示只读
def score(self):
return self._score # 注意内部将变量添加_或者__表示私有变量,不允许访问,但是__的其实可以通过_Student__score和_score访问
@score.setter
def score(self,value):
if not isinstance(value,int):
raise ValueError('score must be an integer!')
if value<0 or value>100 :
raise ValueError('value must between 0~100')
self._score = value
注意:如果不把score变量声明为_score, 那么score(self, value)为了得出最后的self.score = value,会对self.score这个函数进行循环调用。如果声明了, self._score = value,那么很明显self._score必定是变量,就会一次性得出结果
举一个例子:利用@property给一个Screen对象加上width和height属性,以及一个只读属性resolution
class Screen(object):
@property
def width(self):
return self._width
@width.setter
def width(self,width):
self._width = width
@property
def height(self):
return self._height
@height.setter
def height(self, height):
self._height = height
@property
def resolution(self):
return self._width*self._height
__str__
__str__用于自定义打印一个实例,代码如下
class Student():
def __init__(self,name):
self.name = name
def __str__(self):
return ('student name:%s') % self.name
>>>print(Student('Micyle'))
打印出来后
student name:Micyle
但如果我们直接敲变量,还是之前的变量地址
>>>s = Student('Micyle')
>>>s
<__main__.Student at 0x10b0996d8>
这是因为直接敲变量调用的是__repr__而不是__str__,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__
__iter__
如果一个类想被用于for … in循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。
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 > 1000:
raise StopIteration()
return self.a
for i in Fib():
print(i)
__getitem__
Fib实例虽然能作用于for循环,看起来和list有点像,但是,把它当成list来使用还是不行,比如,取第5个元素:
>>>Fib()[5]
会报错,这里我们可以使用__getitem__
class Fib(object):
def __getitem__(self,n):
a,b = 0,1
for i in range(n):
a,b = b,a+b
return a # 这里其实返回的一个a的列表
但是如果我们使用切片方法,会报错
Fib()[5:10]
这是因为__getitem__()传入的参数可能是一个int,也可能是一个切片对象slice,所以要做判断:
class Fib(object):
def __getitem__(self,n): # getitem将返回的值组成一个列表的形式,而返回的L也是一个列表,因而是列表中的元素还是列表
if isinstance(n,int):
a,b = 1,1
for i in range(n):
a,b = b,a+b
return a
elif isinstance(n,slice):
a,b = 1,1
start = n.start
stop = n.stop
if n.start is None:
start = 0
l = []
for i in range(n.stop):
if i>=start:
l.append(a)
a,b = b,a+b
return l
也没有对负数作处理,所以,要正确实现一个__getitem__()还是有很多工作要做的。此外,如果把对象看成dict,getitem()的参数也可能是一个可以作key的object,例如str。
与之对应的是__setitem__()方法,把对象视作list或dict来对集合赋值。最后,还有一个__delitem__()方法,用于删除某个元素。
总之,通过上面的方法,我们自己定义的类表现得和Python自带的list、tuple、dict没什么区别,这完全归功于动态语言的“鸭子类型”,不需要强制继承某个接口。
__getattr__
当我们调用类的方法或属性时,如果不存在,就会报错。比如定义Student类,如果没有定义score属性,调用就会报错
class Student():
def __init__(self):
self.name = 'Micyle'
s = Student()
s.score
要避免这个错误,除了可以加上一个score属性外,Python还有另一个机制,那就是写一个__getattr__()方法,动态返回一个属性。
class Student():
def __init__(self):
self.name = 'Micyle'
def __getattr__(self,attr):
if attr=='score':
return 99
当调用不存在的属性时,比如score,Python解释器会试图调用__getattr__(self, ‘score’)来尝试获得属性,这样,我们就有机会返回score的值:
class Student():
def __init__(self):
self.name = 'Micyle'
def __getattr__(self,attr):
if attr=='score':
return lambda :1+2
a = Student()
a.score()
只需调用a.score()
可以返回函数运算值
注意:只有在没有找到属性的情况下,才调用__getattr__,已有的属性,比如name,不会在__getattr__中查找。
此外,注意到任意调用如s.abc都会返回None,这是因为我们定义的__getattr__默认返回就是None。要让class只响应特定的几个属性,我们就要按照约定,抛出AttributeError的错误:
class Student():
def __init__(self):
self.name = 'Micyle'
def __getattr__(self,attr):
if attr=='score':
return lambda :1+2
raise AttributeError('student object has no %s'%attr)
举一个很好的例子应用getattr方法,输出给每个URL对应的API
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('xxx').status.user.timeline.list
可以得到如下结果
xxx/status/user/timeline/list
部分api的生成包含了用户名,如果可以采用以下方式调用就比较方便
Chain().users('michael').repos
可以用以下代码实现
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
def users(self,str):
return Chain('%s/%s'%(self._path,'/users/' + str))
__repr__ = __str__
__call__
实例有自己的属性和方法,通过instance.method()来调用,同时也可以直接通过实例调用,只需要定义一个__call__()方法
class Student(object):
def __init__(self,name):
self.name = name
def __call__(self):
print(self.name)
通过__call__()方法就只可以直接调用实例方法
a = Student('song')
a()
另外,函数与对象没有根本的区别,我们可以通过callable来判断对象能否被调用
callable(a)
结果为True