前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >魔法方法(1)

魔法方法(1)

作者头像
不可言诉的深渊
发布2019-07-26 17:16:48
7140
发布2019-07-26 17:16:48
举报
文章被收录于专栏:Python机器学习算法说书人

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

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

构造函数

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

代码语言:javascript
复制
  >>> f = FooBar()
  >>> f.init()

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

代码语言:javascript
复制
  >>> f = FooBar()

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

代码语言:javascript
复制
  class FooBar:
      def __init__(self):
          self.somevar = 42
  
  >>> f = FooBar()
  >>> f.somevar
  42

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

代码语言:javascript
复制
  class FooBar:
      def __init__(self, value=42):
          self.somevar = value

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

代码语言:javascript
复制
  >>> 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的文档。

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

代码语言:javascript
复制
  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来计算它的值。下面的示例演示了如何使用这个类。

代码语言:javascript
复制
  >>> s = ArithmeticSequence(1, 2)
  >>> s[4]
  9
  >>> s[4] = 2
  >>> s[4]
  2
  >>> s[5]
  11

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

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

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

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

代码语言:javascript
复制
  >>> 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负责的。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-01-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Python机器学习算法说书人 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 构造函数
  • 元素访问
    • 基本的序列和映射协议
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档