(由Python大本营付费下载自视觉中国)

作者 | Radek Fabisiak

译者 | 弯月,责编 | 郭芮

出品 | CSDN(ID:CSDNnews)

现如今面向对象编程的使用非常广泛,本文我们就来探讨一下Python中的面向对象编程。

以下为译文:

Python支持多种类型的编程范式,例如过程式编程、函数式编程、面向对象编程,而且还可以融合多种类型的范式。

现如今面向对象编程的使用非常广泛。面向对象编程的基本元素是对象,其包含的数据成员称为属性,函数(例程、过程)称为方法。

对象是类的实例。换句话说,类主要定义对象的结构,然后我们以类为模板创建对象。类不但包含方法定义,而且还包含所有实例共享的数据。

本文我们来探讨一下Python中的面向对象编程。我们将演示如何创建类,并使用类来实例化对象。本文的主要内容如下:

  • 创建Python类

  • 数据属性

  • 实例方法

  • 属性

  • 类和静态方法

  • 继承

本文无法涵盖这些主题的所有详细信息。Python中的面向对象编程还包含其他很多方面。希望本文能够为你学习Python及实现面向对象提供一个良好的开端。

创建Python类

我们可以使用关键字class定义Python类,关键字后面紧跟类的名称、分号和类的实现:

>>> class MyClass:
...     pass
...

按照惯例,Python类的命名采用首字母大写(即PascalCase)。

现在让我们创建这个新类的一个实例,名为MyClass:

>>> a = MyClass()>>> a<__main_>_.MyClass object at 0x7f32ef3deb70>

语句a = MyClass()创建了MyClass的一个实例,并将它的引用赋值给变量a。

我们可以通过Python内置的函数type()或直接通过属性.__class__来获取类型(即对象的类)。在拿到类(类型)之后,我们就可以利用属性.__ name__获取类的名字:

>>> type(a)<class '__main__.MyClass'>>>> a.__class__<class '__main__.MyClass'>>>> a.__class__.__name__'MyClass'

顺便提一句,Python类也是对象。它们是type的实例:

>>> type(MyClass)<class 'type'>

下面,我们来定义一个方法。

Python中每个实例方法的第一个参数必须对应于该实例,即该对象本身。按照惯例,这个参数名为self。后面是其他参数(如果有需要的话)。在调用方法时,我们无需明确提供与参数self相对应的参数。

通常,我们需要定义的一个最重要的方法是.__init__()。在类的实例创建后就会调用这个方法。该方法负责初始化类成员。我们定义的.__init__()如下:

>>> class MyClass:
...     def __init__(self, arg_1, arg_2, arg_3):
...         print(f'an instance of {type(self).__name__} created')
...         print(f'arg_1: {arg_1}, arg_2: {arg_2}, arg_3: {arg_3}')
...

下面,我们来创建一个MyClass实例,看看这个初始化方法的具体工作。我们的.__init__()方法需要三个参数(arg_1、arg_2和arg_3),记住我们不需要传递与self对应的第一个参数。所以,在实例化对象时,我们需要传递三个参数:

>>> a = MyClass(2, 4, 8)an instance of MyClass createdarg_1: 2, arg_2: 4, arg_3: 8

上述声明产生的结果如下:

  • 创建一个MyClass类型的对象的实例。

  • 自动调用该实例的方法.__init__()。

  • 我们传递给MyClass()方法的参数:(2,4和8)会被传递给.__init__()。

  • .__init__()执行我们的请求,并输出结果。它利用type(self).__name__获取类的名称。

现在我们得到了一个类,它有一个方法.__init__(),以及这个类的一个实例。

数据属性

下面我们来修改MyClass,增加一些数据属性。

我们利用.__init__()初始化和定义了实例,我们还可以在这个方法或其他实例方法中,通过给某个数据属性赋值的方式改变属性值:

>>> class MyClass:
...     def __init__(self, arg_1, arg_2, arg_3):
...         self.x = arg_1
...         self._y = arg_2
...         self.__z = arg_3
...

