# 面向对象编程(OOP) > 原文: [https://javabeginnerstutorial.com/python-tutorial/object-oriented-programming-oop/](https://javabeginnerstutorial.com/python-tutorial/object-oriented-programming-oop/) 现在,我们进行了面向对象的编程。 曾经有过这样一种炒作:每种语言都是围绕对象设计的,而 Python 开发人员 Guido van Rossum 认为“为什么不呢?” 并添加了类以支持面向对象的开发。 一些 python 传福音者认为这是一个错误的决定,有些认为这是一个好方法…… 面向对象是一个大话题。 可以写一本关于它的书,但我会坚持在文章的狭小范围内。 或最多有两篇有关 OO 的常规文章和 Python 中的 OO 文章。 ## 一般的面向对象 面向对象技术大约在 60 年代后期,但直到 90 年代初才在开发人员中获得了发展空间。 我们将学习以下四个主要原则: * 封装 * 数据抽象 * 继承 * 多态 如果我想模拟现实生活,我会说 OO 就像一家餐馆。 您可以有两种类型:一种可以在柜台上找到食物,也可以用食物自助服务。 另一个是您进餐的地方,它是由专业服务准备并带给您的。 带有自助服务的第一个版本是命令式语言使用的东西(例如 C 或简单的 Python 脚本),在这里每个人都可以访问所有内容,并且他们可以使用他们想要的东西。 在这种情况下,有时会将碗碟留在桌子上,因为带回碗碟是客户的工作。 第二个版本是 OO。 在那里您可以封装功能,并且只能访问那些公开可用的部分。 如果您已经使用 Java 或 C++ 开发过,您可能会知道公开,受保护和私有访问的概念。 在这种情况下,通过员工来获取食物并带回餐具。 他们知道从何处,何处放东西可以得到什么,而最终用户并不需要了解一切。 如果我们回头看面向对象,那么我们可以说一类是一个对象的定义,一个对象是指定类的实例。 一个类定义了未来对象具有的属性和函数,以及该语言是否使用访问限制,您可以在类定义中告诉公众可以访问哪些部分,该类的扩展还是仅内部使用。 现在是时候深入研究 OOP 的四大原则了。 ### 封装 封装是指将数据和函数打包到单个组件中。 但是,在我们的情况下,进入一类,其他编程语言支持其他替代方法。 该类的函数将根据存储在该类的字段中的数据进行操作。 在某些编程语言中,封装用于隐藏信息,或更准确地说:限制对数据和函数的访问。 这些语言包括 C++ 和 Java,例如,您可以在其中使用`private`,`protected`和`public`来限制对字段和方法的访问。 但是在 Python 中没有这样的限制级别。 您可以访问类的每个字段和函数。 但是,有一个约定没有写下来,但是每个 Python 开发人员都知道并且应该知道:名称以双下划线(`__`)开头的类(字段和函数)的成员应视为私有的,不应调用或访问。 ### 数据抽象 数据抽象强制将类型的抽象属性与实现细节之间的清晰区分。 抽象属性是那些使用此数据类型对客户端可见的属性(在我们的情况下为类,在其他编程语言中为接口定义),并且实现对客户端隐藏并为私有。 而且由于实现是私有的,因此可以随时间更改(例如,使代码更快),并且客户端不会注意到此更改,因为抽象保持不变。 好吧,在 Python 中,没有什么比其他 OO 语言的接口更好。 您只有一个类,这个类有它的字段和功能。 当然,您可以具有一个“公开”函数作为外部代码的接口,以及一个或多个实现该“公开”函数的逻辑的“私有”函数。 但这不是真正的抽象。 但是,Python 知道用于只读字段的解决方案,这些字段是通过所谓的“获取器”方法即时计算的。 当然,对于读写字段,Python 也使我们也可以使用“设置器”方法。 我们将在后面看到这两个示例。 ### 继承 继承是 OOP 的一项关键功能,其中一个类基于另一类的模板/实现(从该类继承)。 这是一种代码重用的基本方法,您可以将子类之间的通用函数和信息封装到一个基类中。 继承模型有不同类型,但是最常见的两种是*单继承*和*多继承*。 Python 使用**多重继承**,这意味着一个类可以根据需要扩展任意多个类。 继承通常与*对象组成*混淆。 有时,新的开发人员会尝试解决继承的所有问题,即使继承应该是对象组合。 对象组合意味着您拥有另一个类的实例的属性,但是您的类没有扩展它。 如果您不知道需要哪一个,请记住以下简单的解决方案: 继承是 **is-a** 关系,意思是*汽车是车辆*。 对象组成是**与**的关系,意味着*汽车具有车轮*(或至少汽车具有车轮)。 ### 多态 在 OOP 中,多态是为多个类型提供单个接口。 在 Python 中,这意味着您希望将超类作为参数(例如,执行`isinstance()`检查)并在对象上调用该超类的通用方法。 现在,通过多态,将在所使用的子类中执行该方法的实际实现。 ```py >>> class Animal: ... def sound(self): ... raise NotImplementedError ... >>> class Dog: ... def sound(self): ... print('woof') ... >>> class Dog(Animal): ... def sound(self): ... print('woof') ... >>> class Cat(Animal): ... def sound(self): ... print('meow') ... >>> def animal_sound(animal): ... if isinstance(animal, Animal): ... animal.sound() ... else: ... print("Not an animal, do not know how to make it sound") ... >>> cat = Cat() >>> dog = Dog() >>> animal_sound(dog) woof >>> animal_sound(cat) meow ``` 如您在上面的示例中所看到的,`animal_sound`函数验证该参数是`Animal`,然后调用该特定动物的`sound`方法。 ## 什么时候使用 OO? 自然,OO 不是万能的油。 因此,在开发时应考虑使用 OOP。 在本节中,我将更深入地探讨何时应用本章的原理和技术。 确定何时使用面向对象的编程并不容易。 我们必须记住,对象具有数据**和**行为,这使事情变得复杂。 这就是为什么许多 Python 开发人员使用简单的数据结构(列表,集合,字典)和简单的函数的原因,除非确实需要额外的层抽象(我也是)。 现在,如果我们看到使用相同数据集调用函数,则可以考虑将数据封装到一个类中,然后将这些函数添加为类函数以表示行为。 一个简单的示例就是几何图形之外的东西。 在那里,您使用一个 2 元组(一对)来存储点。 点列表代表一个形状(多边形)。 因此,您首先要定义一个列表,其中包含一些表示点的对: ```py triangle = [(2,3), (5,7), (0,0)] ``` 现在,如果您要计算该三角形的周长,可以编写一个如下所示的函数: ```py import math def perimeter(points): perimeter = 0 points_extended = points + [points[0]] for i in range(len(points)): perimeter += math.sqrt((points_extended[i][0] - points_extended[i+1][0])**2 + (points_extended[i][1] - points_extended[i+1][1])**2) return perimeter ``` 到达这一点之后,您可能会感觉到有一个对象封装了三角形的所有点(数据)和周长函数(行为)。 如果您想得更多,可以将三角形点的 x 和 y 坐标封装到另一个对象中,然后将两个点的距离计算添加到该对象中。 Python 中有一些类可用于此封装。 在下一篇文章中,我将深入探讨 Python 定义和使用对象(类)的方式,并且我必须事先告诉您还有许多您无法想象的方式。