用特殊方法定制类
前面我们讲了方法的两个重要方面:首先,方法必须在调用前被绑定(到它们相应类的某个实例中);其次,有两个特殊方法可以分别作为构造器和解构器的功能,分别名为__init__()和__del__()。
事实上,__init__()和__del__()只是可自定义特殊方法集中的一部分。它们中有一些有预定义的默认行为,而其他一些则没有,留到需要的时候去实现。这些特殊方法是Python中用来扩充类的强有力的方式,它们可实现:
模拟标准类型
重载草所附

特殊方法允许类通过重载标准操作符+,*,甚至包括分段下标及映射操作符操作[]来模拟标准类型。如同其他很多保留标识符,这些方法都是以双下划线开始以及结尾的。
以下是方法摘抄:
基本定制型 描述
C.__init__(self[, arg1, ...]) 构造器(带一些可选的参数)
C.__new__(self[, arg1, ...]) 构造器(带一些可选的参数);通常用在设置不变数据类型的子类。
C.__del__(self) 解构器
C.__str__(self) 可打印的字符输出;内建 str()及 print 语句
C.__repr__(self) 运行时的字符串输出;内建 repr() 和‘‘ 操作符
C.__unicode__(self) Unicode 字符串输出;内建 unicode()
C.__call__(self, *args) 表示可调用的实例
C.__nonzero__(self) 为 object 定义 False 值;内建 bool() (从 2.2 版开始)
C.__len__(self) “长度”(可用于类);内建 len()

对象(值)比较
C.__cmp__(self, obj) 对象比较;内建 cmp()
C.__lt__(self, obj) and 小于/小于或等于;对应<及<=操作符
C.__gt__(self, obj) and 大于/大于或等于;对应>及>=操作符
C.__eq__(self, obj) and 等于/不等于;对应==,!=及<>操作符

属性
C.__getattr__(self, attr) 获取属性;内建 getattr();仅当属性没有找到时调用
C.__setattr__(self, attr, val) 设置属性
C.__delattr__(self, attr) 删除属性
C.__getattribute__(self, attr) 获取属性;内建 getattr();总是被调用
C.__get__(self, attr) (描述符)获取属性
C.__set__(self, attr, val) (描述符)设置属性
C.__delete__(self, attr) (描述符)删除属性

定制类/模拟类型
数值类型:二进制操作符
C.__*add__(self, obj) 加;+操作符
C.__*sub__(self, obj) 减;-操作符
C.__*mul__(self, obj) 乘;*操作符
C.__*div__(self, obj) 除;/操作符
C.__*truediv__(self, obj) True 除;/操作符
C.__*floordiv__(self, obj) Floor 除;//操作符
C.__*mod__(self, obj) 取模/取余;%操作符
C.__*divmod__(self, obj) 除和取模;内建 divmod()
C.__*pow__(self, obj[, mod]) 乘幂;内建 pow();**操作符
C.__*lshift__(self, obj) 左移位;<<操作符
C.__*rshift__(self, obj) 右移;>>操作符
C.__*and__(self, obj) 按位与;&操作符
C.__*or__(self, obj) 按位或;|操作符
C.__*xor__(self, obj) 按位与或;^操作符

数值类型:一元操作符
C.__neg__(self) 一元负
C.__pos__(self) 一元正
C.__abs__(self) 绝对值;内建 abs()
C.__invert__(self) 按位求反;~操作符

数值类型:数值转换
C.__complex__(self, com) 转为 complex(复数);内建 complex()
C.__int__(self) 转为 int;内建 int()
C.__long__(self) 转为 long;内建 long()
C.__float__(self) 转为 float;内建 float()

数值类型:基本表示法(String)
C.__oct__(self) 八进制表示;内建 oct()
C.__hex__(self) 十六进制表示;内建 hex()

数值类型:数值压缩
C.__coerce__(self, num) 压缩成同样的数值类型;内建 coerce()
C.__index__(self) 在有必要时,压缩可选的数值类型为整型(比如:用于切片索引等等)

序列类型
C.__len__(self) 序列中项的数目
C.__getitem__(self, ind) 得到单个序列元素
C.__setitem__(self, ind,val) 设置单个序列元素
C.__delitem__(self, ind) 删除单个序列元素
C.__getslice__(self, ind1,ind2) 得到序列片断
C.__setslice__(self, i1, i2,val) 设置序列片断
C.__delslice__(self, ind1,ind2) 删除序列片断
C.__contains__(self, val) 测试序列成员;内建 in 关键字

