Python3面向对象-运算符重载

目录

1:运算符重载介绍

2:Python3中常见运算符重载方法

3:运算符重载方法示例

3.1:索引和分片:__getitem__和__setitem__

3.2:返回数值:__index__   (__index__不是索引)

3.3:可迭代对象:__iter__,__next__

3.3.1:单遍迭代

3.3.2:多遍迭代

3.3.3:__iter__ 加 yield 实现多遍迭代

3.4:属性访问:__getattr__,__getattribute__和__setattr__

3.4.1 __getattr__,__getattribute__

3.4.2__setattr__

3.4.3 __getattr__ 和 __setattr__总结

3.5:调用表达式:__call__

3.6:字符串显示:__str__ 和 __repr__

3.7:比较运算



1:运算符重载介绍

运算符重载,就是在某个类的方法中,拦截其内置的操作(比如:+,-,*,/,比较,属性访问,等等),使其实例的行为接近内置类型。

当类的实例出现在内置操作中时(比如:两个实例相加  +),Python会自动调用你的方法(比如:你重载的__add__方法),并且你的方法的返回值会作为相应操作的结果。

Python3中的运算符重载:
运算符重载让类拦截常规的Python操作。
类可以重载所有Python表达式运算符。
类也可以重载打印,函数调用,属性访问等内置运算。
重载是通过在一个类中提供特殊名称的方法来实现的。

2:Python3中常见运算符重载方法

方法名实现功能触发调用的形式
__new__创建在__init__之前的对象创建
__init__构造函数对象创建:X = Class(args)
__del__析构函数X对象回收
__add__'+'运算符X+Y
X += Y  (如果存在__iadd__,则使用重载后的__iadd__)
__or__'|'运算符(按位或)X | Y
X |= Y
 
__repr__,__str__打印,转换print(X),repr(X),str(X)
__call__函数调用X(*args,**kargs)
__getattr__属性访问X.undefined
__setattr__属性赋值X.any=value
__delattr__属性删除del X.any
__getattribute__属性访问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(F,X)

其他

__contains__成员关系测试item in X(X为任意可迭代对象)
__index__整数值转换

hex(X)

bin(X)

oct(X)

o[X],o[X:]

__enter__,__exit__上下文管理器with  obj as var:

__get__,

__set__,

__delete__

描述符属性

X.attr

X.attr = value

del X.attr

3:运算符重载方法示例

3.1:索引和分片:__getitem__和__setitem__

在实例进行 类似 X[2] 这种操作时会调用__getitem__方法;

在实例进行 类似 X[2] = value  这种操作时会调用__setitem__方法;
索引:

# encoding=gbk

class Test:
    def __getitem__(self, item):
        print('item:',item)
        return item**3  # 返回 x 的三次方

    def __setitem__(self, key, value):
        print(key,value)

t = Test()
print(t[2])  # 会调用__getitem__函数,   返回2 的三次方
print(t[3])  # 会调用__getitem__函数,   返回3 的三次方

t[3] = 100 # 会调用__setitem__

分片:

# encoding=gbk

class Test:
    def __getitem__(self, item):
        print('item:',item)
        if isinstance(item,int):
            return item**3  # 返回 x 的三次方
        else:
            print('slicing',item.start,item.stop,item.step)
            return [x**3 for x in range(item.start,item.stop,item.step)]


    def __setitem__(self, key, value):
        print(key,value)
        # do something

t = Test()
# 索引:
print(t[2])  # 会调用__getitem__函数,   返回2 的三次方
print(t[3])  # 会调用__getitem__函数,   返回3 的三次方
t[3] = 100 # 会调用__setitem__

print('*'*60)
# 分片:
print(t[2:10:2]) # 传入的是分片对象
t[2:5] = 100

3.2:返回数值:__index__   (__index__不是索引)

在需要整型数字的上下文中,会调用__index__函数,__index__会为实例返回一个整数值。比如:调用函数hex(X),bin(X)时,会去调用X的__index__方法:

# encoding=gbk

class Test:
    def __index__(self):
        return 100

X = Test()
print(hex(X))
print(bin(X))
print(oct(X))

3.3:可迭代对象:__iter__,__next__

如果要使自己定义的类的对象是可迭代的,那么就必须使这个类支持迭代协议,即重载__iter__,__next__方法。

迭代协议:(包括两种对象)
     可迭代对象(Iterable):里面包含了__iter__(); 可迭代对象X, 通过调用 I = iter(X)  可返回一个迭代器对象,再调用next(I) 就可以迭代X中的元素。
     迭代器对象(Iterator):里面包含了__iter__() 和 __next__()

