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 文件与目录操作
Dec 24 Python
sqlalchemy对象转dict的示例
Apr 22 Python
常见的在Python中实现单例模式的三种方法
Apr 08 Python
python获得文件创建时间和修改时间的方法
Jun 30 Python
Python中time模块和datetime模块的用法示例
Feb 28 Python
python使用Pycharm创建一个Django项目
Mar 05 Python
TensorFlow实现简单的CNN的方法
Jul 18 Python
Python any()函数的使用方法
Oct 28 Python
Pytorch中的VGG实现修改最后一层FC
Jan 15 Python
Python调用shell命令常用方法(4种)
May 11 Python
Python爬虫基于lxml解决数据编码乱码问题
Jul 31 Python
Python Pandas list列表数据列拆分成多行的方法实现
Dec 14 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
SONY ICF-SW07收音机电路分析
2021/03/02 无线电
德劲1107的电路分析与打磨
2021/03/02 无线电
php实现按文件名搜索文件的远程文件查找器
2014/05/10 PHP
PHP函数extension_loaded()用法实例
2015/01/19 PHP
PHP的压缩函数实现:gzencode、gzdeflate和gzcompress的区别
2016/01/27 PHP
php中二分法查找算法实例分析
2016/09/22 PHP
redis+php实现微博(一)注册与登录功能详解
2019/09/23 PHP
定义select的边框颜色
2008/04/28 Javascript
JS获取dom 对象 ajax操作 读写cookie函数
2009/11/18 Javascript
使用js判断当前时区TimeZone是否是夏令时
2014/02/23 Javascript
js阻止事件追加的具体实现
2014/10/15 Javascript
JS动态改变表格边框宽度的方法
2015/03/31 Javascript
Javascript中replace()小结
2015/09/30 Javascript
分享15个大家都熟知的jquery小技巧
2015/12/02 Javascript
JavaScript事件学习小结(三)js事件对象
2016/06/09 Javascript
微信小程序实现留言板功能
2018/11/02 Javascript
微信小程序实现预览图片功能
2020/10/22 Javascript
在Node.js下运用MQTT协议实现即时通讯及离线推送的方法
2019/01/24 Javascript
vue中使用百度脑图kityminder-core二次开发的实现
2019/09/26 Javascript
javascript实现移动端轮播图
2020/12/09 Javascript
Python 搭建Web站点之Web服务器与Web框架
2016/11/06 Python
利用python实现xml与数据库读取转换的方法
2017/06/17 Python
python实现淘宝秒杀脚本
2020/06/23 Python
python 使用plt画图,去除图片四周的白边方法
2019/07/09 Python
浅谈Python中re.match()和re.search()的使用及区别
2020/04/14 Python
Html5 页面适配iPhoneX(就是那么简单)
2019/09/05 HTML / CSS
Ruby中的保护方法和私有方法与一般面向对象程序设计语言的一样吗
2013/05/01 面试题
大学生毕业自我评价范文分享
2013/11/11 职场文书
爽歪歪广告词
2014/03/20 职场文书
英语专业毕业生求职信
2014/05/24 职场文书
乡镇组织委员个人整改措施
2014/09/16 职场文书
办公用品质量保证书
2015/05/11 职场文书
2015年预算员工作总结
2015/05/14 职场文书
python 下载文件的几种方式分享
2021/04/07 Python
Python如何使用循环结构和分支结构
2022/04/13 Python
HTML 里 img 元素的 src 和 srcset 属性的区别详解
2023/05/21 HTML / CSS