【Python】面向对象的特性

205 阅读9分钟

Python 是面向对象的语言,因此支持面向对象编程的三大特性。

面向对象,具有三个特性:

  • 封装
  • 继承
  • 多态

什么是封装:

是对具体对象的一种抽象:把部分属性限制在某一区域内,其他程序无法调用(即:私有化)

为什么要进行封装?有啥用?

1、保护隐私(把不想别人知道的东西封装起来)
2、隔离复杂度(比如:电脑、手机,我们看见的就是一个商品,其实里面有很多电器元件等,对用户来说,我们不需要清楚里面都有哪些元件,电脑把那些电器元件封装在黑匣子里,提供给用户的只是个按钮接口,通过键盘就能实现对电脑的操作。)

封装的两个层面:

封装其实分为两个层面,但无论哪种层面的封装,都要对外界提供好访问你内部隐藏内容的接口(接口可以理解为入口,有了这个入口,使用者无需且不能够直接访问到内部隐藏的细节,只能走接口,并且我们可以在接口的实现上附加更多的处理逻辑,从而严格控制使用者的访问)

第一个层面的封装(什么都不用做):

创建类和对象会分别创建二者的名称空间,我们只能用类名.或者obj.的方式去访问里面的名字,这本身就是一种封装。

第二个层面的封装:

类中把某些属性和方法隐藏起来(或者说定义成私有的),只在类的内部使用、外部无法访问,或者留下少量接口(函数)供外部访问。

实例:

class person:
    # 私有属性
    def __init__(self, name, age):
        self.__name = name
        self.age = age

    # 私有方法
    def __getName(self):
        print(f'{self.name},{self.age}')

    # 实例方法
    def printInfo(self):
        print(f'我是{self.__name},今年{self.age}岁。')


# 创建对象,实例化对象
a = person('崔东山', '18')
a.printInfo()
# a.__getName()  # 私有方法,无法直接调用
# a._person__getName()  # 报错:属性错误:AttributeError: 'person' object has no attribute 'name'

什么是继承:

在生活中,常见的有子承父业..

那么在代码中:

一个新类继承自一个设计好的类,就具备了已有类的特征。

已有的类,称之为父类或基类
新的类,称之为子类或派生类

继承的分类:

  • 单继承
  • 多继承
单继承:

子类只继承一个父类。【深度优先机制】

当对象调用方法时,查找顺序从自身类找,若未找到则去父类找;
父类无,再去父类的父类找,直到object类,若还无,则报错。

image.png

代码实例:
class grandFather:
    def __init__(self):
        print('父类的父类')

    def sleep(self):
        print('父类的父类,睡觉')


# 继承父类
class father(grandFather):
    def eat(self):
        print('父类:吃')

    def drink(self):
        print('父类:喝')


# 继承父类
class son(father):
    def study(self):
        print('我在学习Python')


a = son()
a.study()
a.eat()
a.sleep()
多继承:子类继承了多个父类,并且具有它们的特征。
情景一:左优原则

image.png

class func1:
    def run(self):
        print('父类1-run')


class func2:
    def run(self):
        print('父类2-run')


# 继承多个父类:拥有相同方法时,左边优先执行
class son(func1, func2):
    pass


b = son()
b.run()
情景二:一条道走到黑,先执行左边,去找父类,父类没有去父类的父类。

image.png(左边一条路走到黑)

class firstLevel:
    def sleep(self):
        print('1.0:sleep')


class secondLevel(firstLevel):
    def run(self):
        print('2.0,run')


class thirdStage:
    def sleep(self):
        print('2.1,sleep')


class fourthStage(secondLevel, thirdStage):
    pass


c = fourthStage()
c.sleep()   # 最后找到的是class firstLevel()
情景三:左优 + 根最后执行

image.png

class grandFather:
    def play(self):
        print('嗨起来')


class father(grandFather):
    def rap(self):
        print('一起跳起来')


class father1(grandFather):
    def dance(self):
        print('一起来跳舞吧')


# 如果是同根的话,根是最后执行
class son(father, father1):
    pass


d = son()
d.dance()
print(son.__mro__)  # 通过mro方法可以查看程序执行情况或继承顺序
什么是重写?

当子类与父类拥有同名称的方法时,子类对象调用该方法 优先执行自身 的方法。

那么实际上就是子类的方法 覆盖 父类的方法,也称为 重写。

实际的开发中,遵循开放封闭原则。并不会完全的重写父类的方法,而是希望同时实现父类的功能。

代码实例:

class A:
    def __init__(self):
        print('AAA')

    def demo(self):
        print('aaa')


class B(A):
    def __init__(self):
        print('BBBbbb')

    def demo(self):  # 与父类拥有相同名称的方法
        print('bbb')

        # 需要重写谁,就super().实例方法(),具备父类的方法
        super().demo()
        super().__init__()


e = B()
e.demo()

什么是多态:

一类事物有多种形态,一个抽象类有多个子类(因而多态的概念依赖于继承);
不同的子类对象调用相同的方法,产生不同的执行结果(增加代码的灵活度);

实现多态的步骤:

定义一个父类,实现某个方法;
定义多个子类,在子类中重写父类的方法,每个子类方法实现不同的功能;
假设定义一个函数,需要一个base类型的对象的参数,那么调用函数的时候,传入base类不同的子类对象,那么这个函数就会执行不同的功能。

步骤解析:

1、定义一个父类(Base),实现某个方法(func)

