2021-08-19 读书笔记:Python 学习手册(3)

读书笔记: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章学习到,一个函数在其外部修改名称时可能的,使用globalnonlocal;他们提供了写入访问,修改了赋值的命名空间绑定规则;

命名空间字典

对象的命名空间实际以字典形式实现,并且可由内置属性__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语句创建,总是位于一个模块中;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值