operator Overloading
Key concepts:
1. Operator overloading allows the class to intercept regular Python operations.
2. The class can overload all Python expression operators.
3. Classes can also overload the built-in operations such as printing, function invocation, and attribute point number operations.
4. Overloading makes the class instance behave like a built-in type.
5. Overloading is implemented by a class method of a special name.
Operator overloading simply means intercepting the built-in action in a class method--When an instance of the class appears in the built-in operation, Python automatically calls your method, and the return value of your method becomes the result of the corresponding operation.
=======================================================================
Constructors and expressions: __init__ and __sub__
See a simple overloaded example. The following number class provides a way to intercept the instance's constructor (__init__), and there is a method to catch the subtraction expression (__sub__). This particular method is a hook that can be bound to a built-in operation.
>>> class Number:def __init__ (self,start): Self.data = Startdef __sub__ (self,other): Return number (Self.data- Other) >>> X = number (5) >>> x.data5>>> Y = x-2>>> y.data3
=======================================================================
Common operator overloading methods
In a class, there is almost always an overloaded method of a special name for what you can do with built-in objects (for example, integers and lists). The following table lists some of the most common overloaded methods.
Method |
Overload |
Call |
__init__ |
constructor function |
Object creation: X = Class (args) |
__del__ |
Destructors |
X Object retract |
__add__ |
operator + |
If there is no _iadd_,x+y,x+=y |
__or__ |
operator | (bit or) |
If there is no _ior_,x| Y,x|=y |
__repr__,__str__ |
printing, converting |
Print (x), repr (x), str (x) |
__call__ |
Function call |
X (*args,**kargs) |
__getattr__ |
Point number arithmetic |
x.undefined |
__setattr__ |
Property Assignment Statements |
X.any = value |
__delattr__ |
Property Delete |
Del X.any |
__getattribute__ |
Property gets |
X.any |
__getitem__ |
Index operations |
X[KEY],X[I:J], for loops and other iterators when not __iter__ |
__setitem__ |
Index assignment statement |
X[key] = value,x[i:j] = sequence |
__delitem__ |
Index and Shard deletion |
Del X[key],del X[i:j] |
__len__ |
Length |
Len (X), if there is no __bool__, the truth test |
__bool__ |
Boolean test |
BOOL (X), true test |
__LT__,__GT__, |
A specific comparison |
X < y,x > Y |
__LE__,__GE__, |
|
X<=y,x >= Y |
__eq__,__ne__ |
|
X = = y,x! = Y |
__radd__ |
Right addition |
Other+x |
__iadd__ |
field (Enhanced) addition |
X + = Y (or else __add__) |
__iter__,__next__ |
Iterative environment |
i = iter (X), Next (i) |
__contains__ |
Member Relationship Testing |
Item in X (any iteration) |
__index__ |
Integer value |
Hex (x), Bin (x), Oct (x), o[x],o[x:] |
__enter__,__exit__ |
Environment Manager |
With obj as Var: |
__get__,__set__ |
Descriptor Properties |
x.attr,x.attr = Value,del x.attr |
__new__ |
Create |
Create an object before __init__ |
The names of all overloaded methods have two underscore characters before and after them, so that the variable names defined in the same class are distinguished from each other.
Operator overloading methods are optional-if you do not write or inherit a method, your class does not support these operations directly, and attempting to use them throws an exception.
Most overloaded methods are used only in high-level programs that require an object's behavior to behave like a built-in type. However, __init__ constructors are often present in most classes.
Some examples of usage of operator overloading are described below.
=======================================================================
Indexes and Shards: __getitem__ and __setitem__
If defined in the class, the index operation for the instance automatically calls __getitem__, passes X as the first argument, and the index value within the square brackets to the second argument. For example, the following class returns the square of the index value.
--------------------------------------------------------------------------------------------------------------- ---
Intercept shards
Interestingly, in addition to indexes, __GETITEM__ is also called for Shard expressions. Formally, built-in types handle shards in the same way. For example, here is a shard that works on a built-in list, using the upper and lower bounds, and a stride. (Shard of knowledge can be recalled here)
>>> L = [5,6,7,8,9]>>> l[2:4][7, 8]>>> l[1:][6, 7, 8, 9]>>> l[:-1][5, 6, 7, 8]>> > l[::2][5, 7, 9]
In fact, the Shard boundary is bound to a Shard object (that is, the slice object) and is passed to the list implementation of the index. In fact, we can always manually pass a Shard object-The Shard syntax is primarily a syntactic sugar indexed with a Shard object:
>>> L[slice (2,4)][7, 8]>>> l[slice (1,none)][6, 7, 8, 9]>>> l[slice (none,-1)][5, 6, 7, 8]>& Gt;> L[slice (none,none,2)][5, 7, 9]
__getitem__ is called both for the base index and for the Shard. The class before us does not handle shards, but the following classes will handle the shards. When called against a base index, the parameter is an integer as before.
>>> class indexer:data = [5,6,7,8,9]def __getitem__ (Self,index):p rint (' GetItem: ', index) return self.data[ index]>>> X = Indexer () >>> x[0]getitem:05>>> x[1]getitem:16>>> X[-1]getitem: 19
However, when called on a shard, the method receives a Shard object that passes a new index expression directly to the nested list index:
>>> X[2:4]getitem:slice (2, 4, none) [7, 8]>>> X[1:]getitem:slice (1, none, none) [6, 7, 8, 9]>>> X[:-1]getitem:slice (None,-1, None) [5, 6, 7, 8]>>> X[::2]getitem:slice (None, none, 2) [5, 7, 9]
If used, the __setitem__ index assignment method intercepts the index and shard assignment similarly-it receives a Shard object for the latter, which may be passed to another index assignment in the same way:
def __setitem__ (self,index,value): ... self.data[index] = value
------------------------------------------------------------------------------------------------------------------
index iterations: __getitem__
This is a very useful technique. The For statement is performed from 0 to a larger index value, repeating the "index operation" of the sequence until an exception that exceeds the bounds is detected. Therefore, __getitem__ can also be a way of overloading iterations in Python. If this method is defined, the for loop invokes the class's __getitem__ method each time it loops, and continues to match with a higher offset value. As follows:
We know that any class that supports a for loop will also automatically support all of Python's iterative environments, such as member relationship test in, list parsing, built-in function map, list and Yuanzu value operations, and type construction methods, as shown in the following example:
>>> ' P ' in xtrue>>> [C for C in x][' s ', ' P ', ' a ', ' m ']>>> list (map (str.upper,x)) [' s ', ' P ', ' a ', ' m ']>>> a,b,c,d = x>>> a,c,d (' s ', ' a ', ' m ') >>> list (x), tuple (x), ' ". Join (x) ([' s ', ' P ', ' a ', ' m '], (' S ', ' P ', ' a ', ' m '), ' Spam ') >>> X<__main__.stepper object at 0x0376a6b0>
In practical applications, this technique can be used to create an object that provides a sequence interface, and adds logic to the type operation of the built-in sequence.
=======================================================================
Iterator objects: __iter__ and __next__
Although the __getitem__ technique described above is effective, it is only a fallback approach to iteration. The iterative environment in general Python will first try the __iter__ method and then try the __getitem__ method.
The iterative environment is implemented by calling the built-in function iter to try to find the __iter__ method, and this method should return an iterator object. If provided, Python repeats the next method of invoking the iterator object until the stopiteration exception occurs. If such a __iter__ method is not found, Python uses the __getitem__ mechanism instead, repeating the index with an offset as before, until the Indexerror exception is thrown.
------------------------------------------------------------------- -----------------------------------------------
User-defined Iterators
In the __iter__ mechanism, the class is implementing a user-defined iterator through the "iterator protocol" described earlier. The following example defines a user-defined iterator class to generate a square value.
In this example, the iterator object is the instance self, because the next method is part of the class.
Unlike __getitem__, __iter__ loops only once, not multiple times. Each time a new loop is created, you have to create a new iterator object, as follows:
>>> X = squares (1,5) >>> [n for N in X][1, 4, 9, +, 25]>>> [n for n ' x][]>>> [n for N in Squares (1,5)][1, 4, 9, 16, 25]
------------------------------------------------------------------------------------------------------------------
Objects with multiple iterators
As mentioned before, an iterator object can be defined as a separate class with its own state information to support multiple iterations of the same data. Look at the following example:
>>> s = ' Ace ' >>> for x in S:for y in S:print (x+y,end= ') AA ac AE CA cc CE ea EC ee
Here, the outer loop calls ITER to get the iterator from the string, and each nested loop does the same thing to get a separate iterator.
As previously mentioned, generator expressions, as well as built-in functions such as map and zip, all prove to be single-iteration objects; Instead, range built-in functions and other built-in types, such as lists, support multiple active iterators in separate locations.
When we use classes to write our own defined iterators, we decide to support a single or multiple active iterators. To achieve the effect of multiple iterators, __iter__ simply defines a new state object for the iterator, rather than returning self.
For example, the following defines an iterator class that, when iterated, skips the next element. Because the iterator object is recreated at each iteration, it is able to support multiple loops in the active state.
>>> class Skipiterator:def __init__ (self,wrapped): self.wrapped = Wrappedself.offset = 0def __next__ (self): if Self.offset >= Len (self.wrapped): Raise Stopiterationelse:item = Self.wrapped[self.offset]self.offset + = 2return Item>>> class Skipobject:def __init__ (self,wrapped): self.wrapped = Wrappeddef __iter__ (self): return Skipiterator (self.wrapped) >>> alpha = ' abcdef ' >>> skipper = Skipobject (alpha) >>> I = iter ( Skipper) >>> print (Next (i), next (i), next (i)) a C e>>> for x in Skipper:for y in skipper:print (x+y,end = " ) AA AC AE CA cc CE ea EC ee
This example works like nesting loops on built-in strings, because each loop has its own iterator object that records its own information, so the loop in each active state has its own position in the string.
=======================================================================
Memberships: __contains__, __iter__, and __getitem__
In the iterative realm, a class typically implements an in member-relational operator as an iteration, using the __iter__ method or the __getitem__ method. To support a more specific member relationship, the class might write a __contains__ method-which, when present, takes precedence over the __iter__ method, and the __iter__ method takes precedence over the __getitem__ method.
The __contains__ method should define a member relationship to apply a key to a map and a search for the sequence.
Consider the following class, which writes 3 methods and test membership relationships and various iterative environments that apply to an instance. When called, its method prints a trace message.
Class Iters: def __init__ (self,value): self.data = value def __getitem__ (self,i): print (' get[%s]: '% I,end = ") return self.data[i] def __iter__ (self): print (' iter=> ', end= ') self.ix = 0 return Self def __next__: 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.dataif __name__ = = ' __main__ ': x = Iters ([1,2,3,4,5]) print (3 in x) C21/>for i in X: print ( i,end= ") print ( [i**2 for I in X]) print (list (map (bin,x))) i = ITER (X) while 1: try: print (Next (I), end = ' @ ') except stopiteration: break
When this script runs, its output is as follows:
Contains:trueiter=> next:1next:2next:3next:4next:5next:iter=> next:next:next:next:next:next:[1, 4, 9, 16, 25] iter=> next:next:next:next:next:next:[' 0b1 ', ' 0b10 ', ' 0b11 ', ' 0b100 ', ' 0b101 ']iter=> next:1 @ next:2 @ next:3 @ Next : 4 @ next:5 @ Next:
As you can see, the specific __contains__ intercepts the membership, and the generic __iter__ captures other iterations of the environment so that __next__ is called repeatedly, and __getitem__ is not called.
However, to see what happens to the output of the code after commenting out the __contains__ method-the membership is now routed to the generic __iter__:
iter=> next:next:next:trueiter=> next:1next:2next:3next:4next:5next:iter=> Next:next:next:next:next:next : [1, 4, 9, +, 25]iter=> next:next:next:next:next:next:[' 0b1 ', ' 0b10 ', ' 0b11 ', ' 0b100 ', ' 0b101 ']iter=> next:1 @ Next : 2 @ next:3 @ next:4 @ next:5 @ Next:
However, if both __contains__ and __iter__ are commented out, the output is as follows--the index __getitem__ workaround is called, and successive higher indexes are used for the membership and other iterative environments:
Get[0]:get[1]:get[2]:trueget[0]:1get[1]:2get[2]:3get[3]:4get[4]:5get[5]:get[0]:get[1]:get[2]:get[3]:get[4]:get [5]:[1, 4, 9, 25]get[0]:get[1]:get[2]:get[3]:get[4]:get[5]:[' 0b1 ', ' 0b10 ', ' 0b11 ', ' 0b100 ', ' 0b101 ']get[0]:1 @ get[1 ]:2 @ get[2]:3 @ get[3]:4 @ get[4]:5 @ GET[5]:
As we can see, the __getitem__ method is more generic.
python--operator overloading (1)