zoukankan      html  css  js  c++  java
  • Python学习笔记:Python类的特殊方法

    第8章 Python类的特殊方法

    在Python类中有些方法名、属性名的前后都添加了双下画线,这种方法、属性通常都属于Python的特殊方法和特殊属性,开发者可以通过重写这些方法或直接调用这些方法来实现特殊的功能。最常见的特殊方法就是前面介绍的构造方法:init,开发者可以通过重写类中的init方法来实现自己的初始化逻辑。
    提示:python采用的是一种“约定”的机制,python按照约定,以特殊名字的方法、属性来提供特殊的功能。
    python类中的特殊方法、特殊属性有些需要开发者重写,有些则可以直接调用

    常见的特殊方法

    重写__repr__方法

    class Item:
        def __init__ (self, name, price):
            self.name = name
            self.price = price
    # 创建一个Item对象,将之赋给im变量
    im = Item('鼠标', 29.8)
    # 打印im所引用的Item对象
    print(im)
    
    im_str = im.__repr__() + ""
    print(im_str)
    
    输出结果:
    <__main__.Item object at 0x0000013BDD3FE148>
    <__main__.Item object at 0x0000013BDD3FE148>
    

    按道理来说,print()函数只能在控制台打印字符串,而Item实例是内存中的一个对象,怎么能直接转换为字符串输出呢?事实上,当使用该方法输出Item对象时,实际上输出的是Item对象的__repr__方法的返回值。


    repr()是Python类中的一个特殊方法,由于object类己提供了该方法,而所有的Python类都是object类的子类,因此所有的Python对象都具有__repr__()方法,当程序需要将任何对象与字符串进行连接时,都可先调用__repr__方法将对象转换成字符串,然后将两个字符串连接在一起。
    repr()方法是一个非常特殊的方法,它是一个“自我描述”的方法,该方法通常用于实现这样一个功能:当程序员直接打印该对象时,系统将会输出该对象的“自我描述”信息,用来告诉外界该对象具有的状态信息。object类提供的__repr__()方法总是返回该对象实现类的“类名+objectat+内存地址”值,这个返回值并不能真正实现“自我描述”的功能,因此,如果用户需要自定义类能实现“自我描述”的功能,就必须重写__repr__()方法。

    class Apple:
        # 实现构造器
        def __init__(self, color, weight):
            self.color = color;
            self.weight = weight;
        # 重写toString()方法,用于实现Apple对象的“自我描述”
        def __repr__(self):
            return "Apple[color=" + self.color +
                ", weight=" + str(self.weight) + "]"
    a = Apple("红色" , 5.68)
    # 打印Apple对象
    print(a)
    
    输出结果:
    Apple[color=红色, weight=5.68]
    

    析构方法:del

    与__init__()法对应的是__del__()方法,init()方法用于初始化Python对象,而__del__()方法则用于销毁Python对象 --- 在任何Python对象将要被系统回收之时,系统都会自动调用该对象的__del__()方法当程序不再需要一个Python对象时,系统必须把该对象所占用的内存空间释放出来,这个过程被称为垃圾回收(GC,GarbageCollector),Python会自动回收所有对象所占用的内存空间,因此开发者无须关心对象垃圾回收的过程。


    提示:
    Python采用自动引用计数(ARC)方式来回收对象所占用的空间,当程序中有一个变量引用该Python对象时,Python会自动保证该对象引用计数为1;当程序中有两个变量引用该Python对象时,Python会自动保证该对象引用计数为2……依此类推,|如果一个对象的引用计数变成了0,则说明程序中不再有变量引用该对象,表明程序不再需要该对象,因此Python就会回收该对象。


    当一个对象被垃圾回收时,python会自动调用该对象的__del__方法。
    需要说明的是,不要以为对一个变量执行del操作,该变量所引用的对象就会被回收---只有当对象的引用计数变成0时,该对象才会被回收。因此,如果一个对象有多个变量引用它,那么del其中一个变量是不会回收该对象的。

    class Item:
        def __init__ (self, name, price):
            self.name = name
            self.price = price
        # 定义析构函数
        def __del__ (self):
            print('del删除对象')
    # 创建一个Item对象,将之赋给im变量
    im = Item('鼠标', 29.8)
    x = im   # ①
    # 打印im所引用的Item对象
    del im
    print('--------------')
    
    
    
    输出结果:
    --------------
    del删除对象
    

    del im执行之后,程序并没有回收Item对象,只有等到程序执行将要结束时(系统必须回收所有对象),系统才会回收Item对象。
    注释掉1处代码,当程序执行del im之后,此时程序中不再有任何变量引用该Item对象,因此系统会立即回收该对象,则无须等到程序结束之前,示例:

    class Item:
        def __init__ (self, name, price):
            self.name = name
            self.price = price
        # 定义析构函数
        def __del__ (self):
            print('del删除对象')
    # 创建一个Item对象,将之赋给im变量
    im = Item('鼠标', 29.8)
    # = im   # ①
    # 打印im所引用的Item对象
    del im
    print('--------------')
    
    
    输出结果:
    del删除对象
    --------------
    

    注意:
    如果父类提供了__del__方法,则系统重写__del__()方法时必须显示调用父类的__del__()方法,这样才能保证合理地回收父类实例的部分属性。

    __dir__方法

    对象的dir()方法用于列出该对象内部的所有属性(包括方法)名,该方法将会返回包含所有属性(方法)名的序列。当程序对某个对象执行dir(object)函数时,实际上就是将该对象的__dir__()方法返回值进行排序然后包装成列表。

    class Item:
        def __init__ (self, name, price):
            self.name = name
            self.price = price
        def info ():
            pass
    # 创建一个Item对象,将之赋给im变量
    im = Item('鼠标', 29.8)
    print(im.__dir__())  # 返回所有属性(包括方法)组成列表
    print("-----------------------------------------------")
    print(dir(im))  # 返回所有属性(包括方法)排序之后的列表
    
    
    输出结果:
    ['name', 'price', '__module__', '__init__', 'info', '__dict__', '__weakref__', '__doc__', '__repr__', '__hash__', '__str__', '__getattribute__', '__setattr__', '__delattr__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__new__', '__reduce_ex__', '__reduce__', '__subclasshook__', '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__']
    -----------------------------------------------
    ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'info', 'name', 'price']
    

    __dict__属性

    __dict__属性用于查看对象内部存储的所有属性名和属性值组成的字典,通常程序直接使用该属性即可。程序使用__dict__属性既可查看对象的所有内部状态,也可通过字典语法来访问或修改指定属性的值

    class Item:
        def __init__ (self, name, price):
            self.name = name
            self.price = price
    im = Item('鼠标', 28.9)
    print(im.__dict__)  # ①
    # 通过__dict__访问name属性
    print(im.__dict__['name'])
    # 通过__dict__访问price属性
    print(im.__dict__['price'])
    im.__dict__['name'] = '键盘'
    im.__dict__['price'] = 32.8
    print(im.name) # 键盘
    print(im.price) # 32.8
    
    
    输出结果:
    {'name': '鼠标', 'price': 28.9}
    鼠标
    28.9
    键盘
    32.8
    

    getattr、__setattr__等

    当程序操作(包括访问、设置、删除)对象的属性时,Python系统同样会执行该对象特定的方法。这些方法共涉及如下几个。

    • getattribute(self,name):当程序访问对象的name属性fl才被自动调用。
    • getattr(self,name):当程序访问对象的name属性且该属性不存在时被自动调用。
    • setattr(self,name,value):当程序对对象的name属性赋值时被自动调用。
    • delattr(self,name):当程序删除对象的name属性时被自动调用。

    通过重写上面的方法,可以为Python类“合成”属性一一当属性不存在时,程序会委托给上面的__getattr__、setattr、__delattr__方法来实现,因此程序可通过重写这些方法来“合成”属性

    class Rectangle:
        def __init__ (self, width, height):
            self.width = width
            self.height = height
        def __setattr__(self, name, value):
            print('----设置%s属性----' % name)
            if name == 'size':
                self.width, self.height = value
            else:
                self.__dict__[name] = value
        def __getattr__(self, name):
            print('----读取%s属性----' % name)
            if name == 'size':
                return self.width, self.height
            else:
                raise AttributeError
        def __delattr__(self, name):
            print('----删除%s属性----' % name)
            if name == 'size':
                self.__dict__['width'] = 0
                self.__dict__['height'] = 0
               
    rect = Rectangle(3, 4)
    print("---------------------------------0")
    print(rect.size)
    print("---------------------------------1")
    rect.size = 6, 8
    print("---------------------------------2")
    print(rect.width)
    print("---------------------------------3")
    del rect.size
    print("---------------------------------4")
    print(rect.size)
    print("---------------------------------5")
    
    
    输出结果:
    ----设置width属性----
    ----设置height属性----
    ---------------------------------0
    ----读取size属性----
    (3, 4)
    ---------------------------------1
    ----设置size属性----
    ----设置width属性----
    ----设置height属性----
    ---------------------------------2
    6
    ---------------------------------3
    ----删除size属性----
    ---------------------------------4
    ----读取size属性----
    (0, 0)
    ---------------------------------5
    ----读取shape属性----
    ----读取size属性----
    ----读取size属性----
    

    读取、设置属性之前进行某种拦截处理(比如检查数据是否合法),示例:

    class User:
        def __init__ (self, name, age):
            self.name = name
            self.age = age
        # 重写__setattr__()方法对设置的属性值进行检查
        def __setattr__ (self, name, value):
            # 如果正在设置name属性
            if name == 'name':
                if 2 < len(value) <= 8 or len(value) > 8:
                    self.__dict__['name'] = value
                else:
                    raise ValueError('name的长度必须在2~8之间')
                
            elif name == 'age':
                if 10 < value < 60:
                    self.__dict__['age'] = value
                else:
                    raise ValueError('age值必须在10~60之间')
                
    u = User('fkit', 24)
    print(u.name)
    print(u.age)
    #u.name = 'fk' # 引发异常
    u.age = 2  # 引发异常
    

    与反射相关的属性和方法

    如果程序在运行过程中要动态判断是否包含某个属性(包括方法),甚至要动态设置某个属性值,则可通过python的反射支持来实现

    动态操作属性

    在动态检查对象是否包含某些属性(包括方法)的相关的函数如下:

    • hasattr(obj,name):检查obj对象是否包含名为name的属性或方法。
    • getattr(object,name[,default]):获取object对象中名为name的属性的属性值。
    • setattr(obj,name,value,/):将obj对象的name属性设为value。
    class Comment:
        def __init__ (self, detail, view_times):
            self.detail = detail
            self.view_times = view_times
        def info ():
            print("一条简单的评论,内容是%s" % self.detail)
            
    c = Comment('疯狂Python讲义很不错', 20)
    # 判断是否包含指定的属性或方法
    print(hasattr(c, 'detail')) # True
    print(hasattr(c, 'view_times')) # True
    print(hasattr(c, 'info')) # True
    # 获取指定属性的属性值
    print(getattr(c, 'detail')) # '疯狂Python讲义很不错'
    print(getattr(c, 'view_times')) # 20
    # 由于info是方法,故下面代码会提示:name 'info' is not defined
    #print(getattr(c, info, '默认值'))
    # 为指定属性设置属性值
    setattr(c, 'detail', '天气不错')
    setattr(c, 'view_times', 32)
    # 输出重新设置后的属性值
    print(c.detail)
    print(c.view_times)
    
    # 设置不存在的属性,即为对象添加属性
    setattr(c, 'test', '新增的测试属性')
    print(c.test) # 新增的测试属性
    
    def bar ():
        print('一个简单的bar方法')
    # 将c的info方法设为bar函数    
    setattr(c, 'info', bar)
    c.info()
    
    # 将c的info设置为字符串'fkit'
    setattr(c, 'info', 'fkit')
    c.info()
    
    
    输出结果:
    True
    True
    True
    疯狂Python讲义很不错
    20
    天气不错
    32
    新增的测试属性
    一个简单的bar方法
    Traceback (most recent call last):
    
      File "C:Userszz.spyder-py3	emp.py", line 37, in <module>
        c.info()
    
    TypeError: 'str' object is not callable
    

    __call__属性

    上面程序可用hasattr()函数判断指定属性(或方法)是否存在,但到底是属性还是方法,则需要进一步判断它是否可调用。程序可通过判断属性(或方法)是否包含__call__属性来确定它是否可调用

    class User:
        def __init__(self, name, passwd):
            self.name = name
            self.passwd = passwd
        def validLogin (self):
            print('验证%s的登录' % self.name)        
    u = User('crazyit', 'leegang')
    # 判断u.name是否包含__call__方法,即判断是否可调用
    print(hasattr(u.name, '__call__')) # False
    # 判断u.passwd是否包含__call__方法,即判断是否可调用
    print(hasattr(u.passwd, '__call__')) # False
    # 判断u.validLogin是否包含__call__方法,即判断是否可调用
    print(hasattr(u.validLogin, '__call__')) # True
    
    
    输出结果:
    False
    False
    True
    

    实际上,一个函数(甚至对象)之所以能执行,关键就在于__call__()方法。实际上x(argl,arg2,...)只是x.call(argl,arg2,…)的快捷写法,因此我们甚至可以为自定义类添加__call__方法,从而使得该类的实例也变成可调用的。示例:

    # 定义Role类
    class Role:
        def __init__ (self, name):
            self.name = name
        # 定义__call__方法
        def __call__(self):
            print('执行Role对象')
    r = Role('管理员')
    # 直接调用Role对象,就是调用该对象的__call__方法
    r()
    
    输出结果:
    执行Role对象
    

    上面程序中最后一行代码使用调用函数的语法来调用对象,这看上去似乎是错误的,但由于该Role类提供了__call__方法,因此调用对象的本质就是执行该对象的__call__方法。
    对于程序中的函数,同样既可使用函数的语法来调用它,也可把函数当成对象,调用它的__call__方法,示例:

    def foo ():
        print('--foo函数--')
    # 下面示范了2种方式调用foo()函数
    foo()
    foo.__call__()
    
    输出结果:
    --foo函数--
    --foo函数--
    

    与序列相关的特殊方法

    序列相关方法

    序列最重要的特征就是可包含多个元素,因此和序列有关的特殊方法有如下几个;

    • len(self):该方法的返回值决定序列中元素的个数。
    • getitem(self):该方法获取指定索引对应的元素。该方法的key应该是整数值或slice对象,否则该方法会引发KeyError异常。
    • contains(self,item):该方法判断序列是否包含指定元素。
    • setitem(self,key,value):该方法设置指定索引对应的元素。该方法的key应该是整数

    值或slice对象,否则该方法会引发KeyError异常。

    • delitem(self,key):该方法删除指定索引对应的元素。
    def check_key (key):
        '''
        该函数将会负责检查序列的索引,该索引必须是整数值,否则引发TypeError
        且程序要求索引必须为非负整数,否则引发IndexError
        '''
        if not isinstance(key, int): raise TypeError('索引值必须是整数')
        if key < 0: raise IndexError('索引值必须是非负整数')
        if key >= 26 ** 3: raise IndexError('索引值不能超过%d' % 26 ** 3)   
    class StringSeq:
        def __init__(self):
            # 用于存储被修改的数据
            self.__changed = {}
            # 用于存储已删除元素的索引
            self.__deleted = []
        def __len__(self):
            return 26 ** 3
        def __getitem__(self, key):
            '''
            根据索引获取序列中元素
            '''
            check_key(key)
            # 如果在self.__changed中找到已经修改后的数据
            if key in self.__changed :
                return self.__changed[key]
            # 如果key在self.__deleted中,说明该元素已被删除
            if key in self.__deleted :
                return None
            # 否则根据计算规则返回序列元素
            three = key // (26 * 26)
            two = ( key - three * 26 * 26) // 26
            one = key % 26
            return chr(65 + three) + chr(65 + two) + chr(65 + one) 
        def __setitem__(self, key, value):
            '''
            根据索引修改序列中元素
            '''
            check_key(key)
            # 将修改的元素以key-value对的形式保存在__changed中
            self.__changed[key] = value
        def __delitem__(self, key):
            '''
            根据索引删除序列中元素
            '''
            check_key(key)
            # 如果__deleted列表中没有包含被删除key,添加被删除的key
            if key not in self.__deleted : self.__deleted.append(key)
            # 如果__changed中包含被删除key,删除它
            if key in self.__changed : del self.__changed[key]
    # 创建序列
    sq = StringSeq()
    # 获取序列的长度,实际上就是返回__len__()方法的返回值
    print(len(sq))
    print(sq[26*26])
    # 打印没修改之后的sq[1]
    print(sq[1]) # 'AAB'
    # 修改sq[1]元素
    sq[1] = 'fkit'
    # 打印修改之后的sq[1]
    print(sq[1]) # 'fkit'
    # 删除sq[1]
    del sq[1]
    print(sq[1]) # None
    # 再次对sq[1]赋值
    sq[1] = 'crazyit'
    print(sq[1]) # crazyit
    
    
    
    输出结果:
    17576
    BAA
    AAB
    fkit
    None
    crazyit
    

    实现迭代器

    如果开发者需要实现法代器,只要实现如下两个方法即可。

    • iter(self):该方法返回一个法代器(iterator),迭代器必须包含一个next()方法,该方法返回迭代器的下一个元素。
    • reversed(self):该方法主要为内建的reversed()反转函数提供支持,当程序调用reversed()函数对指定迭代器执行反转时,实际上是由该方法实现的。
    # 定义一个代表斐波那契数列的迭代器
    class Fibs:
        def __init__(self, len):
            self.first = 0
            self.sec = 1
            self.__len = len
        # 定义迭代器所需的__next__方法
        def __next__(self):
            # 如果__len__属性为0,结束迭代
            if self.__len == 0:
                raise StopIteration
            # 完成数列计算:
            self.first, self.sec = self.sec, self.first + self.sec
            # 数列长度减1
            self.__len -= 1
            return self.first
        # 定义__iter__方法,该方法返回迭代器
        def __iter__(self):
            return self
    # 创建Fibs对象
    fibs = Fibs(10)
    # 获取迭代器的下一个元素
    print(next(fibs))
    # 使用for循环遍历迭代器
    for el in fibs:
        print(el, end=' ')
    # 将列表转换为迭代器
    my_iter = iter([2, 'fkit', 4])
    # 依次获取迭代器的下一个元素
    print(my_iter.__next__()) # 2
    print(my_iter.__next__()) # fkit
    
    
    输出结果:
    1
    1 2 3 5 8 13 21 34 55 2
    fkit
    

    扩展列表、元组和字典

    继承系统已有的列表、元组或字典,重写或新增方法即可

    # 定义ValueDict类,继承dict类
    class ValueDict(dict):
        # 定义构造函数
        def __init__(self, *args, **kwargs):
            # 调用父类的构造函数
            super().__init__(*args, **kwargs)
        # 新增getkeys方法
        def getkeys(self, val):
            result = []
            for key, value in self.items():
                if value == val: result.append(key)
            return result
    my_dict = ValueDict(语文 = 92, 数学 = 89, 英语 = 92)
    # 获取92对应的所有key
    print(my_dict.getkeys(92)) # ['语文', '英语']
    my_dict['编程'] = 92
    print(my_dict.getkeys(92)) # ['语文', '英语', '编程']
    
    输出结果:
    ['语文', '英语']
    ['语文', '英语', '编程']
    

    生成器

    生成器和法代器的功能非常相似,它也会提供__next__()方法,这意味着程序同样可调用内置的next()函数来获取生成器的下一个值,也可使用for循环来遍历生成器。生成器与法代器的区别在于:迭代器通常是先定义一个迭代器类,然后通过创建实例来创建迭代器;而生成器则是先定义一个包含yield语句的函数,然后通过调用该函数来创建生成器。生成器是一种非常优秀的语法,Python使用生成器可以让程序变得很优雅。

    创建生成器

    创建生成器需要两步操作:

    1. 定义一个包含yield语句的函数
    2. 调用第1步创建的函数得到生成器
    def test(val, step):
        print("--------函数开始执行------")
        cur = 0
        # 遍历0~val
        for i in range(val):
            # cur添加i*step
            cur += i * step
            yield cur
    #        print(cur, end=' ')
    # 执行函数,返回生成器
    t = test(10, 2)
    print('=================')
    # 获取生成器的第一个值
    print(next(t)) # 0,生成器“冻结”在yield处
    print(next(t)) # 2,生成器再次“冻结”在yield处
    
    输出结果:
    =================
    --------函数开始执行------
    0
    2
    

    yield cur语句的作用有两点:

    • 每次返回一个值,有点类似于return语句
    • 冻结执行,程序每次执行到yield语句时就会被暂停

    在程序被yield语句冻结之后,当程序再次调用next()函数获取生成器的下一个值时,程序才会继续往下执行。
    需要指出的是,调用包含yield语句的函数并不会立即执行,它只是返回一个生成器。只有当程序通过next()函数调用生成器或遍历生成器时,函数才会真正执行。
    程序可使用list()函数将生成器生成的所有值转换成列表,也可使用tuple()函数将生成器生成的所有制转换成元组。

    def test(val, step):
        print("--------函数开始执行------")
        cur = 0
        # 遍历0~val
        for i in range(val):
            # cur添加i*step
            cur += i * step
            yield cur
    #        print(cur, end=' ')
    # 执行函数,返回生成器
    t = test(10, 2)
    print('=================')
    # 获取生成器的第一个值
    print(next(t)) # 0,生成器“冻结”在yield处
    print(next(t)) # 2,生成器再次“冻结”在yield处
    
    # 再次创建生成器
    t = test(10, 1)
    # 将生成器转换成列表
    print(list(t))
    # 再次创建生成器
    t = test(10, 3)
    # 将生成器转换成列表
    print(tuple(t))
    
    
    输出结果:
    
    --------函数开始执行------
    0
    2
    --------函数开始执行------
    [0, 1, 3, 6, 10, 15, 21, 28, 36, 45]
    --------函数开始执行------
    (0, 3, 9, 18, 30, 45, 63, 84, 108, 135)
    

    生成器是Python的一个特色功能,在其他语言中往往没有对应的机制,因此很多Python开发者对生成器机制不甚了解。但实际上生成器是一种非常优秀的机制,以我们实际开发的经验来看,使用生成器至少有以下几个优势。

    • 当使用生成器来生成多个数据时,程序是按需获取数据的,它不会一开始就把所有数据都生成出来,而是每次调用next()获取下一个数据时,生成器才会执行一次,因此可以减少代码的执行次数。比如前面介绍的示例,程序不会一开始就把生成器函数中的循环都执行完成,而是每次调用next()时才执行一次循环体。
    • 当函数需要返回多个数据时,如果不使用生成器,程序就需要使用列表或元组来收集函数返回的多个值,当函数要返回的数据量较大时,这些列表、元组会带来一定的内存开销;如果使用生成器就不存在这个问题,生成器可以按需、逐个返回数据。
    • 使用生成器的代码更加简洁。

    生成器的方法

    外部程序通过send()方法发送数据
    生成器函数使用yield语句接收收据
    close(): 该方法用于停止生成器
    throw():该方法用于在生成器内部(yield吾句内)引发一个异常 
    程序调用 stop()方法关闭生成器之后 ,程序就不能再去获取生成器的下一个值,否则就会引发异常

    def square_gen(val):
        i = 0
        out_val = None
        while True:
            # 使用yield语句生成值,使用out_val接收send()方法发送的参数值
            out_val = (yield out_val ** 2) if out_val is not None else (yield i ** 2)
            # 如果程序使用send()方法获取下一个值,out_val会获取send()方法的参数
            if out_val is not None : print("====%d" % out_val)
            i += 1
    
    sg = square_gen(5)
    # 第一次调用send()方法获取值,只能传入None作为参数
    print(sg.send(None))  # 0
    print(next(sg))  # 1
    print('--------------')
    # 调用send()方法获取生成器的下一个值,参数9会被发送给生成器
    print(sg.send(9))  # 81
    # 再次调用next()函数获取生成器的下一个值
    print(next(sg))  # 9
    
    # 让生成器引发异常
    #sg.throw(ValueError)
    
    # 关闭生成器
    sg.close()
    print(next(sg)) # StopIteration
    
    
    输出结果:
    0
    1
    --------------
    ====9
    81
    9
    Traceback (most recent call last):
    
      File "C:Userszz.spyder-py3	emp.py", line 26, in <module>
        print(next(sg)) # StopIteration
    
    StopIteration
    

    运算符重载的特殊方法

    Python 允许为自定义类提供特殊方法,这样就可以让自定义类的对象也支持各种运算符的运算。

    与数值运算符相关的特殊方法

    class Rectangle:
        def __init__(self, width, height):
            self.width = width
            self.height = height
        # 定义setSize()函数
        def setSize (self , size):
            self.width, self.height = size
        # 定义getSize()函数
        def getSize (self):
            return self.width, self.height
        # 使用property定义属性
        size = property(getSize, setSize)
        # 定义__add__方法,该对象可执行+运算
        def __add__(self, other):
            # 要求参与+运算的另一个运算数必须是Rectangle
            if not isinstance(other, Rectangle):
                raise TypeError('+运算要求目标是Rectangle')
            return Rectangle(self.width + other.width, self.height + other.height)
        def __repr__(self):
            return 'Rectangle(width=%g, height=%g)' % (self.width, self.height)
    r1 = Rectangle(4, 5)
    r2 = Rectangle(3, 4)
    # 对两个Rectangle执行加法运算
    r = r1 + r2
    print(r) # Rectangle(width=7, height=9)
    
    
    输出结果:
    Rectangle(width=7, height=9)
    

    支持“+=”运算,示例:

    class Rectangle:
        def __init__(self, width, height):
            self.width = width
            self.height = height
        # 定义setSize()函数
        def setSize (self , size):
            self.width, self.height = size
        # 定义getSize()函数
        def getSize (self):
            return self.width, self.height
        # 使用property定义属性
        size = property(getSize, setSize)
        # 定义__iadd__方法,该对象可支持+=运算
        def __iadd__(self, other):
            # 要求参与+=运算的另一个运算数必须是数值
            if not (isinstance(other, int) or isinstance(other, float)):
                raise TypeError('+=运算要求目标是数值')
            return Rectangle(self.width + other, self.height + other)
        def __repr__(self):
            return 'Rectangle(width=%g, height=%g)' % (self.width, self.height)
    r = Rectangle(4, 5)
    # r有__iadd__方法,因此它支持+=运算
    r += 2
    print(r) # Rectangle(width=6, height=7)
    
    
    输出结果:
    Rectangle(width=6, height=7)
    

    与比较运算法符相关的特殊方法

    class Rectangle:
        def __init__(self, width, height):
            self.width = width
            self.height = height
        # 定义setSize()函数
        def setSize (self , size):
            self.width, self.height = size
        # 定义getSize()函数
        def getSize (self):
            return self.width, self.height
        # 使用property定义属性
        size = property(getSize, setSize)
        # 定义__gt__方法,该对象可支持>和<比较
        def __gt__(self, other):
            # 要求参与>运算的另一个运算数必须是Rectangle
            if not isinstance(other, Rectangle):
                raise TypeError('>运算要求目标是Rectangle')
            return True if self.width * self.height > other.width * other.height else False
        # 定义__eq__方法,该对象可支持==和!=比较
        def __eq__(self, other):
            # 要求参与==运算的另一个运算数必须是Rectangle
            if not isinstance(other, Rectangle):
                raise TypeError('==运算要求目标是Rectangle')
            return True if self.width * self.height == other.width * other.height else False
        # 定义__ge__方法,该对象可支持>=和<=比较
        def __ge__(self, other):
            # 要求参与>=运算的另一个运算数必须是Rectangle
            if not isinstance(other, Rectangle):
                raise TypeError('>=运算要求目标是Rectangle')
            return True if self.width * self.height >= other.width * other.height else False 
        def __repr__(self):
            return 'Rectangle(width=%g, height=%g)' % (self.width, self.height)
    r1 = Rectangle(4, 5)
    r2 = Rectangle(3, 4)
    print(r1 > r2) # True
    print(r1 >= r2) # True
    print(r1 < r2) # False
    print(r1 <= r2) # False
    print(r1 == r2) # False
    print(r1 != r2) # True
    print('------------------')
    r3 = Rectangle(2, 6)
    print(r2 >= r3) # True
    print(r2 > r3) # False
    print(r2 <= r3) # True
    print(r2 < r3) # False
    print(r2 == r3) # True
    print(r2 != r3) # False
    
    
    输出结果:
    True
    True
    False
    False
    False
    True
    ------------------
    True
    False
    True
    False
    True
    False
    

    与单目运算符相关的特殊方法

    class Rectangle:
        def __init__(self, width, height):
            self.width = width
            self.height = height
        # 定义setSize()函数
        def setSize (self , size):
            self.width, self.height = size
        # 定义getSize()函数
        def getSize (self):
            return self.width, self.height
        # 使用property定义属性
        size = property(getSize, setSize)
        # 定义__neg__方法,该对象可执行求负(-)运算
        def __neg__(self):
            self.width, self.height = self.height, self.width
        def __repr__(self):
            return 'Rectangle(width=%g, height=%g)' % (self.width, self.height)
    r = Rectangle(4, 5)
    # 对Rectangle执行求负运算
    -r
    print(r) # Rectangle(width=5, height=4)
    
    
    输出结果:
    Rectangle(width=5, height=4)
    

    与类型转换相关的特殊方法

    Python 提供了 str ()、 int()、 float()、 complex ()等函数(其实是这些类的构造器)将其他类型的对象转换成字符串、整数、浮点数和复数,这些转换同样也是由特殊方法在底层提供支持的。

    class Rectangle:
        def __init__(self, width, height):
            self.width = width
            self.height = height
        # 定义setSize()函数
        def setSize (self , size):
            self.width, self.height = size
        # 定义getSize()函数
        def getSize (self):
            return self.width, self.height
        # 使用property定义属性
        size = property(getSize, setSize)
        # 定义__int__方法,程序可调用int()函数将该对象转成整数
        def __int__(self):
            return int(self.width * self.height)
        def __repr__(self):
            return 'Rectangle(width=%g, height=%g)' % (self.width, self.height)
    r = Rectangle(4, 5)
    print(int(r)) # 20
    

    与常见的内建函数相关的特殊方法

    Python 还提供了一些常见的内建函数,当使用这些内建的函数处理对象时,实际上也是由特殊方法来提供支持的 。

    class Rectangle:
        def __init__(self, width, height):
            self.width = width
            self.height = height
        # 定义setSize()函数
        def setSize (self , size):
            self.width, self.height = size
        # 定义getSize()函数
        def getSize (self):
            return self.width, self.height
        # 使用property定义属性
        size = property(getSize, setSize)
        # 定义__round__方法,程序可调用round()函数将该对象执行四舍五入取整
        def __round__(self, ndigits=0):
            self.width, self.height = round(self.width, ndigits), round(self.height, ndigits)
            return self
        def __repr__(self):
            return 'Rectangle(width=%g, height=%g)' % (self.width, self.height)
    r = Rectangle(4.13, 5.56)
    # 对Rectangle对象执行四舍五入取整
    result = round(r, 1)
    print(r) # Rectangle(width=4.1, height=5.6)
    print(result) # Rectangle(width=4.1, height=5.6)
    
    输出结果:
    Rectangle(width=4.1, height=5.6)
    Rectangle(width=4.1, height=5.6)
    

    原文来源于我的语雀,我的微信公众号:细细研磨

  • 相关阅读:
    JavaScript对原始数据类型的拆装箱操作
    Javascript继承(原始写法,非es6 class)
    动态作用域与词法作用域
    自行车的保养
    探索JS引擎工作原理 (转)
    C语言提高 (7) 第七天 回调函数 预处理函数DEBUG 动态链接库
    C语言提高 (6) 第六天 文件(续) 链表的操作
    C语言提高 (5) 第五天 结构体,结构体对齐 文件
    C语言提高 (4) 第四天 数组与数组作为参数时的数组指针
    C语言提高 (3) 第三天 二级指针的三种模型 栈上指针数组、栈上二维数组、堆上开辟空间
  • 原文地址:https://www.cnblogs.com/onelikeone/p/15162992.html
Copyright © 2011-2022 走看看