C.__*add__(self,obj) 串连;+操作符
C.__*mul__(self,obj) 重复;*操作符
C.__iter__(self) 创建迭代类;内建 iter()

映射类型
C.__len__(self) mapping中的项的数目
C.__hash__(self) 散列(hash)函数值
C.__getitem__(self,key) 得到给定键(key)的值
C.__setitem__(self,key,val) 设置给定键(key)的值
C.__delitem__(self,key) 删除给定键(key)的值
C.__missing__(self,key) 给定键如果不存在字典中,则提供一个默认值

基本的定制和对象(值)比较特殊方法在大多数类中都可以被实现,且没有同任何特定的类型模型绑定。延后设置,也就是所谓的福比较,在Python2.1中加入。属性组帮助管理你的类的实例属性。这同样独立于模型。还有一个,__getattribute__(),它仅用在新式类中,我们将在后面章节中对它进行描述。
特殊方法中数值类型部分可以用来模拟很多数值操作,包括那些标准操作符、类型转换、基本表示法及亚索。还有用来模拟序列和映射类型的特殊方法。实现这些类型的特殊方法将会重载操作符,以使它们可以处理你的class类型实例。
表格中,在他们的名字中,用星号通配符标注的数值二进制操作符则表示这些方法有多个版本,在名字上有些许不同。星号可代表在字符串中没有额外的字符,或者一个简单的"r"指明是一个右结合操作。

简单定制
我们之前从Python类型中派生出了派生类RoundFloat。这次我们想创建一个苗条的例子,这样可以对类的定制的工作方法有一个更好的理解。这种类的前提与其他类是一样的:我们只需要一个类来保存浮点型,四舍五入,保留两位小数位。

>>> class RoundFloatManual(object):
  def __init__ (self, val):
    assert isinstance(val, float),\
      "Value must be a float!"
    self.value = round(val, 2)

这个类接受一个浮点值——它断言了传递给构造器的参数必须为一个浮点型——并且将其保存为实例属性值。接下来创建这个类的实例:

>>> rfm = RoundFloatManual(12)

Traceback (most recent call last):
File "<pyshell#6>", line 1, in <module>
rfm = RoundFloatManual(12)
File "<pyshell#5>", line 4, in __init__
"Value must be a float!"
AssertionError: Value must be a float!
>>> rfm = RoundFloatManual(1.23)
>>> rfm
<__main__.RoundFloatManual object at 0x02918A50>
>>> print rfm
<__main__.RoundFloatManual object at 0x02918A50>

输入非法时,就不执行程序返回断言错误。如果输入正确,没有输出,但是当我们把这个对象输出时,却得不到我们需要的信息,调用print语句也没有帮助。
print(使用 str()) 和真正的字符串对象表示(使用repr())都没能显示更多有关我们对象的信息。一个好的办法是,去实现__str__()和__repr__()两者之一,或者两者都实现。让我们来添加一个__str__()方法,以覆盖默认的行为。

class RoundFloatManual(object):
def __init__ (self, val):
assert isinstance(val, float),\
"Value must be a float!"
self.value = round(val,2)
def __str__(self):
return str(self.value)
>>> rfm = RoundFloatManual(4.568)
>>> print rfm
4.57

我们还有一些问题,一个问题是在解释器中转储对象是,仍然显示的是默认对象符号,如果我们想修复它,只需要覆盖__repr__()。因为字符串表示法也是Python对象,我们可以让__repr__()和__str__()的输出一致。
我们可以让__repr__()作为__str__()的一个别名:
__repr__ = __str__
现在,可以看到,同时具备了str()和repr()的输出了:

>>> class RoundFloatManual(object):
  def __init__ (self, val):
    assert isinstance(val, float),\
      "Value must be a float!"
    self.value = round(val,2)
  def __str__(self):
    return str(self.value)
  __repr__ = __str__ >>> rfm = RoundFloatManual(4.568)
>>> rfm
4.57
>>> print rfm
4.57

在前几天提到的RoundFloat例子里,我们没有担心对象的显示问题,原因是__str__()和__repr__()作为float类的一部分已经为我们定义好了。我们只需要继承他们就行了。而这一次需要做另外的工作,这就是派生的好处。

数值定制
这里将有一个实际的例子,假设我们需要创建一个简单的应用,用于操作时间,精确到小时和分,用于跟踪运行时间等。
我们创建一个Time60类,把整型的小时和分钟作为输入传给构造器:

