【python】40_面向对象编程三大特性

【摘要】面向对象的三大特性是指:封装、继承和多态。 在学习面向对象编程三大特性之前,先了解一下类和对象。

1.类和对象

类和对象是面向对象编程技术中的最基本的概念。

类(Class) 是现实或思维世界中的实体在计算机中的反映,它将数据以及这些数据上的操作封装在一起。
对象(Object) 是具有类类型的变量。

类(Class) 是是创建实例的模板
对象(Object) 是一个一个具体的实例

类和对象 的区别就是 鱼和三文鱼 的区别; 就是 猫和蓝猫 的区别。

属性: 类里面的变量名
方法: 类里面的函数

1.1 定义类

如何定义类?
class 类名():
  pass

class Person( ):
    mind = '思想'  #属性, 静态变量
    animal = '高级动物'

    def work(self):  # 方法 ,动态变量
        print('都会工作...')

从类名角度考虑:

  1. 操作属性
    1).查询类中的全部内容
    _ _dict _ _方法

    print(Person.__dict__)
    在这里插入图片描述
    该类中定义的所有内容都可以打印出来。
    2).万能的点 .
    可以实现对类中属性的增删改查。

 print(Person.mind)  # 查

在这里插入图片描述

 Person.money = '货币' # 增

在这里插入图片描述

Person.animal = '低等动物'  # 改

在这里插入图片描述

del Person.mind  # 删

在这里插入图片描述
可以看到,Person类中mind静态变量没有了。

当然,这里是也可以在类中执行(增删改查)---->在方法中定义即可.

  1. 操作类中的方法
    【注意】除了类方法,静态方法需要类名调用之外,剩下的方法都要对象调用。

1.2 如何将类转换成对象?

实例化是指在面向对象的编程中,把用类创建对象的过程称为实例化。是将一个抽象的概念类,具体到该类实物的过程。
实例化过程中一般由类名 对象名 = 类名(参数1,参数2…参数n)构成。

xiaoming = People( )

2.封装特性

封装,顾名思义就是将内容封装到某个地方,以后再去调用被封装在某处的内容。
所以,在使用面向对象的封装特性时,需要:

  1. 将内容封装到某处
  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()方法,
在子类中的方法会覆盖掉父类中同名的方法。这就是重写父类方法。
在这里插入图片描述

那如果我们不仅想执行子类的方法,同时还想执行父类中同名的方法。应该怎么办呢?有如下两种办法
调用父类的方法

  1. 父类名.父类的方法名( )

在子类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)  #若要调用私有属性 调用格式为_类__属性

在这里插入图片描述
私有属性与私有方法的优势:

  1. 确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。
  2. 如果又要允许外部代码修改属性怎么办?可以给类增加专门设置属性方
    法。 为什么大费周折?因为在方法中,可以对参数做检查,避免传入无效的参数。

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):不重写,直接继承父类方法函数
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值