• 运算符重载


    1.基础知识

    python中所谓运算符重载,其实质为在类中定义从新定义其内在的函数,该类函数的显著特征表现为以"__"开始和结束。因此,当实例化该类时,会调用你从新定义的方法。

    2.常见的运算符重载方法

    __init__ (构造函数);触发方式:X=classname(args),实例化类,传参。
    
    __del__(析构函数);触发方式:X对象收回。
    
    __add__(‘+’运算符);触发方式:X+Y,X+=Y
    
    __or__(‘|’运算符);触发方式:X|Y,X=|Y
    
    __repr__,__str__(打印、转换);触发方式:print(X)、repr(X)、str(X)。
    
    __call__(函数调用);触发方式:X(args)。
    
    __getattr__(访问属性);触发方式:X.undefinded。
    
    __setattr__(属性赋值);触发方式:X.any=value。
    
    __delattr__(删除属性);触发方式:del X.any。
    
    __getitem__(索引、分片、迭代);触发方式:X[key],X[i:j]、没有重载__iter__方法的for循环和其他迭代操作。
    
    __setitem__(索引赋值和切片赋值);触发方式:X[key]=value,X[i:j]=iterable。
    
    __delitem__(索引删除和分片删除);触发方式:del X[key]、del X[i:j]。
    
    __len__(长度);触发方式:len(X),没有重载__bool__方法的真值测试。
    
    __bool__(布尔测试);触发方式:bool(X)。
    
    __lt____gt____le____ge____eq____ne__(比较);触发方式:X<Y、X>Y、X<=Y、X>=Y、X==Y、X!=Y。
    
    __radd__(右侧‘+’);触发方式:Other+X。
    
    __iadd__(原位置‘+=’操作);触发方式:X+=Y,若没有重载该方法,则使用__add__。
    
    __iter____next__(迭代上下文);触发方式:I=iter(X),next(I);for循环,没有重载__contains()方法的in操作、所有的推导表达式、map(func,iterable)等。
    
    __contains__(成员关系测试);触发方式:item in X (测试item是否在X中)。
    
    __index__(整数值转化);触发方式:hex(x),bin(x),oct(x),O(x)。
    
    __enter____exit__(上下文管理器);触发方式:with obj as var:。
    
    __get____set____delete__(描述符属性);触发方式:X.attr、X.attr=value、del X.attr。
    
    __new__(创建);触发方式:在__init__前创建。
    View Code

    2.1构造函数

    class Number:
        def __init__(self,value):
            self.data=value
    num=Number(3)#构造函数主要用于为类传参
    print(num.data)
    View Code

    2.2索引和切片:__getitem__、__setitem__ 

    如果定义一个类重载了__getitem__,该方法就会自动被调用并进行实例的索引运算,该方法就像模拟列表的下标访问元素类似,即实例X[i],将X作为第一个参数传入,i作为第二个参数传入。此外该方法还可以用于切片表达式。

    在列表中,如L=[1,2,3,4,5]中,切片L[0:1],其中“0:1”传入其实质就是一个切片对象。slice(0,1),该切片对象有3个常见属性,start、stop、step。

    __setitem__赋值传入3个参数,self默认,key和value。key相当于X[i]=10,中的i,value相当于10。

    for语句通过使用从0到更大索引值,重复对序列进行索引运算,直到检测到超出边界的IndexError异常。因此,该方法可以用于重载迭代方式。若重载了该方法for循环每次都会通过连续的更高的偏移量来调用__getitem__方法。

    其中in、列表推导、内置函数map、列表和元组赋值运算以及类型构造都会调用该方法。

    class Indexer:
        data=[1,2,3,4,5]
        def __getitem__(self, item):
            if isinstance(item,int):
                print('indexing:',item)  #若传入为一个整数则返回其下标
            else:          #传统入切片对象返回切片列表和start、stop、step值
                print('slicing',item.start,item.stop,item.step)
                return self.data[item]
    
        def __setitem__(self, index, value):#为切片赋值
            self.data[index]=value
            print(self.data)
    
    X=Indexer()  #实例化对象
    print(X[0:2:1])
    X[0]=10
    View Code
    class StepperIndex:
        def __getitem__(self, i):
            return self.data[i]
    X=StepperIndex()
    X.data='spam'
    for i in X:
        print(i)
    print('p' in X)#in关系测试
    print([c for c in X])#列表推导
    print(list(map(str.upper,X))) #map内置函数
    (a,b,c,d)=X
    list(X)
    tuple(X)
    View Code

    2.3__index__方法在使用时会返回一个整数值

    class C:
        def __index__(self):
            return 255
    X=C()
    print(hex(X))
    print(bin(X))
    print(oct(X))
    View Code

    2.4可迭代对象:__iter__和__next__

    尽管__getitem__可以用做迭代,但在python中所有迭代会首先寻找__iter__方法。即若类中重载了__iter__就首先选择该类方法来实现迭代。从技术的角度,迭代上下文是通过将一个可迭代对象传入到内置函数iter中,并调用__iter__方法实现,该方法返回的是一个迭代器对象,python会重复调用__next__方法产生元素。同样该方法也可以进行成员关系in、类型构造、序列赋值运算等,与__getitem__不同的是一个类的__iter__被设计成单次遍历。

    class Squares:
        def __init__(self,start,stop):
            self.value=start-1
            self.stop=stop
    
        def __iter__(self):
            return self
    
        def __next__(self):
            if self.value==self.stop:
                raise StopIteration
            self.value+=1
            return self.value**2
    
    if __name__=='__main__':
        for i in Squares(1,5):
            print(i)
        X=Squares(1,3)
        I=iter(X)
        print(next(I))
        print(next(I))
        print(next(I))
        print([n for n in X]) #单次迭代,返回空[]
    View Code

    当然,在定义类是也可以通过一些方法实现多次迭代,要想实现多次迭代效果,__iter__只需替换为一个新的状态对象,而不是像上例中返回self。

    class SkipObject:
        def __init__(self,wrapped):
            self.wrapped=wrapped
    
        def __iter__(self):
            return SkipIterator(self.wrapped)
    
    class SkipIterator:
        def __init__(self,wrapped):
            self.wrapped=wrapped
            self.offset=0
    
        def __next__(self):
            if self.offset>=len(self.wrapped):
                raise StopIteration
            else:
                item=self.wrapped[self.offset]
                self.offset+=2
                return item
    
    if __name__=='__main__':
        skipper=SkipObject('abcdef')
        I=iter(skipper)
        print(next(I),next(I),next(I))
        print([c for c in skipper])  #['a', 'c', 'e']支持多次迭代
    View Code

    2.5成员关系:__contains__,__iter__,__getitem__。

    在in测试,__contains__优先级最高,其次__iter__,最后__getitem__。

    class Iters:
        def __init__(self,values):
            self.data=values
    
        def __getitem__(self, item):
            print('get[%s]:' %item,end='')
            return self.data[item]
    
        def __iter__(self):
            print('iter=>',end='')
            self.ix=0
            return self
    
        def __next__(self):
            print('next:',end='')
            if self.ix==len(self.data):raise StopIteration
            item=self.data[self.ix]
            self.ix+=1
            return item
        def __contains__(self, x):
            print('contains:',end='')
            return x in self.data
    
    if __name__=='__main__':
        X=Iters([1,2,3,4,5])
        print(3 in X)   #contains:True
        for i in X:     #iter=>next:1| next:2| next:3| next:4| next:5| next:
            print(i,end='| ')
        print('
    ')
        print([i**2 for i in X])  #iter=>next:next:next:next:next:next:[1, 4, 9, 16, 25]
        print(list(map(bin,X)))    #iter=>next:next:next:next:next:next:['0b1', '0b10', '0b11', '0b100', '0b101']
        I=iter(X)
        while True:
            try:
                print(next(I),end='@') #iter=>next:1@next:2@next:3@next:4@next:5@next:
            except StopIteration:
                break
    View Code

    2.6属性访问__getattr__、__setattr__,

    __getattr__方法用于属性访问。通俗来说就是对象的点访问操作。如:X.age。当对一个未定义的属性名访问(即没有在类中定义该属性)时,会调用该方法。

    __setattr__会拦截默认的属性赋值方法,如果定义或继承了该方法self.attr=value会变成self.__setattr__('attr',value)。允许你的类捕获对属性的改变。但直接使用时要注意。在__setattr__中对任何self属性赋值,都会再次调用__setattr__,这潜在地导致了无穷递归循环。可以通过实例属性的赋值转换为对属性字典的赋值来避免无限循环。还可以通过调用把任意一个属性赋值传递给一个更高级的父类;object.__setattr__(self,attr,value)。

    __delattr__将传入属性删除,和__setattr__属性一样,要通过__dict__或父类来避免无穷递归调用

    class Empty:
        def __getattr__(self, attrname):
            if attrname=='age':
                return 40
            else:
                raise AttributeError(attrname)
    
        def __setattr__(self, attr, value):
            if attr=='name':
                object.__setattr__(self,attr,value)
                self.__dict__[attr]=value
    
            else:
                raise AttributeError(attr+' not allowed')
    
        def __delattr__(self, item):
            if  item=='name':
                self.__dict__[item]=None
    X=Empty()
    print(X.age)
    
    X.name='hello'
    print(X.name)
    
    del X.name
    print(X.name) #None
    View Code

    2.7字符串显示:__repr__、__str__

    简单理解,定制该两类对象可以改变默认print值。

    __str__:会首先被打印操作和str内置函数尝试。通常应当返回一个用户友好的显示。

    __repr__:用于所有场景,包括交互式命令行、repr函数、嵌套显示,以及没有重写__str__的打印和str调用。通常应该返回一个编码字符串,可以用来从新创造对象,或者给开发者一个详细显示。

    两个方法都必须返回字符串。 其他类型不会被自动转化;

    __str__用户友好显示只会应用在对象出现在打印操作时,内嵌在更大对象仍然使用__repr__或默认方式;

    2.8右侧加法和原位置加法:__radd__、__iadd__

    一般条件下,__add__方法不支持实例对象写在+运算符右侧。如下所示

    class Adder:
        def __init__(self,value=0):
            self.value=value
    
        def __add__(self, other):
            return self.value+other
    
    x=Adder(5)
    print(x+2)
    print(2+x) #TypeError: unsupported operand type(s) for +: 'int' and 'Adder'
    View Code

    __radd__:只有当+右侧是实例对象且左侧不是实例对象时,才会调用__radd__方法,在其他所有情况下都是调用__add__

    class Commuter1:
        def __init__(self,val):
            self.val=val
    
        def __add__(self, other):
            print('add',self.val,other)
            return other+self.val
    
        def __radd__(self, other):
            print('radd',self.val,other)
            return other+self.val
    
    x=Commuter1(88)
    y=Commuter1(99)
    # print(x+1)
    print(1+x)
    print(x+y)
    View Code

    在__radd__中重用__add__:

    为了在运算中没有位置限制,有时只需从用__add__来实现__radd__。简单来说,有三种实现方式,

    直接调用__add__;

    交换位置并再次使用加法来间接地触发__add__;

    在类的作用域把__radd__=__add__。

    class Communter2:
        def __init__(self,val):
            self.val=val
    
        def __add__(self, other):
            print('add',self.val,other)
            return self.val+other
    
        def __radd__(self, other):
            return self.__add__(other)
    
    class Communter3:
        def __init__(self,val):
            self.val=val
    
        def __add__(self, other):
            print('add',self.val,other)
            return self.val+other
        def __radd__(self, other):
            return self+other
    
    class Communter4:
        def __init__(self,val):
            self.val=val
    
        def __add__(self, other):
            print('add',self.val,other)
            return self.val+other
        __radd__=__add__
    
    x=Communter2(2)
    y=Communter3(2)
    z=Communter4(2)
    print(1+x,2+y,3+z)
    View Code

    为了同时实现+=原位置加法扩展,可以编写一个__iadd__或__add__,如果前者缺省的话就会退而求其次使用后者。

    2.9调用表达式:__call__

    当调用实例时会使用__call__方法,

    class Prod:
        def __init__(self,value):
            self.value=value
    
        def __call__(self, other):
            return self.value *other
    
    x=Prod(2)
    print(x(3))
    View Code
  • 相关阅读:
    第12课
    第11课
    第6课
    第5课
    ubuntu apache 通过端口新建多个站点
    phpstudy所需运行库
    ubuntu 修改和配置ip
    Linux Cp命令
    Ubuntu各个版本的镜像下载地址
    ubuntu 虚拟机添加多个站点
  • 原文地址:https://www.cnblogs.com/2019zjp/p/12225799.html
Copyright © 2020-2023  润新知