Python面向对象

一、面向对象的概念

  • 面向对象编程OOP):Object Oriented Programming
  • 面向对象:就是在编程时尽可能地模拟现实世界,按照现实世界中的逻辑去处理问题,分析问题中参与其中的有哪些实体、这些实体应该有什么属性和方法,我们如何通过调用这些实体的属性和方法去解决问题。
  • Python是一门面向对象的语言。

1.1 两种编程思想

  • 面向过程(Procedural Programming)
    • 一种以过程为中心的编程思想;
    • 首先分析解决问题所需要的步骤;
    • 然后用函数将这些步骤一步一步的实现;
    • 最后按顺序依次调用运行。
  • 面向对象(Object-Oriented Programming,简称OOP)
    • 是一种更符合我们人类思维习惯的编程思想;
    • 面向对象开发就是不断的创建对象,使用对象,操作对象做事情;
    • 可以将复杂的事情简单化。

1.2 面向对象编程与面向过程编程的区别

  1. 抽象程度:面向过程编程将程序划分为一系列的过程或函数,通过函数之间的调用来完成任务。而面向对象编程将程序看作是一组相互作用的对象,每个对象都有其自己的属性和行为。

  2. 封装性:面向过程编程强调的是数据和函数的分离,函数可以直接访问和操作数据。而面向对象编程通过封装将数据和对数据的操作绑定在一起,只暴露出必要的接口,隐藏了内部的具体实现细节。

  3. 继承性:面向对象编程具有继承的特性,可以通过继承机制从已存在的类派生出新的类,并继承父类的属性和方法,以实现代码的重用和扩展。而面向过程编程没有继承的概念,代码重用通常通过函数的封装和复用来实现。

  4. 多态性:面向对象编程支持多态,即同一种类型的对象在不同的上下文中有不同的行为,这使得代码更加灵活和可扩展。而面向过程编程没有多态的概念,需要通过条件语句来实现不同情况的处理。

  5. 设计思想:面向对象编程注重的是问题领域的建模和抽象,关注对象之间的关系和交互。而面向过程编程更加注重问题的解决步骤和过程化的思维。

选择面向过程还是面向对象编程,取决于具体的需求和项目情况。
面向对象编程更适合复杂的系统和大规模的项目,能够提高代码的模块化、可维护性和扩展性;而面向过程编程则更适合简单的任务和较小规模的项目,可以减少不必要的复杂性。

二、类与对象

2.1 类

  • 在面向对象编程中,类(class)是一种定义现实事物属性和方法的蓝图和模板。类描述了现实事物的特征(属性)和行为(方法)。可以把类看作是创建现实事物的原型。

  • 类是现实事物的抽象,它定义了一类具有相似特征和行为的通用结构和行为。类提供了对象所需的状态和行为,并定义了对象的初始化、操作和销毁等方法。

  • 类由属性(也称成员变量)和方法组成。属性是类的特征,用于描述类的状态。方法是定义在类中的函数,用于描述类的行为和操作。

  • 通过使用类,我们可以创建多个具有相同属性和方法的对象。类中的属性和方法是相对独立的,每个对象都有自己的属性副本,但共享类中的方法。

  • 通过类的封装和抽象,可以更好地组织和管理代码,提高代码的可读性、可维护性和重用性。类是面向对象编程的重要概念之一。

2.1.1 类的定义

  • Python使用class关键字来创建一个新类,class之后为类的名称。
  • Python存在一个根类object类,所有的类都由根类派生而来,如果自定义类继承于根类,可以省略。

语法:

class ClassName:
    pass
# 或
class ClassName(object):
    pass

示例:

class Plane(object):

    def flying(self,hour):
        print(f"飞机已飞行{hour}小时")

image

2.2 对象

  • 对象(object):称为类的一个实例,是具体存在的、具有状态和行为的实体。

  • 可以把类看作是对象的模板或蓝图,描述了对象应该具有的属性和方法。当我们通过类创建一个具体的实例时,这个实例就是一个对象。

  • 对象有两个关键的特征:状态和行为。

  1. 状态(state):对象的状态由它的属性(也称为成员变量)决定,属性表示了对象的特点和特征。例如:一个人对象的状态包括姓名、年龄、性别等属性,这些属性的值可以根据对应的实际情况而不同。

  2. 行为(behavior):对象的行为由它的方法(也称为成员函数)决定,方法表示了对象能够执行的操作和行为。例如:一个人对象的行为可以包括走路、说话、工作等方法,通过调用这些方法可以让对象执行相应的行为。

