魔法方法(1)

在Python中,有些名称很特别,开头和结尾都是两个下划线。我们可能用过一些,如__future__。这样的拼写表示名称有特殊意义,因此绝不要在程序中创建这样的名称。在这样的名称中,很大一部分都是魔法(特殊)方法的名称。如果你的对象实现了这些方法,它们将在特定情况下(具体是那种情况取决于方法的名称)被Python调用,而几乎不需要直接调用。

今天讨论几个重要的魔法方法,其中最重要的是__init__以及一些处理元素访问的方法(它们让你能够创建序列或映射)。

构造函数

我们要介绍的第一个魔法方法是构造函数。你可能从未通说过构造函数(constructor),它其实就是所谓的初始化方法,只是命名为__init__。然而,构造函数不同于普通方法的地方在于,将在对象创建后自动调用他们。因此无需采用之前一直使用的做法:

  >>> f = FooBar()
  >>> f.init()

构造函数只需要让你像下面这样做:

  >>> f = FooBar()

在Python中创建构造函数很容易,只需要将方法init的名称从普通的init改为魔法版__init__即可。

  class FooBar:
      def __init__(self):
          self.somevar = 42
  
  >>> f = FooBar()
  >>> f.somevar
  42

到目前为止一切顺利。但你可能会问,如果给构造函数添加几个参数,结果将如何呢?请看下面的代码:

  class FooBar:
      def __init__(self, value=42):
          self.somevar = value

你认为该如何使用这个构造函数呢?由于参数是可选的,你可以当什么事都没发生,还像原来那样做。但是如果要指定这个参数(或者说如果这个参数不是可选的)呢?你肯定猜到了,不过这里还是演示一下:

  >>> f = FooBar('This is a constructor argument')
  >>> f.somevar
  'This is a constructor argument'

在所有的Python魔法方法中,__init__绝对是你用的最多的。

注意 Python提供了魔法方法__del__,也称为析构函数(destructor)。这个方法在对象被销毁(作为垃圾被收集)前被调用,但鉴于你无法知道准确的调用时间,建议尽可能不要使用__del__。

元素访问

虽然__init__无疑是你目前遇到最重要的特殊方法,但还有不少其他的特殊方法,让你能够完成很多很酷的任务。接下来将介绍一组很有用的魔法方法,让你能够创建行为类似于序列或映射的对象。

基本的序列和映射协议非常简单,但要实现序列和映射的所有功能,需要实现很多魔法方法。

注意 在Python中,协议通常指的是对范行为的规则,有点类似于接口。协议指定应实现哪些方法以及这些方法该做什么。在Python中,多态仅仅基于对象的行为(而不基于祖先,如属于哪个类或其超类等),因此这个概念很重要:其他语言可能要求对象属于特定的类或实现了特定的接口,而Python通常只要求对象遵循特定的协议。因此,要成为序列,只需遵循序列协议即可。

基本的序列和映射协议

序列和映射基本上是元素(item)的集合,要实现它们基本的行为(协议),不可变对象要实现2个方法,而可变对象要实现4个。

  • __len__(self):这个方法应返回集合包含的项数,对映射来说为键-值对数。如果__len__返回零(且没有实现覆盖这种行为的__nonzero__),对象在布尔上下文中将被视为假(就像空的列表、元组、字符串和字典一样)。
  • __getitem__(self, key):这个方法应返回与指定键相关联的值。对序列来说,键应该是0~n-1的整数(也可以是负数,这将在后面说明),其中n为序列的长度。对映射来说键可以是任何类型。
  • __setitem__(self, key, value):这个方法应以与键相关联的方式存储值,以便以后能够使用__getitem__来获取。当然,仅当对象可变时才需要实现这个方法。
  • __delitem__(self, key):这个方法在对对象的组成部分使用__del__语句时,应删除与key相关联的值。同样,仅当对象可变(且允许其项被删除)时,才需要实现这个方法。

对于这些方法,还有一些额外的要求。

  • 对于序列,如果键为负整数,应从末尾往前数。换而言之,x[-n]应与x[len(x)-n]等效。
  • 如果键的类型不合适(如对序列使用字符串键),可能引发TypeError异常。
  • 对于序列,如果索引的类型是正确的,但不在允许的范围内,应引发IndexError异常。

要了解更复杂的接口和使用的抽象基类(Sequence),请参阅有关模块collections的文档。