现在MyClass有三个数据属性:

  • .x可以获取arg_1的值

  • ._y可以获取arg_2的值

  • .__ z可以获取arg_3的值

我们可以利用Python的解包机制,用更紧凑的形式编写这段代码:

>>> class MyClass:
...     def __init__(self, arg_1, arg_2, arg_3):
...         self.x, self._y, self.__z = arg_1, arg_2, arg_3
...

属性名称中的下划线(_)是为了表明这些属性是“私有”属性:

  • 开头没有下划线的属性(比如.x)通常可供对象外部的调用和修改。

  • 开头拥有一个下划线的属性(比如._y)通常也可以从对象外部调用和修改。然而,下划线是一种惯用的标志,即该类的创建者强烈建议不要使用该变量。应该仅通过类的功能成员(比如方法和属性)调用和修改该变量。

  • 开头拥有双下划线的属性(比如.__ z)将在名字修饰过程中被改名(在本例中它将被改名为._MyClass__z)。你也可以通过这个新名称从对象外部调用和修改它们。但是,我强烈反对这种做法。应该尽通过类的功能成员以其原始名称进行调用和修改。

Python对象的数据属性通常存储在名为.__ dict__的字典中,它也是对象的属性之一。但是,你也可以将数据属性存储在其他地方。我们可以直接访问__dict__,或利用Python的内置函数vars()获取.__ dict__:

>>> a = MyClass(2, 4, 8)>>> vars(a)
{'x': 2, '_y': 4, '_MyClass__z': 8}>>> a.__dict__
{'x': 2, '_y': 4, '_MyClass__z': 8}

名字修饰过程把键'__z'变成了'_MyClass__z'。

我们可以把.__ dict__当成普通的Python字典使用。

获取和修改与数据属性关联的值的常规方法如下:

>>> a.x2>>> a._y4>>> a.__z
Traceback (most recent call last):
  File "", line 1, in <module>AttributeError: 'MyClass' object has no attribute '__z'>>> a.x = 16>>> a.x16>>> vars(a)
{'x': 16, '_y': 4, '_MyClass__z': 8}

请注意,我们无法访问.__ z,因为.__ dict__没有键'__z'。

实例方法

下面,我们来创建两个实例方法:

●.set_z():修改.__ z。

●.get_z():返回.__ z的值。

请记住,每个实例方法的第一个参数(按照约定名为self)引用对象本身,但我们无需在调用方法时指定这个参数:

>>> class MyClass:
...     def __init__(self, arg_1, arg_2, arg_3):
...         self.x, self._y, self.__z = arg_1, arg_2, arg_3
...     
...     def set_z(self, value):
...         self.__z = value
...     
...     def get_z(self):
...         return self.__z
...>>> b = MyClass(2, 4, 8)

方法.get_z()和.set_z()提供了传统的检索和修改.__ z值的方法:

>>> b.get_z()8>>> b.set_z(16)>>> vars(b)
{'x': 2, '_y': 4, '_MyClass__z': 16}

你也可以在.get_z()和.set_z()中添加其他功能,例如检查数据的有效性。这种方法实现了面向对象编程中的一个主要概念:封装。

属性

还有一种方法(一种更Python的方式)访问和修改数据属性是使用属性。属性封装了一系列方法:getter、setter和deleter,但其行为与普通的数据属性相同。

下面的代码实现了属性.z,其中还包含.get_z()和.set_z()的功能:

>>> class MyClass:
...     def __init__(self, arg_1, arg_2, arg_3):
...         self.x, self._y, self.__z = arg_1, arg_2, arg_3
...     
...     @property
...     def z(self):
...         return self.__z
...     
...     @z.setter
...     def z(self, value):
...         self.__z = value
...>>> b = MyClass(2, 4, 8)

如下,我们利用相应的属性.z来访问和修改数据属性.__ z:

>>> b.z8>>> b.z = 16>>> vars(b)
{'x': 2, '_y': 4, '_MyClass__z': 16}

这段代码比上述示例更精简优雅。

类与静态方法

除了实例方法和属性之外,类还可以拥有类方法和静态方法。

下面让我们为MyClass添加三个方法:

>>> class MyClass:
...     def __init__(self, arg_1, arg_2, arg_3):
...         self.x, self._y, self.__z = arg_1, arg_2, arg_3
...     
...     def f(self, arg):
...         print('instance method f called')
...         print(f'instance: {self}')
...         print(f'instance attributes:{vars(self)}')
...         print(f'class: {type(self)}')
...         print(f'arg: {arg}')
...     
...     @classmethod
...     def g(cls, arg):
...         print('class method g called')
...         print(f'cls: {cls}')
...         print(f'arg: {arg}')
...     
...     @staticmethod
...     def h(arg):
...         print('static method h called')
...         print(f'arg: {arg}')
...
>>> c = MyClass(2, 4, 8)

方法.f()是一个实例方法。实例方法的第一个参数是对象本身的引用。这些方法可以利用self访问对象,利用vars(self)或self.__dict__访问对象的数据属性,还可以利用type(self)或self.__class__访问对象对应的类,而且它们还可以拥有自己的参数。

方法.g()的开头包含修饰器@classmethod,表明这是一个类方法。每个类方法的第一个参数都会指向类本身,按照约定该参数名为cls。与实例方法的情况一样,我们不需要明确提供与cls对应的参数。而类方法可以利用cls和自己的参数访问类本身。

方法.h()的开头包含修饰器@staticmethod,表明这是一个静态方法。静态方法只能访问自己的参数。

Python中常见的调用实例方法的方法如下:

>>> c.f('my-argument')instance method f calledinstance: <__main_>_.MyClass object at 0x7f32ef3def98>instance attributes:{'x': 2, '_y': 4, '_MyClass__z': 8}class: __main__.MyClass'>arg: my-argument

通常,我们应该直接通过类(而不是实例)调用类方法和静态方法:

>>> MyClass.g('my-argument')class method g calledcls: <class '__main__.MyClass'>arg: my-argument>>> MyClass.h('my-argument')
static method h calledarg: my-argument

请记住,我们不需要传递类方法的第一个参数:与cls相对应的参数。

但是,我们可以像下面这样调用类方法和静态方法:

>>> c.g('my-argument')class method g calledcls: <class '__main__.MyClass'>arg: my-argument>>> c.h('my-argument')
static method h calledarg: my-argument

当我们调用c.g或c.h,但实例成员没有这样的名称时,Python会搜索类和静态成员。

继承

继承是面向对象编程的另一个重要特性。在这个概念中,类(称为子类或派生类)会继承其他类(称为超类或基类)的数据和函数成员。

在Python中,所有类都会默认继承Python自带的类对象。但是,我们可以根据需要定义合适的类继承层次结构。

例如,我们可以创建一个名为MyOtherClass的新类,该类继承了MyClass:

>>> class MyOtherClass(MyClass):
...     def __init__(self, u, v, w, x, y, z):
...         super().__init__(x, y, z)
...         self.__u, self.__v, self.__w = u, v, w
...     
...     def f_(self, arg):
...         print('instance method f_ called')
...         print(f'instance: {self}')
...         print(f'instance attributes:{vars(self)}')
...         print(f'class: {type(self)}')
...         print(f'arg: {arg}')
...
>>> d = MyOtherClass(1, 2, 4, 8, 16, 32)

如上,MyOtherClass拥有MyClass的成员:.x、._y、.__z以及.f()。你可以通过语句super().__init__(x, y, z)初始化基类的数据成员x、._y和.__z,该语句会调用基类的.__init__()方法。

除此之外,MyOtherClass还有自己的成员:.__u、.__v、.__w和.f_()。

下面,我们通过vars()获取数据成员:

>>> vars(d)
{'x': 8,'_y': 16,'_MyClass__z': 32,'_MyOtherClass__u': 1,'_MyOtherClass__v': 2,'_MyOtherClass__w': 4}

我们可以调用基类和派生类中的所有方法:

>>> d.f('some-argument')
instance method f calledinstance: <__main_>_.MyOtherClass object at 0x7f32ef3e7048>
instance attributes:
{'x': 8,'_y': 16,'_MyClass__z': 32,'_MyOtherClass__u': 1,'_MyOtherClass__v': 2,'_MyOtherClass__w': 4}class: __main__.MyOtherClass'>arg: some-argument>>> d.f_('some-argument')
instance method f_ calledinstance: <__main_>_.MyOtherClass object at 0x7f32ef3e7048>
instance attributes:
{'x': 8,'_y': 16,'_MyClass__z': 32,'_MyOtherClass__u': 1,'_MyOtherClass__v': 2,'_MyOtherClass__w': 4}class: __main__.MyOtherClass'>arg: some-argument

但是,如果派生类包含的某个成员与基类同名,则优先使用派生类的成员。

总结

面向对象编程是Python支持的编程范式之一。面向对象蕴含的抽象以及表征的现实世界行为在某些时候会非常有帮助性。然而,有时也可能会违反直觉,并为开发过程带来不必要的麻烦。

在本文中,我们介绍了如何利用Python编写基本的面向对象程序。Python中还有很多类和面向对象的功能,例如:

  • 方法:.__repr__()和.__str__()

  • 方法:.__new__()

  • 操作符

  • 方法:.__getattribute__()、.__getattr__()、.__setattr__()和.__delattr__()

  • 生成器

  • 可调用性

  • 创建序列

  • 描述器

  • 上下文管理

  • 抽象类和成员

  • 多重继承

  • 使用super()

  • 拷贝

  • 序列化

  • slot

  • 类修饰器

  • 数据类

等等……

现如今面向对象是非常流行的编程方式。如果你立志做一名Python开发人员,那么就应该学习面向对象编程。但请不要忘记,Python还支持其他编程范式,例如过程式编程、函数式编程等,在某些情况下也许选用这些范例更为合适。

尽情享受编程的快乐!

原文:

https://www.blog.duomly.com/object-oriented-programming-in-python/

本文为 CSDN 翻译,转载请注明来源出处。

(本文由Python大本营转载,转载请联系原作者)

精彩推荐

2019 中国大数据技术大会(BDTC)历经十一载,再度火热来袭!10月1日--10月7日,购买早鸟票,即可获得「CSDN·二十周年」纪念T恤一件,大会活动现场-签到处领取~

推荐阅读

  • 对比C++和Python,谈谈指针与引用

  • Python老司机给上路新手的3点忠告

  • 即学即用的30段Python实用代码

  • 5大必知的图算法,附Python代码实现

  • 吐血整理!140种Python标准库、第三方库和外部工具都有了

  • 如何用爬虫技术帮助孩子秒到心仪的幼儿园(基础篇)

  • Python传奇:30年崛起之路

你点的每个“在看”,我都认真当成了喜欢

