您好,欢迎访问三七文档
当前位置:首页 > 财经/贸易 > 资产评估/会计 > python学习笔记--类与模块
类(New-Style-Class):名字空间:classUser(object):passu=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(methodresolutionorder)要稍微复杂⼀点。归纳⼀下就是:从下到上(深度优先,从派⽣类到基类),从左到右(基类声明顺序)。mro和成员查找规则是有区别的,__mro__列表中并没有instance。在多重继承中使用supper会引发一些问题,复杂化,建议改用组合模式实现类似的功能。6、__bases__类型对象有两个相似的成员:__base__:只读,总是返回__bases__[0]。__bases__:基类列表,可直接修改来更换基类,影响mro顺序。7、抽象类:无法实例化。如果派⽣类也是抽象类型,那么可以部分实现或完全不实现基类抽象成员。派⽣类Manager也是抽象类,它实现了部分基类的抽象成员,⼜增加了新的抽象成员。这种做法在面向对象模式⾥很常见,只须保证整个继承体系⾛下来,所有层次的抽象成员都被实现即可。8、开发类:在运行期也可以随意的改动对象,增加删除成员。增加成员时需要明确放在哪。将实例方法放到instance.__dict__是没效果的。因为不是boundmethod,所以必须显式传递对象引用。正确的做法是放到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:importimpimp.find_module(os)spanstyle=white-space:pre/span#获取模块具体文件信息(!openfile'/System/.../2.7/lib/python2.7/os.py',mode'U'at0x1013aa420,’’!'/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)spanstyle=white-space:pre/span#类似reload,每次都会新建模块对象module'add'from'./test/add.pyc'6、frompackageimport*仅导入__init__.py的名字空间,而该文件通常⼜只是个空文件,这意味着没有任何模块被导⼊。此时就需要⽤__all__指定可以被导入的模块名字列表,该定义无需将模块显式引⼊入到__init__.py名字空间。7、有太多理由不建议使⽤import*,比如引入不需要的模块,意外覆盖当前空间同名对象等。换种做法,将要公开的模块和模块成员显式导⼊到__init__.py名字空间中,调用者只需importpackage,然后用package.member就可访问所需的目标对象。如此可规避上述问题,还有助于隐藏包的实现细节,减少外部对包⽂件组织结构的依赖。某些时候,包内的⽂件太多,需要分类存放到多个目录中,但⼜不想拆分成新的包或⼦包。只要在__init__.py中⽤__path__指定所有⼦目录的全路径即可(⼦目录可放在包外)。testdir__init__.pyadiradd.pybdirsub.py$cattest/__init__.py__path__=[/mac/Desktop/py/test/a,/mac/Desktop/py/test/b]还可以用os.listdir()扫描全部⼦目录,自动形成路径列表。fromos.pathimportabspath,joinsubdirs=lambda*dirs:[abspath(join(__path__[0],sub))forsubindirs]__path__=subdirs(a,b)pkgutil模块可获取包⾥面的所有模块列表,函数iter_modules()和walk_packages()的区别在于:后者会迭代所有深度的⼦包。pkgutil.get_data()可读取包内任何⽂件内容。
本文标题:python学习笔记--类与模块
链接地址:https://www.777doc.com/doc-4355056 .html