2.2.1 实例对象

  • 一个类定义好了,还不能使用,比如系统的list类。类知识规定了该类型的数据具有什么特征和行为,而真正要去使用这些特征和行为,必须有一个真实的列表存在。

  • 实例对象本质上就是使用自定义类型去声明一个变量。

  • 格式:实例对象名=类名()

class Plane(object):

    def flying(self,hour):
        print(f"飞机已飞行{hour} 小时。。。")

# 实例了两个对象
airPlane1 = Plane()
airPlane2 = Plane()
# 使用不同的对象调用了类中的方法
airPlane1.flying(3)
airPlane1.flying(5)
airPlane2.flying(3)

2.2.2 实例属性

动态绑定

  • python中的属性变量都是使用动态绑定的方式绑定到实例对象上的。

  • 格式:实例对象名.实例属性名

class Student:
    pass

# 实例对象
s1 = Student()
s2 = Student()

# 为实例对象s1动态绑定属性
s1.name = "Tommy"
s1.age = 21

# 访问实例对象s1的属性
print(s1.name)
print(s1.age)

# 那没有绑定的s2,会输出什么?
print(s2.name)
print(s2.age)

2.3 构造方法

  • 构造方法__init__(self)在实例对象时自动调用,self参数不需要手动传参,该参数在实例对象时,由解释器自动传入被实例的对象。
class Student():
    def __init__(self):
        print("Init Run ...")

s1 = Student()
s2 = Student()

2.3.1 self

  • self是一个特殊的关键字,用来表示当前被实例的对象,可以理解为人称代词

  • 通过self可以定义或访问实例对象的属性或方法。

  • 格式:self.属性名 = 值

class Student():
    def __init__(self):
        self.name = "Timmy"
        self.age = 21

s1 = Student()
s2 = Student()

print(s1.name)
print(s1.age)

print(s2.name)
print(s2.age)

  • 通过构造方法self,实现了为实例对象定义属性,其本质上还是为实例对象动态绑定属性,只是动态绑定的时机变了,从实例完对象再绑定属性,变成了在自动调用执行的构造方法中进行动态绑定。

  • 并且,从以上代码发现另一个问题,就是所有实例的对象都有共同的属性值,这显然不符合逻辑。

2.3.2 带参构造方法

  • 构造方法也可以携带参数,根据类中属性的定义,传入对应的参数对实例属性进行初始化。

  • 格式:__init__(self, args)

class Student():
    def __init__(self, name, age,):
        self.name = name
        self.age = age

s1 = Student("Rose", 31)
s2 = Student("Jack", 35)

print(s1.name)
print(s1.age)

print(s2.name)
print(s2.age)

2.3.3 __str__(self) 方法

  • 在实例对象后,如果直接打印对象,输出该对象的相关信息,发现实际输出的并不是想要的结果,而是该实例对象的类型和地址。

  • 如果想在输出实例对象时,按指定的格式输出,需要实现__str__(self)方法。

  • 该方法不接收除了self以外的参数,self参数自动传入,函数只能返回一个字符串。

class Student():
    def __init__(self, name, age,):
        self.name = name
        self.age = age

    def __str__(self):
        return f"姓名:{self.name} -- 年龄:{self.age}"

s1 = Student("Rose", 31)
s2 = Student("Jack", 35)

print(s1)
print(s2)

2.4 实例方法

  • 实例方法用来定义对象的行为,本质上是定义在类中的函数。

  • 实例方法默认携带一个参数self,在程序执行时,由解释器自动传入调用该方法的实例对象,通过此参数,可以在当前实例方法中调用其它实例方法或属性。

  • 格式:

def 方法名(self):
        pass
或者
def 方法名(self, args):
        pass

示例:

class Student():
    def __init__(self, name, age,):
        self.name = name
        self.age = age
        self.courses = []

    def __str__(self):
        return f"姓名:{self.name} -- 年龄:{self.age}"

    def select_course(self, courseName):
        self.courses.append(courseName)

    def all_course(self):
        print(f"{self.name} 本学期选课如下:")
        for idx,c in enumerate(self.courses):
            print(f"第{idx+1}门课:{c}")


