Python Cookbook学习笔记ch8

编程入门 行业动态 更新时间:2024-10-17 02:56:32

Python Cookbook<a href=https://www.elefans.com/category/jswz/34/1770117.html style=学习笔记ch8"/>

Python Cookbook学习笔记ch8

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

8.1改变对象的字符串显示

  • 问题:想要改变对象实例的打印或显示输出,让他们更具可读性
  • 方案:可以重新定义它的__str__()和__repr__()方法
class Pair:def __init__(self,x,y):self.x = xself.y = ydef __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 = yearself.month = monthself.day = daydef __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 = addressself.family = familyself.type = typeself.sock = Nonedef __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.sockdef __exit__(self,exc_ty,exc_val,tb):self.sock.close()self.sock = None
#  该类的特点是他表示了一个网络的连接,但是初始化的时候并不会做任何事情
# (比如他并没有建立一个连接),连接的建立和关闭是使用with语句自动完成的   
from functools import partial
conn = LazyConnection(('www.python',80))
with conn as s:s.send(b'GET /index.html HTTP/1.0\r\n')s.send(b'Host: www.pyhton\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 = addressself.family = familyself.type = typeself.connections = []def __enter__(self):sock = socket(self.family,self.type)sock.connect(self.address)self.connections.append(sock)return sockdef __exit__(self, exc_ty, exc_val, tb):self.connections.pop().close
# 应用
from functools import partial
conn = LazyConnection(('www.python',80))
with conn as s1:passwith conn as s2:pass

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

  • 问题:要创建很多对象,将会占用很多内存,如何改善
  • 方案:对于主要是用来当成简单的数据结构的类而言,可以在类中添加__slots__()属性来减少内存占用
class Data:__slots__ = ['year', 'month', 'day']def __init__(self, year, month, day):self.year = yearself.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):'''公有方法'''passdef _internal_method(self):'''内部方法'''pass

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

class B:def __init__(self):self.__private = 0  #私有属性def __private_method(self):  #私有方法passdef 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_methoddef __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@propertydef first_name(self):return self._first_name#Setter function@first_name.setterdef first_name(self,value):if not isinstance(value, str):raise TypeError("Expected a string")self._first_name = value#Deleter function@first_name.deleterdef 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)<ipython-input-12-d009ff19468e> in <module>()
----> 1 a.first_name = 12 #应该赋值为一个字符串<ipython-input-9-c81dfd2aea37> 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 = value15 TypeError: Expected a string
a.first_name = 'Jack'
a.first_name
'Jack'
del a.first_name
---------------------------------------------------------------------------AttributeError                            Traceback (most recent call last)<ipython-input-16-50ce9077a584> in <module>()
----> 1 del a.first_name<ipython-input-9-c81dfd2aea37> in first_name(self)17     @first_name.deleter18     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 functiondef get_first_name(self):return self._first_name#Setter functiondef 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 memthodsname = 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@propertydef area(self):return math.pi * self.radius * self.radius@propertydef diameter(self):return self.radius * 2@propertydef 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 = 0class B(A):def __init__(self):super()._init__()self.y = 1
  • super()函数的另一个用法出现在覆盖Python特殊方法的代码中
class Proxy:def __init__(self, obj):self.obj = objdef __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)<ipython-input-52-791cf36ee427> in <module>()1 #直接使用这个类会出错2 a = A()
----> 3 a.spam()<ipython-input-50-c02167eedf0e> 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学习笔记ch8

本文发布于:2024-03-23 18:31:27,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1741414.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:学习笔记   Python   Cookbook

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!