结于2021-08-19;
OREILY的书籍,可读性很强,入门类,而且这本书很厚;
模块是最高级别的程序组织单元;实际中,模块往往对应着Python程序文件;
模块作用:
Python程序结构:
import会一次运行在目标文档中的语句从而建立其中的内容;
标准库模块:
导入是运行时运算,第一次导入时会执行三个步骤:
之后的导入会跳过这三个步骤,而只提取内存中已加载的模块对象;
只有被导入的文件才会在机器上留下.pyc
;顶层文件的字节码是在内部使用后就丢弃了;被导入文件的字节码则保存在文件中从而可以提高之后的导入速度;
导入实际是在执行模块中代码;如果需要再次导入,可通过调用reload强制处理;
如果想看已经导入的模块,可以导入sys并打印list(sys.modules.keys())
模块搜索路径:
.pth
文件的内容(如果有):
sys.path:
sys.path.append(dirname)
(从左到右搜索)注意 导入模块时 后缀名是省略的;
Python也支持最佳化字节码文件.pyo,这种文件在创建和执行时需要加上-O
标志位;要比普通的.pyc文件快一点;但它们并未被频繁使用;
from语句其实只是稍微扩展了import语句而已,它照常入了模块文件,但是多了一个步骤,将文件中的一个或多个变量名从文件中复制出来;
import aaa
from aaa import bbb, ccc
from aaa import * # aaa模块顶层所有赋值的变量名的拷贝,另外这种方式只能用于文件顶层,不能用于函数中;
import 和 from是可执行的语句,而非编译期间的声明;
from语句有破坏命名空间的潜质;
模块文件的顶层每一个赋值了的变量名都会变成该模块的属性;
__dict__
或dir(M)
获取;Python也会在模块命名空间内加一些变量名,如__file__
指明模块从哪个文件加载;
Python中,作用域绝对不会被函数调用或模块导入所影响;(静态作用域)
如果mod1中导入了mod2,mod2中导入了mod3,在使用时,可以
import mod2
然后mod2.mod3,但是无法import mod2.mod3
,这个语法对应下一章的包导入,包导入也会形成模块命名空间嵌套,导入语句会反映目录结构,而非简单的导入链;
import changer
changer.printer() # First version
from imp import reload
reload(changer) # 重载
changer.printer() # new version
重载使程序能够提供高度动态的接口;每次修改代码后,不需要停止重启;
除了模块之外,导入也可以指定目录路径:Python代码的目录就称为包;
包导入是把计算机上的目录变成另一个Python命名空间;而属性则对应于目录中所包含的子目录和模块文件;
包导入基础:
import dir1.dir2.mod
对应目录下的模块文件 dir1/dir2/mod.py
__init__.py
包文件:
__init__.py
这个文件;__init__.py
可以包含Python代码,就像普通模块文件,也可以是空的;__init__.py
包文件的作用:
首次导入某个目录
时,会自动执行该目录下__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模块
注意:
__init__.py
文件的Python模块的直接目录,这使得一个导入中可以使用A.B.C
目录路径语法;在A.B.C
的一条导入中,名为A的目录位于相对于sys.path的常规模块导入搜索,B是A中的另一个包子目录;C是一个模块或B中的其他可导入项;如果不是用作一个包的部分的一个文件中,甚至不允许相对导入语法;相对导入所引用的模块必须在包目录中存在;
__future__
模块__name__
变量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 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从一个模块中导入两个变量名,在导入者内修改变量名,只会重设该变量名在本地作用域版本的绑定值,而不是另一模块中的变量名;
reload不会影响from导入的变量,因为from导入的是复制,相当于是本地变量了;
导入后重载,重载后重新执行from语句;(最好不要将from和reload结合使用)
使用from只能读取模块中已经赋值的变量名;
不要在递归导入中使用from!!!通常也要避免导入循环;如果避免不了请使用import和点号运算;
类是在Python实现支持继承的新种类的对象的部件;
class
语句读取属性 会启动 搜索对象链接的树:arribute object class superclass ... object
;
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__
方法:
&
,也就是编写名为__and__
的方法来处理所需逻辑;多态是指运算的意义取决于运算对象;
使用Python对象的pickle功能可以把对象存储在文件中,使用shelve模块,可以把类实例的pickle形式存储在以键读取的文件系统中;第三方开源ZODB系统也做了同样的事;
类对象提供默认行为,是实例对象的工厂;实例对象是程序处理的实际对象,各自都是独立的命名空间;
类对象来自于语句,而实例来自于调用;
类是产生多个实例的工厂;而模块只有一个副本会导入某一个程序中(因此必须调用reload来更新单个模块对象);
类对象:
注意:类对象的属性记录状态信息和行为,可由这个类所创建的所有实例共享;
实例对象:
继承
类的属性并获得自己的命名空间;Python的类对象,要灵活得多,你可以用点语法直接附一个新的属性给它;
类的属性存在于类层次结构中,越向下的层次越特定;模块的属性存在于单一、平坦的命名空间内,且不接受定制化;
Python中,实例从类继承,类从超类继承;
我们把继承树中较低位置发生的重新定义的、取代属性的动作称为重载
;
继承可以在外部组件(子类)内进行定制修改,类所支持的扩展和重用通常比函数和模块更好;
类和模块都是附加属性的命名空间,但它们是非常不同的源代码结构:模块反应了整个文件,而类只是文件内的语句;
这也是类与模块的主要差别之一:运算符重载;
已经接触到的运算符重载方法:__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的类模型相当动态;类和实例只是命名空间对象,属性是通过赋值语句动态建立;
通常可以这样理解:
实例的属性则是通过在方法函数内对self属性进行赋值运算而创建的
;不过,充重点在于:Python中的OOP其实就是在已连接的命名空间对象内寻找属性而已;
模块名使用小写开头,类名首字母大写;
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自动填充;class Manager(Person):
def giveRaise(self, percent, bonus=0.10):
# 添加涨薪基础参数 默认10%
# 扩展覆盖的接口 而非完全替代
Person.giveRaise(self, percent + bonus)
instance.method(args...)
等价于class.method(instance, args...)
!
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
调用的结果,会包含继承的名称,和上边这一段效果相同;
命名考虑:
伪私有类属性
;Pickle和Shelve:
Pickle:
shelve模块:
len in dict.keys
这样的字典工具来获取信息;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:
Web站点:
Web服务:
数据库:
ORM:
《Programming Python》更关注Python应用;
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__ #
sub.__bases__ # (, )
super.__bases__ # (, )
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语句创建,总是位于一个模块中;