你是如何理解Python中的面向对象编程的?
off999 2024-09-23 11:32 32 浏览 0 评论
现如今面向对象编程的使用非常广泛,本文我们就来探讨一下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 created
arg_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.x 2 >>> a._y 4 >>> a.__z Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'MyClass' object has no attribute '__z' >>> a.x = 16 >>> a.x 16 >>> 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.z 8 >>> 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 called instance: <__main__.MyClass object at 0x7f32ef3def98> instance attributes: {'x': 2, '_y': 4, '_MyClass__z': 8} class: <class '__main__.MyClass'> arg: my-argument
通常,我们应该直接通过类(而不是实例)调用类方法和静态方法:
>>> MyClass.g('my-argument') class method g called cls: <class '__main__.MyClass'> arg: my-argument >>> MyClass.h('my-argument') static method h called arg: my-argument
请记住,我们不需要传递类方法的第一个参数:与cls相对应的参数。
但是,我们可以像下面这样调用类方法和静态方法:
>>> c.g('my-argument') class method g called cls: <class '__main__.MyClass'> arg: my-argument >>> c.h('my-argument') static method h called arg: 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 called instance: <__main__.MyOtherClass object at 0x7f32ef3e7048> instance attributes: {'x': 8, '_y': 16, '_MyClass__z': 32, '_MyOtherClass__u': 1, '_MyOtherClass__v': 2, '_MyOtherClass__w': 4} class: <class '__main__.MyOtherClass'> arg: some-argument >>> d.f_('some-argument') instance method f_ called instance: <__main__.MyOtherClass object at 0x7f32ef3e7048> instance attributes: {'x': 8, '_y': 16, '_MyClass__z': 32, '_MyOtherClass__u': 1, '_MyOtherClass__v': 2, '_MyOtherClass__w': 4} class: <class '__main__.MyOtherClass'> arg: some-argument
但是,如果派生类包含的某个成员与基类同名,则优先使用派生类的成员。
总结
面向对象编程是Python支持的编程范式之一。面向对象蕴含的抽象以及表征的现实世界行为在某些时候会非常有帮助性。然而,有时也可能会违反直觉,并为开发过程带来不必要的麻烦。
在本文中,我们介绍了如何利用Python编写基本的面向对象程序。Python中还有很多类和面向对象的功能,知道的伙伴可以评论区留言一波!
相关推荐
- apisix动态修改路由的原理_动态路由协议rip的配置
-
ApacheAPISIX能够实现动态修改路由(DynamicRouting)的核心原理,是它将传统的静态Nginx配置彻底解耦,通过中心化配置存储(如etcd)+OpenRest...
- 使用 Docker 部署 OpenResty Manager 搭建可视化反向代理系统
-
在之前的文章中,xiaoz推荐过可视化Nginx反向代理工具NginxProxyManager,最近xiaoz还发现一款功能更加强大,界面更加漂亮的OpenRestyManager,完全可以替代...
- OpenResty 入门指南:从基础到动态路由实战
-
一、引言1.1OpenResty简介OpenResty是一款基于Nginx的高性能Web平台,通过集成Lua脚本和丰富的模块,将Nginx从静态反向代理转变为可动态编程的应用平台...
- OpenResty 的 Lua 动态能力_openresty 动态upstream
-
OpenResty的Lua动态能力是其最核心的优势,它将LuaJIT嵌入到Nginx的每一个请求处理阶段,使得开发者可以用Lua脚本动态控制请求的生命周期,而无需重新编译或rel...
- LVS和Nginx_lvs和nginx的区别
-
LVS(LinuxVirtualServer)和Nginx都是常用的负载均衡解决方案,广泛应用于大型网站和分布式系统中,以提高系统的性能、可用性和可扩展性。一、基本概念1.LVS(Linux...
- 外网连接到内网服务器需要端口映射吗,如何操作?
-
外网访问内网服务器通常需要端口映射(或内网穿透),这是跨越公网与私网边界的关键技术。操作方式取决于网络环境,以下分场景详解。一、端口映射的核心原理内网服务器位于私有IP地址段(如192.168.x.x...
- Nginx如何解决C10K问题(1万个并发连接)?
-
关注△mikechen△,十余年BAT架构经验倾囊相授!大家好,我是mikechen。Nginx是大型架构的必备中间件,下面我就全面来详解NginxC10k问题@mikechen文章来源:mikec...
- 炸场!Spring Boot 9 大内置过滤器实战手册:从坑到神
-
炸场!SpringBoot9大内置过滤器实战手册:从坑到神在Java开发圈摸爬滚打十年,见过太多团队重复造轮子——明明SpringBoot自带的过滤器就能解决的问题,偏偏要手写几十...
- WordPress和Typecho xmlrpc漏洞_wordpress主题漏洞
-
一般大家都关注WordPress,毕竟用户量巨大,而国内的Typecho作为轻量级的博客系统就关注的人并不多。Typecho有很多借鉴WordPress的,包括兼容的xmlrpc接口,而WordPre...
- Linux Shell 入门教程(六):重定向、管道与命令替换
-
在前几篇中,我们学习了函数、流程控制等Shell编程的基础内容。现在我们来探索更高级的功能:如何控制数据流向、将命令链接在一起、让命令间通信变得可能。一、输入输出重定向(>、>>...
- Nginx的location匹配规则,90%的人都没完全搞懂,一张图让你秒懂
-
刚配完nginx网站就崩了?运维和开发都头疼的location匹配规则优先级,弄错顺序直接导致500错误。核心在于nginx处理location时顺序严格:先精确匹配=,然后前缀匹配^~,接着按顺序正...
- liunx服务器查看故障命令有那些?_linux查看服务器性能命令
-
在Linux服务器上排查故障时,需要使用一系列命令来检查系统状态、日志文件、资源利用情况以及网络状况。以下是常用的故障排查命令,按照不同场景分类说明。1.系统资源相关命令1.1查看CPU使...
- 服务器被入侵的常见迹象有哪些?_服务器入侵可以被完全操纵吗
-
服务器被入侵可能会导致数据泄露、服务异常或完全失控。及时发现入侵迹象能够帮助你尽早采取措施,减少损失。以下是服务器被入侵的常见迹象以及相关的分析与处理建议。1.服务器被入侵的常见迹象1.1系统性能...
- 前端错误可观测最佳实践_前端错误提示
-
场景解析对于前端项目,生产环境的代码通常经过压缩、混淆和打包处理,当代码在运行过程中产生错误时,通常难以还原原始代码从而定位问题,对于深度混淆尤其如此,因此Mozilla自2011年开始发起并...
- 8个能让你的Kubernetes集群“瞬间崩溃”的配置错误
-
错误一:livenessProbe探针“自杀式”配置——30秒内让Pod重启20次现象:Pod状态在Running→Terminating→CrashLoopBackOff之间循环,重启间隔仅...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- apisix动态修改路由的原理_动态路由协议rip的配置
- 使用 Docker 部署 OpenResty Manager 搭建可视化反向代理系统
- OpenResty 入门指南:从基础到动态路由实战
- OpenResty 的 Lua 动态能力_openresty 动态upstream
- LVS和Nginx_lvs和nginx的区别
- 外网连接到内网服务器需要端口映射吗,如何操作?
- Nginx如何解决C10K问题(1万个并发连接)?
- 炸场!Spring Boot 9 大内置过滤器实战手册:从坑到神
- WordPress和Typecho xmlrpc漏洞_wordpress主题漏洞
- Linux Shell 入门教程(六):重定向、管道与命令替换
- 标签列表
-
- python计时 (73)
- python安装路径 (56)
- python类型转换 (93)
- python进度条 (67)
- python吧 (67)
- python的for循环 (65)
- python格式化字符串 (61)
- python静态方法 (57)
- python列表切片 (59)
- python面向对象编程 (60)
- python 代码加密 (65)
- python串口编程 (77)
- python封装 (57)
- python写入txt (66)
- python读取文件夹下所有文件 (59)
- python操作mysql数据库 (66)
- python获取列表的长度 (64)
- python接口 (63)
- python调用函数 (57)
- python多态 (60)
- python匿名函数 (59)
- python打印九九乘法表 (65)
- python赋值 (62)
- python异常 (69)
- python元祖 (57)