一、面向对象的概念
- 面向对象编程(OOP):Object Oriented Programming
- 面向对象:就是在编程时尽可能地模拟现实世界,按照现实世界中的逻辑去处理问题,分析问题中参与其中的有哪些实体、这些实体应该有什么属性和方法,我们如何通过调用这些实体的属性和方法去解决问题。
- Python是一门面向对象的语言。
1.1 两种编程思想
- 面向过程(Procedural Programming)
- 一种以过程为中心的编程思想;
- 首先分析解决问题所需要的步骤;
- 然后用函数将这些步骤一步一步的实现;
- 最后按顺序依次调用运行。
- 面向对象(Object-Oriented Programming,简称OOP)
- 是一种更符合我们人类思维习惯的编程思想;
- 面向对象开发就是不断的创建对象,使用对象,操作对象做事情;
- 可以将复杂的事情简单化。
1.2 面向对象编程与面向过程编程的区别
-
抽象程度:面向过程编程将程序划分为一系列的过程或函数,通过函数之间的调用来完成任务。而面向对象编程将程序看作是一组相互作用的对象,每个对象都有其自己的属性和行为。
-
封装性:面向过程编程强调的是数据和函数的分离,函数可以直接访问和操作数据。而面向对象编程通过封装将数据和对数据的操作绑定在一起,只暴露出必要的接口,隐藏了内部的具体实现细节。
-
继承性:面向对象编程具有继承的特性,可以通过继承机制从已存在的类派生出新的类,并继承父类的属性和方法,以实现代码的重用和扩展。而面向过程编程没有继承的概念,代码重用通常通过函数的封装和复用来实现。
-
多态性:面向对象编程支持多态,即同一种类型的对象在不同的上下文中有不同的行为,这使得代码更加灵活和可扩展。而面向过程编程没有多态的概念,需要通过条件语句来实现不同情况的处理。
-
设计思想:面向对象编程注重的是问题领域的建模和抽象,关注对象之间的关系和交互。而面向过程编程更加注重问题的解决步骤和过程化的思维。
选择面向过程还是面向对象编程,取决于具体的需求和项目情况。
面向对象编程更适合复杂的系统和大规模的项目,能够提高代码的模块化、可维护性和扩展性;而面向过程编程则更适合简单的任务和较小规模的项目,可以减少不必要的复杂性。
二、类与对象
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}小时")
2.2 对象
-
对象(object):称为类的一个实例,是具体存在的、具有状态和行为的实体。
-
可以把类看作是对象的模板或蓝图,描述了对象应该具有的属性和方法。当我们通过类创建一个具体的实例时,这个实例就是一个对象。
-
对象有两个关键的特征:状态和行为。
-
状态(state):对象的状态由它的属性(也称为成员变量)决定,属性表示了对象的特点和特征。例如:一个人对象的状态包括姓名、年龄、性别等属性,这些属性的值可以根据对应的实际情况而不同。
-
行为(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 封装
-
封装是面向对象编程中三大特征之一,指的是将数据和操作数据的方法打包在一起,形成一个类或对象。
-
封装的目的是隐藏对象的内部实现细节,提供一个安全且易于使用的接口,使得对象之间的交互更加简单和可靠。
-
封装主要包括:
-
数据隐藏:通过将对象的数据属性设置为私有或受保护的,防止外部直接访问和修改对象的数据。这样可以确保对象的数据在被操作时不会被意外篡改或破坏。
-
方法封装:将对象对自身数据的操作封装在方法中,只通过方法来访问和修改对象的数据。这样可以确保对对象的操作符合预期,避免了外部错误地修改对象的数据。
-
接口定义:通过定义公共接口,将对象的功能暴露给外部使用者。使用者只需关心如何使用接口提供的方法,而不需要了解内部实现细节。这样可以提高代码的可读性和可维护性,同时也能够实现代码的模块化和复用。
- 封装的优势:
-
数据安全性:封装隐藏了对象的内部实现细节,保护了数据的完整性和安全性。外部无法直接访问或修改对象的数据,必须通过规定的方法进行操作,减少了意外错误的发生。
-
代码模块化:封装将对象的数据和操作打包在一起,实现了代码的模块化。不同的对象可以独立开发和测试,降低了代码的耦合性,增加了系统的可维护性和扩展性。
-
简化接口:封装将对象的功能通过公共接口暴露给外部,隐藏了内部实现细节。外部使用者只需了解接口的使用方法,而无需关心具体的实现。这降低了外部使用者的使用难度,也提高了代码的可读性和易用性。
2.7.1 访问控制
-
在python中并没有像Java、C++一样,提供了
public
、protected
、private
这样的访问控制修饰符,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 继承
-
继承是面向对象编程中的三大概念之二,指的是一个类基于另一个类来创建。
-
创建出来的新类称为子类或派生类。被继承的类称为父类或基类。
-
通过继承,子类可以继承父类的属性和方法,并且可以在此基础上添加新的属性和方法,或者对继承的属性和方法进行修改。
-
继承的主要特点:
-
继承关系:继承创建了一个父类和子类之间的关系。子类继承了父类的特性,包括属性和方法。子类可以重用父类的代码,减少了代码的冗余。
-
子类的扩展:子类可以在继承父类的基础上,添加新的属性和方法。这样可以对父类进行扩展,使得子类具有更多的功能。
-
代码共享和重用:通过继承,子类可以共享父类的代码。父类中通用的属性和方法可以被多个子类继承和使用,提高了代码的重用性,并减少了开发实践和成本。
-
继承的层次结构:继承可以形成一个层次结构,其中一个父类可以有多个子类,而子类又可以成为其他子类的父类。这种层次结构可以更好地组织和管理代码,使得代码更加结构化和模块化。
- 继承的优势:
-
代码重用:继承允许子类重用父类的代码,减少了代码的冗余,提高了代码的可维护性和复用性。
-
扩展性:通过继承,子类可以在父类的基础上添加新的属性和方法,实现对父类的扩展,使得子类具有更多的功能。
-
类型的兼容性:由于子类继承了父类的特性,子类可以被当作父类的实例来使用。这样在需要父类类型的地方,可以使用子类的实例,增加了代码的灵活性和可扩展性。
- 需要注意的是,虽然继承可以提供代码重用和扩展的好处,但过度使用继承可能导致代码的复杂性和耦合性增加。因此,在设计代码时,应该合理使用继承,并遵循单一责任原则和开闭原则,保持代码的简介和灵活。
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 多态
-
多态是面向对象编程中三大概念之三,它允许不同的对象对同一个消息作出不同的响应。
-
简单说,多态是指同一个方法或操作符,在不同的对象实例上可以有不同的行为。这意味着可以通过一个共同的接口或基类,引用不同的子类对象,并根据实际的对象类型来调用相应的方法。
-
多态的优点:
-
简化代码:通过以相同的方式处理不同的对象,并使用统一的接口进行编程,可以降低代码的复杂性和重要性。
-
可维护性:多态可以提高代码的可维护性。当需要新增一种子类时,不需要修改已有的代码,只需要创建一个新的子类并继承父类,就能够在原有代码的基础上,实现新的功能。
-
扩展性:由于多态允许在不修改已有代码的情况下新增功能,因此可以更容易地对系统进行扩展和适应需求的变化。
- 多态通常通过继承和方法重写来实现。在继承关系中,子类可以重写父类的方法,在父类引用子类对象时,调用的实际上是子类重写后的方法。
# 中医
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__
是一个魔法属性,用来获取当前实例对象的类。