class Time60(object):
  'Time60 - track hours and minutes'
  def __init__ (self, hr, min):
    self.hr = hr
    self.min = min

1.显示
如果我们想有一个有意义的输出,就应该覆盖__str__,有必要还要覆盖__repr__。我们按这样输出时间:

def __str__ (self):
  return '%d:%d' % (self.hr, self.min)
__repr__ = __str__

现在我们就可以用这个类来实例化一些对象了

>>> mon = Time60(10, 30)
>>> tue = Time60(11, 15)
>>> print mon, tue
10:30 11:15

2.加法
Python的重载操作很简单。重载加号只要重载__add__()方法,如果合适还可以用__radd__()及__iadd__()
实现加法我们只需要把分和小时加载一块儿,处理一下进位操作:

def __add__ (self, other):
  newhr = self.hr + other.hr
  newmin = self.min + other.min
  if newmin >= 60:
    newmin -= 60
    newhr += 1
  if newhr >= 24: newhr -= 24
  return self. __class__(newhr, \
               newmin)

3.原位加法
有了增量赋值,我们还希望有原位操作,比如说__iadd__(),用来支持像 mon += tue 这样的操作,重载一个__i*__()这样的方法的唯一秘密是必须返回self:

def __iadd__ (self, other):
  self.hr += other.hr
  self.min += other.min
  if self.min >= 60:
    self.min -= 60
    self.hr += 1
  if self.hr >= 24: self.hr -= 24
  return self

这样这个类可以算是初步完成,但在这个类中,还有很多需要优化和改良的地方。这里我们暂时不继续工作它。

迭代器
我们可以用__iter__()和next()方法,来创建一个迭代器。介绍两个例子。
1.随机序列迭代器
我们给我们的类传入一个初始序列,然后通next()去迭代,这个例子展示了随机且无穷迭代。

from random import choice

class RandSeq(object):
  def __init__ (self, seq):
    self.data = seq
  def __iter__ (self):
    return self
  def next(self):
    return choice(self.data)

2.任意项的迭代器
这是代码:

class AnyIter(object):
  def __init__ (self, data, safe=False):
    self.safe = safe
    self.iter = iter(data)
  def __iter__ (self):
    return self
  def next(self, howmany=1):
    retval = []
    for eachItem in range(howmany):
      try:
        retval.append(self.iter.next())
      except StopIteration:
        if self.safe:
          break
        else:
          raise
    return retval

我们给出一个迭代器和一个安全标识符来创建这个对象。如果这个标识符为真,我们将在遍历完这个迭代器前,返回所获取的任意条目,但如果这个标识符为假,则在用户请求过多的条目时,将会引发一个异常。错综复杂的核心在于next(),特别是它如何退出的。
在next()的最后一部分中,我们创建用于返回的一个列表项,并且调用对象的next()方法来获得每一项条目。如果我们遍历完列表,得到一个StopIteration异常,这时则检查安全标识符。如果不安全,则将异常抛还给调用者;否则,退出并返回已经保存过的所有项。

>>> class AnyIter(object):
  def __init__ (self, data, safe=False):
    self.safe = safe
    self.iter = iter(data)
  def __iter__ (self):
    return self
  def next(self, howmany=1):
    retval = []
    for eachItem in range(howmany):
      try:
        retval.append(self.iter.next())
      except StopIteration:
        if self.safe:
          break
        else:
          raise
    return retval >>> a = AnyIter(range(10))
>>> i = iter(a)
>>> for j in range(1,5):
    print j, ':', i.next(j) 1 : [0]
2 : [1, 2]
3 : [3, 4, 5]
4 : [6, 7, 8, 9]

上面程序的运行没有问题,因为迭代器正好符合项的个数。当情况出现偏差,会发生什么呢?让我们首先试试“不安全”的模式,这就是紧随其后创建我们的迭代器:

>>> i = iter(a)
>>> i.next(14) Traceback (most recent call last):
File "<pyshell#10>", line 1, in <module>
i.next(14)
File "<pyshell#3>", line 11, in next
retval.append(self.iter.next())
StopIteration

因为超出了项的支持量,所以出现了StopIteration异常,并且这个异常还被重新引发回调用者。如果我们以“安全”模式重建迭代器,再次运行一次同一个例子的话,我们就可以在项失控前得到迭代器所得到的元素:

>>> a = AnyIter(range(10), True)
>>> i = iter(a)
>>> i.next(14)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