s1 = Student("Rose", 31)
s1.select_course("Python")
s1.select_course("Java")
s1.select_course("PHP")
s1.all_course()

2.5 类属性

  • 在python中,一切皆为对象,类也不例外。在程序运行过程中,类也会作为一个对象使用。

  • 类对象与实例对象不同,可以理解为实例对象是由类对象复制而来,每个实例对象之间具有数据独立性。而类对象在程序运行过程中,只有一个。

  • 既然是对象,那就可以拥有自己的属性。在类中定义属性时,属性名有self前缀的是实例属性,而在类中直接定义的属性即为类属性。

示例:

class WaterDispenser:
    # 剩余水量
    surplus_water = 1500
    # 出水口
    def water_outlet(self, n):
        WaterDispenser.surplus_water -= n
        print("剩余水量", WaterDispenser.surplus_water)

# 定义一个人的类,持有一个饮水机对象
class Person(object):
    def __init__(self, wd):
        self.wd = wd
    # 人有一个接水的行为(方法)
    def get_water(self, n):
        self.wd.water_outlet(n)

wd = WaterDispenser()

Tom = Person(wd)
Jack =Person(wd)

Tom.get_water(100)
print(wd.surplus_water)
Jack.get_water(200)
print(wd.surplus_water)

  • 类属性的特征
    • 在类中直接定义的变量为类属性;
    • 在方法中使用类属性时,需要使用类名作为前缀,类名.类属性名
    • 在类的外部可以通过类名或实例对象名访问类属性;
    • 所有的实例对象名共享一个类属性;
    • 实例对象只能获取类属性的值,不能直接进行修改,只能通过方法进行修改。

2.6 类方法

  • 类方法可以通过类名直接进行使用,类方法在定义时,需要使用@classmethod装饰器进行修饰。

  • 与实例方法不同的是,实例方法有一个默认参数’self’,代表当前调用方法的实例对象,而类方法的默认参数为cls,该参数也是在使用时由解释器自动传入的,但传入的对象不是实例对象,而是类对象。

  • 在类方法中,可以通过参数cls使用类属性。

  • 一般类方法用来封装工具类使用,将一些复杂的代码逻辑封装成类方法,由类名直接调用,不需要实例对象,比如时间处理、网络请求处理等。

  • 需要注意的是:如果类中既定义了实例属性,又定义了类方法,那么在类方法中是不能使用实例属性的,因为在使用类方法的过程中,实例对象不存在,所以不能使用实例属性。

示例:封装一个日期时间获取的工具类。

import datetime
class Utils:
    now = datetime.datetime.now()

    @classmethod
    def current_date_time(cls):
        return cls.now

    @classmethod
    def current_date(cls):
        return cls.now.strftime("%Y-%m-%d")

    @classmethod
    def current_time(cls):
        return cls.now.strftime("%H-%M-%S")

    @classmethod
    def getYear(cls):
        return cls.now.year

    @classmethod
    def getMonth(cls):
        return cls.now.month

    @classmethod
    def getDay(cls):
        return cls.now.day


print(Utils.current_date_time())
print(Utils.current_date())
print(Utils.current_time())
print(Utils.getYear())
print(Utils.getMonth())
print(Utils.getDay())

2.7 静态方法

  • 除了类方法,python的类中还有一种静态方法。

  • 静态方法在定义时,需要使用@staticmethod装饰器进行装饰,与类方法不同的是,静态方法没有默认参数。

  • 静态方法和普通的函数本质上是一样的,只是定义在了类中。

  • 一般情况下,静态方法同类方法一样,也是在封装工具类时使用,区别在于静态方法中不需要使用类属性(不是不能使用,只是不建议)。

示例:封装两个数字操作的简单计算器。

class Calc:
    @staticmethod
    def add(n1, n2):
        return n1 + n2

    @staticmethod
    def sub(n1, n2):
        return n1 - n2

    @staticmethod
    def mul(n1, n2):
        return n1 * n2

    @staticmethod
    def div(n1, n2):
        return n1 / n2


print(Calc.add(15, 25))
print(Calc.sub(15, 25))
print(Calc.mul(15, 25))
print(Calc.div(15, 25))

