一.面向对象
面向对象和面向过程的区别
1.面向对象可以说是面向过程的一个封装
类的定义
类是某个具体对象的抽象。
类的作用为根据抽象的类生成具体的对象。对象可以抽象为类,类可以实例化具体的对象
类属性的增删改查
增改查都记得p.age即可;查找所有属性是p.__dict__;删除:del p.age。
通过实例化对象的属性查询可以查到类的属性,但是不可以通过实例化对象的属性修改类的属性,只可能修改本身的实例化属性或者新增一个实例化对象的属性。查找实例化对象的属性时会现在自己的属性中找,如果自己的没有才会去类属性中找,但是赋值的话只能赋值自己的实例化对象中的属性值。类属性的增加、修改和删除都只能在自己的类上操作,只有查询有时候可以通过实例化对象查询到。
注意:类和对象的属性都存储在__dict__字典中,类当中的__dict__属性默认是不可以修改的,而对象中的__dict__属性是可以修改的。
限制对象属性的添加,只需要在类中加一个__slots__ = [value],就 只能添加属性为value的属性。
类属性和对象属性的区别:python万物皆对象,所以其实类也是对象,访问类的属性是类.属性,访问实例对象属性是实例.属性,记得不一样的是在一个类里面self代表的是实例,如果想引用类的属性需要用类.属性。在修改时如果写的是对象.属性则修改或新增的是对象的属性,跟类的无关。
方法相关知识点
概念:描述一个目标的行为动作。和函数非常类似都可以被调用之后执行一系列的行为动作,但是主要区别为二者的调用方式不同。
实例方法&类方法&静态方法的划分依据:类方法接收的第一个参数是类cls,调用方法为:类.方法()或者实例.方法,会自动找到实例对应的类;
实例方法接收的第一个参数是示例self,调用方法为object.方法();
静态方法没有第一个参数的限制,调用方法为类.方法()。
三种不同的方法都是存储在类对象的__dict__里面,而不是实例对象里面。
不同方法的不同调用方式的原则是不管是自己传递还是解释器帮我们处理,最终要保证不同类型的方法第一个接收到的参数是他们想要的类型。
python万物皆对象,函数也是对象。
函数也是可以赋值的,也是可以传递的。
属性分为类属性和实例属性,方法分为类方法、实例方法和静态方法。
类相关补充知识点
元类:创建类对象的类。type(元类),可以用type来直接创建一个类type(str,tuple,__dict__),截至目前有两个创建类的方法,一个是class;一个是type。元类并不都是type,可以通过__metaclass__来找(指明)元类,找到元类之后有利于在元类上进行操作和控制。
元类检索机制:检索类中是否存在__metaclass__属性——检索父类中是否存在__metaclass__属性——检索模块中是否存在__metaclass__属性——通过内置的type这个元类来创建这个类对象。
元类的应用场景:拦截类的创建;修改类;返回修改之后的类;
补充:项目生成文档用pydoc模块。
公有属性、私有化属性的访问权限:公有化属性在类内部、子类内部、模块内其他位置和跨模块都能访问;私有化属性(_a)在类内部、子类内部、模块内其他位置(警告)都能访问,跨模块(报错)不能访问,加上__all__=[变量]就可以访问了;私有化属性(__a)只在类内部都能访问;私有属性会起到保护作用。私有属性进行了名字重整机制,可以通过__dict__查看重整之后的属性。
变量添加下划线的规范:xx_是为了与系统内置的函数做区分的方式;xx__是系统内置的.
只读属性:通常是对于实例的属性,是内部根据外部不同场景自动修改的,外部不能修改只能读取,通过全部隐藏+部分公开的方式可以得到只读属性,其实就是创建一个私有化属性,再构造一个方法来return这个私有化属性,优化的方法是设置继承object,在方法上加上一个装饰器@property,把方法变成属性的方式来使用,这样的好处是如果有人对进行赋值操作则会报错。
经典类和新式类:经典类是没有继承自object;新式类是继承自object。用__bases__可以查看,python 3.x的版本下默认是新式类。property在新式类和经典类中的使用是不同的,在经典类中只能关联可读的,一般使用新式类比较好。
__setattr__可以避免实例.属性=值这种方式来新增属性,在这个方法内部是通过self.__dict__[key]=value来赋值的,可以解决部分伪私有问题。
方法私有化:在方法前面加上两个下划线。
注意:用实例.__class__是查看哪个类创建了这个实例,用实例.__bases__是查看这个实例继承自哪个对象,新式类都继承自object,所以都含有object内部的一些方法和属性,type也继承自object,object是由type实例化出来的。其中 type的父类是object object的类型(元类)是type 所以这是鸡和鸡蛋的问题 可以认为object是制作任何内容原材料,而type是最底层的分类类型
不同的内置方法可以得到不同的操作,如下
信息格式化操作:__str__(面向用户) __repr__(面向开发人员)
调用操作:__call__是对象具备当作函数,来调用的能力。一个类实例也可以变成一个可调用对象,只需要实现一个特殊方法__call__(),直接实例(参数)就可以了。
索引操作:可以对一个实例对象进行索引操作,增/改、查、删,__setitem__ __getitem__ __delitem__.
切片操作:可以对一个实例对象进行切片[start:end:step:]操作,增/改、查、删,__setitem__ __getitem__ __delitem__,跟上面不同的是要判断一下key是slice[start:end:step:]类型。
比较操作:可以自定义对象“比较大小,相等及真假”规则。自己创建类,通过类自己创建两个实例,自定义比较实例的大小。__eq__(相等),__nq__(不相等),__gt__(大于),__ge__(大于等于),__lt__(小于),__le__(小于等于),__bool__(上下文布尔值)在方法中写下具体的比较规则就行。注意如果对于反向操作的比较符,我们只定义了其中一种比较方法,但使用的是另外一种比较运算,那么解释器会采用调用参数的方式调用该方法,这种可以简化代码,但是参数的调换注意一下。注意二,不能对这些运算进行叠加,比如用大于和等于的运算规则,不可以进行大于等于的运算比较,但是可以通过装饰器@fuctools.total_ordering来实现补全,其中一个比较运算满足了就不会执行另外的一个运算了。
遍历操作:一般通过两种方法,即for in 或者 next(生成器)。__getitem__ 方法中要设置终止条件。__iter__(优先级高于__getitem__),__next__不是很懂!!迭代器一定能通过next访问,但是能通过next访问的不一定是迭代器。只有实现了__iter__和__next__两种方法的才能叫迭代器。一个可迭代对象一定能够通过for in 遍历,但是反之不成立,比如用__getitem__同样可以用for in 访问。__iter__是返回的是迭代对象,__next__是取取来值了。iter()函数的两个方式,一是直接将实例塞进来,二是第一个参数为可调用对象(对象中必须有__call__方法来来保证对象是可调用的),第二个参数为终止值。
描述器
可以描述一个属性操作的对象。描述器的本质是一个对象。简单说,就是一个类里面的属性指向了一个特殊的对象,这个对象实现了__set__,__get__,__delete__三种方法,通过这三种方法实现对属性值的过滤,这样的对象称为描述器。解释器会自动识别描述器并进行转换,所以我们只需要注意外界的赋值和方法的实现。
一般是创建一个类(描述器),将另外一个类中的属性当作先前类(描述器)的实例,外界赋值的时候,也会通过类(描述器)中的规定进行过滤,类中类吧^-^.注意最后的类定义好之后,通过实例来调用属性值,不要直接通过类来调用属性值,才能将描述器中的增删改查都访问到。描述器只在新式类才有效。注意描述器的调用方法很可能会被其他方法覆盖,比如__getattribute__方法的优先级高于__get__.
实现了get set方法的叫做资料描述器,仅仅实现get方法的叫做非资料描述器。优先级为资料描述器>实例属性>非资料描述器。
数据存储在实例中,不要存储在描述器中。
通过类来执行装饰器:在初始化方法中保存所传递过来的函数本身,在__call__中执行之前传递过来的函数,在执行之前可以做一些额外的操作。
定义方式一:property
生命周期
指的是一个对象从诞生到消亡的过程。涉及到监听对象生命周期和内存管理机制两个方面。
监听对象生命周期:__new__(创建一个对象时,给这个对象分配内存的方法) __init__ __del__(释放内存)
内存管理机制:在存储方面 a.在python中万物皆对象 b.所有对象都会在内存中开辟一片空间进行存储 c. 对于整数和短小字符,python会进行缓存,不会创建多个对象。 d.容器对象,存储的其他对象,仅仅是其他对象的引用,并不是其他对象本身,存储的仅仅是地址。在垃圾回收方面 a. 查看对象引用次数 import sys sys.getrefcount(对象),当一个对象引用增加一时内部的引用机制会自动增加1,删除时内部的引用机制会自动减少1。对象作为一个参数传入函数中和对象作为一个元素存储在容器中(l=[p])这两种情况的引用都会增加1。对象的别名被赋予新的对象,一个对象离开他的作用域,对象所在的容器被销毁,这几种情况都会导致引用减1.
objgraph.count(类)可以查出来由这个类创建出来的实例的个数,循环引用会造成内存泄漏,可以通过垃圾回收机制来解决这个问题。
垃圾回收机制主要作用是从经历过“引用计数器机制”仍未被释放的对象中找到“循环引用”,干掉相关对象。主要机制是检测每一个容器对象,如果该容器对象引用了其他容器对象则引用次数减1,如果引用次数变为0则会被回收。为了增加性能,python会使用“分代回收”机制来检测。垃圾回收器中新增的对象个数-消亡的对象个数达到一定阈值时才会触发这个垃圾回收机制。
可到达引用指的时访问对象的真实入口(实例本身,而不是属性,属性是不可到达引用,因为需要通过实例才能访问)。
使用弱引用weakref.ref()会直接被引用计数器释放掉,不需要启动垃圾回收机制。
注意在python2.x版本中的循环引用对象中只要有一个实现了__del__方法,那就不会自动进行内存的释放。
面向对象的三大特性
封装:可以把类当作模块导入。
继承:一个类“拥有”(资源的使用权)另外一个类的“资源”(非私有的属性和方法)方式之一。拥有资源的继承,资源的使用,资源的覆盖和资源的累加这些特性。
资源的使用分为单继承,无重叠的多继承和有重叠的多继承。
资源使用的调用顺序注意:python2.2以前没有新式类,调用顺序会违背重复可用原则,所以python2.2以前对于有重叠的继承需要自己写代码来规避这种错误,python2.2以后需要表明继承自object,变成新式类,python3.x以后的直接就是新式类了。
在python2.2版本中针对新式类产生的MRO Method Resolution Order(方法解析顺序)原则是“深度优先”从左到右原则,并不是广度优先。深度优先是先进后出。并且python2.2版本会产生无法检测出有问题的继承和违背“局部优先”原则等问题。
python2.3以后针对新式类都采用了C3算法,通过C3算法可以检测出来有问题的继承。L(A)=[A]+merge(L(B),L(C),[B,C]).
C3算法的本质根本就不是拓扑排序算法!切记!
需要掌握根据一个项目会设计其继承结构,最低标准是会根据inspect.getmro()来查看某个类的资源查找顺序。
优先级比较高的类能覆盖优先级比较低的类的一些资源。
资源的累加注意使用super()方法,调用高优先级类,可以沿着参数二的MRO链条找到参数一的下一级节点,取调用对应方法。
不要把通过super来调用其他资源与通过类来调用其他资源的方法混在一起!
多态,两种含义,即一个类所延伸的多种形态或者调用时的多种形态。在python中没有真正意义上的多态,也不需要多态!??
补充:抽象类和抽象方法指的是抽象出来的东西,并不能具体使用,比如Animal类,“叫”方法。可以用abc模块来创建抽象类和抽象方法。
面向对象遵循的原则:SOLID
S单一职责原则,一个类只负责一项原则;
O开放封闭原则,对扩展开放,对修改关闭;
L里氏替换原则,使用基类引用的对象必需能时用继承类的对象;
I接口分离原则,如果一个类包含了过多的接口方法,而这些方法在使用过程中并非“不可分割”,那么应当把他们进行分离;
D依赖倒置原则,高层模块不应该直接依赖低层模块,他们应该依赖抽象类或者接口。
网友评论