多类型定制
我们创造一个新类,NumStr,由一个数字-字符对组成,相应地,记为n和s,数值类型使用整型。尽管这组顺序对的合适的记号是(n, s)但是我们选用[n::s]来表示它。暂不管这个记号,我们只需要考虑数据元素模型就好了,是一个整体。这个新类有这样的特征:
初始化:对数字和字符串进行初始化,如果其中一(或二)个没有初始化,数字用0,字符串用空字符串作为默认参数。
加法:功能是数字相加,字符串按顺序相连。
乘法:数字相乘,字符串累积相连。
False值:当数字为0且字符串为空是,这个实体有一个False值。
比较:一对NumStr对象比较应该有九种不同的组合(n1>n2 and s1<s2,n1 == n2 and s1>s2等等)。对数字和字符串,我们按标准的数值和字典顺序进行比较,然后将比较的值相加返回结果。
代码如下:

#!/usr/bin/env python

class NumStr(object):

    def __init__(self, num=0, string=''):
self.__num = num
self.__string = string def __str__(self): # define for str()
return '[%d :: %r]' % \
(self.__num, self.__string)
__repr__ = __str__ def __add__(self, other): # define for s+o
if isinstance(other, NumStr):
return self.__class__(self.__num + \
other.__num,
self.__string + other.__string)
else:
raise TypeError, \
'Illegal argument type for built-in operation' def __mul__(self, num): # define for s*o
if isinstance(num, int):
return self.__class__(self.__num * num,
self.__string * num)
else:
raise TypeError, \
'Illegal argument type for built-in operation' def __nonzero__(self): # reveal tautology
return self.__num or len(self.__string) def __norm_cval(self, cmpres): # normalize cmp()
return cmp(cmpres, 0) def __cmp__(self, other): # define for cmp()
return self.__norm_cval(
cmp(self.__num, other.__num)) + \
self.__norm_cval(
cmp(self.__string, other.__string))

至此,定制类暂告一段落,虽然对名称的命名还存在很多疑问,为什么有下划线和双下划线一直让人很困扰。接下来学习私有化。

私有化
默认情况下,属性在Python中都是“public”,类所在模块和导入了类所在模块的其他代码都可以访问到。很多OO语言给数据加上一些可见性,只提供访问函数来访问其值。这就是熟知的实现隐藏,是对象封装的一个关键部分。
大多数OO语言提供“访问控制符”来限定成员函数的访问。
而Python只为类属性的私有性提供了初步的访问形式。

双下划线(__)
Python中,由双下划线开始的属性在运行时被“混淆”,所以直接访问是不允许的。实际上,会在名字前面加上下划线和类名。比如,上例中self.__num属性被“混淆”后,用于访问这个数据值的标识就变成了self._NumStr__num.把类名加上后形成的新的“混淆”结果将可以防止在祖先类或子孙类中的同名冲突。
尽管这样做提供了某种层次上的私有化,但算法处于公共域中并且很容易被“击败”。这更多的是一种对导入源代码无法获得的模块或对同一模块中的其他代码的保护机制。
这种名字混淆的另一个目的是为了保护__XXX变量不与父类名字空间相冲突。如果在类中有一个__XXX属性,它将不会被其子类中的__XXX属性覆盖。而如果父类中仅有一个XXX属性,子类也定义了这个属性,那么子类的XXX就会覆盖父类的XXX。使用__XXX,子类的代码就可以安全地使用__XXX,而不必担心它会影响父类中的__XXX

单下划线(_)
简单的模块级私有化只需要在属性名前使用一个但下划线字符。这就防止模块的属性用“from mymodule import *”来加载这个属性。这是严格给予作用域的,所以这同样适合于函数。
Python2.2中引进的新式类,增加了一套全新的特征,让程序员在类及实例属性提供保护的多少上拥有大量重要的控制权。经管Python没有在语法上把private,protected,friend或protected friend 等特征内建在语言中,但是可以按你的需要严格地定制访问权。