2.7 封装

  • 封装是面向对象编程中三大特征之一,指的是将数据和操作数据的方法打包在一起,形成一个类或对象。

  • 封装的目的是隐藏对象的内部实现细节,提供一个安全且易于使用的接口,使得对象之间的交互更加简单和可靠。

  • 封装主要包括:

  1. 数据隐藏:通过将对象的数据属性设置为私有或受保护的,防止外部直接访问和修改对象的数据。这样可以确保对象的数据在被操作时不会被意外篡改或破坏。

  2. 方法封装:将对象对自身数据的操作封装在方法中,只通过方法来访问和修改对象的数据。这样可以确保对对象的操作符合预期,避免了外部错误地修改对象的数据。

  3. 接口定义:通过定义公共接口,将对象的功能暴露给外部使用者。使用者只需关心如何使用接口提供的方法,而不需要了解内部实现细节。这样可以提高代码的可读性和可维护性,同时也能够实现代码的模块化和复用。

  • 封装的优势:
  1. 数据安全性:封装隐藏了对象的内部实现细节,保护了数据的完整性和安全性。外部无法直接访问或修改对象的数据,必须通过规定的方法进行操作,减少了意外错误的发生。

  2. 代码模块化:封装将对象的数据和操作打包在一起,实现了代码的模块化。不同的对象可以独立开发和测试,降低了代码的耦合性,增加了系统的可维护性和扩展性。

  3. 简化接口:封装将对象的功能通过公共接口暴露给外部,隐藏了内部实现细节。外部使用者只需了解接口的使用方法,而无需关心具体的实现。这降低了外部使用者的使用难度,也提高了代码的可读性和易用性。

2.7.1 访问控制

  • 在python中并没有像Java、C++一样,提供了publicprotectedprivate这样的访问控制修饰符,python通过一种称为名称改写的方式,实现其它语言中访问控制修饰符的作用。

  • 但要注意的是,在python中名称改写只是一种约定,并没有真正的实现私有的作用,在python中只要想访问,所有的数据都可以拿到。

2.7.2 无下划线前缀(共有权限)

  • python中默认定义的属性和方法,都是公有的方法。无论是在类外,还是在派生的子类中,都可以进行访问,类似其他语言中的public修饰符的作用。
class A(object):
    def __init__(self):
        # 公有属性
        self.a = 10

    # 公有方法
    def show(self):
        # 在类中使用公有属性
        print(f"A: {self.a}")


obj = A()
# 在类外使用公有属性
print(obj.a)

# 在类外使用公有方法
obj.show()

2.7.3 _单下划线前缀(保护权限)

  • python在类中使用单下划线前缀实现其他语言中protected保护权限的功能,在属性或方法(包括类属性和类方法,作用相同)前添加一个单下划线,该属性或方法在当前类中访问,在类外理论上不可访问(使用时不提示,但写出来程序可以运行,但有警告),在通过继承派生的子类中可以访问。
class A(object):
    def __init__(self):
        # 公有属性
        self.a = 10
        # 保护属性
        self.b = 20

    # 公有方法
    def show(self):
        # 在类中使用公有属性
        print(f"A: {self.a}")
        # 在类中使用保护属性
        print(f"B: {self.b}")
        # 在类中使用保护权限的方法
        self.display()

    # 保护权限的方法
    def _display(self):
        print(f"B: {self._b}")

obj = A()
# 在类外使用公有属性
print(obj.a)
# 在类外无法使用保护权限的属性(不建议这样用)
print(obj._b)

# 在类外使用公有方法
obj.show()
# 在类外无法使用保护权限的方法(不建议这样用)
obj._display()

2.7.4 __双下划线前缀(私有属性)

  • python在类中使用双下划线前缀实现其它语言中private私有权限的功能,在属性或方法(包括类属性和类方法,作用相同)前面加一个双下划线,该属性或方法只能在当前类中访问,在类外任何位置不可访问(只是理论上不可访问,通过某些方式还是可以在类外访问,不建议这样用)。
