Python面向对象编程 (OOP) 初学者指南

编程是一门艺术。和艺术一样,选择合适的画笔和颜料对于创作出最好的作品至关重要。 Python面向对象编程就是这样一种技能。

选择正确的编程语言是任何项目的关键部分,它可以导致流畅和愉快的开发或彻底的噩梦。因此,最好为您的用例使用最适合的语言。

这是在Python中学习面向对象编程的主要原因,Python也是最流行的编程语言之一。让我们来学习!

  1. 一个示例Python程序
  2. 学习Python OOP的要求
  3. 什么是Python中的面向对象编程?
  4. 为什么我们在Python中使用面向对象编程?
  5. Python中的一切都是对象
  6. 你在Python中的第一个对象
  7. Python中OOP的4个支柱
  8. 构建区域形状解析器计算器

一个示例Python程序

在深入探讨这个问题之前,让我们提出一个问题:你有没有写过像下面这样的Python程序?

secret_number = 20
 
while True:
   number = input('Guess the number: ')
 
   try:
       number = int(number)
   except:
       print('Sorry that is not a number')
       continue
 
   if number != secret_number:
       if number > secret_number:
           print(number, 'is greater than the secret number')
 
       elif number < secret_number:
           print(number, 'is less than the secret number')
   else:
       print('You guessed the number:', secret_number)
       break

此代码是一个简单的数字猜测器。尝试将其复制到 Python 文件中并在您的系统中运行它。它完美地实现了它的目的。

但是这里出现了一个大问题:如果我们要求您实现一个新功能怎么办?它可能很简单——例如:

“如果输入是密码的倍数,给用户一个提示。”

随着特征数量的增加以及嵌套条件的总数的增加,程序会迅速变得复杂和繁重。

这正是面向对象编程试图解决的问题。

学习Python OOP的要求

在开始面向对象编程之前,我们强烈建议您牢牢掌握Python基础知识。