迭代过程:(for循环,等迭代中默认的操作)
        首先调用 iter函数: I  = iter(X);   调用的是X.__iter__()

        然后对返回对象I调用next:next(I); 调用的是 I.__next__(),直到迭代完成。

3.3.1:单遍迭代

即只能迭代一次:

# encoding=gbk

class Fibonacci:
    def __init__(self, n):
        self.a = 0
        self.b = 1
        self.max_cnt = n

    def __iter__(self):
        return self

    def __next__(self):
        self.a, self.b = self.b, self.a + self.b
        if self.a > self.max_cnt:
            raise StopIteration
        return self.a



fib = Fibonacci(5)
print(fib)

print('1:'+'*' * 30)
I = iter(fib)  # 调用 fib.__iter__(),(返回的是self,及fib)
print(next(I))  # 调用的是 fib.__next__(),fib对象中的a,b属性值会改变。
print(next(I))  # 调用的是 fib.__next__()

print('1:'+'*' * 30)

# for循环:首先调用的是I = iter(fib),此处返回的是self即fib,再调用next(I),即fib.__next__(),此时其值已经取完2个了,因此从第3个开始取。
for x in fib:
    print(x)
print('2:'+'*' * 30)
# 此处与上面的循环一样,但是fib.__next__()已经把数据取完了,故这里不会有输出!
for x in fib:
    print(x)


print('3:'+'*' * 30)

#
i = iter(fib)  # 返回self,即fib
for ii in i:  #  与上面的 for x in fib 一样,不会再输出!
    print(ii)

3.3.2:多遍迭代

即可以多次迭代使用:

# encoding=gbk

class Fibonacci:
    def __init__(self, n):
        self.a = 0
        self.b = 1
        self.max_cnt = n

    def __iter__(self):
        return FibonacciIter(self.a,self.b,self.max_cnt)

class FibonacciIter:
    def __init__(self,a,b,max_cnt):
        self.a = a
        self.b = b
        self.max_cnt = max_cnt

    def __next__(self):
        self.a, self.b = self.b, self.a + self.b
        if self.a > self.max_cnt:
            raise StopIteration
        return self.a

fib = Fibonacci(5)

I = iter(fib)
print(next(I))  # 调用的是FibonacciIter对象中的__next__方法,fib对象中的a,b属性没有任何变化。
print(next(I))
print(next(I))
print(next(I))
print(next(I))  # 迭代完了
# print(next(I))   # 上一步迭代完了,再次调用next(I)会抛出异常,

print('1:'+'*' * 30)

print(fib)
print('1:'+'*' * 30)
# for循环(默认调用):首先调用一次 I = iter(fib) 即fib.__iter__(),返回一个重新创建FibonacciIter对象, ;
#                      然后再调用 next(I),即I.__next__() (也就是FibonacciIter类中的__next__()函数),直到迭代完。
for x in fib:
    print(x)
print('2:'+'*' * 30)
# 与上面for循环调用过程一样。
for x in fib:
    print(x)

print('3:'+'*' * 30)

# i = iter(fib)
# for ii in i:  # 这里会报错,因为iter(fib)返回FibonacciIter的实例 i ,
                # 而for循环首先会调用 iter(i) 即 调用FibonacciIter中的__iter__函数,而FibonacciIter类中没有重载此函数;
#     print(ii)

3.3.3:__iter__ 加 yield 实现多遍迭代

# encoding=gbk

class Test:
    def __init__(self,start,stop):
        self.start = start
        self.stop = stop

    def __iter__(self):
        print(self.start,self.stop+1)
        for value in range(self.start,self.stop+1):
            yield value**2

t = Test(1,3)
# 说明:for循环中,首先调用 I = iter(t),即调用的是t.__iter__(),在__iter__函数中有yield语句,
#      yield语句会自动创建一个包含 __next__ 方法的类,并返回它的实例,
#      然后会调用 next(I),I 为yield自动创建类的实例
for i in t:
    print(i)
print('*'*40)
for i in t:
    print(i)

3.4:属性访问:__getattr__,__getattribute__和__setattr__

3.4.1 __getattr__,__getattribute__

__getattr__ 会拦截未定义的属性,即在使用点号访问属性时(如:X.属性) ,如果Python通过其继承树搜索过程中没有找到这个属性,那么就会自动调用__getattr__方法。

__getattribute__ 会拦截所有属性。

# encoding=gbk

class Test:
    aa = 0
    def __init__(self):
        self.age = 100
    def __getattr__(self, item):
        print('in __getattr__:',item)