class A(object):
    def __init__(self):
        # 公有属性
        self.a = 10
        # 保护属性
        self.b = 20
        # 私有属性
        self.c = 30

    # 公有方法
    def show(self):
        # 在类中使用公有属性
        print(f"A: {self.a}")
        # 在类中使用保护属性
        print(f"B: {self.b}")
        # 在类中使用私有属性
        print(f"C: {self.__c}")
        # 在类中使用保护权限的方法
        self.display()
        # 在类中使用私有方法
        self.__info()

    # 保护权限的方法
    def _display(self):
        print(f"B: {self._b}")

    # 私有权限的方法
    def __info(self):
        # 在类中使用私有属性
        print(self.__c)

obj = A()
# 在类外使用私有属性,访问失败
print(obj.__c)

# 在类外访问私有方法,访问失败
obj.__info()

2.7.5 双下划线前缀与后缀

  • 在python中还有一种同时具有前后双下划线的变量或方法,这些方法是python中的魔法属性或魔法方法,这些属性或方法名被赋予了特殊的作用,比如:初始化方法__init__(),对象描述方法__str__()__iter__()__next__()方法等,这些都是魔法方法。

2.8 继承

  • 继承是面向对象编程中的三大概念之二,指的是一个类基于另一个类来创建。

  • 创建出来的新类称为子类或派生类。被继承的类称为父类或基类。

  • 通过继承,子类可以继承父类的属性和方法,并且可以在此基础上添加新的属性和方法,或者对继承的属性和方法进行修改。

  • 继承的主要特点:

  1. 继承关系:继承创建了一个父类和子类之间的关系。子类继承了父类的特性,包括属性和方法。子类可以重用父类的代码,减少了代码的冗余。

  2. 子类的扩展:子类可以在继承父类的基础上,添加新的属性和方法。这样可以对父类进行扩展,使得子类具有更多的功能。

  3. 代码共享和重用:通过继承,子类可以共享父类的代码。父类中通用的属性和方法可以被多个子类继承和使用,提高了代码的重用性,并减少了开发实践和成本。

  4. 继承的层次结构:继承可以形成一个层次结构,其中一个父类可以有多个子类,而子类又可以成为其他子类的父类。这种层次结构可以更好地组织和管理代码,使得代码更加结构化和模块化。

  • 继承的优势:
  1. 代码重用:继承允许子类重用父类的代码,减少了代码的冗余,提高了代码的可维护性和复用性。

  2. 扩展性:通过继承,子类可以在父类的基础上添加新的属性和方法,实现对父类的扩展,使得子类具有更多的功能。

  3. 类型的兼容性:由于子类继承了父类的特性,子类可以被当作父类的实例来使用。这样在需要父类类型的地方,可以使用子类的实例,增加了代码的灵活性和可扩展性。

  • 需要注意的是,虽然继承可以提供代码重用和扩展的好处,但过度使用继承可能导致代码的复杂性和耦合性增加。因此,在设计代码时,应该合理使用继承,并遵循单一责任原则和开闭原则,保持代码的简介和灵活。

2.8.1 单继承

  • 单继承是指一个子类只继承一个父类。
class A(object):
    # A 继承自 object 根类
    def show(self):
        print("父类A的方法")


class B(A):
    # B 类继承自 A类
    def display(self):
        print("子类B的方法")


b = B()
# 子类对象使用自己的方法
b.display()
# 子类对象使用父类的方法,如果父类没有改方法,则继续向上查找,直到根类
b.show()

2.8.2 方法重写

  • 在子类中,可以对父类中的方法实现进行重写,实现新的功能。
class A(object):
    # A 继承自 object 根类
    def show(self):
        print("父类A的方法")


class B(A):
    # 子类重写父类方法
    def show(self):
        print("子类B的方法")


b = B()
# 当子类方法与父类方法同名时,调用子类方法
b.show()

2.8.3 super()

  • 如果在子类中还要使用父类的方法,可以使用super()函数来调用父类中的方法。

  • 比如在重写父类方法时,还要保留父类方法的功能。

class A(object):
    # A 继承自 object 根类
    def show(self):
        print("父类A的方法")


class B(A):
    # 子类重写父类方法
    def show(self):
        # 使用 super() 调用父类方法
        super().show()
        print("子类B的方法")


b = B()
# 当子类方法与父类方法同名时,调用子类方法
b.show()

2.8.4 单继承的初始化

  • 在子类对象初始化时,需要给出父类初始化所需的参数,然后使用super()调用父类初始化方法去初始化父类的属性。
