零基础入门学Python(十二)—— 魔法方法(上)

零基础入门学Python系列内容的学习目录                                 →                            \rightarrow                 →零基础入门学Python系列内容汇总


魔法方法(上)


  需要学习的基础知识有:构造和析构、算术运算、简单定制、属性访问、描述符、定制序列、迭代器、生成器等。因本部分内容较多,故分为上下两个篇章。
    1、2、3部分内容见零基础入门学Python(十二)—— 魔法方法(上)
    4、5、6、7、8部分内容见零基础入门学Python(十二)—— 魔法方法(下)

1. 构造和析构

  关于魔法方法的几点说明:

  • 魔法方法总是被双下划线包围,例如_ _init_ _()
  • 魔法方法是面向对象的Python的一切;
  • 魔法方法的“魔力”体现在它们总能够在适当的时候被调用。

1.1 _ _ init _ _(self[, …])

  _ _init_ _()方法相当于其他面向对象编程语言的构造方法,也就是类在实例化成对象的时候首先会调用的一个方法。
  举个例子:

class Rectangle:
    """
    定义一个矩形类,
    需要长和宽两个参数,
    拥有计算周长和面积两个方法,
    需要对象在初始化的时候拥有“长”和“宽”两个参数,
    因此需要重写__init__()方法
    """
    
    def __init__(self, x, y):
        self.x = x
        self.y = y        
    def getPeri(self):
        return(self.x + self.y) * 2

    def getArea(self):
        return self.x *self.y

  运行上面代码:
  >>> rect = Rectangle(3, 4)
  >>> rect.getPeri()
  14
  >>> rect.getArea()
  12

  这里需要注意的是,_ _init_ _()方法的返回值一定是None,不能是其他:

  example1: >>> class A:
           def _ _ init _ _(self):
              return " A for A-Cup"

        >>> cup = A()
        Traceback (most recent call last):
         File “

  所以一般在需要进行初始化的时候才重写_ _ init _ _()方法,其实,这个_ _ init _ _()并不是实例化对象时第一个被调用的魔法方法。

1.2 _ _ new _ _(cls[, …])

  关于_ _new_ _(cls[, ...])方法:

  • _ _new_ _()才是在一个对象实例化的时候所调用的第一个方法,它跟其他魔法方法不同,它的第一个参数不是self而是这个类(cls),而其他的参数会直接传递给_ _init_ _()方法的。
  • _ _new_ _()方法需要返回一个实例对象,通常是cls这个类实例化的对象,当然也可以返回其他对象。
  • _ _new_ _()方法平时很少去重写它,一般让Python用默认的方案执行就可以了。但是当其继承一个不可变的类型的时候需要重写这个魔法方法。
class CapStr(str):
    def __new__(cls, string):
        string = string.upper()
        return str.__new__(cls, string)

  运行上面代码:
  >>> a = CapStr(“Hello,Python!”)
  >>> a
  ‘HELLO,PYTHON!’