t = Test()
print('1:' ,t.__dict__)
# 属性引用,属性找不到时,就会调用__getattr__方法
print(t.aa)  # 在类中存在类属性 aa,
print('2:'+ '*' * 30)

print(t.bb)  # t.__dict__ 中不存在属性 bb,其父类中也没有属性bb,故会调用__getattr__方法
print('3:'+ '*' * 30)
print(t.age)  #  存在实例属性age,不会调用__getattr__方法
t.age = 200
print('4:' ,t.__dict__)

"""
1: {'age': 100}
0
2:******************************
in __getattr__: bb
None
3:******************************
100
4: {'age': 200}
"""

3.4.2__setattr__

__setattr__:会拦截所有的属性赋值

如果定义或者继承了__setattr__方法,那么 self.attr = value,将会变成  self.__setattr__('attr',value)

这里要注意的是 如果在__setattr__方法中有使用 self.attr = value 的赋值形式,那么__setattr__将会进入死循环,因为self.attr = value 的赋值形式会调用self.__setattr__('attr',value),而__setattr__方法中又使用self.attr = value进行赋值,从而进入一个循环。

# encoding=gbk

class Test:

    def __init__(self):
        #  构造函数中对 self.age  进行赋值,如果继承了__setattr__方法,
        #  就会把self.age = 100   变成 self.__setattr__('age',100)
        self.age = 100
    def __getattr__(self, item):
        print('in __getattr__:',item)

    def __setattr__(self, key, value):
        print('in __setattr__:',key,value)
        # self.aa = 100  # 这样赋值会导致死循环,因为 self.aa = 100  会变成 self.__setattr__('aa',100),而后者又调用了前者
        if key != 'age':  # 拦截 age属性
            self.__dict__[key] = value



t = Test()
print('1:' + '*'*30)
print(t.__dict__)   #  此处输出为{};虽然在构造函数中有self.age = 100赋值,但是在 __setattr__方法中把它过滤掉啦
print('2:' + '*'*30)
print(t.age)  # 由于 age属性被拦截掉了,故访问t.age会调用 __getattr__方法
print('3:' + '*'*30)
t.age = 200 # 会把 age给拦截掉
t.name = 'ixusy' #  不存在name属性,因此会调用__setattr__,在__setattr__方法中把 name属性 添加到属性字典__dict__中,
                  #  后面就可以通过使用t.name进行访问。
print('4:' + '*'*30)
print(t.__dict__)
print('5:' + '*'*30)


"""
输出结果:
in __setattr__: age 100
1:******************************
{}
2:******************************
in __getattr__: age
None
3:******************************
in __setattr__: age 200
in __setattr__: name ixusy
4:******************************
{'name': 'ixusy'}
5:******************************
"""

3.4.3 __getattr__ 和 __setattr__总结

__getattr__ :拦截不存在的属性引用!

__setattr__:拦截所有的属性赋值,当心死循环!

3.5:调用表达式:__call__

在实例上执行函数调用表达式,就会自动调用__call__函数

# encoding=gbk

class Test:
    def __call__(self, *args, **kwargs):
        print('call:',args,kwargs)

t = Test()
t(1,2,3)
t(1,2,3,b=22)
# 传递参数,需要符合函数传递参数的规则
# t(1,a=2,3,b=22)  # 这样传递会报错

3.6:字符串显示:__str__ 和 __repr__

__str__ 和 __repr__ 都是用于显示字符串,只不过是他们的使用场景不同而已。

__str__  :打印操作(print),内置函数str调用,会优先调用__str__ ,如果没有重载__str__,就会去调用__repr__;

__repr__:用于所有其他场景:包括交互式命令行,repr函数,嵌套显示,以及没有可用__str__时,print和str的调用。

__repr__  可用于任何地方,__str__用于print 和 str函数。

3.7:比较运算

# encoding=gbk

class Person:
    def __init__(self,name,age,height):
        self.name = name
        self.age = age
        self.height = height

    #  比较规则可以自行定义,
    # 下面规则为:
    # 1:年龄小的 比较结果为小
    # 2:年龄相等的,比较身高:身高小,结果为小
    # 3:其他情况返回False
    def __lt__(self, other):
        if self.age < other.age:
            return  True
        elif self.age ==  other.age:
            return  self.height < other.height
        else:
            return False

    """
    还可以重载:
        __gt__
        __le__
        __ge__
        __eq__
        __ne__
        
        需要注意的是 p1 == p2,并不表示p1 != p2, 具体要看你怎么实现 __eq__,__ne__方法,
        实际中尽可能使得__eq__,__ne__方法的实现符合正常的逻辑。
    """


