python学习笔记四--类与模块(forever917)
类(New-Style-Class):
名字空间:
>>> class User(object): pass
>>> u = User()
>>> type(u)
<class '__main__.User'>
>>> u.__class__
<class '__main__.User'>
1、类型 (class) 存储了所有的静态字段和⽅法 (包括实例⽅法),而实例 (instance) 仅存储实例字段,从基类 object 开始,所有继承层次上的实例字段。官⽅文档将所有成员统称为 Attribute。
2、类型和实例各自拥有⾃己的名字空间。
3、访问对象成员时,就从这几个名字空间中查找,⽽非以往的 globals、locals。成员查找顺序: instance.__dict__ -> class.__dict__ -> baseclass.__dict__注意分清对象成员和普通名字的差别。就算在对象⽅法中,普通名字依然遵循 LEGB (local,enclosing,global,builtin)规则。
字段Field:
• 实例字段存储在 instance.__dict__,代表单个对象实体的状态。
• 静态字段存储在 class.__dict__,为所有同类型实例共享。
• 必须通过类型和实例对象才能访问字段。
• 以双下划线开头的 class 和 instance 成员视为私有,会被重命名。(module 成员不变)
1、可以在任何时候添加实例字段,仅影响该实例名字空间,与其他同类型实例⽆关。
2、要访问静态字段,除了 class.<name> 外,也可以⽤ instance.<name>。按照成员查找规则,只要没有同名的实例成员,那么就继续查找 class.__dict__。
3、私有字段以双下划线开头,⽆论是静态还是实例成员,都会被重命名: _<class>__<name>。
4、某些时候,我们既想使用私有字段,又不想放弃外部访问权限。
• 用重命名后的格式访问。
• 只⽤一个下划线,仅提醒,不重命名。
属性Property:
1、属性方法多半都很简单,⽤ lambda 实现会更加简洁。鉴于 lambda 函数不能使⽤赋值语句,故改用 setattr。还得注意别⽤会被重命名的私有字段名做参数。
2、属性总是比同名实例字段优先,尽可能用属性,而不是直接暴露内部字段。
方法:
1、实例⽅法和函数的最⼤区别是 self 这个隐式参数。
2、在⽅法里访问对象成员时,必须使⽤对象实例引用。否则会当做普通名字,依照 LEGB 规则查找。
3、个特殊的可选⽅法:
• __new__: 创建对象实例。构造⽅法 __new__ 可返回任意类型,但不同的类型会导致 __init__ ⽅法不被调用。
• __init__: 初始化对象状态。
• __del__: 对象回收前被调用。
继承:
1、除了所有基类的实例字段都存储在 instance.__dict__ 外,其他成员依然是各归各家。
2、如果派⽣类不提供初始化⽅法,则默认会查找并使用基类的⽅法。基类引用存储在 __base__,直接派⽣类存储在 __subclasses__。
3、可以用 issubclass() 判断是否继承⾃某个类型,或用 isinstance() 判断实例对象的基类。
4、成员查找规则允许我们⽤实例引用基类所有成员,包括实例⽅法、静态⽅法、静态字段。 但这⾥有个坑:如果派生类有一个与基类实例⽅法同名的静态成员,那么首先被找到的是该静态成员,⽽不是基类的实例⽅法了。因为派生类的名字空间优先于基类。故此,可以在派生类中创建一个同名实例方法,就可实现“覆盖(override)”。
5、多重继承成员搜索顺序,也就是 mro (method resolution order) 要稍微复杂⼀点。归纳⼀下就是:从下到上 (深度优先,从派⽣类到基类),从左到右 (基类声明顺序)。mro 和成员查找规则是有区别的,__mro__ 列表中并没有 instance。在多重继承中使用supper会引发一些问题,复杂化,建议改用组合模式实现类似的功能。
6、__bases__类型对象有两个相似的成员: __base__: 只读,总是返回 __bases__[0]。__bases__: 基类列表,可直接修改来更换基类,影响 mro 顺序。
7、抽象类:无法实例化。如果派⽣类也是抽象类型,那么可以部分实现或完全不实现基类抽象成员。派⽣类 Manager 也是抽象类,它实现了部分基类的抽象成员,⼜增加了新的抽象成员。这种做法在面向对象模式⾥很常见,只须保证整个继承体系⾛下来,所有层次的抽象成员都被实现即可。
8、开发类:在运行期也可以随意的改动对象,增加删除成员。增加成员时需要明确放在哪。
将实例方法放到 instance.__dict__ 是没效果的。因为不是 bound method,所以必须显式传递对象引用。正确的做法是放到 class.__dict__。静态⽅法必须⽤装饰器 staticmethod、classmethod 包装⼀下,否则会被当做实例⽅法。
在运⾏期调整对象成员,时常要用到几个以字符串为参数的内置函数。其中 hasattr、getattr 依照成员查找规则搜索对象成员,而 setattr、delattr 则直接操作实例和类型的名字空间。
__slots__ 属性会阻止虚拟机创建实例 __dict__,仅为名单中的指定成员分配内存空间。这有助于减少内存占用,提升执⾏性能,尤其是在需要⼤量此类对象的时候。可以⽤ dir() 和 inspect.getmembers() 获取实例成员信息。其派⽣类同样必须⽤ __slots__ 为新增字段分配存储空间 (即便是空 __slots__ = []),否则依然会创建 __dict__,反而导致更慢的执⾏效率。
9、操作符重载
__setitem__ 索引器,像序列或字典类型那样操作对象。
__call__ 像函数那样调⽤用对象,也就是 callable。
__dir__ 配合 __slots__ 隐藏内部成员。
__getattr__
• __getattr__: 访问不存在的成员。
• __setattr__: 对任何成员的赋值操作。
• __delattr__: 删除成员操作。
• __getattribute__: 访问任何存在或不存在的成员,包括 __dict__。
不要在这⼏个⽅法里直接访问对象成员,也不要⽤hasattr/getattr/setattr/delattr 函数,因为它们会被再次拦截,形成⽆限循环。正确的做法是直接操作 __dict__。而 __getattribute__ 连 __dict__ 都会拦截,只能⽤基类的 __getattribute__ 返回结果。
__cmp__ 通过返回数字来判断⼤小,⽽而 __eq__ 仅用于相等判断。
模块:
模块是一种组织形式,包含可执行代码、函数和类。
模块对象有⼏个重要属性:
• __name__: 模块名 <package>.<module>,在 sys.modules 中以此为主键。
• __file__: 模块完整⽂件名。
• __dict__: 模块 globals 名字空间。
虚拟机按以下顺序搜索模块 (包):
• 当前进程根目录。
• PYTHONPATH 环境变量指定的路径列表。
• Python 标准库目录列表。
• 路径⽂件 (.pth) 保存的目录 (通常放在 site-packages 目录下)。
虚拟机按以下顺序匹配目标模块:
• py 源码⽂件。
• pyc 字节码文件。
• egg 包⽂件或目录。
• so、dll、pyd 等扩展文件。
• 内置模块。
• 其他。
import:
>>> import imp
>>> imp.find_module("os")<span style="white-space:pre"> </span>#获取模块具体文件信息
(
! <open file '/System/.../2.7/lib/python2.7/os.py', mode 'U' at 0x1013aa420>,
! '/System/.../2.7/lib/python2.7/os.py',
! ('.py', 'U', 1)
)
import:
1、如果待导⼊对象和当前名字空间中已有名字冲突,可用 as 更换别名。需要注意,"import *" 不会导入模块私有成员 (以下划线开头的名字) 和 __all__ 列表中未指定的对象。
2、因为 import 实际导⼊的是⺫标模块 globals 名字空间中的成员,建议在模块中用 __all__ 指定可被批量导出的成员名单。
__import__:
1、和 import 关键字不同,内置函数 __import__() 以字符串为参数导⼊模块。导⼊的模块会被添加到sys.modules,但不会在当前名字空间中创建引⽤。
2、用 __import__ 导入 package.module 时,返回的是 package ⽽非 module。只有 fromlist 参数不为空时,才会返回目标模块。
3、关键字 import 总是优先查找当前模块所在目录,⽽ __import__、import_module 则是优先查找进程根目录。所以⽤ __import__、import_module 导⼊包模块时,必须带上包前缀。
4、当模块源⽂件发生变更时,可使⽤用内置函数 reload() 重新导入模块。新建模块对象依旧使⽤用原内存地址,只是原先被引⽤的内部成员对象不会被同步刷新。
5、imp 另提供了 load_source()、load_compiled() 等⼏个函数,可用来载入不在 sys.path 搜索路径列表中的模块文件。优先使⽤已编译的字节码⽂件,模块对象会被添加到 sys.modules。
>>> imp.load_source("add", "./test/add.py")<span style="white-space:pre"> </span>#类似reload,每次都会新建模块对象
<module 'add' from './test/add.pyc'>
6、"from <package> import *" 仅导入 __init__.py 的名字空间,而该文件通常⼜只是个空文件,这意味着没有任何模块被导⼊。此时就需要⽤ __all__ 指定可以被导入的模块名字列表,该定义无需将模块显式引⼊入到 __init__.py 名字空间。
7、有太多理由不建议使⽤ "import *",比如引入不需要的模块,意外 "覆盖" 当前空间同名对象等。换种做法,将要公开的模块和模块成员显式导⼊到 __init__.py 名字空间中,调用者只需 "import<package>",然后用 "<package>.<member>" 就可访问所需的目标对象。如此可规避上述问题,还有助于隐藏包的实现细节,减少外部对包⽂件组织结构的依赖。
某些时候,包内的⽂件太多,需要分类存放到多个目录中,但⼜不想拆分成新的包或⼦包。只要在 __init__.py 中⽤ __path__ 指定所有⼦目录的全路径即可 (⼦目录可放在包外)。
test <dir>
__init__.py
a <dir>
add.py
b <dir>
sub.py
$ cat test/__init__.py
__path__ = ["/mac/Desktop/py/test/a", "/mac/Desktop/py/test/b"]
还可以用 os.listdir() 扫描全部⼦目录,自动形成路径列表。
from os.path import abspath, join
subdirs = lambda *dirs: [abspath(join(__path__[0], sub)) for sub in dirs]
__path__ = subdirs("a", "b")
pkgutil 模块可获取包⾥面的所有模块列表,函数 iter_modules() 和 walk_packages() 的区别在于:后者会迭代所有深度的⼦包。pkgutil.get_data() 可读取包内任何⽂件内容。
查看原文>> 0
看过本文的人也看了:
Python知识结构图
Python模块探秘之EasyGui
(Python编程)一个简单的C扩展模块
Python之路: 模块篇
Python 模块学习:os模块
Python中的string模块详解——string的中...
|
|