面向对象编程(OOP)是一种编程范式,它使用对象的概念来表示现实世界中的实体及其状态和行为。本章将帮助您成为使用 Python 语言中的面向对象编程支持的专家。
Python 是一种支持面向对象编程的语言,这让创建和使用类和对象变得简单。如果您之前没有面向对象编程的经验,那么您来对地方了。让我们从一个小的面向对象编程(OOP)介绍开始。
过程式编程方法
早期在 50 和 60 年代开发的编程语言被认为是过程式(或面向过程)语言。
计算机程序通过编写一系列有序的指令来描述完成某项任务的过程。更复杂的程序逻辑会被分解成较小但独立且可重用的代码块,称为函数。
每个函数都以这样的方式编写,它可以与其他函数在程序中进行接口。属于某个函数的数据可以很容易地以参数的形式与其他函数共享,被调用的函数可以将其结果返回给调用函数。
过程式方法的主要问题是:
-
-
它使用了大量的全局数据项,这是不希望的。过多的全局数据项会增加内存开销。
-
它更多地关注过程,而忽视了同等重要的数据,因此数据在整个程序中自由流动。
-
数据在函数间的移动不受限制。而在现实生活中,通常期望一个函数与其处理的数据有明确的关联。
Python 中的 OOP 概念
在现实生活中,我们处理并加工对象,比如学生、员工、发票、汽车等。对象不仅是数据,也不仅是函数,而是两者的结合。每个现实世界的对象都有与其相关的属性和行为。
属性
每个属性都会有一个与之相关的值。属性相当于数据。
行为
处理与对象相关的属性。
行为相当于函数。在现实生活中,属性和行为并不是相互独立的,而是共存的。
面向对象方法最重要的特点是将属性及其功能定义为一个单一单元,称为类。它充当所有具有相似属性和行为的对象的蓝图。
在 OOP 中,类定义了它的对象有哪些属性以及它们的行为如何。另一方面,对象是类的实例。
OOP 概念的原则
面向对象编程范式以以下原则为特征:
类与对象
类是用户定义的对象原型,定义了一组表征该类任何对象的属性。属性是数据成员(类变量和实例变量)和方法,通过点号访问。
对象是指某一类的实例。例如,一个名为 obj 的对象属于 Circle 类,就是该类的一个实例。这是一个由其类定义的独特数据结构实例。对象既包含数据成员(类变量和实例变量)也包含方法。
示例
下面的例子展示了如何在 Python 中创建一个类及其对象。
class Smartphone:
def __init__(self, device, brand):
self.device = device
self.brand = brand
def description(self):
return f"{self.device} of {self.brand} supports Android 14"
phoneObj = Smartphone("Smartphone", "Samsung")
print(phoneObj.description())
运行上述代码后,会显示如下输出:
Smartphone of Samsung supports Android 14
封装
类的数据成员仅可供定义在类内的函数处理。另一方面,类的函数可以从外部访问。因此,对象数据对外部环境隐藏起来。类的函数(也称作方法)封装了对象数据,防止其被不当访问。
示例
在这个例子中,我们使用封装的概念来设定桌面的价格。
class Desktop:
def __init__(self):
self.__max_price = 25000
def sell(self):
return f"Selling Price: {self.__max_price}"
def set_max_price(self, price):
if price > self.__max_price:
self.__max_price = price
desktopObj = Desktop()
print(desktopObj.sell())
desktopObj.__max_price = 35000
print(desktopObj.sell())
desktopObj.set_max_price(35000)
print(desktopObj.sell())
当执行上述代码时,会产生如下结果:
Selling Price: 25000
Selling Price: 25000
Selling Price: 35000
继承
面向对象编程的一种软件建模方法允许扩展现有类的功能以构建新类,而不是从零开始构建。在 OOP 术语中,现有的类被称为基类或父类,而新的类被称为子类或派生类。
子类继承父类的数据定义和方法。这便于重用已经可用的功能。子类可以添加一些额外的定义或重新定义基类的函数。
语法
派生类的声明方式与它们的父类相似;然而,在类名之后给出了要继承的基类列表。
class SubClassName (ParentClass1[, ParentClass2, ...]):
'Optional class documentation string'
class_suite
示例
下面的例子演示了 Python 中的继承概念:
class Parent:
parentAttr = 100
def __init__(self):
print ("Calling parent constructor")
def parentMethod(self):
print ("Calling parent method")
def setAttr(self, attr):
Parent.parentAttr = attr
def getAttr(self):
print ("Parent attribute :", Parent.parentAttr)
class Child(Parent):
def __init__(self):
print ("Calling child constructor")
def childMethod(self):
print ("Calling child method")
c = Child()
c.childMethod()
c.parentMethod()
c.setAttr(200)
c.getAttr()
当执行上述代码时,会产生如下结果:
Calling child constructor
Calling child method
Calling parent method
Parent attribute : 200
同样地,您可以从多个父类派生一个类,如下所示:
class A:
.....
class B:
.....
class C(A, B):
.....
您可以使用 issubclass()
或 isinstance()
函数来检查两个类之间的关系和实例。
issubclass(sub, sup)
布尔函数如果给定的子类 sub
确实是超类 sup
的子类则返回真。
isinstance(obj, Class)
布尔函数如果 obj
是类 Class
的实例或者是 Class
的子类的实例则返回真。
多态性
多态性是一个希腊词汇,意思是拥有多种形态。在面向对象编程中,当每个子类在其基类中提供抽象方法的不同实现时,就发生了多态。
您可以覆盖父类的方法。覆盖父类方法的一个原因是您可能想要在子类中有特殊或不同的功能。
示例
在这个例子中,我们覆盖了父类的方法。
class Parent:
def myMethod(self):
print ("调用父类方法")
class Child(Parent):
def myMethod(self):
print ("调用子类方法")
c = Child()
c.myMethod()
当执行上述代码时,会产生如下结果:
调用子类方法
Python 中的基类方法重载
下表列出了一些可以在您自己的类中覆盖的一般功能:
序号 |
方法、描述及示例调用 |
1 |
__init__ ( self [,args...] ) |
|
构造函数(带任何可选参数) |
|
示例调用: obj = className(args) |
2 |
__del__( self ) |
|
析构函数,删除一个对象 |
|
示例调用: del obj |
3 |
__repr__( self ) |
|
可评估的字符串表示形式 |
|
示例调用: repr(obj) |
4 |
__str__( self ) |
|
可打印的字符串表示形式 |
|
示例调用: str(obj) |
5 |
__cmp__ ( self, x ) |
|
对象比较 |
|
示例调用: cmp(obj, x) |
Python 中的操作符重载
假设您创建了一个 Vector 类来表示二维向量,当您使用加号运算符来相加时会发生什么?很可能是 Python 会报错。
但是,您可以在您的类中定义 __add__
方法来进行向量加法,然后加号运算符就会如预期那样工作。
示例
class Vector:
def __init__(self, a, b):
self.a = a
self.b = b
def __str__(self):
return 'Vector (%d, %d)' % (self.a, self.b)
def __add__(self,other):
return Vector(self.a + other.a, self.b + other.b)
v1 = Vector(2,10)
v2 = Vector(5,-2)
print (v1 + v2)
当执行上述代码时,会产生如下结果:
Vector(7,8)