下面来试一试,看看能否创建一个无穷序列。

  def check_key(key):
      """
      指定的键是否是可接受的索引?
      键必须是非负整数,才是可以接受的。如果不是整数,
      将引发TypeError异常;如果是负数,将引发IndexError
      异常(因为这个序列的长度是无穷的)
      """
      if not isinstance(key, int):
          raise TypeError
      if key < 0:
          raise IndexError
  
  
  class ArithmeticSequence:
      def __init__(self, start=0, step=1):
          """
          初始化这个算数序列
          start   -序列中的第一个值
          step    -两个相邻值的差
          changed -一个字典,包含用户修改后的值
          """
          self.start = start  # 存储起始值
          self.step = step    # 存储步长值
          self.changed = {}   # 没有任何元素被修改
      
      def __getitem__(self, key):
          """
          从算数序列中获取第一个元素
          """
          check_key(key)
          try:
              return self.changed[key]
          except KeyError:
              return self.start+key*self.step
          
      def __setitem__(self, key, value):
          """
          修改算数序列中的元素
          """
          check_key(key)
          self.changed[key] = value  # 存储修改后的值

这些代码实现的是一个算数序列,其中任何两个相邻数字的差都相同。第一个值是由构造函数的参数start(默认为0)指定的,而相邻值之间的差是由参数step(默认为1)指定的。你允许用户修改某些元素。这是通过将不符合规则的值保存在字典changed中实现的。如果元素未被修改,就使用公式self.start+key*self.step来计算它的值。下面的示例演示了如何使用这个类。

  >>> s = ArithmeticSequence(1, 2)
  >>> s[4]
  9
  >>> s[4] = 2
  >>> s[4]
  2
  >>> s[5]
  11

请注意,我要禁止删除元素,因此没有实现__del__:

  >>> del s[4]
  Traceback (most recent call last):
      File "<stdin>", line 1, in ?
  AttributeError: ArithmeticSequence instance has no attribute '__delitem__'

另外,这个类没有方法__len__,因为其长度是无穷的。

如果所使用的索引类型非法,将引发TypeError异常;如果索引类型正确,但不再允许的范围内(即为负数),将引发IndexError异常。

  >>> s["four"]
  Traceback (most recent call last):
      File "<stdin>", line 1, in ?
      ...
  >>> s[-42]
  Traceback (most recent call last):
      File "<stdin>", line 1, in ?
      ...

索引检查是由我为此编写的辅助函数check_index负责的。

本文分享自微信公众号 - Python机器学习算法说书人(Python-ML-Algorithm),作者:小陈学Python

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-01-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 魔法方法(2)

    在学习面向对象程序设计时,我们通常会学到存取方法,它们是名称类似于getHeight和setHeight的方法,用于获取和设置属性(这些属性可能是私有的)。如果...

    不可言诉的深渊
  • Python设计模式(9):桥接模式

    在实际生活中,某些事物由于自身的逻辑,具有两个或多个维度的变化。例如,国家武装力量分为海军、陆军、空军三个军种,海、陆、空三军各自又有军、师、旅、团、营等建制。...

    不可言诉的深渊
  • 古老的机械钟表蕴含着神秘的数学原理

    时间是一个比较抽象的概念,是物质运动、变化的持续性、顺序性的表现。正因为人们需要研究物质的运动,就必须通过一个中介者来认识和度量时间,这个中介者就是计时器,从古...

    不可言诉的深渊
  • 原 matplotlib动画入门(3):弹球

    在平面上画一个方框代表墙壁,框内有一个运动的弹球,当弹球碰到墙壁时就弹回去,小球不停的运动。

    Stanley Sun
  • 一日一技:在Python中,调用对象不存在的方法时自定义提示信息

    现在,我实例化这个类,并调用它的 play方法,由于这个方法不存在,所以现在必定导致报错,如下图所示。

    青南
  • 【AlphaGo Zero 核心技术-深度强化学习教程代码实战04】Agent类和SARSA算法实现

    【导读】Google DeepMind在Nature上发表最新论文,介绍了迄今最强最新的版本AlphaGo Zero,不使用人类先验知识,使用纯强化学习,将价值...

    WZEARW
  • 158行代码!程序员复现DeepMind图像生成神器

    递归神经网络是一种用于图像生成的神经网络结构。Draw Networks 结合了一种新的空间注意机制,该机制模拟了人眼的中心位置,采用了一个顺序变化的自动编码框...

    新智元
  • 158行Python代码复现:DeepMind提图像生成的递归神经网络DRAW

    [ 导读 ]最近,谷歌 DeepMInd 发表论文(DRAW: A Recurrent Neural Network For Image Generation)...

    数据派THU
  • Python学习【魔术方法】

    py3study
  • Python 基础 类的继承

    如果寂静定义了Person类,需要定义新的Student 和Teacher 类时     可以直接从Person 中继承     class Person(Ob...

    用户1197315

扫码关注云+社区

领取腾讯云代金券