class A(object):
    # A 继承自 object 根类
    def __init__(self, a):
        self.a = a


class B(A):
    def __init__(self, a, b):
        super().__init__(a)
        self.b = b

b = B("A", "B")
print(b.a)
print(b.b)

2.8.5 继承的访问控制

  • 无论在方法的重写,还是初始化时,父类的工作就让父类自己去完成,子类只负责自己部分的实现。

  • 比如:如果在初始化时,想在子类中初始化父类的一个私有属性,这是不能实现的,但是可以调用父类的初始化方法对私有属性进行初始化。

class A(object):
    # A 继承自 object 根类
    def __init__(self, a, b, c):
        self.a = a
        self.b = b
        self.c = c

    def show(self):
        print(f"A: {self.a}")
        print(f"B: {self.b}")
        print(f"C: {self.c}")


class B(A):
    def __init__(self, a, b, c, d):
        super().__init__(a, b, c)
        self.d = d

    def show(self):
        super().show()
        print(f"D: {self.d}")

b = B(1,2,3,4)
b.show()

2.8.6 多继承

  • 多继承是指一个子类可以同时继承多个父类,此时子类同时拥有多个父类中的属性和方法。
class FA(object):
    def fa_show(self):
        print("FA show Run...")


class FB(object):
    def fb_show(self):
        print("FB show Run...")


class S(FA, FB):
    def s_show(self):
        print("S show Run...")


s = S()
s.s_show()
s.fa_show()
s.fb_show()

2.8.7 多继承同名方法查找顺序

  • 如果在一个子类所继承的多个父类中,具有同名方法,那在调用该方法名的方法时,python会使用C3算法实现的MRO(方法解析顺序)来确定查找的先后顺序,一般情况可以理解成是按继承类的书写顺序。
class FA(object):
    def show(self):
        print("FA show Run...")


class FB(object):
    def show(self):
        print("FB show Run...")


class S(FA, FB):
    def s_show(self):
        print("S show Run...")


s = S()
s.s_show()
s.show()

2.8.8 多继承初始化

  • 在多继承中,由于有多个父类,每个父类的属性都需要单独初始化,这时super()函数只能引用继承书写顺序上的第一个父类,其它的父类是无法通过super()引用的,所以也就无法利用super()函数进行初始化。

  • 此时,可以使用直接指定父类名的方式调用该父类中的方法。次方法也适用于多继承中的方法重写。

class FA(object):
    def __init__(self, a):
        self.a = a


class FB(object):
    def __init__(self, b):
        self.b = b


class S(FA, FB):
    def __init__(self, a, b, c):
        FA.__init__(self, a)
        FB.__init__(self, b)
        self.c = c


c = S(1, 2, 3)
print(c.a)
print(c.b)
print(c.c)

2.9 多态

  • 多态是面向对象编程中三大概念之三,它允许不同的对象对同一个消息作出不同的响应。

  • 简单说,多态是指同一个方法或操作符,在不同的对象实例上可以有不同的行为。这意味着可以通过一个共同的接口或基类,引用不同的子类对象,并根据实际的对象类型来调用相应的方法。

  • 多态的优点:

  1. 简化代码:通过以相同的方式处理不同的对象,并使用统一的接口进行编程,可以降低代码的复杂性和重要性。

  2. 可维护性:多态可以提高代码的可维护性。当需要新增一种子类时,不需要修改已有的代码,只需要创建一个新的子类并继承父类,就能够在原有代码的基础上,实现新的功能。

  3. 扩展性:由于多态允许在不修改已有代码的情况下新增功能,因此可以更容易地对系统进行扩展和适应需求的变化。

  • 多态通常通过继承和方法重写来实现。在继承关系中,子类可以重写父类的方法,在父类引用子类对象时,调用的实际上是子类重写后的方法。
# 中医
class Father:
    def cure(self):
        print("使用中医方法进行治疗")

# 西医
class Son(Father):
    def cure(self):
        print("使用西医方法进行治疗")

# 患者
class Patient:
    def needDoctor(self, doctor):
        doctor.cure()

if __name__ == '__main__':
    oldDoctor = Father()
    newDoctor = Son()

    patient = Patient()

    patient.needDoctor(oldDoctor)
    patient.needDoctor(newDoctor)

