热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Python面向对象编程中的类和对象学习教程

这篇文章主要介绍了Python面向对象编程中的类和对象学习教程,面向对象是Python的基础特性,其中的类与对象的特性和使用方法是Python学习当中的基本功,需要的朋友可以参考下
Python中一切都是对象。类提供了创建新类型对象的机制。这篇教程中,我们不谈类和面向对象的基本知识,而专注在更好地理解Python面向对象编程上。假设我们使用新风格的python类,它们继承自object父类。
定义类

class 语句可以定义一系列的属性、变量、方法,他们被该类的实例对象所共享。下面给出一个简单类定义:

class Account(object):
  num_accounts = 0
 
  def __init__(self, name, balance):
   self.name = name
   self.balance = balance
   Account.num_accounts += 1
 
  def del_account(self):
   Account.num_accounts -= 1
 
  def deposit(self, amt):
   self.balance = self.balance + amt
 
  def withdraw(self, amt):
   self.balance = self.balance - amt
 
  def inquiry(self):
   return self.balance

类定义引入了以下新对象:

类对象
实例对象
方法对象

类对象

程序执行过程中遇到类定义时,就会创建新的命名空间,命名空间包含所有类变量和方法定义的名称绑定。注意该命名空间并没有创建类方法可以使用的新局部作用域,因此在方法中访问变量需要全限定名称。上一节的Account类演示了该特性;尝试访问num_of_accounts变量的方法需要使用全限定名称Account.num_of_accounts,否则,如果没有在__init__方法中使用全限定名称,会引发如下错误:

class Account(object):
 num_accounts = 0
 
 def __init__(self, name, balance):
  self.name = name
  self.balance = balance
  num_accounts += 1
 
 def del_account(self):
  Account.num_accounts -= 1
 
 def deposit(self, amt):
  self.balance = self.balance + amt
 
 def withdraw(self, amt):
  self.balance = self.balance - amt
 
 def inquiry(self):
  return self.balance
 
>>> acct = Account('obi', 10)
Traceback (most recent call last):
 File "python", line 1, in 
 File "python", line 9, in __init__
UnboundLocalError: local variable 'num_accounts' referenced before assignment

类定义执行的最后,会创建一个类对象。在进入类定义之前有效的那个作用域现在被恢复了,同时类对象被绑定到类定义头的类名上。

先偏离下话题,你可能会问如果创建的类是对象,那么类对象的类是什么呢?。与一切都是对象的python哲学一致,类对象确实有个类,即python新风格类中的type类。

>>> type(Account)

让你更迷惑一点,Account类型的类型是type。type类是个元类,用于创建其他类,我们稍后教程中再介绍。

类对象支持属性引用和实例化。属性通过标准的点语法引用,即对象后跟句点,然后是属性名:obj.name。有效的属性名是类对象创建后类命名空间中出现的所有变量和方法名。例如:

>>> Account.num_accounts
>>> 0
>>> Account.deposit
>>> 

类实例化使用函数表示法。实例化会像普通函数一样无参数调用类对象,如下文中的Account类:

>>> Account()


类对象实例化之后,会返回实例对象,如果类中定义了__init__方法,就会调用,实例对象作为第一个参数传递过去。这个方法会进行用户自定义的初始化过程,比如实例变量的初始化。Account类为例,账户name和balance会被设置,实例对象的数目增加1。
实例对象

如果类对象是饼干切割刀,饼干就是实例化类对象的结果。实例对象上的全部有效操作为对属性、数据和方法对象的引用。
方法对象

方法对象和函数对象类似。如果x是Account类的实例,x.deposit就是方法对象的例子。方法定义中有个附加参数,self。self指向类实例。为什么我们需要把实例作为参数传递给方法?方法调用能最好地说明:

>>> x = Account()
>>> x.inquiry()
10

实例方法调用时发生了什么?你应该注意到x.inquiry()调用时没有参数,虽然方法定义包含self参数。那么这个参数到底发生了什么?

特殊之处在于方法所作用的对象被作为函数的第一个参数传递过去。在我们的例子中,对x.inquiry()的调用等价于Account.f(x)。一般,调用n参数的方法等同于将方法的作用对象插入到第一个参数位置。

python教程上讲:

当引用的实例属性不是数据属性时,就会搜索类。如果名称表示一个合法的函数对象,实例对象和函数对象将会被打包到一个抽象对象,即方法对象中。包含参数列表的方法对象被调用时,将会根据实例对象和参数列表创建一个新的参数列表,然后函数对象将会使用新的参数列表被调用。

这适用于所有的实例方法对象,包括__init__方法。self参数其实不是一个关键字,任何有效的参数名都可以使用,如下Account类定义所示:

class Account(object):
 num_accounts = 0
 
 def __init__(obj, name, balance):
  obj.name = name
  obj.balance = balance
  Account.num_accounts += 1
 
 def del_account(obj):
  Account.num_accounts -= 1
 
 def deposit(obj, amt):
  obj.balance = obj.balance + amt
 
 def withdraw(obj, amt):
  obj.balance = obj.balance - amt
 
 def inquiry(obj):
  return obj.balance
 
>>> Account.num_accounts
>>> 0
>>> x = Account('obi', 0)
>>> x.deposit(10)
>>> Account.inquiry(x)
>>> 10

静态和类方法

类中定义的方法默认由实例调用。但是,我们也可以通过对应的@staticmethod和@classmethod装饰器来定义静态或类方法。
静态方法

静态方式是类命名空间中的普通函数。引用类的静态方法返回的是函数类型,而不是非绑定方法类型:

class Account(object):
 num_accounts = 0
 
 def __init__(self, name, balance):
  self.name = name
  self.balance = balance
  Account.num_accounts += 1
 
 def del_account(self):
  Account.num_accounts -= 1
 
 def deposit(self, amt):
  self.balance = self.balance + amt
 
 def withdraw(self, amt):
  self.balance = self.balance - amt
 
 def inquiry(self):
  return "Name={}, balance={}".format(self.name, self.balance)
 
 @staticmethod
 def type():
  return "Current Account"
 
>>> Account.deposit

>>> Account.type

使用@staticmethod装饰器来定义静态方法,这些方法不需要self参数。静态方法可以更好地组织与类相关的代码,也可以在子类中被重写。
类方法

类方法由类自身来调用,而不是实例。类方法使用@classmethod装饰器定义,作为第一个参数被传递给方法的是类而不是实例。

import json
 
class Account(object):
 num_accounts = 0
 
 def __init__(self, name, balance):
  self.name = name
  self.balance = balance
  Account.num_accounts += 1
 
 def del_account(self):
  Account.num_accounts -= 1
 
 def deposit(self, amt):
  self.balance = self.balance + amt
 
 def withdraw(self, amt):
  self.balance = self.balance - amt
 
 def inquiry(self):
  return "Name={}, balance={}".format(self.name, self.balance)
 
 @classmethod
 def from_json(cls, params_json):
    params = json.loads(params_json)
  return cls(params.get("name"), params.get("balance"))
 
 @staticmethod
 def type():
  return "Current Account"

类方法一个常见的用法是作为对象创建的工厂。假如Account类的数据格式有很多种,比如元组、json字符串等。由于Python类只能定义一个__init__方法,所以类方法在这些情形中就很方便。以上文Account类为例,我们想根据一个json字符串对象来初始化一个账户,我们定义一个类工厂方法from_json,它读取json字符串对象,解析参数,根据参数创建账户对象。另一个类实例的例子是dict.fromkeys 方法,它从一组键和值序列中创建dict对象。
Python特殊方法

有时我们希望自定义类。这需要改变类对象创建和初始化的方法,或者对某些操作提供多态行为。多态行为允许定制在类定义中某些如+等python操作的自身实现。Python的特殊方法可以做到这些。这些方法一般都是__*__形式,其中*表示方法名。如__init__和__new__来自定义对象创建和初始化,__getitem__、__get__、__add__、__sub__来模拟python内建类型,还有__getattribute__、__getattr__等来定制属性访问。只有为数不多的特殊方法,我们讨论一些重要的特殊方法来做个简单理解,python文档有全部方法的列表。
进行对象创建的特殊方法

新的类实例通过两阶段过程创建,__new__方法创建新实例,__init__初始化该实例。用户已经很熟悉__init__方法的定义;但用户很少定义__new__方法,但是如果想自定义类实例的创建,也是可以的。
属性访问的特殊方法

