Python不依赖语言的特性去封装数据,而是通过遵循一定的数据属性或函数属性的命名来达到封装的效果。任何以单下划线开头的名字都应该是内部的、私有的。封装的意义在内部业务逻辑的数据隐藏。Python真正意义上的封装是用类的内外访问来区分的。并且它与Java的private属性有很大的区别,Python并没有强制性的拒绝外部类对私有属性的访问。换句话说,以单下划线或双下划线开头的私有属性仅仅只是一种约定。
(1)第一层封装: 使用以单下划线开头的私有属性。对象在调用时可直接使用“对象名 . 私有属性名”;
(2)第二层封装:使用以双下划线开头的私有属性。对象在调用时不能直接使用“对象名 . 私有属性名”。需要在双下划线开头的私有属性前加上“_类名”,如:“对象名 . _类名 . 私有属性名”。
(3)第三层封装:使用自定义的函数来提供接口。明确区分内外,实现内部业务逻辑的封装,并像java的setter和getter方法一样给出外部程序的使用自定义的接口函数,让该接口函数返回一个值私有的性。
Python的继承与C++的多继承非常相似,它的继承关系会被解释器解释为一个MRO列表,该MRO列表就是一个简单的所有基类的线性顺序列表。MRO列表的构造是通过一个C3线性化算法来实现的。Python和java一样,所有类的最终基类是Object。继承从某种角度上来讲是有害的,继承将类与类之间耦合到一块了。这大大的破坏了系统的封闭原则。继承真正有意义的是接口式的继承。Python提供的接口式的继承。Python 的继承顺序可由类使用__mro__查看。解释器查看顺序遵循以下三条规则:
(1)子类会先于父类被检查
(2)多个父类会根据他们在列表中的顺序进行检查
(3)如果对下一个类存在两个合法的选择,选择第一个父类
Python的多态和java的多态一样。多态的概念指出了对象如何通过他们共有的属性及函数来访问,不需要考虑他们之间的具体的类。
python的duck类型,很多人把它称为反射。反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。反射展示了某对象在运行期间是如何取得自己的状态的。如果传一个对象给你,你可以查出他的所有能力,那是极好的。如果Python不支持某种形式的反射功能,dir和type内置函数,将很难工作。还有那些特殊的属性,像__dict__、__name__及__doc__。反射的好处在于它可以事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用,这其实是一种“后期绑定”,你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能。python的反射比起java的反射更加灵活,用着用着就能体会到。
duck提供了四个非常重要的函数,这四个函数贼鸡儿六。
判断指定对象中有没有一个name字符串对应的方法或属性,如果有可以继续使用getatt
获取对象的属性值或这函数的地址。当获取对象的属性值不存在时返回None,而函数不存在时报错。如果要想函数不存在时不报错能并且能通知我们函数不存在,恶可以使用default参数。该函数的功能与对象调用属性相同。如:getattr(x, 'y') <==> x.y
为对象x的属性y设置或修改一个值,当属性不存在时,会将setattr设置的不存在的属性向对象的字典中追加。如:setattr(x, y, v) <==> x.y =v
删除对象的属性。如:delattr(x, y) <==> del x.y
在类中包含着很多默认的内置属性,当我们创建一个类时,如果有必要,我们可以进行重载。Python为我们提供了标准数据类型,以及丰富的内置函数,其实在很多场景下我们都需要基于标准数据类型来定制我们自己的数据类型,新增或者改写方法,这就用到了继承/派生知识。
_ _getattr_ _ 属性不存在时自动触发
_ _setattr_ _ 设置属性时自动触发
_ _delattr_ _ 删除属性时自动触发
_ _getattribute_ _ 程序调用时(无论属性存在与否),当它使用raise关键字抛出AttributeError异常时,由_ _getattr_ _捕捉处理
当对象在内存中被释放时,自动触发执行。如果产生的对象仅仅只是python程序级的(用户级),那么无需定义_ _del_ _,如果产生的对象的同时还会向操作系统发起系统调用,即一个对象有用户级与内核级两种资源,比如(打开一个文件,创建一个数据库链接),则必须在清除对象的同时回收系统资源,这就用到了_ _del_ _。需要注意的是,我们删除实例的属性时是不会的执行_ _del_ _的,只有在删除实例时才会触发_ _del_ _。系统在回收内存时也会触发它。
比如当我们创建数据库类时,用该类实例化出数据库链接对象,对象本身是存放于用户空间内存中,而链接则是由操作系统管理的,存放于内核空间内存中。当程序结束时,python只会回收自己的内存空间,即用户态内存,而操作系统的资源则没有被回收,这就需要我们重载del,在对象被删除前向操作系统发起关闭数据库链接的系统调用,回收资源。
Python为每一个实例提供了一个独自的字典。我们常用的“.”运算符在底层其实操作的是字典。__slots__对所有的实例对象取消了字典,实例使用一种更加紧凑的内部表示,通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个字典,这跟元组或列表很类似。类变量列出的属性名在内部被映射到这个数组的小标上。它限制了实例对象的访问操作,当访问的属性不存在时,会直接报错。
当我们一个类有很多的实例时,每一个实例都会有一个单独的内存空间,如果使用__slots__确实是能够减少资源空间的占用,但是python很多特性都依赖于普通的基于字典的实现。而使用类变量的弊端是不再支持基于字典的特性,比如多继承等等。
__format__内置属性是一种自定义的格式化。通常 Python自带的format格式化通常无法满足我们的需求,因此我们需要对其进行修改。
我们知道构造函数的执行是由创建实例对象触发的,即:对象 = 类名()。Python中一切皆对象,我们创建的类本身也是一个对象。内置属性__call__的执行是由于对象创建后被作为函数使用而触发的。重载__call__的意义在于将对象作为函数使用,__call__不影响对象的生命周期,不影响一个对象的构造和析构。
在迭代器中我们说实现了迭代器协议的对象称为可迭代对象。而迭代器协议强调对象必须提供一个next或_ _next_ _()方法,并且执行该方法只有两种决策,要么返回迭代中的下一项,要么者引起一个StopIteration异常,以终止迭代。其实这个协议也不过是通过内置属性实现的。我们知道一个普通的对象是无法对它进行遍历的,但是Python提供了_ _next_ _和_ _iter_ _这两个内置属性供我们重载对象。通过这两个内置属性的使用使得普通对象可变为可迭代对象。而迭代器的实现也基于此。
当我们使用str函数并以对象作为参数的时候,返回的是一个对象的地址及所属类。它的默认执行路径是:str( obj ) ==> obj._ _str_ _()。str 与 repr 都用于控制输出,当他们在程序中共存时,repr不会执行;当程序中只有repr时,虽然它被用于解释器,但是在控制台会执行,因为str找不到,所以它是str的替代品。