2.9.1 鸭子类型

  • 鸭子类型(Duck Typing)是一种动态类型的概念,它源自于“走起来像鸭子、叫声像鸭子、看起来像鸭子,那么它就是鸭子”的观念。

  • 在鸭子类型中,一个对象的适用性不是由它的类或接口决定,而是由它的方法和属性是否与所需的方法和属性匹配来决定。换句话说,只要一个对象具有特定方法和属性,我们就可以将其视为具有相同类型。

  • 举个例子,如果需要一个能“叫”的对象,并且某个对象有一个名为quack()的方法,那我们可以将对象视为一个“鸭子”,不管它实际是什么类的对象。换句话说,我们关注的是对象的行为而不是其类型。

  • 鸭子类型在动态语言中特别常见,比如python。在python中,不需要显式地继承或实现接口,只要一个对象具有必需的方法和属性,它就可以被认为是某种类型。这使得python具有灵活性和简洁性,可以更自由地处理不同类型的对象。

# 中医
class Father:
    def cure(self):
        print("使用中医方法进行治疗")

# 西医
class Son(Father):
    def cure(self):
        print("使用西医方法进行治疗")

# 兽医
class AnimalDoctor:
    def cure(self):
        print("使用兽医方法进行治疗")

# 患者
class Patient:
    def needDoctor(self, doctor):
        doctor.cure()

if __name__ == '__main__':
    oldDoctor = Father()
    newDoctor = Son()
    animalDoctor = AnimalDoctor()

    patient = Patient()

    patient.needDoctor(oldDoctor)
    patient.needDoctor(newDoctor)
    patient.needDoctor(animalDoctor)

  • 鸭子类型通常是动态语言的特性,相比于静态类型语言,它在编译时没有类型检查。这意味着无法在编译对类型不匹配或缺失方法和属性进行检测,可能会导致运行时错误。

2.9.2 类型检查

  • python中提供了isinstance()issubclass()两个函数,用来对数据进行检查判断。

isinstance()

  • python中使用isinstance()来检查一个实例的类型。

  • 格式:isinstance(obj, type)

  • 判断obj对象是否与Type指定类型或其父类类型的实例。

# 中医
class Father:
    def cure(self):
        print("使用中医方法进行治疗")

# 西医
class Son(Father):
    def cure(self):
        print("使用西医方法进行治疗")

# 兽医
class AnimalDoctor:
    def cure(self):
        print("使用兽医方法进行治疗")

# 患者
class Patient:
    def needDoctor(self, doctor):
        if isinstance(doctor,Father):
            doctor.cure()
        else:
            print("此医生的方法不适用于病人")

if __name__ == '__main__':
    oldDoctor = Father()
    newDoctor = Son()
    animalDoctor = AnimalDoctor()

    patient = Patient()

    patient.needDoctor(oldDoctor)
    patient.needDoctor(newDoctor)
    patient.needDoctor(animalDoctor)

    print(isinstance(newDoctor, Father))
    print(isinstance(newDoctor, Son))
    print(isinstance(newDoctor, AnimalDoctor))

issubclass()

  • python中还可以使用issubclass()来检查类的继承关系。

  • 格式:issubclass(Type1, Type2)

  • 判断Type1是否Type2的子类:

# 中医
class Father:
    def cure(self):
        print("使用中医方法进行治疗")

# 西医
class Son(Father):
    def cure(self):
        print("使用西医方法进行治疗")

# 兽医
class AnimalDoctor:
    def cure(self):
        print("使用兽医方法进行治疗")

# 患者
class Patient:
    def needDoctor(self, doctor):
        if issubclass(doctor.__class__, Father):
            doctor.cure()
        else:
            print("此医生的方法不适用于病人")

if __name__ == '__main__':
    oldDoctor = Father()
    newDoctor = Son()
    animalDoctor = AnimalDoctor()

    patient = Patient()

    patient.needDoctor(oldDoctor)
    patient.needDoctor(newDoctor)
    patient.needDoctor(animalDoctor)

    print(issubclass(Father, Father))
    print(issubclass(Son, Father))
    print(issubclass(AnimalDoctor, Father))

  • __class__是一个魔法属性,用来获取当前实例对象的类。