我们可以通过实现以下方法来定制类实例的属性访问。

class Account(object):
 num_accounts = 0
 
 def __init__(self, name, balance):
  self.name = name
  self.balance = balance
  Account.num_accounts += 1
 
 def del_account(self):
  Account.num_accounts -= 1
 
 def __getattr__(self, name):
  return "Hey I dont see any attribute called {}".format(name)
 
 def deposit(self, amt):
  self.balance = self.balance + amt
 
 def withdraw(self, amt):
  self.balance = self.balance - amt
 
 def inquiry(self):
  return "Name={}, balance={}".format(self.name, self.balance)
 
 @classmethod
 def from_dict(cls, params):
  params_dict = json.loads(params)
  return cls(params_dict.get("name"), params_dict.get("balance"))
 
 @staticmethod
 def type():
  return "Current Account"
 
x = Account('obi', 0)

__getattr__(self, name)__:这个方法只有当name既不是实例属性也不能在对象的类继承链中找到时才会被调用。这个方法应当返回属性值或者引发AttributeError异常。例如,如果x是Account类的实例,尝试访问不存在的属性将会调用这个方法。

>>> acct = Account("obi", 10)
>>> acct.number
Hey I dont see any attribute called number

注意如果 __getattr__引用不存在的实例属性,可能会发生死循环,因为__getattr__方法不断被调用。

2.__setattr__(self, name, value)__:这个方法当属性赋值发生时调用。__setattr__将会把值插入到实例属性字典中,而不是使用self.name=value,因为它会导致递归调用的死循环。

3.__delattr__(self, name)__:del obj发生时调用。

4.__getattribute__(self, name)__:这个方法会被一直调用以实现类实例的属性访问。
类型模拟的特殊方法

对某些类型,Python定义了某些特定语法;比如,列表和元组的元素可以通过索引表示法来访问,数值可以通过+操作符来进行加法等等。我们可以创建自己的使用这些特殊语法的类,python解释器遇到这些特殊语法时就会调用我们实现的方法。我们在下面用一个简单的例子来演示这个特性,它模拟python列表的基本用法。

class CustomList(object):
 
 def __init__(self, cOntainer=None):
  # the class is just a wrapper around another list to
  # illustrate special methods
  if container is None:
   self.cOntainer= []
  else:
   self.cOntainer= container
 
 def __len__(self):
  # called when a user calls len(CustomList instance)
  return len(self.container)
 
 def __getitem__(self, index):
  # called when a user uses square brackets for indexing
  return self.container[index]
 
 def __setitem__(self, index, value):
  # called when a user performs an index assignment
  if index <= len(self.container):
   self.container[index] = value
  else:
   raise IndexError()
 
 def __contains__(self, value):
  # called when the user uses the 'in' keyword
  return value in self.container
 
 def append(self, value):
  self.container.append(value)
 
 def __repr__(self):
  return str(self.container)
 
 def __add__(self, otherList):
  # provides support for the use of the + operator
  return CustomList(self.container + otherList.container)

上面,CustomList是个真实列表的简单包装器。我们为了演示实现了一些自定义方法:

__len__(self):对CustomList实例调用len()函数时被调用。

>>> myList = CustomList()
>>> myList.append(1) 
>>> myList.append(2)
>>> myList.append(3)
>>> myList.append(4)
>>> len(myList)
4

2.__getitem__(self, value):提供CustomList类实例的方括号索引用法支持:

>>> myList = CustomList()
>>> myList.append(1) 
>>> myList.append(2)
>>> myList.append(3)
>>> myList.append(4)
>>> myList[3]
4

3.__setitem__(self, key, value):当对CustomList类实例上self[key]赋值时调用。

>>> myList = CustomList()
>>> myList.append(1) 
>>> myList.append(2)
>>> myList.append(3)
>>> myList.append(4)
>>> myList[3] = 100
4
>>> myList[3]
100

4.__contains__(self, key):成员检测时调用。如果包含该项就返回true,否则false。

>>> myList = CustomList()
>>> myList.append(1) 
>>> myList.append(2)
>>> myList.append(3)
>>> myList.append(4)
>>> 4 in myList
True

5.__repr__(self):当用print打印self时调用,将会打印self的对象表示。