对被视为“基本”的主题进行分类可能很困难。因此,我们设计了一份备忘单,其中包含在Python中学习面向对象编程所需的所有主要概念。

  • 变量:指向特定对象的符号名称(我们将通过文章了解对象的含义)。
  • 算术运算符:加法 (+)、减法 (-)、乘法 (*)、除法 (/)、整数除法 (//)、模 (%)。
  • 内置数据类型:数字(整数、浮点数、复数)、序列(字符串、列表、元组)、布尔值(真、假)、字典和集合。
  • 布尔表达式:结果为假的表达式。
  • 条件:评估布尔表达式并根据结果进行一些处理。由if/else语句处理。
  • 循环:重复执行代码块。它可以是forwhile循环。
  • 功能:有组织和可重用的代码块。您可以使用关键字def创建它们。
  • 参数:传递给函数的对象。例如: sum([1, 2, 4])
  • 运行Python脚本:打开终端或命令行并输入“python <文件名>”。
  • 打开Python脚本:打开终端并键入pythonpython3(取决于您的系统)。

现在您已经清楚地了解了这些概念,您可以继续理解面向对象编程。

什么是Python中的面向对象编程?

面向对象编程 (OOP) 是一种编程范式,我们可以在其中将复杂问题视为对象。

范式是为解决问题提供基础的理论。

因此,当我们谈论OOP时,我们指的是一组用于解决对象问题的概念和模式。

Python中的对象是数据(属性)和行为(方法)的单一集合。您可以将物体视为您周围真实的事物。例如,考虑计算器:

Python面向对象编程 (OOP) 初学者指南-2

计算器可以是一个对象

您可能会注意到,数据(属性)始终是名词,而行为(方法)始终是动词。

这种划分是面向对象编程的核心概念。您构建存储数据并包含特定类型功能的对象。

为什么我们在Python中使用面向对象编程?

OOP允许您创建安全可靠的软件。许多Python框架和库使用这种范式来构建它们的代码库。一些示例是Django、Kivy、pandas、NumPy和TensorFlow。

让我们看看在Python中使用OOP的主要优势。

Python OOP的优点

以下原因将使您选择在Python中使用面向对象编程。

所有现代编程语言都使用OOP

这种范式与语言无关。如果您在Python中学习了OOP,您将能够在以下方面使用它:

所有这些语言要么是本机面向对象的,要么包含面向对象功能的选项。如果你想在Python之后学习它们中的任何一个,它会更容易——你会发现处理对象的语言之间有许多相似之处。

OOP使您可以更快地编码

更快的编码并不意味着编写更少的代码行。这意味着您可以在更短的时间内实现更多功能,而不会影响项目的稳定性。

面向对象编程允许您通过实现抽象来重用代码。这一原则使您的代码更加简洁易读。

您可能知道,程序员花在阅读代码上的时间比编写代码的时间多得多。这就是易读性始终比尽快推出功能更重要的原因。

代码不清晰导致生产力下降

代码不清晰导致生产力下降

稍后您将看到有关抽象原则的更多信息。

OOP帮助您避免意大利面条式代码

还记得本文开头的猜数程序吗?

如果您继续添加功能,将来您将拥有许多嵌套的if语句。这种无休止的代码行缠结称为意大利面条式代码,您应该尽可能避免使用它。

OOP为我们提供了压缩对象中所有逻辑的可能性,因此避免了嵌套的长段if

OOP改善您对任何情况的分析

一旦您获得了一些OOP的经验,您就能够将问题视为小而具体的对象。

这种理解导致快速的项目初始化。

结构化编程与面向对象编程

结构化编程是初学者最常用的范式,因为它是构建小程序的最简单方法。

它涉及按顺序运行Python程序。这意味着你给计算机一个任务列表,然后从上到下执行它们。

让我们看一个带有咖啡店程序的结构化编程示例。

small = 2
regular = 5
big = 6
 
user_budget = input('What is your budget? ')
 
try:
   user_budget = int(user_budget)
except:
   print('Please enter a number')
   exit()
 
if user_budget > 0:
   if user_budget >= big:
       print('You can afford the big coffee')
       if user_budget == big:
           print('It\'s complete')
       else:
           print('Your change is', user_budget - big)
   elif user_budget == regular:
       print('You can afford the regular coffee')
       print('It\'s complete')
   elif user_budget >= small:
       print('You can buy the small coffee')
       if user_budget == small:
           print('It\'s complete')
       else:
           print('Your change is', user_budget - small)

上面的代码充当咖啡店供应商。它会询问你的预算,然后“卖”给你你能买到的最大的咖啡。

尝试在终端中运行它。它将根据您的输入逐步执行。

这段代码运行良好,但我们有三个问题:

  1. 它有很多重复的逻辑。
  2. 它使用许多嵌套的if条件。
  3. 阅读和修改会很困难。

OOP的发明是为了解决所有这些问题。

让我们看看上面用OOP实现的程序。如果您还不明白,请不要担心。它仅用于比较结构化编程和面向对象编程。

class Coffee:
        # Constructor
        def __init__(self, name, price):
                self.name = name
                self.price = float(price)
        def check_budget(self, budget):
                # Check if the budget is valid
                if not isinstance(budget, (int, float)):
                        print('Enter float or int')
                        exit()
                if budget < 0: 
                    print('Sorry you don\'t have money') 
                    exit() 
        def get_change(self, budget):
                return budget - self.price
        
        def sell(self, budget):
                self.check_budget(budget)
                if budget >= self.price:
                        print(f'You can buy the {self.name} coffee')
                        if budget == self.price:
                                print('It\'s complete')
                        else:
                                print(f'Here is your change {self.get_change(budget)}$')

                        exit('Thanks for your transaction')

注意:本文将更深入地解释以下所有概念。

上面的代码代表一个名为“Coffee”的。它有两个属性——“name”和“price”——它们都在方法中使用。主要方法是“销售”,它处理完成销售过程所需的所有逻辑。

如果您尝试运行该类,您将不会得到任何输出。这主要是因为我们只是为咖啡声明了“模板”,而不是咖啡本身。

让我们使用以下代码实现该类:

small = Coffee('Small', 2)
regular = Coffee('Regular', 5)
big = Coffee('Big', 6)
 
try:
   user_budget = float(input('What is your budget? '))
except ValueError:
   exit('Please enter a number')
  
for coffee in [big, regular, small]:
   coffee.sell(user_budget)

在这里,我们正在制作“Coffee”类的实例或咖啡对象,然后调用每种咖啡的“sell”方法,直到用户负担得起任何选项。

我们将使用两种方法获得相同的输出,但我们可以使用OOP更好地扩展程序功能。

下表比较了面向对象编程和结构化编程:

面向对象编程 结构化编程
更易于维护 难以维护
不要重复自己 (DRY) 方法 多处重复代码
多处复用的小段代码 几个地方的大量代码
对象方法 块码方法
更容易调试 更难调试
大学习曲线 更简单的学习曲线
用于大型项目 优化为简单程序

总结范式比较:

  • 两种范式都不是完美的(在简单的项目中使用OOP可能会让人不知所措)。
  • 这只是解决问题的两种方法;还有其他人在那里。
  • OOP用于大型代码库,而结构化编程主要用于简单的项目。

让我们继续讨论Python中的内置对象。

Python中的一切都是对象

我们会告诉你一个秘密:你一直在使用OOP而没有注意到它。

即使在Python中使用其他范式时,您仍然使用对象来完成几乎所有事情。

那是因为,在Python中,一切都是对象。

记住对象的定义:Python中的对象是数据(属性)和行为(方法)的单一集合。

这匹配Python中的任何数据类型。

字符串是数据(字符)和行为(upper()lower()等)的集合。这同样适用于整数浮点数布尔值列表和字典。

在继续之前,让我们回顾一下属性和方法的含义。

属性和方法

属性是对象内部的内部变量,而方法是产生某些行为的函数。

让我们在Python终端中做一个简单的练习。您可以通过在终端中pythonpython3

Python终端

Python终端

现在,让我们使用Python终端来发现方法和类型。

>>> wbolt = 'WBOLT, Premium WordPress Themes and Plugins'
>>> wbolt.upper()
'WBOLT, PREMIUM WORDPRESS THEMES AND PLUGINS'

在第二行中,我们调用了一个字符串方法upper() 。它以大写形式返回字符串的内容。但是,它不会更改原始变量。

>>> wbolt
'Wbolt, Premium WordPress Themes and Plugins'

在处理对象时,让我们深入研究有价值的功能。 type()函数允许您获取对象的类型。 “类型”是对象所属的类。

>>> type(wbolt)
# class 'str'

dir()函数返回对象的所有属性和方法。让我们用wbolt变量来测试一下。

>>> dir(wbolt)
['__add__', '__class__',  ........... 'upper', 'zfill']

现在,尝试打印此对象的一些隐藏属性。

 >>> wbolt.__class__ # class ‘str’ e>

这将输出对象wbolt所属的类。所以我们可以说type函数唯一返回的是对象的__class__属性。

您可以试验所有数据类型,直接在终端上发现它们的所有属性和方法。您可以在官方文档中了解有关内置数据类型的更多信息。

你在Python中的第一个对象

一个就像一个模板它允许您根据您定义的属性和方法创建自定义对象。

你可以把它想象成一个饼干切割器,你可以修改它来烘焙完美的饼干(对象,而不是跟踪饼干),具有定义的特征:形状、大小等。

另一方面,我们有实例。实例是类的单个对象,它具有唯一的内存地址。

Python中的实例

Python中的实例

现在您知道什么是类和实例,让我们定义一些!

要在Python中定义类,请使用class关键字,后跟其名称。在本例中,您将创建一个名为Cookie的类。

注意:在Python中,我们使用驼峰命名约定来命名类。

class Cookie:
	pass

打开你的Python终端并输入上面的代码。要创建类的实例,只需在其后键入其名称和括号。这与调用函数的过程相同。

cookie1 = Cookie()

恭喜——你刚刚用Python创建了你的第一个对象!您可以使用以下代码检查其 id 和类型:

id(cookie1)
140130610977040 # Unique identifier of the object

type(cookie1)
<class '__main__.Cookie'>

可以看到,这个cookie在内存中有一个唯一的标识符,它的类型是Cookie

您还可以使用isinstance()函数检查对象是否是类的实例。

isinstance(cookie1, Cookie)
# True
isinstance(cookie1, int)
# False
isinstance('a string', Cookie)
# False

构造方法

__init__()方法也称为“构造函数”。每次我们实例化一个对象时,它都被称为Python。

构造函数使用它需要存在的最小参数集创建对象的初始状态。让我们修改Cookie类,使其在其构造函数中接受参数。

class Cookie:
	# Constructor
	def __init__(self, name, shape, chips='Chocolate'):
		# Instance attributes
		self.name = name
		self.shape = shape
		self.chips = chips

Cookie类中,每个cookie都必须有一个name、shape和chips。我们将最后一种定义为“Chocolate”。

另一方面, self指的是类的实例(对象本身)。

尝试将类粘贴到终端中并像往常一样创建cookie的实例。

cookie2 = Cookie()
# TypeError

你会得到一个错误。那是因为您必须提供对象生存所需的最少数据集——在本例中,nameshape因为我们已经将chips设置为“Chocolate”。

cookie2 = Cookie('Awesome cookie', 'Star')

要访问实例的属性,您必须使用点表示法。

cookie2.name
# 'Awesome cookie'
cookie2.shape
# 'Star'
cookie2.chips
# 'Chocolate'

目前, Cookie类没有任何内容。让我们添加一个示例方法bake()以使事情变得更有趣。

class Cookie:
	# Constructor
	def __init__(self, name, shape, chips='Chocolate'):
		# Instance attributes
		self.name = name
		self.shape = shape
		self.chips = chips

	# The object is passing itself as a parameter
	def bake(self):
		print(f'This {self.name}, is being baked with the shape {self.shape} and chips of {self.chips}')
		print('Enjoy your cookie!')

要调用方法,请使用点表示法并将其作为函数调用。

cookie3 = Cookie('Baked cookie', 'Tree')
cookie3.bake()
# This Baked cookie, is being baked with the shape Tree and chips of Chocolate
Enjoy your cookie!

Python中OOP的4个支柱

面向对象编程包括四个主要支柱:

1. 抽象

抽象对用户隐藏了应用程序的内部功能。用户可以是最终客户或其他开发人员。

我们可以在日常生活中找到抽象。例如,您知道如何使用手机,但每次打开应用程序时,您可能并不确切知道手机内部发生了什么。

另一个例子是Python本身。您知道如何使用它来构建功能性软件,即使您不了解Python的内部工作原理,您也可以做到。

将相同的应用于代码允许您收集问题中的所有对象并将 标准功能抽象到类中。

2.继承

继承允许我们从一个已经定义的类中定义多个类。

它的主要目的是遵循DRY原则。通过将所有共享组件实现到超类中,您将能够重用大量代码。

您可以将其视为现实生活中的基因遗传概念。Children(子类)是两个parents(父)之间的继承的结果。它们继承了所有的物理特性(属性)和一些常见的行为(方法)。

3. 多态性

多态允许我们稍微修改之前在超类中定义的子类的方法和属性。

字面意思是“多种形式” 。那是因为我们构建了名称相同但功能不同的方法。

回到之前的想法,孩子也是多态的一个完美例子。它们可以继承定义的行为get_hungry()但方式略有不同,例如,每4小时而不是每6小时饿一次。

4.封装

封装是我们保护类中数据内部完整性的过程。

尽管Python中没有private语句,但您可以通过在Python中使用mangling来应用封装。有一些名为gettersetter的特殊方法允许我们访问独特的属性和方法。

让我们想象一个Human类,它有一个名为_height的唯一属性。您只能在某些约束内修改此属性(几乎不可能高于3米)。

构建区域形状解析器计算器

Python最好的事情之一是它让我们可以创建各种各样的软件,从CLI(命令行界面)程序到复杂的Web应用程序。

现在您已经了解了OOP的支柱概念,是时候将它们应用到实际项目中了。

注意:以下所有代码都将在此GitHub存储库中可用。一种代码修订工具,可帮助我们使用Git管理代码版本。

您的任务是创建以下形状的面积计算器:

  • 正方形
  • 长方形
  • 三角形
  • 圆圈
  • 六边形

Shape Base类

首先,创建一个文件calculator.py并打开它。因为我们已经有了要使用的对象,所以很容易在一个类中抽象它们

您可以分析它们的共同特征并发现所有这些都是二维形状。因此,最好的选择是使用get_area()方法创建一个类Shape ,每个形状都将从该方法继承。

注意:所有的方法都应该是动词。这是因为此方法名为get_area()而不是area()

class Shape:
	def __init__(self):
		pass

	def get_area(self):
		pass

上面的代码定义了类;然而,其中还没有任何有趣的东西。

让我们实现大多数这些形状的标准功能。

class Shape:
	def __init__(self, side1, side2):
		self.side1 = side1
		self.side2 = side2

	def get_area(self):
		return self.side1 * self.side2

	def __str__(self):
		return f'The area of this {self.__class__.__name__} is: {self.get_area()}'

让我们分解一下我们正在用这段代码做什么:

  • __init__方法中,我们要求两个参数,SIDE1side2这些将保留为实例属性
  • get_area()函数返回形状的面积。在这种情况下,它使用矩形的面积公式,因为使用其他形状更容易实现。
  • __str__() 和__init__()一样是一种“神奇的方法”。它允许您修改实例的打印方式。
  • self.__class__.__name__隐藏属性指的是类的名称。如果您使用的是Triangle类,则该属性将为“Triangle”。

Rectangle类

由于我们实现了 Rectangle 的面积公式,我们可以创建一个简单的Rectangle类,它只继承Shape类。

要在Python中应用继承,您将像往常一样创建一个类,并用括号将要继承的超类括起来。

# Folded base class
class Shape: ...
 
class Rectangle(Shape): # Superclass in Parenthesis
	pass

Square类

我们可以对Square类采取一种很好的多态方法。

请记住,正方形只是四个边都相等的矩形。这意味着我们可以使用相同的公式来获得面积。

我们可以通过修改init方法来做到这一点,只接受一个作为参数,并将该边的值传递给Rectangle类的构造函数。

# Folded classes
class Shape: ...
class Rectangle(Shape): ...
 
class Square(Rectangle):
	def __init__(self, side):
		super().__init__(side, side)

如您所见, super 函数side参数传递给 superclass两次。换句话说,这既是传递SIDE1侧面2先前定义的构造函数。

Triangle类

三角形的大小是它周围的矩形的一半。

三角形和矩形之间的关系(图片来源:Varsity Tutors)

三角形和矩形之间的关系(图片来源:Varsity Tutors)

因此,我们可以继承Rectangle类,修改get_area方法来匹配三角形面积公式,即底乘以高的二分之一。

# Folded classes
class Shape: ...
class Rectangle(Shape): ...
class Square(Rectangle): ...
 
class Triangle(Rectangle):
	def __init__(self, base, height):
		super().__init__(base, height)
 
	def get_area(self):
		area = super().get_area()
		return area / 2

super()函数的另一个用例是调用超类中定义的方法并将结果存储为变量。这就是 get_area()方法内部发生的事情。

Circle类

您可以使用公式πr²找到圆面积,其中r是圆的半径。这意味着我们必须修改get_area()方法来实现该公式。

注意:我们可以从math模块中导入π的近似值

# Folded classes
class Shape: ...
class Rectangle(Shape): ...
class Square(Rectangle): ...
class Triangle(Rectangle): …
 
# At the start of the file
from math import pi
 
class Circle(Shape):
	def __init__(self, radius):
		self.radius = radius
 
	def get_area(self):
		return pi * (self.radius ** 2)

上面的代码定义了Circle类,它使用不同的构造函数和get_area()方法。尽管Circle继承自Shape类,但您可以重新定义每个方法并根据自己的喜好将其归因。

Regular Hexagon类

我们只需要正六边形的边长来计算它的面积。它类似于Square类,我们只向构造函数传递一个参数。

六边形面积公式(图片来源:BYJU'S)

六边形面积公式(图片来源:BYJU’S)

但是,公式完全不同,它意味着使用平方根。这就是您将使用math模块中的sqrt()函数的原因

# Folded classes
class Shape: ...
class Rectangle(Shape): ...
class Square(Rectangle): ...
class Triangle(Rectangle): …
class Circle(Shape): …
 
# Import square root
from math import sqrt
 
class Hexagon(Rectangle):
	
	def get_area(self):
		return (3 * sqrt(3) * self.side1 ** 2) / 2

测试我们的类

您可以在使用调试器运行Python文件时进入交互模式。最简单的方法是使用内置的断点函数。

注意:此函数仅在Python 3.7或更高版本中可用。

from math import pi, sqrt
# Folded classes
class Shape: ...
class Rectangle(Shape): ...
class Square(Rectangle): ...
class Triangle(Rectangle): …
class Circle(Shape): …
class Hexagon(Rectangle): …
 
breakpoint()

现在,运行Python文件并使用您创建的类。

$ python calculator.py
 
(Pdb) rec = Rectangle(1, 2)(Pdb) print(rec)
The area of this Rectangle is: 2
(Pdb) sqr = Square(4)
(Pdb) print(sqr)
The area of this Square is: 16
(Pdb) tri = Triangle(2, 3)
(Pdb) print(tri)
The area of this Triangle is: 3.0
(Pdb) cir = Circle(4)
(Pdb) print(cir)
The area of this Circle is: 50.26548245743669
(Pdb) hex = Hexagon(3)
(Pdb) print(hex)
The area of this Hexagon is: 23.382685902179844

挑战

使用run方法创建一个类,用户可以在其中选择形状并计算其面积。完成挑战后,您可以向GitHub存储库发送拉取请求或在评论部分发布您的解决方案。

小结

面向对象编程是一种范式,我们通过将问题视为对象来解决问题。如果您了解Python OOP,您还可以轻松地将其应用于Java、PHP、Javascript和C#等语言。

在本文中,您已了解:

  • Python中面向对象的概念
  • 面向对象相对于结构化编程的优势
  • Python面向对象编程的基础知识
  • 的概念以及如何在Python中使用它们
  • Python中类的构造函数
  • Python中的方法属性
  • OOP的四大支柱
  • 在项目中实现抽象继承多态