p1 = Person('ixusy88',18,188)
p2 = Person('i',18,180)
print(p1 < p2)  # False

p1 = Person('ixusy88',18,177)
p2 = Person('i',18,180)
print(p1 < p2)  # True

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 运算符重载是指在类中定义特殊方法,使得该类的实例对象可以像内置类型一样进行运算。Python中支持运算符重载,可以通过定义特殊方法来实现。例如,可以通过定义__add__方法来重载加号运算符,使得两个对象可以相加。 在Python中,运算符重载的方法名都以双下划线开头和结尾,例如__add__、__sub__等。这些方法可以重载加减乘除等运算符,也可以重载比较运算符、逻辑运算符等。 运算符重载可以使得代码更加简洁、易读,同时也可以提高代码的可维护性和可扩展性。但是需要注意的是,过度使用运算符重载可能会导致代码难以理解和维护,因此需要谨慎使用。 ### 回答2: Python是一门支持面向对象(OOP)编程的语言,它提供了很多方便的机制来帮助我们更好地定义和使用对象。其中之一就是运算符重载运算符重载是指在类中定义特殊方法来重载Python内置运算符的行为。比如,我们可以重载加号(+)运算符,使得两个对象相加时返回我们自己定义的结果。这样就可以让我们的对象像普通类型(如int和float)一样使用加号。 以下是使用方法: ```python class MyClass: def __init__(self, value): self.value = value def __add__(self, other): # 自己定义的加法 return MyClass(self.value + other.value) a = MyClass(1) b = MyClass(2) c = a + b # c的值是一个MyClass对象,其value属性为3 ``` 在上述代码中,`__add__`方法是用来重载`+`运算符的方法。当两个MyClass对象相加时,它们的`__add__`方法会被调用,然后返回自己定义的结果。 还有其他运算符也可以被重载,比如减号`-`,乘号`*`,等等。此外,Python还提供了一些通用的重载方法,比如`__eq__`用来实现相等比较,`__str__`用来定义对象的字符串表示等等。 需要注意的是,运算符重载虽然可以让我们的代码更加方便易读,但也容易被滥用。如果重载的运算符和普通的类型行为不一致,就容易引起混淆和错误。因此,在使用运算符重载时需要做好注释和测试,确保代码的正确性和可读性。 总之,Python的面向对象编程支持运算符重载,这为我们提供了更加灵活和方便的对象定义和使用方式。但重载运算符也需要谨慎使用,化简代码的同时不要失去代码的可读性和正确性。 ### 回答3: 运算符重载是 Python 面向对象编程中的一个重要概念,它是指将已有的运算符赋予新的功能,使之具有更广泛的适用性。Python 提供了许多运算符,如加减乘除、比较、逻辑运算符等,这些运算符可以用于不同的数据类型,如整数、浮点数、字符串、列表等。运算符重载可以自定义这些运算符的行为,使之适用于用户自己定义的类。 在 Python 中,运算符的重载使用特殊的方法来实现。这些方法的名称都以 "__" 开头和结尾,例如 "__add__" 就是用来实现加法运算符重载的方法。这些方法通常会被定义在类中,用来定义类的运算符行为。在使用运算符时,Python 会自动调用对应的运算符重载方法,以便完成需要的运算。 以下是一些常用的运算符重载方法: __add__(self, other):重载加法运算符“+”,用来添加两个对象的值。 __sub__(self, other):重载减法运算符“-”,用来计算两个对象的差。 __mul__(self, other):重载乘法运算符“*”,用来计算两个对象的积。 __div__(self, other):重载除法运算符“/”,用来计算两个对象的商。 __eq__(self, other):重载相等运算符“==”,用来比较两个对象是否相等。 __ne__(self, other):重载不等运算符“!=”,用来比较两个对象是否不相等。 __lt__(self, other):重载小于运算符“<”,用来比较两个对象的大小。 __le__(self, other):重载小于等于运算符“<=”,用来比较两个对象的大小。 __gt__(self, other):重载大于运算符“>”,用来比较两个对象的大小。 __ge__(self, other):重载大于等于运算符“>=”,用来比较两个对象的大小。 使用运算符重载可以方便地扩展 Python 的运算符行为,使之适用于用户自定义的类。但是,使用运算符重载也需要注意一些问题,如运算符的优先级、运算符的结合性等。在使用运算符重载时,需要谨慎思考,以确保运算符行为符合预期。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值