>>> myList = CustomList()
>>> myList.append(1) 
>>> myList.append(2)
>>> myList.append(3)
>>> myList.append(4)
>>> print myList
[1, 2, 3, 4]

6.__add__(self, otherList):使用+操作符来计算两个CustomList实例相加时调用。

>>> myList = CustomList()
>>> otherList = CustomList()
>>> otherList.append(100)
>>> myList.append(1) 
>>> myList.append(2)
>>> myList.append(3)
>>> myList.append(4)
>>> myList + otherList + otherList
[1, 2, 3, 4, 100, 100]

上面的例子演示了如何通过定义某些特殊类方法来定制类行为。可以在Python文档中查看这些自定义方法的完整列表。在接下来的教程中,我们会将特殊方法放到一起来讨论,并解释描述符这个在python面向对象编程中广泛使用的重要功能。

推荐阅读
  • 使用正则表达式爬取36Kr网站首页新闻的操作步骤和代码示例
    本文介绍了使用正则表达式来爬取36Kr网站首页所有新闻的操作步骤和代码示例。通过访问网站、查找关键词、编写代码等步骤,可以获取到网站首页的新闻数据。代码示例使用Python编写,并使用正则表达式来提取所需的数据。详细的操作步骤和代码示例可以参考本文内容。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • 使用在线工具jsonschema2pojo根据json生成java对象
    本文介绍了使用在线工具jsonschema2pojo根据json生成java对象的方法。通过该工具,用户只需将json字符串复制到输入框中,即可自动将其转换成java对象。该工具还能解析列表式的json数据,并将嵌套在内层的对象也解析出来。本文以请求github的api为例,展示了使用该工具的步骤和效果。 ... [详细]
  • 本文介绍了高校天文共享平台的开发过程中的思考和规划。该平台旨在为高校学生提供天象预报、科普知识、观测活动、图片分享等功能。文章分析了项目的技术栈选择、网站前端布局、业务流程、数据库结构等方面,并总结了项目存在的问题,如前后端未分离、代码混乱等。作者表示希望通过记录和规划,能够理清思路,进一步完善该平台。 ... [详细]
  • Android JSON基础,音视频开发进阶指南目录
    Array里面的对象数据是有序的,json字符串最外层是方括号的,方括号:[]解析jsonArray代码try{json字符串最外层是 ... [详细]
  • jmeter实践:从csv中获取带引号的数据详情的技巧和运行全部数据的方法
    本文分享了jmeter实践中从csv中获取带引号的数据的解决办法,包括设置CSV Data Set Config和运行脚本获取数据的方法。另外还介绍了循环运行csv中全部数据的解决方法,避免每次修改csv用例都需要修改脚本的麻烦。通过了解和掌握工具的细节点,可以更好地解决问题和提高技术水平。 ... [详细]
  • 单点登录原理及实现方案详解
    本文详细介绍了单点登录的原理及实现方案,其中包括共享Session的方式,以及基于Redis的Session共享方案。同时,还分享了作者在应用环境中所遇到的问题和经验,希望对读者有所帮助。 ... [详细]
  • 本文讨论了Kotlin中扩展函数的一些惯用用法以及其合理性。作者认为在某些情况下,定义扩展函数没有意义,但官方的编码约定支持这种方式。文章还介绍了在类之外定义扩展函数的具体用法,并讨论了避免使用扩展函数的边缘情况。作者提出了对于扩展函数的合理性的质疑,并给出了自己的反驳。最后,文章强调了在编写Kotlin代码时可以自由地使用扩展函数的重要性。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
  • 本文介绍了前端人员必须知道的三个问题,即前端都做哪些事、前端都需要哪些技术,以及前端的发展阶段。初级阶段包括HTML、CSS、JavaScript和jQuery的基础知识。进阶阶段涵盖了面向对象编程、响应式设计、Ajax、HTML5等新兴技术。高级阶段包括架构基础、模块化开发、预编译和前沿规范等内容。此外,还介绍了一些后端服务,如Node.js。 ... [详细]
  • React项目中运用React技巧解决实际问题的总结
    本文总结了在React项目中如何运用React技巧解决一些实际问题,包括取消请求和页面卸载的关联,利用useEffect和AbortController等技术实现请求的取消。文章中的代码是简化后的例子,但思想是相通的。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
author-avatar
BigUncle
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有