1.3 _ _ del _ _(self)

  如果说_ _init_ _()_ _new_ _()方法是对象的构造器的话,那么Python也提供了一个析构器,叫作_ _del_ _()方法。当对象将要被销毁的时候,这个方法就会被调用。
  但一定要注意的是,并非del x就相当于自动调用x._ _del_ _()_ _del_ _()方法是当垃圾回收机制回收这个对象的时候调用的。举个例子:

  example1: >>> class C:
           def _ _ init _ _ (self):
              print(“我是_ _ init _ 方法,我被调用了…")
           def  _ _ del _ _ (self):
              print("我是
_ del _ _方法,我被调用了…”)

        >>> c1 = C()
        我是_ _ init _ 方法,我被调用了…
       >>> c2 = c1
       >>> c3 = c2
       >>> del c1
       >>> del c2
       >>> del c3
       我是
_ del _ _方法,我被调用了…

2. 算术运算

  Python2.2以后,对类和类型进行了统一,做法就是将int ()float()str()list()tuple()这些BIF转换为工厂函数。

  >>>type(int)
  

  普通的BIF应该是,而工厂函数则是。如果定义一个类:

  example1: >>> class C:
           pass

        >>>type( C )
       

  类对象的类型也是type类型,其实所谓的工厂函数就是一个类对象。当你调用它们的时候,事实上就是创建一个相应的实例对象:

  example2: >>> a = int(“123”)
        >>> b = int(“456”)
        >>> a + b
        579

  通过上述例子,我们可以发现对象是可以进行计算的。当求a+b等于多少的时候,事实上Python就是在将两个对象进行相加操作。
  Python的魔法方法还提供了自定义对象的数值处理,通过对下面这些魔法方法的重写,可以自定义任何对象间的算术运算。

2.1 算术操作符

  表1 列举了算数运算相关的魔法方法。

表1 算数运算相关的魔法方法
魔法方法 含义
_ _ add _ _(self, other) 定义加法的行为:+
_ _ sub _ _(self, other) 定义减法的行为:-
_ _ mul _ _(self, other) 定义乘法的行为:*
_ _ truediv _ _(self, other) 定义真除法的行为:/
_ _ floordiv _ _(self, other) 定义整数除法的行为://
_ _ mod _ _(self, other) 定义取模算法的行为:%
_ _ divmod _ _(self, other) 定义当被divmod()调用时的行为
_ _ pow _ _ (self, other[, modulo]) 定义当被power()调用或**运算时的行为
_ _ lshift _ _ (self, other) 定义按位左移位的行为:<<
_ _ rshift _ _ (self, other) 定义按位右移位的行为:>>
_ _ and _ _ (self, other) 定义按位与操作的行为:&
_ _ xor _ _ (self, other) 定义按位异或操作的行为:^
_ _ or _ _ (self, other) 定义按位或操作的行为:竖线

  举个例子,下面定义一个比较特立独行的类:

  example1: >>> class New_int(int):
           def _ _ add _ _(self, other):
             return int. _ _ sub _ _(self, other)
           def _ _ sub _ _(self, other):
             return int. _ _ add _ _(self, other)

        >>>a = New_int(3)
        >>>b = New_int(5)
        >>>a + b
        -2
        >>>a - b
        8

  如果想自己写代码,不想通过调用Python默认的方案:

  example2: >>> class Try_int(int):
           def _ _ add _ _(self, other):
             return self + other
           def _ _ sub _ _(self, other):
             return self - other

       >>> a = Try_int(1)
       >>> b = Try_int(3)
       >>> a + b
       Traceback (most recent call last):
         File “

  为什么会陷入无限递归呢?问题就出在这里:
  def _ _ add _ _(self, other):
    return self + other
  当对象涉及加法操作时,自动调用魔法方法_ _add_ _(),但上边的魔法方法写的是returm self + other,也就是返回对象本身加另外一个对象,就会又自动触发调用_ _add_ _()方法,这样就形成了无限递归。所以,应该为下面这样:

  example3: >>> class New_int(int):
           def _ _ add _ _(self, other):
             return int(self) + int(other)
           def _ _ sub _ _(self, other):
             return int(self) - int(other)

       >>> a = New_int(1)
       >>> b = New_int(3)
       >>> a + b
       4

  我们通过对指定魔法方法的重写,完全可以让Python根据我们的意愿去执行结果。

  example4: >>> class int(int):
           def _ _ add _ _(self, other):
             return int. _ _ sub _ _(self, other)

       >>> a = int(‘5’)
       >>> b = int(‘3’)
       >>> a + b
       2

  通过上面的例子我们可以发现,随着对Python学习的深入,Pyhton允许我们做的事情就更多、更灵活!

2.2 反运算

  表2列举了反运算相关的魔法方法。

表2 反运算相关的魔法方法
魔法方法 含义
_ _ radd _ _(self, other) 定义加法的行为:+(当左操作数不支持相应的操作时被调用)
_ _ rsub _ _(self, other) 定义减法的行为:-(当左操作数不支持相应的操作时被调用)
_ _ rmul _ _(self, other) 定义乘法的行为:*(当左操作数不支持相应的操作时被调用)
_ _ rtruediv _ _(self, other) 定义真除法的行为:/(当左操作数不支持相应的操作时被调用)
_ _ rfloordiv _ _(self, other) 定义整数除法的行为://(当左操作数不支持相应的操作时被调用)
_ _ rmod _ _(self, other) 定义取模算法的行为:%(当左操作数不支持相应的操作时被调用)
_ _ rdivmod _ _(self, other) 定义当被divmod()调用时的行为(当左操作数不支持相应的操作时被调用)
_ _ rpow _ _ (self, other[, modulo]) 定义当被power()调用或**运算时的行为(当左操作数不支持相应的操作时被调用)
_ _ rlshift _ _ (self, other) 定义按位左移位的行为:<<(当左操作数不支持相应的操作时被调用)
_ _ rrshift _ _ (self, other) 定义按位右移位的行为:>>(当左操作数不支持相应的操作时被调用)
_ _ rand _ _ (self, other) 定义按位与操作的行为:&(当左操作数不支持相应的操作时被调用)
_ _ rxor _ _ (self, other) 定义按位异或操作的行为:^(当左操作数不支持相应的操作时被调用)
_ _ ror _ _ (self, other) 定义按位或操作的行为:竖线(当左操作数不支持相应的操作时被调用)

  不难发现,这里的反运算魔法方法跟之前的算术运算符保持相对应,不同之处就是反运算的魔法方法多了一个“r”,例如,_ _ add _ _()就对应_ _ radd _ _()。举个例子:

  example1: >>> a + b
        # 如果a对象的_ _ add _ _ ()方法没有实现或者不支持相应的操作,那么Python就会自动调用b的_ _ radd _ _ ()方法

  example2: >>> class Nint(int):
           def _ _ radd _ _(self, other):
             return int. _ _ sub _ _(other, self)

       >>> a = Nint(5)
       >>> b = Nint(3)
       >>> a + b
       8
       # 由于a对象默认有_ _ add _ _ ()方法,所以b的_ _ radd _ _ ()没有执行
       >>> 1 + b
       -2

  关于反运算,要注意一点:对于a + bb_ _radd_ _(self, other)selfb对象,othera对象。看如下一个例子:

  example3: >>> class Nint(int):
           def _ _ rsub _ _(self, other):
             return int. _ _ sub _ _(self, other)

       >>> a = Nint(5)
       >>> 3 - a
       2

  所以对于注重操作数顺序的运算符(例如减法、除法、移位),在重写反运算魔法方法的候,就一定要注意顺序问题了。

2.3 增量赋值运算

  Python也有大量的魔术方法可以来定制增量赋值语句,增量赋值其实就是一种偷懒的形式,它将操作符与赋值来结合起来。例如:

  example1: >>> a = a + b
        # 写成增量赋值的形式就是:
       >>> a += b

2.4 一元操作符

  一元操作符就是只有一个操作数的意思;像a+b这样,加号左右有ab两个操作数叫作二元操作符
  Python支持的一元操作符主要有_ _neg_ _()(表示正号行为),_ _pos_ _()(定义负号行为),_ _abs_ _()(定义当被abs()调用时的行为,就是取绝对值的意思),还有一个_ _invert_ _()(定义按位取反的行为)。

3. 简单定制

  基本要求:

  • 定制一个计时器的类。
  • startstop方法代表启动计时和停止计时。
  • 假设计时器对象t1print(t1)和直接调用t1均显示结果。
  • 当计时器未启动或已经停止计时,调用stop方法会给予温馨的提示。
  • 两个计时器对象可以进行相加:t1 + t2
  • 只能使用提供的有限资源完成。

  这里需要限定只能使用哪些资源,因为Python的模块是非常多的。

  需要下面的资源:

  • 使用time模块的localtime方法获取时间。
  • time.localtime返回struct_time的时间格式。
  • 表现类:_ _str_ _ ()_ _repr_ _()魔法方法。

  example1: >>> class A:
           def _ _ str _ _ (self):
             return “Hello, Python!”

       >>> a = A()
       >>> print(a)
       Hello, Python!
       >>> a
       

  example2: >>> class B:
           def _ _ repr _ _ (self):
             return “Hello, Python!”

       >>> b = B()
       >>> b
       Hello, Python!

  开始来编写代码:

import time as tclass MyTimer:
    # 开始计时
    def start(self):
        self.start = t.localtime()
        print("计时开始…")
    # 停止计时
    def stop(self):
        self.stop = t.localtime()
        print("计时结束!")

  写好基础之后,进行计算。.localtine()返回的是一个时间元组的结构,只需要前边6个元素,然后将stop的元素依次减去start对应的元素,将差值存放在一个新的列表里:

    #停止计时
    def stop(self):
        self.stop = t.localtime()
        self._calc()
        print("计时结束!")
     # 内部方法,计算运行时间
    def _calc(self):
        self.lasted=[]
        self.prompt = "总共运行了"
        for index in range(6):
            self.lasted.append(self.stop[index]- self.start[index])
            self.prompt += str(self.lasted[index])
        print(self.prompt)

  运行程序:
  >>> t1 = MyTimer()
  >>> t1.start()
  计时开始…
  >>> t1.stop()
  总共运行了00001-52
  计时结束!

  已经基本实现计时功能了,接下来需要完成"print(t1)和直接调用t1均显示结果",那就要通过重写_ _str_ _ ()_ _repr_ _()魔法方法来实现:

    def __str__(self):
        return self.prompt
    __repr__ = __str__

  运行程序:
  >>> t1 = MyTimer()
  >>> t1.start()
  计时开始…
  >>> t1.stop()
  计时结束!
  >>> t1
  总共运行了000005

  上面的程序如下运行的时候会出现问题:

  >>> t1 = MyTimer()
  >>> t1
  Traceback (most recent call last):
    File “

  当直接执行t1的时候,Python会调用_ _str_ _()魔法方法,但运行程序会说这个类没有prompt属性。这是因为我们没有执行stop()方法,_calc()方法就没有被调用到,所以也就没有prompt属性的定义了,这时就需要我们使用_ _init_ _()魔法方法了。

    def __init__(self):
        self.prompt = "未开始计时!"
        self.lasted = []
        self.start = 0
        self.stop = 0

  运行程序:
  >>> t1 = MyTimer()
  >>> t1
  未开始计时!
  >>> t1.start()
  Traceback (most recent call last):
    File “

  运行t1.start()又出错了,Python 这里抛出了一个异常:TypeError: ‘int’ object is not callable。这是说Python认为start是一个整型变量,而不是一个方法。因为在_ _init_ _()方法里,我们也命名了一个叫作self.start的变量,如果类中的方法名和属性同名,属性会覆盖方法。所以我们需要把所有的self.startself.end都改为self.beginself.end
  我们再将显示时间000005改为“年月日小时分钟秒”去显示,所以程序修改为:

    def __init__(self):
        self.unit = ['年', '月', '日', '小时', '分钟', '秒']
        self.prompt = "未开始计时!"
        self.lasted = []
        self.begin = 0
        self.end = 0
    # 计算运行时间
    def _calc(self):
        self.lasted=[]
        self.prompt = "总共运行了"
        for index in range(6):
            self.lasted.append(self.end[index]- self.begin[index])
            if self.lasted[index]:
                self.prompt += (str(self.lasted[index]) + self.unit[index])

  运行程序:
  >>> t1=MyTimer()
  >>> t1.start()
  计时开始…
  >>> t1.stop()
  计时结束!
  >>> t1
  总共运行了4秒

  然后再在适当的地方增加温馨提示:

    # 开始计时
    def start(self):
        self.begin = t.localtime()
        self.prompt = "提示:请先调用stop()结束计时!"
        print("计时开始…")
    #停止计时
    def stop(self):
        if not self.begin:
            print("提示:请先调用start()开始计时!")
        else:
            self.end = t.localtime()
            self._calc()
            print("计时结束!")
    # 计算运行时间
    def _calc(self):
        self.lasted=[]
        self.prompt = "总共运行了"
        for index in range(6):
            self.lasted.append(self.end[index] - self.begin[index])
            if self.lasted[index]:
                self.prompt += (str(self.lasted[index]) + self.unit[index])  
        # 为下一轮计算初始化变量
        self.begin = 0
        self.end = 0

  最后,再重写一个魔法方法_ _add_ _(),让两个计时器对象相加会自动返回时间的和。

    def __add__(self, other):
        prompt = "总共运行了"
        result =[]
        for index in range(6):
            result.append(self.1asted[index] + other.lasted[index])
            if result[index]:
                prompt += (str(result[index]) + self.unit[index])
        return prompt

  整个代码如下:

import time as tclass MyTimer:
    def __init__(self):
        self.unit = ['年', '月', '日', '小时', '分钟', '秒']
        self.prompt = "未开始计时!"
        self.lasted = []
        self.begin = 0
        self.end = 0   
    def __str__(self):
        return self.prompt
    __repr__ = __str__    # 开始计时
    def start(self):
        self.begin = t.localtime()
        self.prompt = "提示:请先调用stop()结束计时!"
        print("计时开始…")
    #停止计时
    def stop(self):
        if not self.begin:
            print("提示:请先调用start()开始计时!")
        else:
            self.end = t.localtime()
            self._calc()
            print("计时结束!")
    # 计算运行时间
    def _calc(self):
        self.lasted=[]
        self.prompt = "总共运行了"
        for index in range(6):
            self.lasted.append(self.end[index] - self.begin[index])
            if self.lasted[index]:
                self.prompt += (str(self.lasted[index]) + self.unit[index])  
        # 为下一轮计算初始化变量
        self.begin = 0
        self.end = 0   
    def __add__(self, other):
        prompt = "总共运行了"
        result =[]
        for index in range(6):
            result.append(self.lasted[index] + other.lasted[index])
            if result[index]:
                prompt += (str(result[index]) + self.unit[index])
        return prompt

  运行程序:
  >>> t1=MyTimer()
  >>> t1
  未开始计时!
  >>> t1.stop()
  提示:请先调用start()开始计时!
  >>> t1.start()
  计时开始…
  >>> t1
  提示:请先调用stop()结束计时!
  >>> t1.stop()
  计时结束!
  >>> t1
  总共运行了10秒
  >>> t2 = MyTimer()
  >>> t2.start()
  计时开始…
  >>> t2.stop()
  计时结束!
  >>> t2
  总共运行了8秒
  >>> t1 + t2
  ‘总共运行了18秒’

  后半部分内容见零基础入门学Python(十二)—— 魔法方法(下)

4. 属性访问

5. 描述符

6. 定制序列

7. 迭代器

8. 生成器

上一篇:JavaSE基础知识(5)—面向对象(对象数组和对象关联)


下一篇:【转载】python 魔法方法详解