python---面向对象编程(重要)
什么是面向对象?
类、方法、类变量的定义
实例引用、实例变量使用
一、对面向对象的理解
1、面向对象的编程---object oriented programming
简称:OOP,是一种编程的思想。OOP把对象当成一个程序的基本单元,一个对象包含了数据和操作数据的函数。面向对象的出现极大的提高了编程的效率,使其编程的重用性增高。
2、python面向对象的重要术语:
- 类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。类是抽象的概念,一类事物。(创建类时补充:特征对应属性,行为对应方法)
- 类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
- 数据成员:类变量或者实例变量, 用于处理类及其实例对象的相关的数据。
- 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
- 局部变量:定义在方法中的变量,只作用于当前实例的类。
- 实例变量:在类的声明中,属性是用变量来表示的。这种变量就称为实例变量,是在类声明的内部但是在类的其他成员方法之外声明的。
- 继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例图,Dog是一个Animal)。
- 实例化:创建一个类的实例,类的具体对象。
- 方法:类中定义的函数,对外提供的(接口、函数)服务。
- 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。
- 实例引用:实例化一个对象。
实例变量:以‘self.变量名’的方式定义变量。
补充:
1、多态(polymorphism):一个函数有多种表现形式,调用一个方法有多种形式,但是表现出的方法是不一样的。
2、继承(inheritance)子项继承父项的某些功能,在程序中表现某种联系
3、封装(encapsulation)把需要重用的函数或者功能封装,方便其他程序直接调用
4、类:对具有相同数据或者方法的一组对象的集合
5、对象:对象是一个类的具体事例
6、实例化:是一个对象事例话的实现
7、标识:每个对象的事例都需要一个可以唯一标识这个事例的标记
8、实例属性:一个对象就是一组属性的集合
9、事例方法:所有存取或者更新对象某个实例一条或者多条属性函数的集合。
10、类属性:属于一个类中所有对象的属性,
11、类方法:那些无须特定的对性实例就能够工作的从属于类的函数。
3、函数和面向对象编程的区别
相同点:都是把程序进行封装、方便重复利用,提高效率。
不同点:函数重点是用于整体调用,一般用于一段不可更改的程序。仅仅是解决代码重用性的问题。
而面向对象除了代码重用性。还包括继承、多态等。使用上更加灵活。
4、python类实例化的实现
说明:
1、类的实例化就是在类对象后面加上一个括号,就是调用类的实例化方法,完成实例化。实例化就真正创建一个该类的对象(实例)。
2、类实例化后一定会获得一个对象,就是实例对象。
实例:
上面的tom,jerry都是Myclass类的实例,通过实例化生成了2个实例,每次实例化后获得的实例,是不同的实例,即使使用同样的参数实例化,也得到不一样的对杨
类实例化后,得到一个实例对象,实例对象会绑定方法,调用方法时参使用jerry.foo()的方式
但是函数签名是foo(self),少传一个参数self吗?
这个self就是jerry,Python会把方法的调用者作为第一个参数传入self的实参传入
self.name就是jerry对象name,name是保存在了jerry对象上,而不是Myclass类上,所以称为实例变量。
这边可以参考:造狗文章
二、封装、继承、多态
1、封装(Encapsulation)
封装,顾名思义就是将内容封装到某个地方,以后再去调用被封装在某处的内容。
对于面向对象的封装来说,其实就是使用构造方法将内容封装到 对象 中,然后通过对象直接或者self间接获取被封装的内容。
(python中同样使用关键字class创建一个类,类名称第一个字母大写,可以带括号也可以不带括号)
封装:是将内容封装到某个地方,以后再去调用被封装在某处的内容,所以,在使用面向对象的封装特性时,需要:
1、将内容封装到某处
2、从某处调用被封装的内容
1.通过对象直接调用被封装的内容:对象.属性名
2.通过self间接调用被封装的内容:self.属性名
3.通过self间接调用被封装的内容:self.方法名()
构建方法_ _init_ _与其他普通方法不同的地方在于,当一个对象被创建后,会立刻调用该构造方法。自动执行构造方法里面的内容
实例:创建一个类People,拥有的属性为姓名, 性别和年龄, 拥有的方法为购物,玩游戏,学习;实例化对象,执行相应的方法。 显示如下:
小明,18岁,男,去西安赛格购物广场购物
小王,22岁,男,去西安赛格购物广场购物
小红,10岁,女,在西部开源学习
代码:
运行结果:
2、继承(Inheritance)
继承,面向对象中的继承和现实生活中的继承相同,即:子可以继承父的内容。
例如:
猫可以:喵喵叫、吃、喝、拉、撒
狗可以:汪汪叫、吃、喝、拉、撒
公共的部分就是 吃、喝、拉、撒
如下实现:
注意: 关于多继承
- 在Python中,如果父类和子类都重新定义了构造方法init( ),在进行子类实例化的时候,子类的构造方法不会自动调用父类的构造方法,必须在子类中显示调用。
- Python的类可以继承多个类,Java和C#中则只能继承一个类
- Python的类如果继承了多个类,那么其寻找方法的方式有两种,分别是:深度优先和广度优先
- 当类是经典类时,多继承情况下,会按照深度优先方式查找,当类是新式类时,多继承情况下,会按照广度优先方式查找
经典类和新式类,从字面上可以看出一个老一个新,新的必然包含了跟多的功能,也是之后推荐的写法,从写法上区分的话,如果 当前类或者父类继承了object类,那么该类便是新式类,否则便是经典类。
2.1 继承的定义
2.2 单继承
单继承:子类只继承一个父类。
Python中继承语法格式如下:
例 1 类的继承示例,子类继承父类方法:
运行结果:
例2. 让Man继承自Master类,并调用继承的showSkill方法。
返回:
例 3 类继承的应用
2.3. 方法重写
在继承关系中,子类会自动拥有父类定义的方法。如果父类定义的方法不能满是子类的需求,子类可以按照自己的方式重新实现从父类继承的方法,这就是方法的重写。
重写使得子类中的方法覆盖掉跟父类同名的方法,但需要注意,在 子类中重写的方法要和父类被重写的方法 具有相同的方法名和参数列表。
例 1 子类重写父类的方法
运行结果
例 2 重写父类方法
python中重写父类方法不需要override等任何的关键字,直接把原来的方法重写一遍就好。
打印返回:
例子3 子类对父类方法的重写
对基类/父类的方法需要修改,可以在子类中重构该方法。如下的talk()方法
2.4. 多继承
多继承:子类继承多个父类
一个子类存在多个父类的现象称为多继承。
Python 语言是支持多继承的,一个子类同时拥有多个父类的共同特征,即子类继承了多个父类的方法和属性。
扩展:
java不支持多继承,只支持单继承(即一个类只能有一个父类)。但是java接口支持多继承,即一个子接口可以有多个父接口。(接口的作用是用来扩展对象的功能,一个子接口继承多个父接口,说明子接口扩展了多个功能,当类实现接口时,类就扩展了相应的功能)。
多继承是在子类名称后的括号中标注出要继承的多个父类,并且多个父类之间使用逗号分隔,其语法格式如下:
例 1 多继承示例
运行结果:
例2:多继承举例。
我们可以在括号中写入多个父类,并用逗号隔开,就可以表示多继承。
需要注意的是,如果父类中有同名的方法或属性被子类继承,子类优先继承第一个父类的方法。
返回:
2.5. 调用父类方法
我们可以通过super().方法名,来调用父类中的 方法,哪怕该方法在子类中被重写了。
打印返回:
2.6. 私有属性与方法
我们可以在方法或属性名前加上 __ 来表示私有,在类的继承中,只有父类的私有方法无法被继承,其他属性与方法都将被继承,但私有属性不能在子类中直接访问,但我们可以同过继承的父类公有方法来对其访问。
下面我们定义私有属性money,并创建共有方法set_money与get_money从子类中对其进行访问。
返回打印:
2.7 构造函数的继承
如果我们要给实例 c 传参,我们就要使用到构造函数,那么构造函数该如何继承,同时子类中又如何定义自己的属性?
继承类的构造方法:
1.经典类的写法: 父类名称.__init__(self,参数1,参数2,...)
2. 新式类的写法:super(子类,self).__init__(参数1,参数2,....)
如果我们只是简单的在子类Chinese中定义一个构造函数,其实就是在重构。这样子类就不能继承父类的属性了。所以我们在定义子类的构造函数时,要先继承再构造,这样我们也能获取父类的属性了。
子类构造函数基础父类构造函数过程如下:
实例化对象c ----> c 调用子类__init__() ---- > 子类__init__()继承父类__init__() ----- > 调用父类__init__()
2.8. 打印继承关系
通过__mro__()方法我们可以得到当前类继承的所有类。
3、多态(Polymorphism)
首先Python不支持多态,也不用支持多态,python是一种多态语言,崇尚鸭子类型。
在程序设计中,鸭子类型(英语:duck typing)是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由当前方法和属性的集合决定。这个概念的名字来源于由James Whitcomb Riley提出的鸭子测试,“鸭子测试”可以这样表述:
“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
在鸭子类型中,关注的不是对象的类型本身,而是它是如何使用的。例如,在不使用鸭子类型的语言中,我们可以编写一个函数,它接受一个类型为鸭的对象,并调用它的走和叫方法。在使用鸭子类型的语言中,这样的一个函数可以接受一个任意类型的对象,并调用它的走和叫方法。如果这些需要被调用的方法不存在,那么将引发一个运行时错误。任何拥有这样的正确的走和叫方法的对象都可被函数接受的这种行为引出了以上表述,这种决定类型的方式因此得名。
鸭子类型通常得益于不测试方法和函数中参数的类型,而是依赖文档、清晰的代码和测试来确保正确使用。从静态类型语言转向动态类型语言的用户通常试图添加一些静态的(在运行之前的)类型检查,从而影响了鸭子类型的益处和可伸缩性,并约束了语言的动态特性。
例子:
没有谁规定test方法是接收的参数是什么类型的。test方法只规定,接收一个参数,调用这个参数的prt方法。在运行的时候如果这个参数有prt方法,python就执行,如果没有,python就报错,因为abcde都有prt方法,而f没有,所以得到了上边得结果,这就是python的运行方式。
三、面向对象的各种方法
1、静态方法 (用这个装饰器来表示 @staticmethod )
意思是把 @staticmethod 下面的函数和所属的类截断了,这个函数就不属于这个类了,没有类的属性了,只不是还是要通过类名的方式调用
看个小例子:
因为用静态方法把eat这个方法与Person这个类截断了,eat方法就没有了类的属性了,所以获取不到self.name这个变量。
2、类方法 (用这个装饰器来表示 @classmethod)
类方法只能访问类变量,不能访问实例变量
看个例子:
因为self.name这个变量是实例化这个类传进去的,类方法是不能访问实例变量的,只能访问类里面定义的变量
3、属性方法 (用这个装饰器表示 @property)
把一个方法变成一个静态属性,属性就不用加小括号那样的去调用了
看个小例子:
因为eat此时已经变成一个属性了, 不是方法了, 想调用已经不需要加()号了,直接d.eat就可以了
四、高级面向对象
1. 成员修饰符
python的类中只有私有成员和公有成员两种,不像c++中的类有公有成员(public),私有成员(private)和保护成员(protected).并且python中没有关键字去修饰成员,默认python中所有的成员都是公有成员,但是私有成员是以两个下划线开头的名字标示私有成员,私有成员不允许直接访问,只能通过内部方法去访问,私有成员也不允许被继承。
上面就是类里面的私有成员了。
2、特殊成员
1.__init__
__init__方法可以简单的理解为类的构造方法(实际并不是构造方法,只是在类生成对象之后就会被执行),之前已经在上一篇博客中说明过了。
2.__del__
__del__方法是类中的析构方法,当对象消亡的时候(被解释器的垃圾回收的时候会执行这个方法)这个方法默认是不需要写的,不写的时候,默认是不做任何操作的。因为你不知道对象是在什么时候被垃圾回收掉,所以,除非你确实要在这里面做某些操作,不然不要自定义这个方法。
3.__call__
__call__方法在类的对象被执行的时候(obj()或者 类()())会执行。
4.__int__
__int__方法,在对象被int()包裹的时候会被执行,例如int(obj)如果obj对象没有、__int__方法,那么就会报错。在这个方法中返回的值被传递到int类型中进行转换。
5.__str__
__str__方法和int方法一样,当对象被str(obj)包裹的时候,如果对象中没有这个方法将会报错,如果有这个方法,str()将接收这个方法返回的值在转换成字符串。
6.__add__
__add__方法在两个对象相加的时候,调用第一个对象的__add__方法,将第二个对象传递进来,至于怎么处理以及返回值,那是程序员自定义的,就如下面的例子:
7.__dict__
__dict__方法在类里面有,在对象里面也有,这个方法是以字典的形式列出类或对象中的所有成员。就像下面的例子:
8.__getitem__ __setitem__ __delitem__
__getitem__方法匹配 对象[索引] 这种方式,__setitem__匹配 对象[索引]=value 这种方式,__delitem__匹配 del 对象[索引] 这种方式,例子如下:
9.__getslice__ __setslice__ __delslice__
这三种方式在python2.7中还存在,用来对对象进行切片的,但是在python3之后,将这些特殊方法给去掉了,统一使用上面的方式对对象进行切片,因此在使用__getitem__ __setitem__ 这两个方法之前要先判断传递进参数的类型是不是slice对象。例子如下:
10.__iter__
类的对象如果想要变成一个可迭代对象,那么对象中必须要有__iter__方法,并且这个方法返回的是一个迭代器。
for 循环的对象如果是一个可迭代的对象,那么会先执行对象中的__iter__方法,获取到迭代器,然后再执行迭代器中的__next__方法获取数据。如果for循环的是一个迭代器,那么直接执行迭代器中的__next__方法。
11.isinstance和issubclass
之前讲过isinstance可以判断一个变量是否是某一种数据类型,其实,isinstance不只可以判断数据类型,也可以判断对象是否是这个类的对象或者是这个类的子类的对象,代码如下:
issubclass用来判断一个类是否是某个类的子类,返回的是一个bool类型数据,代码如下:
3、类与对象
__new__和__metaclass__
在python中,一切皆对象,我们定义的类其实。。。也是一个对象,那么,类本身是谁的对象呢?在python2.2之前(或者叫经典类中),所有的类,都是class的对象,但是在新式类中,为了将类型(int,str,float等)和类统一,所以,所有的类都是type类型的对象。当然,这个规则可以被修改,在类中有一个属性 __metaclass__ 可以指定当前类该由哪个类进行实例化。而创建对象过程中,其实构造器不是__init__方法,而是__new__方法,这个方法会返回一个对象,这才是对象的构造器。下面是一个解释类实例化对象内部实现过程的代码段:
4、异常处理
python中使用try except finally组合来实现异常扑捉,不像java中是使用try catch finally......其中,except中的Exception是所有异常的父类,下面是一个异常处理的示例:
那么既然Exception是所有异常的父类,我们可以自已定义Exception的子类,实现自定义异常处理,下面就是实现例子:
异常处理里面还有一个断言,一般用在判断执行环境上面,只要断言后面的条件不满足,那么就抛出异常,并且后面的代码不执行了。
5、反射/自省
python中的反射/自省的实现,是通过hasattr、getattr、setattr、delattr四个内置函数实现的,其实这四个内置函数不只可以用在类和对象中,也可以用在模块等其他地方,只是在类和对象中用的很多,所以单独提出来进行解释。
1. hasattr(key)返回的是一个bool值,判断某个成员或者属性在不在类或者对象中
2. getattr(key,default=xxx)获取类或者对象的成员或属性,如果不存在,则会抛出AttributeError异常,如果定义了default那么当没有属性的时候会返回默认值。
3. setattr(key,value)假如有这个属性,那么更新这个属性,如果没有就添加这个属性并赋值value
4. delattr(key)删除某个属性
注意,上面的key都是字符串,而不是变量,也就是说可以通过字符串处理类中的成员或者对象中的属性。下面是一个例子代码:
反射/自省能够直接访问以及修改运行中的类和对象的成员和属性,这是一个很强大的功能,并且并不像java中效率很低,所以用的很多。
下面是一个反射/自省用在模块级别的例子:
6、单例模式
这里介绍一个设计模式,设计模式在程序员写了两三年代码的时候,到一定境界了,才会考虑到设计模式对于程序带来的好处,从而使用各种设计模式,这里只是简单的介绍一个简单的设计模式:单例模式。在面向对象中的单例模式就是一个类只有一个对象,所有的操作都通过这个对象来完成,这就是面向对象中的单例模式,下面是实现代码:
可以看到,三个对象的内存地址都是一样的,其实,这三个变量中存储的都是同一个对象的内存地址,这样有什么好处呢?能够节省资源,就比如在数据库连接池的时候就可以使用单例模式,只创建一个类的对象供其他程序调用,还有在web服务中接收请求也可以使用单例模式来实现,节省资源。