A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 易大帅 高级黑马   /  2017-3-17 11:47  /  2476 人查看  /  1 人回复  /   1 人收藏 转载请遵从CC协议 禁止商业使用本文

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的中...

1 个回复

倒序浏览
Python和JAVA最大的区别是启动?
来自宇宙超级黑马专属苹果客户端来自宇宙超级黑马专属苹果客户端
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马