类和对象:
我们经常会对打印一个对象来得到对象的某些信息。
class pair:
def __init__(self,x,y):
self.x=x
self.y=y
if __name__=='__main__':
p=pair(3,4)
print p
E:\python2.7.11\python.exe E:/py_prj/python_cookbook/chapter8.py
<__main__.pair instance at 0x01723B20>
得到的这个结果。Instance at 0x01723B20.这是对象在内存中的地址。这个对我们来说没啥作用,如何才能体现出对象的具体信息呢。有两种方法。__repr__和__str__.代码修改如下
class pair:
def __init__(self,x,y):
self.x=x
self.y=y
def __repr__(self):
return 'Pair({self.x},{self.y})'.format(self=self)
def __str__(self):
return '({self.x},{self.y})'.format(self=self)
那么__repr__和__str__的区别在哪里呢。看下下面的调用,命令行直接调用类实例的时候,是__repr__在起作用,如果是print 实例的时候则是__str__在起作用
>>>ret= pair(3,4)
>>>ret
>>>Pair(3,4)
>>>print ret
>>>(3,4)
让对象支持上下文协议。
说到上下文协议首先先介绍下with的用法。我们经常打开文件进行数据读取或者是更新。如下面的代码
f=open(r'E:\py_prj\test.txt','rb')
for line in f:
print line
如果这时忘记了写f.close()或者说写了f.close()但是由于读取文件的中途出现错误。导致f.close()未执行。这个时候该怎么办呢。我们通常想到的是try…except语句。代码如下。不管是执行成功还是失败。最后finally中都会执行f.close()将文件关闭
try:
f=open(r'E:\py_prj\test.txt','rb')
for line in f:
print line
except:
print 'error'
finally:
f.close()
用try..except…finally倒是能规避掉异常带来的失误。但是代码稍显多了点。有没有可以简化的方法呢。With就是满足这个需求的。代码如下。
with open(r'E:\py_prj\test.txt','rb') as f:
for line in f:
print line
看上去貌似with也没有进行异常保护呢,如果出现前面的问题,会是什么样的结果呢。先来看下with的说明文档
while this might look like magic, the way Python handles with is more clever than magic. The basic idea is that the statement after with has to evaluate an object that responds to an __enter__() as well as an __exit__() function.
After the statement that follows with is evaluated, the __enter__() function on the resulting object is called. The value returned by this function is assigned to the variable following as. After every statement in the block is evaluated, the __exit__() function is called.
从上面的描述来看,with的使用和__enter__以及__exit__有关系。我们来自己测试一把,在类中运用with语句
class sample:
def __enter__(self):
print 'enter'
def __exit__(self,type,value,trace):
print 'exit'
if __name__=='__main__':
s=sample()
with s as test:
print 'test sample'
E:\python2.7.11\python.exe E:/py_prj/python_cookbook/chapter8.py
enter
test sample
exit
从上面的执行顺序可以看到当执行到with s as test的时候,进入了__enter__,并打印enter。然后执行with中的具体语句打印test sample。在将with下面的语句执行完后就开始执行__exit__。这样的with的执行顺序就清楚了。在__exit__中有三个参数,type,value,trace。这是干什么用的呢。这是存储异常的一些消息。通过这3个参数可以得到执行失败的一些信息
关于__slots__
我们在类中定义的属性都是保存在一个字典中的。如果要查看类中的属性,直接查看__dict__就可以了
class date_try(object):
def __init__(self):
self.year=2017
self.month=6
self.day=12
if __name__=='__main__':
d=date_try()
print d.__dict__
E:\python2.7.11\python.exe E:/py_prj/python_cookbook/chapter8.py
{'month': 6, 'day': 12, 'year': 2017}
通过执行__dict__可以查看到全部的属性。如果我们中途还想绑定属性呢。
d=date_try()
d.hour=22 #在这里绑定了hour
print d.__dict__
E:\python2.7.11\python.exe E:/py_prj/python_cookbook/chapter8.py
{'month': 6, 'day': 12, 'hour': 22, 'year': 2017}
可以看到hour也加入到了__dict__中去了
有一种场景,如果我们有很多个类,但是每个类中都是固定的几个属性。如果都用dict来存储,就太浪费内存了,原因在流畅的python的学习笔记中介绍给,dict由于采用hash索引的方法,虽然查找速度快,但是很占内存。既然类中的属性固定。那么我们是否可以限定属性呢而且不用dict存储呢。这里就要用到__slots__
class date_try(object):
__slots__=('year','month','day')
def __init__(self):
self.year=2017
self.month=6
self.day=12
if __name__=='__main__':
d=date_try()
print d.__dict__
E:\python2.7.11\python.exe E:/py_prj/python_cookbook/chapter8.py
Traceback (most recent call last):
File "E:/py_prj/python_cookbook/chapter8.py", line 32, in <module>
print d.__dict__
AttributeError: 'date_try' object has no attribute '__dict__'
这个时候如果去查看__dict__就会报错,证明属性没有存储在__dict__中。这样只会给属性单独分配空间而不会用到dict。那如果我想增加属性呢
d=date_try()
d.hour=22
E:\python2.7.11\python.exe E:/py_prj/python_cookbook/chapter8.py
Traceback (most recent call last):
File "E:/py_prj/python_cookbook/chapter8.py", line 32, in <module>
d.hour=22
AttributeError: 'date_try' object has no attribute 'hour'
是没法添加的。由于定义了__slots__,因此这个类中的属性就只能用__slots__中的属性名。除此之外的无法添加
如果有很多类且类的属性都固定,那么用__slots__的确可以达到节省内存的效果。但是缺点也显而易见,属性全部固定。无法添加。
8.5 在类中封装属性名
这一节介绍的是私有变量。我们先来看下什么是私有变量
class private(object):
def __init__(self):
self.__internal=0
self.public=1
if __name__=='__main__':
p=private()
print p.__internal
在类private中定义了2个变量,__internal以及public。其中internal是以双下划线开头的。这种定义的变量就是私有变量。私有变量通过类属性以及子类都无法访问。我们来看下上面代码的执行结果:
E:\python2.7.11\python.exe E:/py_prj/python_cookbook/chapter8.py
Traceback (most recent call last):
File "E:/py_prj/python_cookbook/chapter8.py", line 54, in <module>
print p.__internal
AttributeError: 'private' object has no attribute '__internal'
访问__internal失败,提示private对象没有__internal属性。其实python将私有变量进行了转换,转换成了__<类名><私有变量>。我们通过下面的打印来看下
print dir(private)
print dir(p)
E:\python2.7.11\python.exe E:/py_prj/python_cookbook/chapter8.py
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_private__internal', 'public']
可以看到print dir(p)中的变量变成了_private__internal.那么我们可以通过下面的方式访问:
p=private()
print p._private__internal
那么私有变量能否被子类所继承呢,来看下面的代码
class private(object):
def __init__(self):
self.__internal=0
self.public=1
class private_child(private):
def __init__(self):
private.__init__(self)
print 'private_child'
self.__internal+=2
if __name__=='__main__':
c=private_child()
E:\python2.7.11\python.exe E:/py_prj/python_cookbook/chapter8.py
Traceback (most recent call last):
File "E:/py_prj/python_cookbook/chapter8.py", line 43, in <module>
c=private_child()
File "E:/py_prj/python_cookbook/chapter8.py", line 38, in __init__
self.__internal+=2
AttributeError: 'private_child' object has no attribute '_private_child__internal'
private_child
同样的提示无法访问。但是通过 self._private__internal是可以访问。从这里可以看到其实python不存在所谓的私有变量,只是把变量换了一种封装方式而已。
8.6 创建可管理的属性
这一节介绍了@property的用法。@property的作用主要有2个:
1 将类方法转换为只读属性
2 重新实现一个属性的setter以及getter方法。
我们分别来看下2个作用的用法,首先是第一个将类方法转换成只读属性
class property_try(object):
def __init__(self,name):
self.name=name
def get_name(self):
return self.name
if __name__=='__main__':
p=property_try('zhf')
print p.get_name()
在代码中,定义了一个name变量,并通过get_name函数来访问。在最后用p.get_name()来访问。我们将geg_name改成用属性的方法来调用
class property_try(object):
def __init__(self,name):
self.name=name
@property
def get_name(self):
return self.name
if __name__=='__main__':
p=property_try('zhf')
print p.get_name
在get_name用@property修饰一下,get_name就变成了属性。用print p.get_name就可以访问了。既然可以访问了,那么我们可以进行赋值么
p.get_name='zhf1'
E:\python2.7.11\python.exe E:/py_prj/python_cookbook/chapter8.py
Traceback (most recent call last):
File "E:/py_prj/python_cookbook/chapter8.py", line 51, in <module>
p.get_name='zhf1'
AttributeError: can't set attribute
运行失败,提示不能设置属性。那么这里就要用到我们的第二个方法。设置属性。
class property_try(object):
def __init__(self,name):
self.name=name
@property
def get_name(self):
return self.name
@get_name.setter
def get_name(self,value):
self.name=value
if __name__=='__main__':
p=property_try('zhf')
print p.get_name
p.get_name='zhf1'
print 'after reset,%s' % p.get_name
E:\python2.7.11\python.exe E:/py_prj/python_cookbook/chapter8.py
zhf
after reset,zhf1
首先用@property装饰get_name,然后用@property.setter来装饰get_name.这样能就可以对get_name进行设置值了。那么@propery.setter的作用就是将一个只读属性变成了属性可赋值。
另外还有一个删除属性方法的deleter:
@get_name.deleter
def get_name(self):
print 'failed'
当调用del p.get_name的时候将触发print ‘failed’