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

(0)

相关推荐

  • Python的面向对象思想分析

    本文实例讲述了Python的面向对象思想.分享给大家供大家参考.具体分析如下: 面向对象的基本思想是封装,继承,多态. 首先是继承: 定义一个类: 复制代码 代码如下: class Bird(object):      have_feather = True      way_of_reproduction  = 'egg' 调用这个类: 复制代码 代码如下: summer = Bird()  print summer.way_of_reproduction 与Java不同是,Python是不需

  • python基础教程之面向对象的一些概念

    Python使用类(class)和对象(object),进行面向对象(object-oriented programming,简称OOP)的编程. 面向对象的最主要目的是提高程序的重复使用性.我们这么早切入面向对象编程的原因是,Python的整个概念是基于对象的.了解OOP是进一步学习Python的关键. 下面是对面向对象的一种理解,基于分类. 相近对象,归为类 在人类认知中,会根据属性相近把东西归类,并且给类别命名.比如说,鸟类的共同属性是有羽毛,通过产卵生育后代.任何一只特别的鸟都在鸟类的原

  • Python入门篇之面向对象

    面向对象设计与面向对象编程的关系   面向对象设计(OOD)不会特别要求面向对象编程语言.事实上,OOD 可以由纯结构化语言来实现,比如 C,但如果想要构造具备对象性质和特点的数据类型,就需要在程序上作更多的努力.当一门语言内建 OO 特性,OO 编程开发就会更加方便高效.另一方面,一门面向对象的语言不一定会强制你写 OO 方面的程序.例如 C++可以被认为"更好的C":而 Java,则要求万物皆类,此外还规定,一个源文件对应一个类定义.然而,在 Python 中,类和 OOP 都不是

  • Python面向对象编程基础解析(二)

    Python最近挺火呀,比鹿晗薛之谦还要火,当然是在程序员之间.下面我们看看有关Python的相关内容. 上一篇文章我们已经介绍了部分Python面向对象编程基础的知识,大家可以参阅:Python面向对象编程基础解析(一),接下来,我们看看另一篇. 封装 1.为什么要封装? 封装就是要把数据属性和方法的具体实现细节隐藏起来,只提供一个接口.封装可以不用关心对象是如何构建的,其实在面向对象中,封装其实是最考验水平的 2.封装包括数据的封装和函数的封装,数据的封装是为了保护隐私,函数的封装是为了隔离

  • Python 面向对象 成员的访问约束

    在Python中是通过一套命名体系来识别成约的访问范围的 class MyObjec(object): username = "developerworks" # public _email = "developerworks#163#.com" #protected __tel = "1391119****" # private 从这段代码中可以看出一些巧妙的命名方法 在python中所有的以字母开头的成语名称被python命名体系自动识别为p

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

    Python中一切都是对象.类提供了创建新类型对象的机制.这篇教程中,我们不谈类和面向对象的基本知识,而专注在更好地理解Python面向对象编程上.假设我们使用新风格的python类,它们继承自object父类. 定义类 class 语句可以定义一系列的属性.变量.方法,他们被该类的实例对象所共享.下面给出一个简单类定义: class Account(object): num_accounts = 0 def __init__(self, name, balance): self.name =

  • Python面向对象编程中关于类和方法的学习笔记

    类和实例 python是一个面向对象的语言,而面向对象最重要的概念就是类和实例, 记得刚学习的时候不太理解这些概念,直到老师说了一句"物以类聚". 没错就是类, 归类 物以类聚 类其实就是把一些相同特性的事物归成一类, 比如人 class Person(object): pass 我们定义了人这个类, 但人有一些特性,比如 两个眼睛,一个嘴巴, 我们把这些添加进去 class Person(object): eyes = 2 mouth = 1 已经把人的一些信息写进去了,但是人还有名

  • Java线程编程中Thread类的基础学习教程

    一.线程的状态 在正式学习Thread类中的具体方法之前,我们先来了解一下线程有哪些状态,这个将会有助于后面对Thread类中的方法的理解. 线程从创建到最终的消亡,要经历若干个状态.一般来说,线程包括以下这几个状态:创建(new).就绪(runnable).运行(running).阻塞(blocked).time waiting.waiting.消亡(dead). 当需要新起一个线程来执行某个子任务时,就创建了一个线程.但是线程创建之后,不会立即进入就绪状态,因为线程的运行需要一些条件(比如内

  • 讲解C#面相对象编程中的类与对象的特性与概念

    类 "类"是一种构造,通过使用该构造,您可以将其他类型的变量.方法和事件组合在一起,从而创建自己的自定义类型.类就像一个蓝图,它定义类型的数据和行为.如果类没有声明为静态类,客户端代码就可以创建赋给变量的"对象"或"实例",从而使用该类.在对变量的所有引用都超出范围之前,该变量始终保持在内存中.所有引用都超出范围时,CLR 将标记该变量以供垃圾回收.如果类声明为静态类,则内存中只存在一个副本,并且客户端代码只能通过该类自身而不是"实例变

  • C++编程中new运算符的使用学习教程

    new运算符用作从自由存储为 type-name 的对象或对象数组分配内存,并将已适当分类的非零指针返回到对象. [::] new [placement] new-type-name [new-initializer] [::] new [placement] ( type-name ) [new-initializer] 备注 如果不成功,则 new 将返回零或引发异常:有关详细信息,请参阅 new 和 delete 运算符. 通过编写自定义异常处理例程并调用 _set_new_handler

  • Python编程中的for循环语句学习教程

    Python for循环可以遍历任何序列的项目,如一个列表或者一个字符串. 语法: for循环的语法格式如下: for iterating_var in sequence: statements(s) 流程图: 实例: #!/usr/bin/python # -*- coding: UTF-8 -*- for letter in 'Python': # 第一个实例 print '当前字母 :', letter fruits = ['banana', 'apple', 'mango'] for f

  • 完全掌握C++编程中构造函数使用的超级学习教程

    构造函数是一种可初始化其类的实例的成员函数.构造函数具有与类相同的名称,没有返回值.构造函数可以具有任意数量的参数,类可以具有任意数量的重载构造函数.构造函数可以具有任何可访问性(公共.受保护或私有).如果未定义任何构造函数,则编译器会生成不采用任何参数的默认构造函数:可以通过将默认构造函数声明为已删除来重写此行为. 构造函数顺序 构造函数按此顺序执行工作: 按声明顺序调用基类和成员构造函数. 如果类派生自虚拟基类,则会将对象的虚拟基指针初始化. 如果类具有或继承了虚函数,则会将对象的虚函数指针

  • C++编程中的数据类型和常量学习教程

    C++数据类型 计算机处理的对象是数据,而数据是以某种特定的形式存在的(例如整数.浮点数.字符等形式).不同的数据之间往往还存在某些联系(例如由若干个整数组成一个整数数组).数据结构指的是数据的组织形式.例如,数组就是一种数据结构.不同的计算机语言所允许使用的数据结构是不同的.处理同一类问题,如果数据结构不同,算法也会不同.例如,对10个整数排序和对包含10个元素的整型数组排序的算法是不同的. C++的数据包括常量与变量,常量与变量都具有类型.由以上这些数据类型还可以构成更复杂的数据结构.例如利

  • C语言编程中的联合体union入门学习教程

    联合体(union)在C语言中是一个特殊的数据类型,能够存储不同类型的数据在同一个内存位置.可以定义一个联合体使用许多成员,但只有一个部件可以包含在任何时候给定的值.联合体会提供使用相同的存储器位置供多用途的有效方式. 定义联合体 要定义联合体,必须使用union语句很相似于定义结构.联合体声明中定义了一个新的数据类型,程序不止一个成员.联合体声明的格式如下: union [union tag] { member definition; member definition; ... member

随机推荐