Python进阶 -- 魔法方法

作者: ChaoesLuol | 来源:发表于2020-04-21 13:45 被阅读0次

    魔法方法及其作用

    在Python中的内置类型都支持一些统一的函数接口,比如len, print, bool等,这些函数是如何实现的呢?当我们建立自己的数据类型时,如何表现的很pythonic,也就是让我们自己的数据类型也能支持这些操作呢?

    在Python中有一些以双下划线开头和结尾的方法,叫做魔法方法,这就是实现这些统一接口的关键。

    例如我们想生成一个Classmate类来保存一个班级的名单,想让len()能够取到里面保存的人数,想让print()能够打印出名单,想让bool()返回名单是否为空,那么如何组织我们的类,让我们可以像使用python内置类型一样使用它呢?

    _no_value = object()
    
    
    class Classmate(object):
        def __init__(self, name_lst=_no_value):
            if name_lst == _no_value:
                self._name_lst = list()
            else:
                self._name_lst = list(name_lst)  # 建立拷贝,防止改动传入数据
    
        def __len__(self):
            return len(self._name_lst)
    
        def __repr__(self):
            return str(self._name_lst)
    
        def __bool__(self):
            return len(self._name_lst) != 0  # 其实可以不写,python会自动调用__len__
    
    
    if __name__ == '__main__':
        c = Classmate(['Xiao Zhang', 'Lao Wang', 'Xiao Zhao'])
        print(c)  # ['Xiao Zhang', 'Lao Wang', 'Xiao Zhao']
        print(bool(c))  # True
        print(len(c))  # 3
    

    在上面的例子中,我们可以看到,尽管Classmate并非python中内置的类,我们仍然可以通过实现魔法方法,将它的使用方式和内置类的使用方式统一起来。

    常用魔法方法总结

    下面总结一下python中常用的魔法方法,并给出使用的例子

    二元操作符

    +   object.__add__(self, other)
    -   object.__sub__(self, other)
    *   object.__mul__(self, other)
    //  object.__floordiv__(self, other)
    /   object.__div__(self, other)
    %   object.__mod__(self, other)
    **  object.__pow__(self, other[, modulo])
    <<  object.__lshift__(self, other)
    >>  object.__rshift__(self, other)
    &   object.__and__(self, other)
    ^   object.__xor__(self, other)
    |   object.__or__(self, other)
    

    我们用一个二维向量的例子来演示其中几个方法的作用:

    class Vector(object):
        def __init__(self, vx, vy):
            self._vx = vx
            self._vy = vy
    
        def __add__(self, other):
            return Vector(self._vx + other.get_x(), self._vy + other.get_y())
    
        def __sub__(self, other):
            return Vector(self._vx - other.get_x(), self._vy - other.get_y())
    
        def __pow__(self, power, modulo=None):
            return Vector(pow(self._vx, power), pow(self._vy, power))
    
        def __repr__(self):
            return f"Vector({self._vx}, {self._vy})"
    
        def get_x(self):
            return self._vx
    
        def get_y(self):
            return self._vy
    
    
    if __name__ == '__main__':
        v1 = Vector(3.0, 5.0)
        v2 = Vector(-1.0, 2.0)
        print(v1 + v2)  # Vector(2.0, 7.0)
        print(v1 - v2)  # Vector(2.0, 7.0)
        print(v1 ** 2)  # Vector(9.0, 25.0)
    

    可以看到在定义了__add__, __sub__, __pow__之后,我们自定义的Vector类也可以像python中内置的int, float类型一样,进行相加、相减、幂次操作了。需要注意的是,在完成二元操作符对应的魔法方法时,我们需要返回一个相同类型的对象,这是因为我们需要考虑使用者进行连续加、连续减等操作的可能性。

    扩展二元操作符

    +=  object.__iadd__(self, other)
    -=  object.__isub__(self, other)
    *=  object.__imul__(self, other)
    /=  object.__idiv__(self, other)
    //= object.__ifloordiv__(self, other)
    %=  object.__imod__(self, other)
    **= object.__ipow__(self, other[, modulo])
    <<= object.__ilshift__(self, other)
    >>= object.__irshift__(self, other)
    &=  object.__iand__(self, other)
    ^=  object.__ixor__(self, other)
    |=  object.__ior__(self, other)
    

    扩展二元操作符的使用方法和二元操作符基本一致:

    class Vector(object):
        def __init__(self, vx, vy):
            self._vx = vx
            self._vy = vy
    
        def __add__(self, other):
            return Vector(self._vx + other.get_x(), self._vy + other.get_y())
    
        def __iadd__(self, other):
            return Vector(self._vx + other.get_x(), self._vy + other.get_y())
    
        def __sub__(self, other):
            return Vector(self._vx - other.get_x(), self._vy - other.get_y())
    
        def __pow__(self, power, modulo=None):
            return Vector(pow(self._vx, power), pow(self._vy, power))
    
        def __repr__(self):
            return f"Vector({self._vx}, {self._vy})"
    
        def get_x(self):
            return self._vx
    
        def get_y(self):
            return self._vy
    
    
    if __name__ == '__main__':
        v1 = Vector(3.0, 5.0)
        v2 = Vector(-1.0, 2.0)
        v1 += v2
        print(v1)  # Vector(2.0, 7.0)
    

    在定义了__iadd__方法之后,+=也可以被用于我们的类了

    一元操作符

    -   object.__neg__(self)
    +   object.__pos__(self)
    abs()   object.__abs__(self)
    ~   object.__invert__(self)
    complex()   object.__complex__(self)
    int()   object.__int__(self)
    long()  object.__long__(self)
    float() object.__float__(self)
    oct()   object.__oct__(self)
    hex()   object.__hex__(self)
    round() object.__round__(self, n)
    floor() object__floor__(self)
    ceil()  object.__ceil__(self)
    trunc() object.__trunc__(self)
    

    比较函数

    <   object.__lt__(self, other)
    <=  object.__le__(self, other)
    ==  object.__eq__(self, other)
    !=  object.__ne__(self, other)
    >=  object.__ge__(self, other)
    >   object.__gt__(self, other)
    

    类的表示与输出

    str()   object.__str__(self) 
    repr()  object.__repr__(self)
    len()   object.__len__(self)
    hash()  object.__hash__(self) 
    bool()  object.__nonzero__(self) 
    dir()   object.__dir__(self)
    sys.getsizeof() object.__sizeof__(self)
    

    __str__方法与__repr__方法

    在类的表示中__str____repr__方法值得一提,这两个方法在一定程度上是有重合的:

    • 他们都提供了将对象转化为某种字符串的方式
    • 当对象没有实现__str__方法时,会用__repr__方法替代

    对于他们的使用方式,可以简单概括为:

    • 如果只想要实现其中之一,那么实现__repr__
    • 如果想要输出的结果可读性更强,那么可以选择去实现__str__

    我们可以看一下下面的例子:

    class Vector(object):
        def __init__(self, vx, vy):
            self._vx = vx
            self._vy = vy
    
        def __repr__(self):
            return f"Repr Vector({self._vx}, {self._vy})"
    
        def __str__(self):
            return f"Str Vector({self._vx}, {self._vy})"
    
    
    if __name__ == '__main__':
        v = Vector(3.0, 5.0)
        print(v)  # Vector({self._vx}, {self._vy})
        print("%s" % v)  # Str Vector(3.0, 5.0)
        print("%r" % v)  # Vector({self._vx}, {self._vy})
        print(str(v))  # Vector({self._vx}, {self._vy})
    

    如果我们注释掉__repr__方法,得到的结果为:

    Str Vector(3.0, 5.0)
    Str Vector(3.0, 5.0)
    <__main__.Vector object at 0x10c4e0eb8>
    Str Vector(3.0, 5.0)
    

    如果我们注释掉__str__方法,得到的结果为:

    Repr Vector(3.0, 5.0)
    Repr Vector(3.0, 5.0)
    Repr Vector(3.0, 5.0)
    Repr Vector(3.0, 5.0)
    

    对比第二行的结果,说明了当没有__str__方法时,会调用__repr__的结果,但是当没有实现__repr__方法时,则会用默认的__repr__输出类似return "%s(%r)" % (self.__class__, self.__dict__)的结果。

    类容器实现

    类容器的实现方法告诉编译器我们的类将执行迭代、调用、索引等行为:

    len()   object.__len__(self)
    self[key]   object.__getitem__(self, key)
    self[key] = value   object.__setitem__(self, key, value)
    从对象取切片 object.__getslice__(self, start, end)
    为切片设置值 object.__setslice__(self, start, end, sequence)
    删除切片 object.__delslice__(self, start, end)
    del[key] object.__delitem__(self, key)
    iter()  object.__iter__(self)
    reversed()  object.__reversed__(self)
    in操作    object.__contains__(self, item)
    字典key不存在时   object.__missing__(self, key)
    

    __getitem__、__setitem__和__delitem__

    这三个方法用于从类容器中用键取值,设置了这三个函数之后,就可以对类容器直接用键来取得、修改和删除里面的键值对。例如:

    class Classmate(object):
        """A demo class which stores some name-age pairs"""
    
        def __init__(self, **kwargs):
            self._info = kwargs
    
        def __str__(self):
            return str(self._info)
    
        def __getitem__(self, item):
            print("__getitem__")
            if item in self._info:
                return self._info[item]
            else:
                print("Name not found")
    
        def __setitem__(self, key, value):
            print("__setitem__")
            self._info[key] = value
    
        def __delitem__(self, key):
            print("__delitem__")
            if key in self._info:
                del self._info[key]
    
    
    c = Classmate(laowang=50, xiaohuang=18, xiaoli=17, laojin=66)
    print(c["laowang"])  # 调用__getitem__
    c["xinren"] = 20  # 调用__setitem__
    del c["xiaohuang"]  # 调用__delitem__
    print(c)
    

    同时,在python3中,将python2对容器进行切片操作的魔法方法__getslice__、__setslice__和__delslice__也整合到了这三个方法当中。如下例:

    class someClass(object):
        """ This is description for the class"""
    
        def __init__(self, startVal, endVal):
            if endVal > startVal:
                self._lst = list(range(startVal, endVal))
            else:
                self._lst = list(range(0, 10))
    
        def __getitem__(self, index):
            print("__getitem__")
            if isinstance(index, slice):
                print(self._lst[index.start:index.stop:index.step])
    
        def __setitem__(self, index, value):
            print("__setitem__")
            if isinstance(index, slice):
                self._lst[index.start:index.stop:index.step] = value
    
        def __delitem__(self, index):
            print("__delitem__")
            if isinstance(index, slice):
                del self._lst[index.start:index.stop:index.step]
    
        def __str__(self):
            return str(self._lst)
    
    
    c = someClass(5, 10)
    print(c)
    print(c[1:3])
    c[1:3] = [10, 11]
    print(c)
    del c[1:3]
    print(c)
    

    相关文章

      网友评论

        本文标题:Python进阶 -- 魔法方法

        本文链接:https://www.haomeiwen.com/subject/kueuihtx.html