2、定义多个子类,在子类中重写父类的方法(func),每个子类 func() 方法实现不同的功能;

3、假设定义一个函数,需要一个 Base类型的对象的参数,那么调用函数时,传入Base 类中不同子类的对象,那么这个函数就会执行不同的功能,这即是多态的体现。

代码实例:

class animal(object):
    def func(self):
        print('动物类')


class cat(animal):
    def func(self):
        print('喵~')


class dog(animal):
    def func(self):
        print('汪~')


class hero(animal):
    def func(self):
        print('英雄类,而非动物类')


# 定义一个方法/函数
def demo(test):  # 传入形参接收
    test.func()  # 对象名.方法名


# 传入不同的对象,产生不同的结果
# 调用更加灵活,更容易写出通用的代码
demo(dog())   # 传入的对象(实参)
demo(cat())   # 传入的对象
demo(hero())  # 传入的对象

注意点:Python(弱类型语言)中函数的参数是没有类型限制的,所以多态在python中的体现并不是很严谨。多态的概念是应用于Java和C#这一类强类型语言中,而Python崇尚“鸭子类型”。

鸭子类型(duck typing):

编程语言具有类型概念,例如 Python 中有数字类型、字符串类型、布尔类型,或者更加复杂的结构,例如元组 tuple 、列表 list 、集合 set 和字典类型 dict 等等。

根据如何将类型解析并赋值给各种构造(例如变量,表达式,函数,函数参数等),编程语言可以归类为“鸭子类型”,“结构化类型”或“标称类型”。

本质上,分类决定了对象如何被解析并推断为具体的类型。

鸭子类型( duck typing )语言使用鸭子测试来评估: 对象是否可以被解析为特定的类型。Python就是其中一种。

这个概念的名字来源于由詹姆斯·惠特科姆·莱利提出的鸭子测试。“鸭子测试”可以这样表述:

“如果看起来像鸭子,叫起来像鸭子,那么它一定是鸭子。”

概念: 鸭子类型在程序设计中是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由"当前方法和属性的集合"决定。

在鸭子类型中,关注点在于对象的行为,能做什么;而不是关注对象所属的类型。

例如,在不使用鸭子类型的语言中,我们可以编写一个函数,它接受一个类型为"鸭子"的对象,并调用它的"走"和"叫"方法。在使用鸭子类型的语言中,这样的一个函数可以接受一个任意类型的对象,并调用它的"走"和"叫"方法。

如果这些需要被调用的方法不存在,那么将引发一个运行时错误。任何拥有这样的正确的"走"和"叫"方法的对象都可被函数接受的这种行为引出了以上表述,这种决定类型的方式因此得名。

鸭子类型通常得益于"不"测试方法和函数中参数的类型,而是依赖文档、清晰的代码和测试来确保正确使用。

在常规类型中,我们能否在一个特定场景中使用某个对象取决于这个对象的类型,而在鸭子类型中,则取决于这个对象是否具有某种属性或者方法——即只要具备特定的属性或方法,能通过鸭子测试,就可以使用。

代码实例1:

# 定义一个计算函数,接收3个入参
def calculate(a, b, c):
    return (a + b) * c


# 分别计算3种情况的结果
result1 = calculate(1, 2, 3)
result2 = calculate([1, 2, 3], [4, 5, 6], 2)
result3 = calculate("打工人", "打工魂", 3)

# 打印3种结果
print(result1, result2, result3, sep='\n')

输出结果:
9
[1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6]
打工人打工魂打工人打工魂打工人打工魂

实例解析:每次调用 calculate() 都使用的是不同的对象(数字、列表和字符串),并且它们在继承关系中没有联系。只要输入对象支持 + 和 * 方法,操作就能成功。

代码实例2:

# 鸭子类
class Duck:

    def quack(self):
        print("这鸭子正在嘎嘎叫")

    def feathers(self):
        print("这鸭子拥有白色和灰色的羽毛")

# 人类
class Person:

    def quack(self):
        print("这人正在模仿鸭子")

    def feathers(self):
        print("这人在地上拿起1根羽毛然后给其他人看")


# 函数/接口
def in_the_forest(duck):
    duck.quack()
    duck.feathers()


if __name__ == '__main__':

    donald = Duck()  # 创建一个Duck类的实例
    john = Person()  # 创建一个Person类的实例

    in_the_forest(donald)  # 调用函数,传入Duck的实例
    in_the_forest(john)    # 调用函数,传入Person的实例
    

输出结果:
这鸭子正在嘎嘎叫
这鸭子拥有白色和灰色的羽毛
这人正在模仿鸭子
这人在地上拿起1根羽毛然后给其他人看
小结:

鸭子类型是一个与动态类型( dynamic typing )相关的概念,其中关注的是它定义的方法,而不太关注对象的类型或所属类。当使用鸭子类型时,无需检查其类型。相反,需要检查给定方法或属性珍惜是否存在。

鸭子类型语言为程序员提供了最大的灵活性,程序员只需写最少量的代码。但是这些语言可能并不安全,可能会产生运行时错误,使用时需要格外注意。

class Duck:
    def test(self):
        print('小鸭子,嘎嘎嘎...')


class Bird:
    def test(self):
        print('鸟儿叽叽喳喳')


class Chicken:
    def test(self):
        print('小鸡咯咯哒')


def inTheForest(a):
    a.test()


duck = Duck()
bird = Bird()
chicken = Chicken()
for i in [duck, bird, chicken]:
    inTheForest(i)