编程是一门艺术。和艺术一样,选择合适的画笔和颜料对于创作出最好的作品至关重要。 Python面向对象编程就是这样一种技能。
选择正确的编程语言是任何项目的关键部分,它可以导致流畅和愉快的开发或彻底的噩梦。因此,最好为您的用例使用最适合的语言。
这是在Python中学习面向对象编程的主要原因,Python也是最流行的编程语言之一。让我们来学习!
- 一个示例Python程序
- 学习Python OOP的要求
- 什么是Python中的面向对象编程?
- 为什么我们在Python中使用面向对象编程?
- Python中的一切都是对象
- 你在Python中的第一个对象
- Python中OOP的4个支柱
- 构建区域形状解析器计算器
一个示例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语句处理。
- 循环:重复执行代码块。它可以是for或while循环。
- 功能:有组织和可重用的代码块。您可以使用关键字def创建它们。
- 参数:传递给函数的对象。例如:
sum([1, 2, 4])
- 运行Python脚本:打开终端或命令行并输入“python <文件名>”。
- 打开Python脚本:打开终端并键入
python
或python3
(取决于您的系统)。
现在您已经清楚地了解了这些概念,您可以继续理解面向对象编程。
什么是Python中的面向对象编程?
面向对象编程 (OOP) 是一种编程范式,我们可以在其中将复杂问题视为对象。
范式是为解决问题提供基础的理论。
因此,当我们谈论OOP时,我们指的是一组用于解决对象问题的概念和模式。
Python中的对象是数据(属性)和行为(方法)的单一集合。您可以将物体视为您周围真实的事物。例如,考虑计算器:
计算器可以是一个对象
您可能会注意到,数据(属性)始终是名词,而行为(方法)始终是动词。
这种划分是面向对象编程的核心概念。您构建存储数据并包含特定类型功能的对象。
为什么我们在Python中使用面向对象编程?
OOP允许您创建安全可靠的软件。许多Python框架和库使用这种范式来构建它们的代码库。一些示例是Django、Kivy、pandas、NumPy和TensorFlow。
让我们看看在Python中使用OOP的主要优势。
Python OOP的优点
以下原因将使您选择在Python中使用面向对象编程。
所有现代编程语言都使用OOP
这种范式与语言无关。如果您在Python中学习了OOP,您将能够在以下方面使用它:
- Java
- PHP
- Ruby
- Javascript
- C#
- Kotlin
所有这些语言要么是本机面向对象的,要么包含面向对象功能的选项。如果你想在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)
上面的代码充当咖啡店供应商。它会询问你的预算,然后“卖”给你你能买到的最大的咖啡。
尝试在终端中运行它。它将根据您的输入逐步执行。
这段代码运行良好,但我们有三个问题:
- 它有很多重复的逻辑。
- 它使用许多嵌套的if条件。
- 阅读和修改会很困难。
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终端中做一个简单的练习。您可以通过在终端中python
或python3
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中定义类,请使用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
你会得到一个错误。那是因为您必须提供对象生存所需的最少数据集——在本例中,name和shape,因为我们已经将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来应用封装。有一些名为getter和setter的特殊方法允许我们访问独特的属性和方法。
让我们想象一个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__方法中,我们要求两个参数,SIDE1和side2。这些将保留为实例属性。
- 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)
因此,我们可以继承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)
但是,公式完全不同,它意味着使用平方根。这就是您将使用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的四大支柱
- 在项目中实现抽象、继承和多态
评论留言