python如何初始化对象数组_如何理解Python中的面向对象编程?相关推荐

  1. c++定义一个动态对象数组_如何在Python中自定义一个可被调用的对象实例?

    前言 在关于Python描述符函数的详解三篇中,我们有提到如何基于类创建一个"描述符函数",之所以能够基于类创建这样一个概念,是因为用到了类中的__call__属性,从前述文章中可 ...

  2. python函数可以作为容器对象吗_正确理解Python函数是第一类对象

    正确理解 Python函数,能够帮助我们更好地理解 Python 装饰器.匿名函数(lambda).函数式编程等高阶技术. 函数(Function)作为程序语言中不可或缺的一部分,太稀松平常了.但函数 ...

  3. python传递utf8 到c++_深入理解Python的TLS机制和Threading.local()

    ? "Python猫" ,一个值得加星标的公众号 花下猫语:分享一篇深度好文,文章有点长,值得收藏细读.作者: Lateautunm4lin 来源:https://zhuanlan ...

  4. python迭代是什么意思_如何理解python中的迭代

    迭代:是通过重复执行的代码处理相似的数据集的过程,并且本次迭代的处理数据要依赖上一次的结果继续往下做,上一次产生的结果为下一次产生结果的初始状态,如果中途有任何停顿,都不能算是迭代.例如: (1)非迭 ...

  5. python是什么意思讯_初步理解Python进程的信号通讯

    信号的概念 信号(signal)-- 进程之间通讯的方式,是一种软件中断.一个进程一旦接收到信号就会打断原来的程序执行流程来处理信号. 几个常用信号: SIGINT 终止进程 中断进程 (contro ...

  6. python读json文件数组_如何在python中从json文件读取json对象数组

    我有一个名为example.json的json文件,包含以下内容[{ "product/productId" : "XXX", "product/ti ...

  7. python类和对象课件_简单解释Python的类和对象

    前言: 对象是模拟真实世界,把数据和程序进行封装 . 对象 = 属性+ 方法 我们需要用类来创造一个对象,就像我们要用图纸来造房子一样.在Python中函数名是以小写字母开头 ,类名是以大写字母开头. ...

  8. python 类初始化参数校验_如何规避python参数的初始化次数?

    我们在内存不足的时候,除了增加内存的可用度,可以进行一个清理内存的初始化操作,当然这种是最后迫不得已的选择.我们在python中也有需要用到初始化的地方,鉴于参数和函数的关系密不可分,本篇我们会简单的 ...

  9. python list 切片 复杂度_彻底理解Python list切片原理

    关于list的insert函数 list#insert(ind,value)在ind元素前面插入value 首先对ind进行预处理:如果ind<0,则ind+=len(a),这样一来ind就变成 ...

最新文章

  1. 软件测试中的冲突测试
  2. 【NLP】270篇ACL 2019代码开源的论文,全在这里了!
  3. cesium加载entity图片缩放_Cesium中级教程4 – 空间数据可视化(二)
  4. js模板引擎art template数组渲染的方法
  5. Bitmap文件格式+生成一个BMP文件
  6. 梅森旋转产生随机数c语言实现,C++生成随机数的实现代码
  7. hdoj1116【欧拉回路】
  8. TCP/IP报文格式及通讯
  9. visual studio 2010和注册码
  10. java实现在线预览----poi操作word转html及03、07版本兼容问题
  11. Lightroom:蒙版
  12. Vercel部署个人博客
  13. coderunner运行c语言提示错误,vscode安装及使用coderunner运行C程序教程
  14. 苹果怎样用小米云服务器,小米云服务ios版
  15. html5文本框获取焦点,CSS3实现文本输入框获取焦点高亮显示
  16. 【面向对象学习笔记day05】面向对象封装案例2+士兵突击+开发枪类+开发士兵类+身份运算符+is 与 == 区别
  17. 键盘 部分 按键 ~ 需要长按才能打出来
  18. 网盘加速器PanDownload+网盘倍速播放
  19. zData Light
  20. 基于android的健身管理APP系统-计算机毕业设计

热门文章

  1. anki 新的卡片类型_用 Anki 建立高效复习错题体系
  2. c语言求偶数的积,《动物的多*语言》阅读理解及*
  3. mysql 半同步 插件_编写半同步复制插件
  4. arcgis风向_[ArcGIS] 空间分析(六) 追踪分析(台风路径)
  5. android dialog内嵌listview样式,android 開發dialog 嵌套listview布局
  6. mysql索引缓存的内容_mysql服务器变量、缓存及索引
  7. python 操作word docproprety_Python 的 类方法 静态方法 和 proprety
  8. Maven私服(一)
  9. 对InputStreamReader 和 OutputStreamWriter的理解
  10. java abstractlist_Java集合类源码解析:AbstractList