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


Posted in Python onMarch 30, 2015

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 <module>
 File "python", line 9, in __init__
UnboundLocalError: local variable 'num_accounts' referenced before assignment

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

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

>>> type(Account)
<class 'type'>

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

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

>>> Account.num_accounts
>>> 0
>>> Account.deposit
>>> <unbound method 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
<unbound method Account.deposit>
>>> Account.type
<function type at 0x106893668>

使用@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面向对象编程中广泛使用的重要功能。

Python 相关文章推荐
Python 命令行参数sys.argv
Sep 06 Python
Python按行读取文件的简单实现方法
Jun 22 Python
win与linux系统中python requests 安装
Dec 04 Python
Python调用C# Com dll组件实战教程
Oct 12 Python
Python中.join()和os.path.join()两个函数的用法详解
Jun 11 Python
Django 实现图片上传和显示过程详解
Jul 18 Python
关于Python3 lambda函数的深入浅出
Nov 27 Python
Python读取分割压缩TXT文本文件实例
Feb 14 Python
解决Python在导入文件时的FileNotFoundError问题
Apr 10 Python
Pycharm如何自动生成头文件注释
Nov 14 Python
python subprocess pipe 实时输出日志的操作
Dec 05 Python
Python datetime模块的使用示例
Feb 02 Python
详细介绍Python函数中的默认参数
Mar 30 #Python
在Python中利用Into包整洁地进行数据迁移的教程
Mar 30 #Python
在Linux上安装Python的Flask框架和创建第一个app实例的教程
Mar 30 #Python
使用Python中PDB模块中的命令来调试Python代码的教程
Mar 30 #Python
深入讨论Python函数的参数的默认值所引发的问题的原因
Mar 30 #Python
使用Python标准库中的wave模块绘制乐谱的简单教程
Mar 30 #Python
Python中使用语句导入模块或包的机制研究
Mar 30 #Python
You might like
php array的学习笔记
2012/05/16 PHP
如何用php获取文件名后缀
2013/06/09 PHP
php面向对象之反射功能与用法分析
2017/03/29 PHP
phpstudy的php版本自由修改的方法
2017/10/18 PHP
js字符编码函数区别分析
2008/06/05 Javascript
javascript下IE与FF兼容函数收集
2008/09/17 Javascript
javascript dom代码应用 简单的相册[firefox only]
2010/06/12 Javascript
JSON序列化与解析原生JS方法且IE6和chrome测试通过
2013/09/05 Javascript
JavaScript实现Base64编码转换
2016/04/23 Javascript
webpack中引用jquery的简单实现
2016/06/08 Javascript
Javascript自定义事件详解
2017/01/13 Javascript
js禁止Backspace键使浏览器后退的实现方法
2017/09/01 Javascript
通过js动态创建标签,并设置属性方法
2018/02/24 Javascript
解决vue2.0动态绑定图片src属性值初始化时报错的问题
2018/03/14 Javascript
Vue 让元素抖动/摆动起来的实现代码
2018/05/31 Javascript
vue实现商品加减计算总价的实例代码
2018/08/12 Javascript
vue data引入本地图片的两种方式小结
2019/11/13 Javascript
vue如何使用async、await实现同步请求
2019/12/09 Javascript
vue实现导航标题栏随页面滚动渐隐渐显效果
2020/03/12 Javascript
JS端基于download.js实现图片、视频时直接下载而不是打开预览
2020/05/09 Javascript
vue router-link 默认a标签去除下划线的实现
2020/11/06 Javascript
微信小程序选择图片控件
2021/01/19 Javascript
Python编程之属性和方法实例详解
2015/05/19 Python
Python读取键盘输入的2种方法
2015/06/16 Python
PyCharm Community安装与配置的详细教程
2020/11/24 Python
python爬虫智能翻页批量下载文件的实例详解
2021/02/02 Python
天猫精选:上天猫,就够了
2016/09/21 全球购物
韩语专业本科生求职信
2013/10/01 职场文书
机关门卫制度
2014/02/01 职场文书
转让协议书范本
2014/04/15 职场文书
计算机系本科生求职信
2014/05/31 职场文书
节能减耗标语
2014/06/21 职场文书
大学生工作求职信
2014/06/23 职场文书
杜甫草堂导游词
2015/02/03 职场文书
入党介绍人意见怎么写
2015/06/03 职场文书
阿里云服务器(windows)手动部署FTP站点详细教程
2022/08/05 Servers