【摘要】面向对象的三大特性是指:封装、继承和多态。 在学习面向对象编程三大特性之前,先了解一下类和对象。
目录
1.类和对象
类和对象是面向对象编程技术中的最基本的概念。
类(Class) 是现实或思维世界中的实体在计算机中的反映,它将数据以及这些数据上的操作封装在一起。
对象(Object) 是具有类类型的变量。
类(Class) 是是创建实例的模板
对象(Object) 是一个一个具体的实例
类和对象 的区别就是 鱼和三文鱼 的区别; 就是 猫和蓝猫 的区别。
属性: 类里面的变量名
方法: 类里面的函数
1.1 定义类
如何定义类?
class 类名():
pass
class Person( ):
mind = '思想' #属性, 静态变量
animal = '高级动物'
def work(self): # 方法 ,动态变量
print('都会工作...')
从类名角度考虑:
-
操作属性
1).查询类中的全部内容
_ _dict _ _方法print(Person.__dict__)
该类中定义的所有内容都可以打印出来。
2).万能的点 .
可以实现对类中属性的增删改查。
print(Person.mind) # 查
Person.money = '货币' # 增
Person.animal = '低等动物' # 改
del Person.mind # 删
可以看到,Person类中mind静态变量没有了。
当然,这里是也可以在类中执行(增删改查)---->在方法中定义即可.
- 操作类中的方法
【注意】除了类方法,静态方法需要类名调用之外,剩下的方法都要对象调用。
1.2 如何将类转换成对象?
实例化是指在面向对象的编程中,把用类创建对象的过程称为实例化。是将一个抽象的概念类,具体到该类实物的过程。
实例化过程中一般由类名 对象名 = 类名(参数1,参数2…参数n)构成。
xiaoming = People( )
2.封装特性
封装,顾名思义就是将内容封装到某个地方,以后再去调用被封装在某处的内容。
所以,在使用面向对象的封装特性时,需要:
- 将内容封装到某处
- 从某处调用被封装的内容
1). 通过对象直接调用被封装的内容: 对象.属性名
2). 通过self间接调用被封装的内容: self.属性名
3). 通过self间接调用被封装的内容: self.方法名()
对于面向对象的封装来说,其实就是使用构造方法(魔术方法) 将内容封装到 对象 中,然后通过
对象直接或者self间接获取被封装的内容。
类里面有构造方法、方法。
构造方法__init__与其他普通方法不同的地方在于,当一个对象被创建后,会立即调用构造方法,自动执行构造方法里面的内容。
# 构造方法(魔术方法), 当创建对象的时候, 自动执行的函数
def __init__(self, name, age, gender):
# python解释器自动将对象传给self这个形参.
# 将对象与该对象的属性绑定在一起.
# 调用对象的属性两种方式:
# - 张三.name
# - self.name
print(self) # 实质上是一个对象, <__main__.人类 object at 0x7f4fdc4864a8>
方法 是在类里面定义的函数
def eat(self):
print("%s 正在吃饭..." %(self.name))
上面讲了概念,代码不是很明晰。下面看一下整体的代码:
# 1). 类的定义
class People:
# 构造方法: 当创建对象时会自动调用并执行;
# self实质上是实例化出来的对象, e.g: xiaoming, xiaohong;
def __init__(self, name, age, gender):
# 将创建对象的属性(name, age, gender)封装到self(就是实例化的对象)变量里面;
# 在类里面定义的变量: 属性
self.name = name
self.age = age
self.gender = gender
# 在类里面定义的函数: 方法
def eat(self):
print('%s eating......' %(self.name))
def sleep(self):
# 获取对象self封装的属性(name, age, gender)
print('%s sleep.......' %(self.name))
# 2). 实例化: 通过类实现对象的创建
xiaoming = People("小明", 20, '男')
# 将对象self/xiaoming封装的属性(name, age, gender)从里面拿出来;
print(xiaoming.name)
xiaoming.eat()
xiaoming.sleep()
xiaohong = People("小红", 20, '女')
print(xiaohong.name)
xiaohong.eat()
xiaohong.sleep()
# # 3). 打印类和打印对象
print(People) # <class '__main__.People'>
print(xiaoming) # <__main__.People object at 0x7f5f42be14a8>
print(xiaohong) # <__main__.People object at 0x7f5f34e6ce10>
此外,需要注意的是:
定义类的时候,会执行类的内容,这点与定义函数不同。
打印如下:
当xiaoming这个对象被创建后,会立即调用构造方法,自动执行构造方法里面的内容。
打印如下:
3.继承特性
继承描述的是事物之间的所属关系,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类、扩展类(Subclass),而被继承的class称为基类、父类或超类(Baseclass、Superclass)。
3.1 继承与super关键字
问题一: 如何实现继承?
子类在继承的时候,在定义类时,小括号( )中为父类的名字
问题二: 继承的工作机制是什么?
父类的属性、方法,会被继承给子类。
举例如下: 如果子类没有定义__init__方法,父类有,那么在子类继承父类的时候这个方法就被继承了,所以只要创建对象,就默认执行了那个继承过来的__init__方法。
下面看代码:
1.首先创建一个Father类
# 定义类的过程
# Father: 父类/基类
class Father:
goal = "先挣它1个亿"
# 如果类里面的方法以双下划线开头/结尾, 魔术方法;
# 构造方法在实例化对象时会自动执行
def __init__(self, name, age):
# 封装,self本质是对象本身,将self对象和属性绑定在一起
self.name = name
self.age = age
def eat(self):
print("%s正在吃大餐" %(self.name))
2.再创建一个Son类,继承于Father类,Son类里面不定义任何的属性和方法
class Son(Father):
pass
3.实例化一个son对象,并为name和age两个属性传参数。
son = Son('王思聪',35)
print(son.name)
print(son.age)
虽然Son类里面没有定义构造方法,但是它继承于Father类,所以它也继承了Father类的构造方法。
4.对son对象调用eat()方法。
son.eat()
son对象是Son类实例化出来的,虽然Son类中没有eat()方法,但是它继承于Father类,因此也继承了eat()方法。
5.对son对象调用goal属性
print(son.goal)
实际上调用的是父类Father类的属性goal。
如果子类有自己的属性
class Son(Father):
goal = '找网红当女朋友'
print(son.goal)
则调用子类自己的属性
重写父类方法: 就是子类中,有一个和父类相同名字的方法,在子类中的方法会覆盖掉父类中同名的方法。直接执行子类的方法
在Son类中,也定义一个eat()方法
class Son(Father):
goal = '找网红当女朋友'
def eat(self):
print('%s吃路边摊...' %(self.name))
再对son对象执行这个方法
son.eat()
虽然父类Father类中含有eat()方法,但是子类Son类中也有eat()方法,
在子类中的方法会覆盖掉父类中同名的方法。这就是重写父类方法。
那如果我们不仅想执行子类的方法,同时还想执行父类中同名的方法。应该怎么办呢?有如下两种办法
调用父类的方法:
- 父类名.父类的方法名( )
在子类Son类的eat()方法中,执行父类名.父类的方法名( )。
class Son(Father):
goal = '找网红当女朋友'
def eat(self):
Father.eat(self)
print('%s吃路边摊...' %(self.name))
再对son对象调用eat()方法
son.eat()
可以看到,先执行了Father类中的eat方法,再执行了Son类中自己的eat()方法。
2. super( ): py2.2+的功能
上面这种父类名.父类的方法名( )的方法可以调用父类中的重名方法,但是如果遇到了父类名称改变,那么用这个语句调用的话就比较麻烦了。因此在python2.2以后中,推荐使用super()
class Son(Father):
goal = '找网红当女朋友'
def eat(self):
# 执行父类的方法
super(Son, self).eat()
print('%s吃路边摊...' %(self.name))
执行效果与父类名.父类的方法名( )一致。
son.eat()
3.1.1 私有属性与私有方法
默认情况下,属性在 Python 中都是“public”, 大多数 OO 语言提供“访问控
制符”来限定成员函数的访问。
在 Python 中,实例的变量名如果以 __ 开头,就变成了一个私有变量/属性
(private), 实例的函数名如果以 __ 开头,就变成了一个私有函数/方法(private)。只有内部可以访问,外部不能访问。
class Person(object):
def __init__(self,name,gender,age,career):
self.name = name
self.gender = gender
self.__age = age
self.career = career
def __gender_show(self): # 方法 ,动态变量
print('%s的性别为%s' %(self.name,self.gender))
def eat(self):
print('%s喜欢吃路边摊' %(self.name))
class Student(Person):
def eat(self):
super(Student, self).eat()
print('%s喜欢吃学校食堂' %(self.name))
xiaoming = Student('小明','male','19','学生')
还是以上面的例子来介绍,有两个类,其中Student继承了Person类,xiaoming是Student类实例化出来的对象。正常情况下,可以访问Student类及Person类中所有属性与方法,但是目前Person类中,__age为私有属性,__gender_show为私有方法,只能在类内调用,外部无法访问。
xiaoming.eat()
xiaoming.gender_show()
print(xiaoming.gender)
print(xiaoming.name)
print(xiaoming.age)
那么,私有属性一定不能从外部访问吗?不是的!
python2版本不能直接访问 __属性名,是因为 Python 解释器对外把 __属性名 改成了_类名__属性名 ,所以,仍然可以通过 _类名__属性名 来访问 __属性名 。因为不同版本的 Python 解释器可能会把 __属性名 改成不同的变量名。
所以以上面的例子来说,如果要访问其中的__age私有属性和__gender_show私有方法,就是换成_Person__age和_Person__gender_show()来访问。(一般不建议这样做,私有属性的初衷就是为了不让外部访问)
xiaoming._Person__gender_show() #若要调用私有方法 调用格式为_类__方法名
print(xiaoming._Person__age) #若要调用私有属性 调用格式为_类__属性
私有属性与私有方法的优势:
- 确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。
- 如果又要允许外部代码修改属性怎么办?可以给类增加专门设置属性方
法。 为什么大费周折?因为在方法中,可以对参数做检查,避免传入无效的参数。
3.2 多继承算法
多继承,即子类有多个父类,并且具有它们的特征。
这里重点讲一下面试中经常问到的新式类与经典类(这两者区别在python2及以前的版本中存在,python3中就已经不存在了)
3.2.1 新式类
在Python 2及以前的版本中,由任意内置类型派生出的类,都属于“新式
类”,都会获得所有“新式类”的特性。
3.2.2 经典类
反之,不由任意内置类型派生出的类,则称之为“经典类”。
3.2.3 新式类与经典类的区别
“新式类”和“经典类”的区分在Python 3之后就已经不存在,在Python 3.x
之后的版本,因为所有的类都派生自内置类型object(即使没有显示的继承
object类型),即所有的类都是“新式类”。
最明显的区别在于继承搜索的顺序不同,即:
-
经典类多继承搜索顺序(深度优先算法):先深入继承树左侧查找,然后再返回,开始查找右侧。
-
新式类多继承搜索顺序(广度优先算法):先在水平方向查找,然后再向上查找。
我们在python2环境中,定义四个类(D、C、B、A)。其中A继承于B和C类,C继承于D,D假设有两种可能,一种是新式类继承于(object),另一种是经典类不继承任何内置类,下面我们看一下这种类多继承的搜索方式。 -
新式类:(广度优先)
1 #coding:utf-8 #因为实在py2中,要解决中文编码问题记得加上这一句
2 #实验目的:在python2环境中测试新式类与经典类的继承方法
3
4 class D(object):
5 def test(self):
6 print('D')
7
8 class C(D):
9 def test(self):
10 print('C')
11 class B(D):
12 def test(self):
13 print('B')
14 #pass
15 class A(B,C):
16 pass
17 a = A()
18 a.test()
可以看到a是A类实例出来的对象,要调用a.test()首先要去A类里面去找这个test方法,A类里面没有,再去B类找。
如果B类里面也没有这个方法,由于D是个新式类,多继承的搜索算法为广度优先算法,所以会去A的父类(B、C)中的C类去找。
查找的是与B同级别的C类
- 经典类:(深度优先)
4 class D:
5 def test(self):
6 print('D')
7
8 class C(D):
9 def test(self):
10 print('C')
11 class B(D):
12 #def test(self):
13 # print('B')
14 pass
15 class A(B,C):
16 pass
17 a = A()
18 a.test()
注意,这里D类没有继承任何内置类,因此是经典类。调用a.test()时,A类中没有该方法,所以A的父类B类中找;但是B类中也没有这个方法,这里一定要注意的是没有再去A的父类C类中找,而是去了B的父类D类中找test方法。因此是深度优先搜索。
也可以用一个魔术方法来查看继承的顺序。类. _ _ mro_ _
class D:
def test(self):
print('D')
class C(D):
def test(self):
print('C')
class B(D):
# def test(self):
# print('B')
pass
class A(B, C):
pass
a = A()
a.test()
print(A.__mro__)
3.3 多态
多态(Polymorphism)按字面的意思就是“多种状态”。
在面向对象语言中,接口的多种不同的实现方式即为多态。
通俗来说: 同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。
多态的优点:
当我们需要传入更多的子类,只需要继承父类就可以了,而方法既可以直接
不重写(即使用父类的),也可以重写一个特有的。这就是多态的意思。调用方只管调用,不管细节,而当我们新增一种的子类时,只要确保新方法编写正确,而不用管原来的代码。这就是著名的“开闭”原则:
- 对扩展开放(Open for extension):允许子类重写方法函数
- 对修改封闭(Closed for modification):不重写,直接继承父类方法函数