Python Cookbook学习笔记ch8_01

前段时间出差,回来后又写开题报告,所以把写博客记录的事放下了(惭愧)。不如正题,坚持!
这里可以在Jupyter notebook模式下查看,效果更好

8.1改变对象的字符串显示

  • 问题:想要改变对象实例的打印或显示输出,让他们更具可读性
  • 方案:可以重新定义它的__str__()和__repr__()方法
class Pair:
    def __init__(self,x,y):
        self.x = x
        self.y = y
    def __repr__(self):
        return 'Pair({0.x!r},{0.y!r})'.format(self)
    def __str__(self):
        return '({0.x!s},{0.y!s})'.format(self)
p = Pair(3,4)
p
Pair(3,4)
print(p)
(3,4)
  • 说明:
    !r 就是 repr
    !s 就是 str
    !a 就是 ascii
  • 例子:
    “Harold’s a clever {0!s}”     # Calls str() on the argument first
    “Bring out the holy {name!r}”  # Calls repr() on the argument first
    “More {!a}”          # Calls ascii() on the argument first
p = Pair(3,4)
print('p is {0!r}'.format(p))
p is Pair(3,4)
print('p is {0}'.format(p))
p is (3,4)
# 也可以使用% 操作符
def __repr__(self):
    return 'Pair(%r,%r)'%(self.x,self.y)

8.2自定义字符串的格式化

  • 问题:想要通过format()函数和字符串方法使得一个对象能支持自定义的格式化
  • 方案:在类上面定义__format__()方法
_formats = {
    'ymd':'{d.year}-{d.month}-{d.day}',
    'mdy':'{d.month}/{d.day}/{d.year}',
    'dmy':'{d.day}/{d.month}/{d.year}'
}
class Date:
    def __init__(self,year,month,day):
        self.year = year
        self.month = month
        self.day = day
    def __format__(self,code):
        if code == '':
            code = 'ymd'
        fmt = _formats[code]
        return fmt.format(d = self)
d = Date(2017,12,21)
format(d)
'2017-12-21'
format(d,'mdy')
'12/21/2017'
format(d,'dmy')
'21/12/2017'
'the data is {:ymd}'.format(d)
'the data is 2017-12-21'
'the date is {:dmy}'.format(d)
'the date is 21/12/2017'
from datetime import date
d = date(2012,9,12)
d
datetime.date(2012, 9, 12)
format(d)
'2012-09-12'
format(d,'%A,%B,%d,%Y')
'Wednesday,September,12,2012'

8.3 让对象支持上下文管理协议

  • 问题:想让你的对象支持上下文管理协议(with语句)
  • 方案:为了让一个对象兼容with语句,必须实现__enter__()和__exit__()方法
from socket import socket,AF_INET,SOCK_STREAM
class LazyConnection:
    def __init__(self,address,family=AF_INET,type=SOCK_STREAM):
        self.address = address
        self.family = family
        self.type = type
        self.sock = None
    
    def __enter__(self):
        if self.sock is not None:
            raise RuntimeError('Already connected')
        self.sock = socket(self.family, self.type)
        self.sock.connect(self.address)
        return self.sock
    def __exit__(self,exc_ty,exc_val,tb):
        self.sock.close()
        self.sock = None
#  该类的特点是他表示了一个网络的连接,但是初始化的时候并不会做任何事情
# (比如他并没有建立一个连接),连接的建立和关闭是使用with语句自动完成的   
        
from functools import partial
conn = LazyConnection(('www.python.org',80))
with conn as s:
    s.send(b'GET /index.html HTTP/1.0\r\n')
    s.send(b'Host: www.pyhton.org\r\n')
    s.send(b'\r\n')
    resp =  b''.join(iter(partial(s.recv,8192),b''))
  • 编写上下文管理器的主要原理是将代码放到with语句块中执行。当出现with语句时,对象的__enter__()方法被触发,它的返回值(如果有)被赋值给as声明的变量。然后执行with语句块中的代码,执行完毕后执行__exit__()

嵌套with语句

from socket import socket,AF_INET,SOCK_STREAM
class LazyConnection:
    def __init__(self, address, family=AF_INET, type=SOCK_STREAM):
        self.address = address
        self.family = family
        self.type = type
        self.connections = []
    def __enter__(self):
        sock = socket(self.family,self.type)
        sock.connect(self.address)
        self.connections.append(sock)
        return sock
    def __exit__(self, exc_ty, exc_val, tb):
        self.connections.pop().close
# 应用
from functools import partial
conn = LazyConnection(('www.python.org',80))
with conn as s1:
    pass
    with conn as s2:
        pass

