读书笔记:Python 学习手册(3)
结于2021-08-19;
OREILY的书籍,可读性很强,入门类,而且这本书很厚;
- 第五部分 模块
- 第六部分 类和OOP(1)
第五部分 模块 ———— 第21章 模块:宏伟蓝图
模块是最高级别的程序组织单元;实际中,模块往往对应着Python程序文件;
- 模块导入其他模块后就可以使用导入模块定义的变量名;
- import 以一个整体获取模块
- from 从模块文件中获取指定变量名
模块作用:
- 代码重用
- 系统命名空间划分
- 实现共享服务和数据
Python程序结构:
- 顶层文件:包含程序主要控制流程,需要运行来启动应用的文件;
- 模块文件:工具库
- 一个文件导入一个模块,来获得这个模块顶层定义的所有对象的访问权;
- 导入模块、获取它的属性并使用它的工具;
- 标准库
import会一次运行在目标文档中的语句从而建立其中的内容;
标准库模块:
- 由Python自身提供的实用模块,大概200个模块;
- 包括:操作系统接口、对象永久保存、文字模式匹配、网络和Internet、GUI等;
导入是运行时运算,第一次导入时会执行三个步骤:
- 找到模块文件
- 编译成位码/字节码(需要时),通过检查时间戳来判断是否需要重新生成字节代码;(字节码可以直接加载)
- 执行模块代码来创建其所需的对象
之后的导入会跳过这三个步骤,而只提取内存中已加载的模块对象;
只有被导入的文件才会在机器上留下.pyc
;顶层文件的字节码是在内部使用后就丢弃了;被导入文件的字节码则保存在文件中从而可以提高之后的导入速度;
导入实际是在执行模块中代码;如果需要再次导入,可通过调用reload强制处理;
如果想看已经导入的模块,可以导入sys并打印list(sys.modules.keys())
模块搜索路径:
- 程序主目录
- PYTHONPATH目录(如果有)
- 标准链接库目录
- 任何
.pth
文件的内容(如果有):- 这是一个新功能,允许用户把有效目录添加到模块搜索路径中;
- 这个后缀的文本文件中一行一行的列出目录;
sys.path:
- 这个目录名称字符串列表就是Python内部实际的搜索路径;
- 通过修改这个列表,可以修改将来的导入的搜索路径;
sys.path.append(dirname)
(从左到右搜索)
注意 导入模块时 后缀名是省略的;
Python也支持最佳化字节码文件.pyo,这种文件在创建和执行时需要加上-O
标志位;要比普通的.pyc文件快一点;但它们并未被频繁使用;
第22章 模块代码编写基础
from语句其实只是稍微扩展了import语句而已,它照常入了模块文件,但是多了一个步骤,将文件中的一个或多个变量名从文件中复制出来;
import aaa
from aaa import bbb, ccc
from aaa import * # aaa模块顶层所有赋值的变量名的拷贝,另外这种方式只能用于文件顶层,不能用于函数中;
import 和 from是可执行的语句,而非编译期间的声明;
- import 将整个模块对象赋值给一个变量名,基于这个模块变量名才可以修改相应模块文件中的全局变量;
- from 将一个或多个变量名赋值给另一个模块中同名的对象;修改一个从模块中取出的可变对象,会影响导入的模块内的对象;
- from后执行import,只会把变量名复制到另一个模块,并不会对模块名本身进行赋值;
from语句有破坏命名空间的潜质;
模块文件的顶层每一个赋值了的变量名都会变成该模块的属性;
- 模块语句会在首个导入时执行;
- 顶层的赋值语句会创建模块属性
- 模块的命名空间能通过属性
__dict__
或dir(M)
获取; - 模块是一个独立的作用域,模块对象作用域;
Python也会在模块命名空间内加一些变量名,如__file__
指明模块从哪个文件加载;
Python中,作用域绝对不会被函数调用或模块导入所影响;(静态作用域)
如果mod1中导入了mod2,mod2中导入了mod3,在使用时,可以
import mod2
然后mod2.mod3,但是无法import mod2.mod3
,这个语法对应下一章的包导入,包导入也会形成模块命名空间嵌套,导入语句会反映目录结构,而非简单的导入链;
reload基础
- reload是内置函数
- reload接收的是存在的模块对象
- reload的模块,必须导入,即便是当前模块自己
import changer
changer.printer() # First version
from imp import reload
reload(changer) # 重载
changer.printer() # new version
重载使程序能够提供高度动态的接口;每次修改代码后,不需要停止重启;
第23章 模块包
除了模块之外,导入也可以指定目录路径:Python代码的目录就称为包;
包导入是把计算机上的目录变成另一个Python命名空间;而属性则对应于目录中所包含的子目录和模块文件;
包导入基础:
import dir1.dir2.mod
对应目录下的模块文件dir1/dir2/mod.py
- 模块搜索路径上的项目提供了平台特定的目录路径前缀;
__init__.py
包文件:
- 包导入语句的目录中的每个目录内都必须有
__init__.py
这个文件; - 这个文件作为一种声明,防止有相同名称的目录被模块搜索为路径;否则Python可能会挑选到和程序无关的目录;
__init__.py
可以包含Python代码,就像普通模块文件,也可以是空的;
__init__.py
包文件的作用:
- 包初始化:Python
首次导入某个目录
时,会自动执行该目录下__init__.py
文件中的所有代码;因此非常适合进行需要代码的初始化; - 模块命名空间的初始化:为目录所创建的模块对象提供了命名空间;
- 实现
from*
语句的行为:可以在__init__.py
包文件内使用__all__
列表,来定义目录以from*
语句形式导入时,需要导出什么;如果没有设定,from*
语句不会自动加载嵌套与该目录下的子模块;而只是加载__init__.py
包文件中赋值语句定义的变量名(包括该文件中程序代码明确导入的任何子模块变量名;);
import dir1.dir2.mod
,该语句内的路径会变成脚本的嵌套对象路径,即mod是对象,嵌套在对象dir2中,dir2又嵌套在dir1中;
- 事实上,路径中的每个目录名称都会变成赋值了模块对象的变量;这个目录对应的模块对象的命名空间(抛开已经生成的)则是由该目录内的
__init__.py
包文件中所有赋值语句进行初始化的; - 相比
import dir1.dir2.mod
,使用from dir1.dir2 import mod
更好些;
包让导入更具信息性,并可以作为组织工具;
root
system1
__init__.py
util.py
main.py
other.py
system2
__init__.py
util.py
main.py
other.py
system3
__init__.py
myfile.py # code here
# code
import system1.util # 这种语法 只限于 从包 嵌套包 到 模块!!!
import system2.util
system1.util.func()
system2.util.func()
相对导入
from . import spam
:把当前语句所在文件相同包路径中的名为spam的一个模块导入;from .spam import name
:从名为spam的模块导入name,且这个spam模块与当前语句所在文件位于同一个包下;
在Pyhton3.0中,不带点的一个import总会引发Pyhton
略过
模块导入搜索路径的相对部分
,并且在sys.path所包含的绝对路径中查找
(这句很重要,注意理解!
)
相对导入的作用:当脚本的同名文件出现在模块搜索路径上的许多地方,可以解决模糊性;
# 定义一个包 包含两个模块
mypkg
__init__.py
main.py
string.py
spam.py
# 现在假设main模块 试图导入名为 string的模块(标准库也有一个string模块)
import string # 这条import语句 总是在包之外找到一个string,通过sys.path的绝对导入搜索;
from string import name # 同上
# 如果想从包中导入一个模块,不需要给出从包根目录的完整路径 使用点语法即可做到相对导入
from . import string # 在当前包导入string模块,此方式包的目录是唯一搜索的目录
from .string import name1, name2 # 同上
# .表示包含文件的包目录 ..则表示执行从当前包的父目录的相对导入
from .. import spam # mypkg包的父目录包下的spam模块
注意:
- 相对导入只适用于在包内导入;
- 相对导入只适用于from语句;
模块查找规则总结
- 简单模块名:通过搜索sys.path路径列表上的每个目录来查找,从左到右进行;
- 包是带有一个特殊的
__init__.py
文件的Python模块的直接目录,这使得一个导入中可以使用A.B.C
目录路径语法;在A.B.C
的一条导入中,名为A的目录位于相对于sys.path的常规模块导入搜索,B是A中的另一个包子目录;C是一个模块或B中的其他可导入项; - 在包文件中,常规的import导入和其他地方的导入一样使用sys.path搜索规则(第一个就是项目主目录CWD);包中导入使用from及点号,则是相对于包的,即只检查包目录,并且不使用常规的sys.path查找;
如果不是用作一个包的部分的一个文件中,甚至不允许相对导入语法;相对导入所引用的模块必须在包目录中存在;
第24章 高级模块话题
- 数据隐藏
__future__
模块__name__
变量- sys.path修改
- 列表工具
- 通过名称字符串来运行模块
- 过度式重载
在模块中隐藏数据
Python模块会导出其文件顶层所赋值的所有变量名;数据隐藏仅仅是惯例,而非语法约束;
_X
:
- 在变量名前放下划线的惯例(
_X
),可以防止使用from*
语句导入模块名时;但注意下划线不是私有声明,使用import语句 依然可以导入该变量;
__all__
:
- 也可以在模块顶层把变量名的字符串列表赋值给变量名
__all__
; __all__ = ['encode', 'decode'] # Export these only
- 使用这种只会把列在
__all__
中的变量名复制出来;
__all__
指出要复制的变量名,_X
指出不被复制的变量名;使用from*
时,Python会先寻找模块内的__all__
列表,如果没有,则会复制开头无单下划线的所有变量名;
二者只对from*
语句形式有效;
启用以后的语言特性
默认是关闭的,要开启此类扩展,可以使用特定的import语句:
from __future__ import featurename
- 一般放在模块文件顶端,以开启特殊的代码编译;
混合用法模式:__name__
和__main__
每个模块都有个__name__
属性,Python会自动设置该属性:
- 如果文件是以顶层程序文件执行,启动时,
__name__
属性就会被设置为字符查收你__main__
; - 如果是被导入的,
__name__
属性就会被设置为模块名;
可以检测自己的__name__
属性,来确定它是在执行还是导入;常用来在文件末端进行代码测试;
在Pyhton中
sys.argv
列表包含了命令行参数;
修改模块搜索路径
模块搜索路径是一个目录列表,该路径可以修改,即修改sys.path
的内置列表;
sys.path
在程序启动时就会进行初始化,之后可以随意修改;
import sys
sys.path
sys.path.append('dir') # 一旦做了修改 就会对将要导入的地方产生影响,因为所有导入和文件都共享了同一个sys.path列表;
import string
使用这个技巧,可以在Python中动态配置搜索路径;
修改sys.path只会在当前的会话或进程中才会存续;程序结束后不会保留;
Import和From语句的as扩展
import modulename as name
# 相当于
import modulename
name = modulename
del modulename
from modulename import attrname as name
import dir1.dir2.mod as mod
模块是对象:元程序
# 获取模块属性
M.name
M.__dict__['name']
sys.modules['M'].name
getattr(M, 'name') # 内省
# 将模块对象 作为参数传递 打印模块对象的属性列表
def listing(module):
for arrt in module.__dict__:
if arrt.startwith('__'):
print('')
else:
print(getarrt(module, arrt))
import mydir
listing(mydir)
用名称字符串导入模块
特殊工具:从运行时生成一个字符串 来动态地载入一个模块;
# exec:缺点是每次都需编译
modname = "string"
exec("import " + nodename)
string# 此时 string就是一个模块对象了
# 使用内置的__import__函数
modname = "string"
string = __import__(modname) # 多次加载更好
string
过渡性模块重载
重载:不需要停止或重新启动程序就能选择代码中的修改的一种方式;
默认情况下,如果模块A导入了模块B和模块C,那么重载模块A reload时,B和C的语句在重载时的重新运行,只会获取已经载入的B和C模块对象;
import B
import C
from imp import reload
reload(A)
如果想自动完成B和C等子部分的重载,即过渡性重载,可以编写一个通用工具:
- 扫描
__dict__
属性,检查每一项的type,以找到重新载入的嵌套模块; - 使用递归调用即可;
"""
reloadall.py: transitively reload nested modules
"""
import types
from imp import reload
def status(module):
print('reloading ' + module.__name__)
def transitive_reload(module, visited):
if not module in visited:
status(module)
reload(module)
visited[module] = None
for attrobj in module.__dict__.values():
if type(arrtobj) == types.ModulesType:
transitive_reload(arrtobj, visited)
def reload_all(*args):
visited = {}
for arg in args:
if type(arg) == types.ModuleType:
transitive_reload(arg, visited)
if __name__ == '__main__':
import reloadall
reload_all(reloadall) # reload myself
使用这一工具,导入其reload_all函数并将一个已经载入的模块的名称传给它;
除非使用过渡性工具,否则重载不会选取对嵌套文件的修改;
模块设计理念
高内聚 低耦合;模块应该少去修改其他模块的变量;
作为一条原则:如果需要把立即执行的代码和def一起混用,就要把def放在文件前面,把顶层代码放在后面;
from复制变量名 而不是连接
使用from从一个模块中导入两个变量名,在导入者内修改变量名,只会重设该变量名在本地作用域版本的绑定值,而不是另一模块中的变量名;
reload不会影响from导入的变量,因为from导入的是复制,相当于是本地变量了;
导入后重载,重载后重新执行from语句;(最好不要将from和reload结合使用)
使用from只能读取模块中已经赋值的变量名;
不要在递归导入中使用from!!!通常也要避免导入循环;如果避免不了请使用import和点号运算;
第六部分 模块 ———— 第25章 OOP:宏伟蓝图
类是在Python实现支持继承的新种类的对象的部件;
- 类建立:
class
语句 - 类基本就是产生对象的工厂,每次调用,就会产生一个有独立命名空间的新对象;
- 类可以建立命名空间的层次结构
- 通过提供特定协议方法,类可以定义对象来响应内置类型的几种运算;(切片 级联 索引 运算符等);由类使用的钩子,可以中断并实现任何内置类型运算;
读取属性 会启动 搜索对象链接的树:arribute object class superclass ... object
;
- 这种搜索就是继承,树中较低位置的对象继承了树中较高位置的对象拥有的属性;
- 搜索方向:从下到上、从左到右(Python中,一个类可以有两个超类);
- 当找到第一个属性所在时,会停止搜索,如果找不到就发生一个错误;
class A1:
def func_a1(self):
print('a1')
class A2:
def func_a1(self):
print('a2')
class A3(A1,A2):
pass
A3().func_a1() # a1
从搜索树上看,实例从类继承属性,类从更上层类类继承属性;
self是用来存储实例的内部变量名,类中方法通常会自动传入参数self;(self是需要明确写明的)
需要注意,如果是__init__
中初始化的属性的值与各个父类初始化的顺序有关:
class A1:
def __init__(self):
self.a1 = 'a1'
class A2:
def __init__(self):
self.a2 = 'a2'
class A3(A1,A2):
def __init__(self):
A1.__init__(self)
A2.__init__(self)
self.a3 = 'a3'
class A4:
def __init__(self):
self.a4 = 'a4'
class A5:
def __init__(self):
self.a5 = 'a5'
class A6(A4,A5):
def __init__(self):
A4.__init__(self)
A5.__init__(self)
self.a6 = 'a6'
self.a3 = 'a63' # 后初始化的 会生效
class A7(A3,A6):
def __init__(self):
A3.__init__(self)
A6.__init__(self)
self.a7 = 'a7'
print(A7().a1)
print(A7().a2)
print(A7().a3)
print(A7().a4)
print(A7().a5)
print(A7().a6)
print(A7().a7)
# a1
# a2
# a63
# a4
# a5
# a6
# a7
__init__
方法:
- 是运算符重载方法中的代表;当对应的类名调用时,Python会自动调用它们;这类方法是可选的,省略时不支持这类运算;
- 举例:重载表达式运算符
&
,也就是编写名为__and__
的方法来处理所需逻辑;
多态是指运算的意义取决于运算对象;
使用Python对象的pickle功能可以把对象存储在文件中,使用shelve模块,可以把类实例的pickle形式存储在以键读取的文件系统中;第三方开源ZODB系统也做了同样的事;
第26章 类代码编写基础
类对象提供默认行为,是实例对象的工厂;实例对象是程序处理的实际对象,各自都是独立的命名空间;
类对象来自于语句,而实例来自于调用;
类是产生多个实例的工厂;而模块只有一个副本会导入某一个程序中(因此必须调用reload来更新单个模块对象);
类对象:
- class语句创建类对象并将其赋值给变量名;class语句一般是在其所在文件导入时执行;
- class语句内的赋值语句会创建类(类对象)的属性;class语句的作用域会变成类对象的属性的命名空间;(有点像模块的全局作用域)
- 类属性提供了对象的状态和行为;
注意:类对象的属性记录状态信息和行为,可由这个类所创建的所有实例共享;
实例对象:
- 像函数那样调用类对象会创建新的实例对象;
- 每个实例对象
继承
类的属性并获得自己的命名空间; - 方法的第一个参数self会引用正在处理的实例对象;
Python的类对象,要灵活得多,你可以用点语法直接附一个新的属性给它;
类通过继承进行定制
类的属性存在于类层次结构中,越向下的层次越特定;模块的属性存在于单一、平坦的命名空间内,且不接受定制化;
Python中,实例从类继承,类从超类继承;
- 超类列在类开头的括号内;
- 类会继承超类中所有属性,就像实例继承类所定义的属性一样;
- 逻辑的修改是通过创建子类,而不是修改超类;
我们把继承树中较低位置发生的重新定义的、取代属性的动作称为重载
;
继承可以在外部组件(子类)内进行定制修改,类所支持的扩展和重用通常比函数和模块更好;
类是模块内的属性
类和模块都是附加属性的命名空间,但它们是非常不同的源代码结构:模块反应了整个文件,而类只是文件内的语句;
类可以截获Python运算符
这也是类与模块的主要差别之一:运算符重载;
- 加法、切片、打印和点号运算符等;
- 以双下划线命名的方法是特殊钩子;
- 当实例出现在内置运算时,这类方法会自动调用;
- 类可覆盖多数内置类型运算;
- 运算符覆盖方法没有默认值;
- 当类需要m模仿内置类型接口时才使用运算符重载,否则请使用简单的命名方法;
已经接触到的运算符重载方法:__init__
,也称为构造函数方法,用于初始化对象状态;
__init__
:构造新实例,调用,让类立即在其新建的实例内添加属性
;__add__
:类实例出现在+
表达式中;__str__
:打印一个对象(str内置函数或其他等价转换)时;
这类特殊命名方法会由 子类和实例继承,就像这个类中赋值的其他变量名;
__init__
通常可手动调用来触发超类的构造函数;
通常的实践说明,重载的运算符应该以与内置的运算符实现同样的方式工作;因为运算符重载其实只是表达式对方法的分发机制,可以在自己的类对象中以任何喜欢的方法解释运算符;
运算符重载是可选的;
一个简单的Python类:
class rec:pass
rec.name = 'Bob'
rec.age = 40
# 是不是特别像一个不需要额外字符串的字典,类本身也是对象
# 对于有属性的类对象 实例化之后 相应的也继承了附在类上的属性
x = rec()
y = rec()
x.name, y.name # ('Bob', 'Bob')
x.name = 'Sue'
rec.name, x.name, y.name # ('Bob', 'Sue', 'Bob')
__dict__
属性针对大多数基于类的对象的命名空间字典(一些类可能在__slots__
中定义属性,这是一个高级且少用的功能)
rec.__dict__.keys() # name age ...
list(x.__dict__.keys()) # ['name']
list(y.__dict__.keys()) # []
每个实例都连接至其类 以便于继承,这也是上边y的属性为空;
# 如果想查看这个继承链接可以查看 实例对象的__class__属性;
x.__class__ # class 'module.rec'
# 类也有一个__bases__属性,是其超类的元组
rec.__bases__ # (class 'object', )
奥秘:Python的类模型相当动态;类和实例只是命名空间对象,属性是通过赋值语句动态建立;
通常可以这样理解:
- 类的属性是在class语句填充的;
实例的属性则是通过在方法函数内对self属性进行赋值运算而创建的
;
不过,充重点在于:Python中的OOP其实就是在已连接的命名空间对象内寻找属性而已;
第27章 更多实例
- Person类
- Manager 一个定制的person类
- 实例存储到shelve的面向对象数据库中,进行持久化
模块名使用小写开头,类名首字母大写;
class Person:
# 可选参数 job 和 pay
def __init__(self, name, job=None, pay=0):
# 实例对象属性 通常在init中 为self赋属性值 来创建
# self就是新创建对象的实例
self.name = name
self.job = job
self.pay = pay
def lastName(self):
return self.name.splite()[-1]
def giveRaise(self, percent):
self.pay = int(self.pay * (1 + percent))
# 格式化打印输出 (交互式下使用__repr__)
def __str__(self):
return '[Person: %s, %s]' % (self.name, self.pay)
__init__
只不过是在产生一个实例时,自动调用的函数,它有特殊的第一个参数self,它由Python自动填充;- 方法只是附加给类并旨在处理那些类的实例的常规函数;实例是方法调用的主体,并且会自动传递给方法self参数;
class Manager(Person):
def giveRaise(self, percent, bonus=0.10):
# 添加涨薪基础参数 默认10%
# 扩展覆盖的接口 而非完全替代
Person.giveRaise(self, percent + bonus)
- 注意:
instance.method(args...)
等价于class.method(instance, args...)
! - Manager的实例,会调用子类的giveRaise,而Person的实例会调用父类的giveRaise,这就是多态;
定制子类构造函数
class Manager(Person):
def __init__(self, name, pay):
# 之前已经用到过:通过类名直接调用并显式地传递self实例,从而运行超类版本
Person.__init__(self, name, 'mgr', pay)
类对象属性是可以被继承的,类实例属性则必须通过调用父类的构造方法才会存在;如果能理解类实例继承自类对象,那么也就理解了为什么类属性可以被实例直接调用;
使用组合 代替 继承
实现一个例子:Manager类包裹Person类,Manager相当于控制层,它把调用向下传递到嵌套对象,而不是向上传递到超类方法;
class Person:
...save...
class Manager:
def __init__(self, name, pay):
self.person = Person(name, 'mgr', pay)
def giveRaise(self, percent, bonus=0.10):
self.person.giveRaise(percent + bonus)
# 使用内置函数getattr拦截未定义的属性的访问
def __getattr__(self, attr):
return getattr(self.person, arrt)
def __str__(self):
return str(self.person)
# Person 及Person子类实例的对象的复合
class Department:
def __init__(self, *args):
self.members = list(args)
def addMamber(self, person):
self.members.append(person)
def giveRaises(self, percent):
for person in self.menbers:
person.giveRaise(percent)
def showAll(self):
for person in self.members:
print(person)
__getattr__
:针对未定义的属性运行;__getattribute__
:针对所有的属性;- 这两个方法,都不能截取并委托向
__str__
这样的运算符重载方法属性,这就是上边例子中必须重新定义__str__
的原因;
使用内省工具
instance.__class__
提供了一个实例到创建它的类的链接class.__name__
(模块也有)类名class.__bases__
序列(元组),提供了超类的访问object.__dict__
属性提供了字典,将每个属性都附加到一个命名控件对象(模块、类、实例 均如此)
# 打印对象所有属性的一个类
class AttrDisplay:
def gatherAtts(self):
attrs = []
for key in sorted(self.__dict__):
attrs.append('%s=%s' % (key, getattr(self, key)))
return ', '.join(attr)
def __str__(self):
return '[%s: %s]' % (self.__class__.__name__, self.gatherAtts())
if __name__ == '__main__':
class TopTest(AttrDisplay):
count = 0
def __init__(self):
self.attr1 = TopTest.count
self.attr2 = TopTest.count + 1
TopTest.count += 2
class SubTest(TopTest):
pass
X, Y = TopTest(), SubTest()
print(X) # [TopTest: att1=0, attr2=1]
print(Y) # [SubTest: att1=2, attr2=3]
实例从类继承的类属性只附加到类,而没有向下复制到实例,这也是为什么上边例子中打印的属性没有count;
如果确实需要包含继承属性:可以把__class__
链接的类,使用__dict__
去获得其属性;之后迭代类的__bases__
属性爬升至更高的超类;
使用内置的
dir
调用的结果,会包含继承的名称,和上边这一段效果相同;
命名考虑:
- Python命名惯例:常常对于不想做其他用途的方法添加一个下划线前缀;
- 一种更好用但不常用的方法:在方法前使用两个下划线,Python自动扩展这样的名称,以包含类的名称,从而使它们变得真正唯一,这一功能通常叫做
伪私有类属性
;
把对象存储到数据库中
Pickle和Shelve:
- Pickle:任意Python对象和字节串之间的序列化/反序列化;
- dbm:实现一个可通过键访问的文件系统,以存储字符串;
- shelve:使用另两个模块按照键把Python对象存储到一个文件中;
Pickle:
- 通用的对象格式化和解格式化工具,对几乎任何Python对象,都能转换为字符串,通过这个字符串还能重建最初的对象;
- 通过在文件中存储一个对象的pickle字符串,就可以有效的持久化;
shelve模块:
- 在pickle上提供了额外的一层结构,允许按照键来存储pickle处理之后的对象;
- 使用pickle转换对象到字符串;并存储早一个dbm文件中;
- shelve的pickle化对象很像字典:通过键访问、指定键存储、使用
len in dict.keys
这样的字典工具来获取信息; - Shelve自动把字典操作映射到存储文件中的对象;
shelve提供了一个简单的数据库来按照键存储和获取本地Python对象;(缺乏高效的查询SQL、也没有企业级数据库的高级功能,如事务处理)
...
import shelve
db = shelve.open('persondb')
for object in (bob, sue, tom)
# 键是字符串 值是任何类型的Python对象
db[object.name] = object
len(db) # 3
list(db.keys())
for key in db:
db[key]
for key in sorted(db):
db[key]
db.close()
import glob
glob.glob('person*')
# ['person.py', 'person.pyc', 'persondb.bak', 'persondb.dat', 'persondb.dir']
注意这里使用shelve载入和使用对象,不一定必须导入Person或Manager类;因为Python对一个类实例进行pickle操作时,记录了其self实例属性,以及实例所创建于的类的名字和位置;在unpickle时,Python将自动重新导入该类并将得到的对象连接到它;
这也有缺点:可以pickle的类必须在一个模块文件的顶部代码,而这个模块可以通过sys.path模块的查找路径所列出来的目录来访问;正因如此,一般选择pickle更简单的对象,如字典或列表;
shelve和pickle适用于简单的对象存储;
其他范畴的工具
GUI:
- tkinter是Python自带的,允许快速构建简单的GUI;
- WxPython和PyQt是第三方GUI,使用起来更复杂,当往往更高级;
Web站点:
- 基于CGI脚本编程工具来构建;
- 或使用第三方Web开发框架:Django、web2Py等;
Web服务:
- 提供web服务接口API;以一种更加直接的形式返回数据,而不是嵌入一个回应页面的HTML中;
数据库:
- ZODB:开源的面向对象数据库系统(OODB),支持更多高级功能;
- 基于SQL的数据库:MySQL、SQLite等;
- Python内置了SQLite数据库;
ORM:
- 对象关系映射器,如SQLObject、SQLAlchemy,可自动实现关系表和行与Python类和实例之间的映射;
《Programming Python》更关注Python应用;
第28章 类代码编写细节
class语句并不是声明式的,而是对象的创建者并且隐含了赋值运算,运行时会产生类对象;直到Python运行定义的class语句前,类都不存在;
class语句内,任何赋值语句都会产生类属性,还有特殊的名称方法 用于重载运算符;class语句内赋值的变量名会变成类对象
中的属性,由所有实例共享,可通过类对象引用修改;
属性
对实例的属性进行赋值运算会在该实例内创建或修改变量名,而不是在共享的类中;通常继承只会在调用是发生,而不是赋值时,例如:y.spam会通过继承在类中查找,但是,对x.spam进行赋值只会把该变量附加在x本身上
;
class MixedNames:
data = 'spam' # 类属性会被实例对象继承,从而被所有没有自己的data属性的类实例共享
def __init__(self, value):
self.data = value
def display(self):
print(self.data, MixedNames.data) # 前者是实例属性,后者是类属性
一个属性:
- 附加在类上,变量名是共享的;
- 附加在实例上,变量名是属于每个实例的数据;
除非该类用
__setattr__
运算符重载方法 重新定义了属性赋值运算做了其他事;
方法
抽象来看:方法替实例对象提供了要继承的行为;
instance.method(args...) <=> class.method(instance,args...)
方法一定要通过self来取出或修改由当前方法调用或正在处理的实例的属性;
调用超类构造函数
class SubClass(SuperClass):
def __init__(self):
SuperClass.__init__(self) # 调用超类方法的方式均与此同
...custom code...
不这样调用,子类会完全取代超类的构造函数;如果在相同类中写几个
__init__
方法,只会使用最后一个;
- 静态方法:编写不预期第一个惩恶参数为实例对象的方法;
- 类方法:调用时接收一个类,而不是一个实例,用来管理基于每个类的数据;
实现SuperClass中delegate方法预期的action方法:
class SuperClass:
def delegate(self):
self.action()
class Provider(Super):
def action(self):
pass
x = Provider()
x.delegate()
抽象超类
上边例子,类似“填空”的代码结构一般就是OOP的软件框架;
所谓抽象超类,即类的部分行为默认是由子类所提供的;类的编写者偶尔会使用assert语句,使这种子类需求更为明显,或引发内置异常NotImplementedError
;
class SuperClass:
def delegate(self):
self.action()
def action(self):
# 如果这个方法被调用
assert False, 'action must bi defined' # False即为断言失败,给出带提示信息的异常
# or 直接产生NotImplementedError异常也是一种方式
raise NotImplementedError('action must bi defined')
class Provider(Super):
def action(self):
pass
更严格的“抽象超类”也得到了Python语法的支持,这样的抽象类不能够被实例化,除非在类树的较低层级定义了该方法;
from abc import ABCMeta, abstractmethod
# 在class头部使用一个关键字参数
class Super(metaclass=ABCMeta):
# 特殊的@装饰器语法
@abstractmethod
def action(self):
pass
# 抽象类中支持普通方法的实现
def delegate(self):
self.action()
抽象类通常用来定义一个期待的接口;继而在客户类中自动验证;
这里用到了两个高级语言工具:
- 元类声明;
- 装饰器语法;
命名空间的涅槃
解析变量名的规则:
- 无点号的变量名与作用域对应
- 点号属性名使用的是对象的命名空间
- 有些作用域会对对象的命名空间进行初始化(模块和类)
赋值将变量名分类(重点理解哈!!!):
- 作用域总是由源代码中的赋值语句的位置来决定的;
# manynames.py
X = 11 # 模块属性
def f():
print(X) # 模块属性
def g():
X = 22 # 函数内本地变量
print(X)
class c:
X = 33 # 类属性
def m(self):
X = 44 # 方法中本地变量
self.X = 55 # 实例属性
if __name__ == '__main__':
print(X) # 11
f() # 11
g() # 22
print(X) # 11
obj = C()
print(obj.X) # 33
obj.m()
print(obj.X) # 55
print(C.X) # 55
print(C.m.X) # fail
print(g.X) # fail
import manynames
X = 66
print(X) # 66
print(manynames.X) # 11
我们在17章学习到,一个函数在其外部修改名称时可能的,使用global
和nonlocal
;他们提供了写入访问,修改了赋值的命名空间绑定规则;
命名空间字典
对象的命名空间实际以字典形式实现,并且可由内置属性__dict__
显示;
class super:
def hello(self):
self.data1 = 'spam'
class sub(super):
def hola(self):
self.data2 = 'eggs' # self是进入实例命名空间的钩子
X = sub()
X.__dict__ # {}
X.__class__ # <class sub>
sub.__bases__ # (<class super>, )
super.__bases__ # (<class object>, )
Y = sub()
X.hello()
X.__dict__ # {'data1':'spam'}
X.hola()
X.__dict__ # {'data1':'spam', 'data2':'eggs'}
sub.__dict__.keys() # ['__module__', '__doc__', 'hola']
super.__dict__.keys() # ['__module__', '__doc__','__dict__','__weakref__', 'hola']
Y.__dict__ # {}
属性实际是Python的字典键:
X.data1 <= X.__dict__['data1']
- 不同的是,点号运算还会执行继承搜索;
dir(object)
类似于object.__dict__.keys()
调用,不过dir会排序其列表并引入一些系统属性,在Python3中,它包含了从所有类的隐含超类object类
继承的名称;
文档字符串出现的位置
已经编写为文档字符串,可以用某对象
其__doc__
属性来获取文档;
# docstr.py
"""
模块的doc
"""
def func(args):
"""
函数的doc
"""
pass
class spam:
"""
类的doc
"""
def method(self, arg):
"""
方法的doc
"""
pass
使用PyDoc工具,可以格式化报表中的所有文档字符串,
help(docstr)
;
模块可以通过编写Python文件或C扩展来创建,通过导入使用;类通过class语句创建,总是位于一个模块中;