2015/9/28 Python基础(19):类的定制和私有性的更多相关文章

  1. 2015/8/28 Python基础(2):对象

    Python用对象模型来存储数据.构造任何类型的值都是一个对象.Python对象都有是三个特性:身份,类型和值 身份是每个对象的唯一身份标识.任何对象都可以用内建函数id()来得到身份.如: > ...

  2. 十九. Python基础(19)--异常

    十九. Python基础(19)--异常 1 ● 捕获异常 if VS异常处理: if是预防异常出现, 异常处理是处理异常出现 异常处理一般格式: try:     <............. ...

  3. python基础——枚举类

    python基础——枚举类 当我们需要定义常量时,一个办法是用大写变量通过整数来定义,例如月份: JAN = 1 FEB = 2 MAR = 3 ... NOV = 11 DEC = 12 好处是简单 ...

  4. Python基础-类的探讨(class)

    Python基础-类的探讨(class) 我们下面的探讨基于Python3,我实际测试使用的是Python3.2,Python3与Python2在类函数的类型上做了改变 1,类定义语法  Python ...

  5. 二十六. Python基础(26)--类的内置特殊属性和方法

    二十六. Python基础(26)--类的内置特殊属性和方法 ● 知识框架 ● 类的内置方法/魔法方法案例1: 单例设计模式 # 类的魔法方法 # 案例1: 单例设计模式 class Teacher: ...

  6. 2015/9/20 Python基础(16):类和实例

    面向对象编程编程的发展已经从简单控制流中按步的指令序列进入到更有组织的方式中,依靠代码块可以形成命名子程序和完成既定的功能.结构化的或过程性编程可以让我们把程序组织成逻辑快,以便重复或重用.创造程序的 ...

  7. python基础(26):类的成员(字段、方法、属性)

    1. 字段 字段:包括普通字段和静态字段,他们在定义和使用中有所区别,而最本质的区别是内存中保存的位置不同. 普通字段属于对象 静态字段属于类 字段的定义和使用: class Province: # ...

  8. python基础----元类metaclass

    1 引子 class Foo: pass f1=Foo() #f1是通过Foo类实例化的对象 python中一切皆是对象,类本身也是一个对象,当使用关键字class的时候,python解释器在加载cl ...

  9. 2015/10/9 Python基础(21):可调用和可执行对象

    在Python中有多种运行外部程序的方法,比如,运行操作系统命令或另外的Python脚本,或执行一个磁盘上的文件,或通过网络来运行文件.这完全取决于想要干什么.特定的环境包括: 在当前脚本继续运行 创 ...

随机推荐

  1. 关《我是IT小小鸟》有感

    我一直认为大学就是一个自由的舒适的学习环境,没有人可以干扰你限制你,以至于我到了大学之后只剩下了颓废的生活.每天上课玩手机,下课玩电脑,吃饭叫外卖,从不去锻炼,周末就熬夜通宵,状态越来越差,导致我逐渐 ...

  2. Task Class .net4.0异步编程类

    文章:Task Class 地址:https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.tasks.task?view=netfra ...

  3. Windows Forms编程实战学习:第二章 欢迎使用Visual Studio

    第二章 欢迎使用Visual Studio 1,AssemblyInfo文件 包含程序集的属性,向应用程序添加元数据 [assembly:<attribute>(<setting&g ...

  4. .NET Core使用EF分页查询数据报错:OFFSET语法错误问题

    在Asp.Net Core MVC项目中使用EF分页查询数据时遇到一个比较麻烦的问题,系统会报如下错误: 分页查询代码: ) * condition.PageSize).Take(condition. ...

  5. 复利计算程序单元测试(C语言)

    对我们和复利计算程序,写单元测试. 有哪些场景? 期待的返回值 写测试程序. 运行测试. 我的复利计算程序是用C语言写的,不懂使用C语言的测试工具,所以用C语言的运行结果来反映测试结果. 测试模块(场 ...

  6. QQueue与QStack使用

    版权声明:若无来源注明,Techie亮博客文章均为原创. 转载请以链接形式标明本文标题和地址: 本文标题:QQueue与QStack使用     本文地址:http://techieliang.com ...

  7. 软工实践团队展示——WorldElite

    软工实践团队展示--WorldElite 本次作业链接 团队成员 031602636许舒玲(组长) 031602237吴杰婷 031602634吴志鸿 081600107傅滨 031602220雷博浩 ...

  8. C#获取当前路径的方法如下

    1. System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName -获取模块的完整路径. 2. System.Environm ...

  9. LoadRunner脚本增强技巧之参数化(一)

    参数化的方式有两种,一种通过File引入参数值,一种通过数据库引入参数值.本篇介绍File方式引入参数值. 一.File方式参数化过程 1.在脚本中找到需要做参数化的字符串,选中,右键点击,选择Rep ...

  10. 图解Redux三大核心的关系

    本周开始用react开发新项目了,而为了配合react我们选择了Redux框架来管理state.由于之前一直在业余时间学习react和腾讯地图api,无暇顾及学习redux,所以项目刚上手时对redu ...