8.4创建大量对象时节省空间

  • 问题:要创建很多对象,将会占用很多内存,如何改善
  • 方案:对于主要是用来当成简单的数据结构的类而言,可以在类中添加__slots__()属性来减少内存占用
class Data:
    __slots__ = ['year', 'month', 'day']
    def __init__(self, year, month, day):
        self.year = year
        self.month = month 
        self.day = day
        
  • 当创建了一个__slots__()之后,实列会以一个更加紧凑的数组方式来构建,而不是为每个实列定义一个字典。在slots中列出的属性名在内部会被映射到制格式数组的指定小标上。缺点是:不能再给实列添加新的属性

8.5 在类中封装属性名

  • 问题:想要封装类的实例上面的“私有数据”,但是Python并没有访问控制机制
  • 方案:Python程序员不依赖语言特性去封装数据,而是通过遵守一定的属性和方法命名规约达到这个效果。

约定1:任何以单下划线_开头的名字都是内部实例

  • python并不会真正阻止对内部名称的访问,只不过非要访问会导致脆弱的代码。同时,单下划线同时适用于模块名和模块级别的函数
class A:
    def __init__(self):
        self._internal = 0  #内部属性
        self.public = 1   #外部属性
    def public_method(self):
        '''公有方法'''
        pass
    def _internal_method(self):
        '''内部方法'''
        pass

规约2:双下划线开头,会导致访问名称变成其他形式,这种属性通过继承无法被覆盖

class B:
    def __init__(self):
        self.__private = 0  #私有属性
    def __private_method(self):  #私有方法
        pass
    def public_method(self):
        pass
  • 在上述的类B中,它的私有属性和私有方法会分别重命名为_B__private和_B__private_method
class C(B):
    def __init__(self):
        super().__init__()
        self.__private = 1# 并不会覆盖B.__private
    #并不会覆盖B.__private_method
    def __private_method(self):
        pass
  • 上述代码之所以不会覆盖父类的“同名”属性或者方法是因为:其实他们并不是真正的同名,类C的__private和__prevate_method分别被重命名为_C__private和_C__private_method

上面的两种规约用来命名私有属性和方法,通常应该让非公有名称以单下划线开头,但是如果清楚的知道代码会涉及到子类,则应该使用双下划线

规约3:如果定义的某个变量和某个保留的关键字重名,可以在变量后面使用单下划线作为后缀

lambda_ = 2.0

8.6 创建可管理的属性

  • 问题:想要给实例的属性增加除了访问和修改之外的处理逻辑,比如类型检查和合法性验证
  • 方案:自定义某个属性的简单方法是将它定义为一个property
class Person:
    def __init__(self, first_name):
        self.first_name = first_name
    #Getter function
    @property
    def first_name(self):
        return self._first_name
    
    #Setter function
    @first_name.setter
    def first_name(self,value):
        if not isinstance(value, str):
            raise TypeError("Expected a string")
        self._first_name = value
    
    #Deleter function
    @first_name.deleter
    def first_name(self):
        raise AttributeError("can't delete attribute")
    
  • 上述代码中有三个关联的方法,它们的名字必须相同。第一个方法使得first_name成为一个属性,后面两个只有在第一个方法之后即first_name被设置为属性之后才能定义。
  • property的关键特征看上去和普通的attribute一样,但是访问的时候会自动地触发getter、setter、和deleter方法
a = Person('Guido')
a.first_name
'Guido'
a.first_name = 12 #应该赋值为一个字符串
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

 in ()
----> 1 a.first_name = 12 #应该赋值为一个字符串


 in first_name(self, value)
     11     def first_name(self,value):
     12         if not isinstance(value, str):
---> 13             raise TypeError("Expected a string")
     14         self._first_name = value
     15 


TypeError: Expected a string
a.first_name = 'Jack'
a.first_name
'Jack'
del a.first_name
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

 in ()
----> 1 del a.first_name


 in first_name(self)
     17     @first_name.deleter
     18     def first_name(self):
---> 19         raise AttributeError("can't delete attribute")
     20 


AttributeError: can't delete attribute
p = Person('Guido')
p.get_first_name()
'Guido'
p.set_first_name('Jjldik')
p.get_first_name()
'Jjldik'
  • 还可以在已经存在的get和set方法基础上定义property
