运算符重载

Posted 2019zjp

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了运算符重载相关的知识,希望对你有一定的参考价值。

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

 

以上是关于运算符重载的主要内容,如果未能解决你的问题,请参考以下文章

C++提高:运算符重载

导航架构片段重载问题

导航架构片段重载问题

Javascript实现运算符重载

GroovyGroovy 运算符重载 ( 运算符重载 | 运算符重载对应方法 )

运算符 + 重载 C++