Basic knowledge
In fact, "operator overloading" simply means intercepting the built-in action in the class method ... When an instance of a class appears in a built-in operation, Python automatically calls your method, and the return value of your method becomes the result of the corresponding operation. The following is a review of the key concepts of overloading:
Operator overloading allows a class to intercept regular Python operations.
Class can overload all Python expression operators
Classes can overload the built-in operations such as printing, function calls, attribute point number operations, and so on
Overloading makes a class instance behave like a built-in type.
Overloading is implemented by a class method of a special name.
In other words, when a method of a particular name is provided in a class, Python automatically calls them when instances of the class appear in their related expressions. As we have learned, the operator overloading method is not required and is usually not the default, and if you do not write or inherit an operator overloaded method, it simply means that your class does not support the corresponding operation. However, when used, these methods allow the class to emulate the interfaces of the built-in objects and therefore behave more consistently.
Constructors and expressions: __init__ and __sub__
Let's take a look at a simple overloaded example. For example, the number class of the following file number.py class provides a way to intercept the instance's constructor (__init__), and there is a method to catch the subtraction expression (__sub__). This special method is a hook that can be tied to a built-in operation.
class number: def __init__ (Self, start): # on number (start) self.data = start def __sub__ (Self, other): # on instance - other return number (Self.data - other) # result is a new instance >>> from Number import number>>> x = number (5) # number.__init__ (x, 5) >>> Y = X - 2 # number.__sub__ (x, 2) >>> Y.data # Y Is new number instance3
As discussed before, the __init__ constructor seen in this code is the most common operator overloading method in Python, which exists in most classes. In this section, we will give an example of some of the other tools available in this area, and take a look at the routines commonly used by these tools.
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 commonly used overloaded methods. In fact, many overloaded methods have several versions (for example, addition has __add__, __radd__, and __iadd__).
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 (called __nonzero__ in Python 2.6) |
__lt__, __gt__ __lt__, __ge__ __eq__, __ne__ |
Specific comparisons |
X<y, X>y, X<=y, x>=y, x = = y, x! = y (or only __cmp__ in Python 2.6) |
__radd__ |
Right addition |
Other + X |
__iadd__ |
field (Enhanced) addition |
X + = Y (or else __add__) |
__iter, __next__ |
Iterative environment |
i = iter (X), Next (i); For loops, with if no __contains__, all comprehensions, map (F, X), others (__next__ becomes next in Python2.6) |
__contains__ |
Member Relationship Testing |
Item in X (any iteration) |
__index__ |
Integer value |
Hex (x), Bin (x), Oct (x), o[x], O[x:] (replaces __oct__, __hex__ in Python 2)
|
__ENTER__, __exit__ |
Environment Manager |
With obj as Var: |
__get__, __set__ __delete |
Descriptor Properties |
x.attr, x.attr = value, Del x.attr |
__new__ |
Create |
Create an object before __init__ |
All overloaded methods have two underscores before and after their names to differentiate the variable names defined in the same class. The mapping of special method names and expressions or operations is pre-defined by the Python language (described in the standard language manual). For example, __add__, as defined by the Python language, always corresponds to the expression +, regardless of what the code in the __add__ method actually does.
If you do not define an operator overloading method, it may inherit from the superclass, just like any other method. Operator overloading methods are also 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. Some built-in operations, such as printing, have a default overloaded method (inherited from the object class implied in Python 3.x), but most built-in functions fail on the class instance if the appropriate operator overloading method is not given.
Most overloaded methods are used only in high-level programs that require objects to behave as if they were built-in types. However, __init__ constructors often appear in most classes. We have seen __init__ initial definition constructors, as well as some of the other methods in the table above. Let's use examples to illustrate the other methods in the table.
Indexes and Shards: __getitem__ and __setitem__
if (or inherited) is defined in the class, __getitem__ is automatically called for the instance's index operation. When instance x appears in an index operation such as X[i], Python invokes the __getitem__ method inherited by the instance (if any), passes X as the first argument, and the index value of the square bracket class is passed to the second argument. For example, the following class returns the square of the index value.
>>> class Indexer:def __getitem__ (Self, Index): Return index * * 2>>> X = Indexer () >>> x[2] # X[i] Calls x.__getitem__ (i) 4>>> for me in range (5):p rint (X[i], end= ") # R Uns __getitem__ (X, i) each TIME0 1 4 9 16
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, the following is a shard that works on a built-in list, uses the upper and lower bounds, and a stride (you can review the knowledge about shards):
>>> L = [5, 6, 7, 8, 9]>>> L[2:4] # Slice with Slice syntax[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 and 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)] # Slice with slice objects[7, 8]>>> l[slice (1, None)][6, 7, 8, 9]>& Gt;> L[slice (None,-1)][5, 6, 7, 8]>>> L[slice (None, none, 2)][5, 7, 9]
For classes with a __getitem__, this is important ... The method will call both the base index (with one index) and the Shard (with one Shard object). The class before us does not process shards because its mathematical assumptions pass an integer index, but the following classes will handle the shards. When called on an 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[ind ex]>>> 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 is passed directly to the nested list index in a new index expression:
>>> 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 indexes and shard assignments in a similar way ... 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
In fact, __getitem__ may be called automatically in environments that are even more than indexes and shards, as described in the following subsections.
shards and indexes in Python 2.6 Prior to Python 3.0, classes could also define __getslice__ and __setslice__ methods to specifically intercept shard fetching and assignment, which would pass a series of Shard expressions and take precedence over __getitem__ and __setitem__ for sharding.
These shard-specific methods have been removed from Python 3.0, so you should use __getitem__ and __setitem__ instead to take into account that both the index and the Shard object may be parameters. |
Index iterations: __getitem__
Beginners may not immediately be able to grasp the technique, but these techniques are very useful, the purpose of the For statement is from 0 to a larger index value, repeat the index operation of the sequence, know the detection of an exception beyond the boundary. 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__ each time it loops, and continues to match with a higher offset value. This is a "buy one, get one" scenario: any built-in or user-defined objects that respond to an index operation will also respond to iterations.
>>> class Stepper:def __getitem__ (Self, i): return self.data[i]>>> X = Stepper () >>> x.data = "S Pam ">>> >>> x[1] ' p ' >>> for item in X:print (item, end= ') S p a M
In fact, this is actually the case of "buy one and get one". Any class that supports a for loop will automatically support all of Python's iterative environments, many of which we've already seen in the front. For example, membership tests in, list parsing, built-in function map, list and tuple assignment operations, and type construction methods also automatically invoke __getitem__ (if defined).
>>> ' 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, b, C (' s ', ' P ', ' a ') >>> list (x), tuple (x), '. Join (x) ([' s ', ' P ' , ' A ', ' m '], (' S ', ' P ', ' a ', ' m '), ' Spam ') >>> X<__main__.stepper object at 0x000001e19957df28>
In practical applications, this technique can be used to create pairs that provide sequence interfaces, and add logic to the built-in sequence type operations.
Iterator objects: __iter__ and __next__
Although the __getitem__ technique above is effective, it is really just a fallback method of iteration. today, all iteration environments in Python try the __iter__ method first, and then try to __getitem__. that is, they prefer to use iterative protocols and then repeat the indexing of objects . An index operation is attempted only if the object does not support an iterative protocol . In general, you should also prioritize the use of __iter__, which is better able to support a general iterative environment than __GETITER__.
Technically, 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, knowing that the stopiteration exception occurred. If this type of __iter__ method is not found, Python will instead use the __getitem__ mechanism, repeating the index as before with an offset, knowing that a Indexerror exception occurs (a next built-in function can be easily used for manual iterations: Next (I) Is the same as i.__next__ ().
User-defined Iterators
In the __iter__ mechanism, a class implements a user-defined iterator by implementing an iterator protocol. For example, the following iters.py defines a user-defined iterator to generate a squared value.
>>> class Squares:def __init__ (self, start, stop): Self.value = Start-1self.stop = Stopdef __iter__ (self): return Selfdef __next__ (self): if Self.value = = self.stop:raise Stopiterationself.value + 1return self.value * 2>>> fo R I in Squares (1, 5):p rint (i, end= ") 1 4 9 16 25
Here, the iterator object is the instance self, and the next method should be part of the class. In a more scum-like scenario, an iterator object can be defined as an object of an individual class or its own state information, supporting multiple iterations of the same data (see this example below). The signal from the Python raise statement indicates the end of the iteration. Manual iterations are also valid for built-in types:
>>> X = Squares (1, 5) >>> I = iter (X) >>> Next (i) 1>>> next (i) 4......>>> next ( i) 25>>> next (i) Traceback (most recent): File "<pyshell#91>", line 1, <module> next (i ) File "<pyshell#77>", line 9, in __next__ raise Stopiterationstopiteration
The equivalent code written by __getitem__ may not be natural, and the for will iterate over all 0 and higher value offset values. The offset value passed in and the range of the resulting value is only indirectly related (0: n needs to be set to start. Stop). Because the __iter__ object retains state information explicitly during invocation, it is more versatile than __getitem__.
Other than that
This article from "Professor elder brother" blog, reprint please contact the author!
Python 3 Operator Overloading detailed