class Person:
    def __init__(self,first_name):
        self.set_first_name(first_name)
    
    #getter function
    def get_first_name(self):
        return self._first_name
    
    #Setter function
    def set_first_name(self, value):
        if not isinstance(value, str):
            raise TypeError('Expected a string')
        self._first_name = value
    
    #Deleter function 
    def del_first_name(self):
        raise AttributeError("can't delete attribute")
    
    # make a property from a existing get/set memthods
    name = property(get_first_name,set_first_name,del_first_name)
  • 一个 property 属性其实就是一系列相关绑定方法的集合。如果你去查看拥有
    property 的类,就会发现 property 本身的 fget、 fset 和 fdel 属性就是类里面的普通方
    法。

  • 注意:只有在你确实需要对属性进行额外的操作时才应该使用property

  • Properties还是一种定义动态计算attribute的方法,这种attributes并不会被实际存储,而是在需要的时候计算出来

import math
class Circle:
    def __init__(self,radius):
        self.radius = radius
        
    @property
    def area(self):
        return math.pi * self.radius * self.radius
    @property
    def diameter(self):
        return self.radius * 2
    @property
    def perimeter(self):
        return 2 * math.pi * self.radius
c = Circle(4.0)
c.area
50.26548245743669
c.diameter
8.0
c.perimeter
25.132741228718345

8.9 调用父类的方法

  • 问题:想要在子类中调用某个父类中已经被覆盖的方法
  • 方法:为了调用父类(超类)中的方法可以使用super()方法
class A:
    def spam(self):
        print('A.spam')
class B(A):
    def spam(self):
        print('B.spam')
        super().spam()
  • super()的一个常用用法是在__init__函数中确保父类被正确的初始化
class A:
    def __init__(self):
        self.x = 0

class B(A):
    def __init__(self):
        super()._init__()
        self.y = 1
  • super()函数的另一个用法出现在覆盖Python特殊方法的代码中
class Proxy:
    def __init__(self, obj):
        self.obj = obj
    
    def __getattr__(self, name):
        return getattr(self._obj, name)
    
    def __setattr__(self, name, value):
        if name.startswith('_'):
            super().__setattr__(name, value)
        else:
            setattr(self._obj, name, value)
        
  • 有时候大家可能会像下面那样调用父类的方法
class Base:
    def __init__(self):
        print('Base.__init__()')
class A(Base):
    def __init__(self):
        Base.__init__(self)
        print('A.__init__()')
        
  • 上述的方式并不好,尤其在多继承时
class Base:
    def __init__(self):
        print('Base.__init__()')
        
class A(Base):
    def __init__(self):
        Base.__init__(self)
        print('A.__init__()')

class B(Base):
    def __init__(self):
        Base.__init__(self)
        print('B.__init__()')

class C(A,B):
    def __init__(self):
        A.__init__(self)
        B.__init__(self)
        print('C.__init__()')
# 调用上述代码会发现Base.__init__()调用了两次
c = C()
Base.__init__()
A.__init__()
Base.__init__()
B.__init__()
C.__init__()
  • 将上述代码用super()代替
class Base:
    def __init__(self):
        print('Base.__init__()')
        
class A(Base):
    def __init__(self):
        super().__init__()
        print('A.__init__()')

class B(Base):
    def __init__(self):
        super().__init__()
        print('B.__init__()')

class C(A,B):
    def __init__(self):
        super().__init__()
        print('C.__init__()')
c = C()
Base.__init__()
B.__init__()
A.__init__()
C.__init__()
  • 为了弄清它的原理,我们需要花点时间解释下 Python 是如何实现继承的。对于你定义的每一个类, Python 会计算出一个所谓的方法解析顺序 (MRO) 列表。这个 MRO列表就是一个简单的所有基类的线性顺序表。例如:
C.__mro__
(__main__.C, __main__.A, __main__.B, __main__.Base, object)
  • 为了实现继承, Python 会在 MRO 列表上从左到右开始查找基类,直到找到第一
    个匹配这个属性的类为止

  • 遵循三个准则:
    1> 子类先于父类被检查
    2> 多个父类会根据它们在列表中的顺序被检查
    3> 如果对下一个类存在两个合法的选择,选择第一个父类

  • super() 有个令人吃惊的地方是它并不一定去查找某个类在 MRO 中下一个直接
    父类,你甚至可以在一个没有直接父类的类中使用它。

class A :
    def spam(self):
        print('A.spam')
        super().spam()
#直接使用这个类会出错
a = A()
a.spam()
A.spam



---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

 in ()
      1 #直接使用这个类会出错
      2 a = A()
----> 3 a.spam()


 in spam(self)
      2     def spam(self):
      3         print('A.spam')
----> 4         super().spam()


AttributeError: 'super' object has no attribute 'spam'
# 但是多继承时会正常
class B:
    def spam(self):
        print('B.spam')
class C(A, B):
    print('C.spam')

c = C()
c.spam()
C.spam
A.spam
B.spam
C.__mro__
(__main__.C, __main__.A, __main__.B, object)

你可能感兴趣的:(Python,Cookbook)