In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-03-28 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/03 Report--
This article introduces the "decorator to achieve Python function overloading of the detailed process" of the relevant knowledge, in the actual case of the operation process, many people will encounter such a dilemma, then let the editor lead you to learn how to deal with these situations! I hope you can read it carefully and be able to achieve something!
Catalogue
Decorator realizes Python function overloading
First, why is there no function overloading in Python?
Second, realize function overloading in Python
Third, encapsulate the function
Build a virtual namespace
Fifth, use the decorator as a hook
Find the correct function from the namespace
Seventh, realize the call of the function
8. Use function overload
IX. Summary
Decorator realizes Python function overloading
Function overloading means that there are multiple functions with the same name, but their signatures or implementations are different. When an overloaded function fn is called, the program verifies the arguments / parameters passed to the function and calls the corresponding implementation accordingly.
Int area (int length, int breadth) {return length * breadth;} float area (int radius) {return 3.14 * radius * radius;}
In the above example (written in C++), the function area is overloaded with two implementations. The first function takes two arguments (both integers) that represent the length and width of the rectangle and returns the area of the rectangle. The other function takes only one integer parameter, which represents the radius of the circle.
When we call the function area like area (7), it calls the second function, while area (3p4) calls the first function.
First, why is there no function overloading in Python?
Python does not support function overloading. When we define multiple functions with the same name, the subsequent functions always override the previous functions, so there is only one registration entry (entry) for each function name in a namespace.
Note: it is said here that Python does not support function overloading, which means without syntax sugar. Using the singledispatch decorator of the functools library, Python can also implement function overloading. The author of the original text specifically mentioned this point in his notes at the end of the article.
By calling the locals () and globals () functions, we can see what is in the namespace of Python, which returns the local and global namespaces, respectively.
Def area (radius): return 3.14 * radius * * 2 > > locals () {. 'area':,...}
After defining a function, we then call the locals () function, which returns a dictionary of all variables defined in the local namespace. The key of the dictionary is the name of the variable, and the value is the reference / value of the variable.
When the program is running, if it encounters another function with the same name, it updates the registration entry in the local namespace, thus eliminating the possibility that the two functions coexist. Therefore, Python does not support function overloading. This is a design decision made when creating the language, but it doesn't prevent us from implementing it, so let's overload some functions.
Second, realize function overloading in Python
We already know how Python manages namespaces, and if you want to implement function overloading, you need to do this:
Maintains a virtual namespace in which function definitions are managed
Try to call the appropriate function according to the parameters passed each time
For the sake of simplicity, when we implement function overloading, we distinguish functions with the same name by different numbers of parameters.
Third, encapsulate the function
We created a class called Function that encapsulates any function and calls it through the overridden _ _ call__ method, and provides a method called key that returns a tuple that makes the function unique throughout the code base.
From inspect import getfullargspecclass Function (object): "" Function class is an encapsulation of standard Python functions "" def _ init__ (self, fn): self.fn = fn def _ call__ (self, * args, * * kwargs): "when called like a function, it calls the encapsulated function. And return the return value of the function "" return self.fn (* args, * * kwargs) def key (self, args=None): "returns a key that uniquely identifies a function (even if it is overloaded)"# if args is not specified Then extract the parameter if args is None: args = getfullargspec (self.fn) .args return tuple ([self.fn.__module__, self.fn.__class__, self.fn.__name__, len (args or []),]) from the definition of the function.
In the code snippet above, the key function returns a tuple that uniquely identifies the function in the code base and records:
The module to which the function belongs
The class to which the function belongs
Function name
The number of parameters received by the function
The overridden _ _ call__ method calls the encapsulated function and returns the calculated value (nothing special). This allows an instance of Function to be called like a function, and it behaves exactly like an encapsulated function.
Def area (l, b): return l * b > > func = Function (area) > func.key () ('_ main__', 'area', 2) > func (3,4) 12
In the above example, the function area is encapsulated in Function and instantiated as func. Key () returns a tuple whose first element is the module name _ _ main__, the second is the class, the third is the function name area, and the fourth is the number of arguments that the function receives, that is, 2.
This example also shows that we can call instance func just as we would call a normal area function, and when we pass in parameters 3 and 4, the result is 12, which is exactly what happens when we call area (3line 4). This behavior will come in handy when we next use the decorator.
Build a virtual namespace
We want to create a virtual namespace to store all the functions collected during the definition phase.
Since there is only one namespace / registry, we create a singleton class and save the function in the dictionary. The key of the dictionary is not the function name, but the tuple we get from the key function, which contains elements that uniquely identify a function.
In this way, we can save all the functions in the registry, even if they have the same name (but different parameters), thus achieving function overloading.
Class Namespace (object): "" Namespace is a singleton class Responsible for saving all functions "" _ _ instance = None def _ _ init__ (self): if self.__instance is None: self.function_map = dict () Namespace.__instance = self else: raise Exception ("cannot instantiate a virtual Namespace again") @ staticmethod def get_instance (): if Namespace.__instance is None: Namespace () return Namespace.__instance def register (self) Fn): "registers the function in the virtual namespace And return the callable instance of the Function class "" func = Function (fn) self.function_map [func.key ()] = fn return func
The Namespace class has a register method that takes the function fn as an argument, creates a unique key for it, stores the function in a dictionary, and finally returns an instance of Function that encapsulates fn. This means that the return value of the register function is also callable and (so far) it behaves exactly the same as the encapsulated function fn.
Def area (l, b): return l * b > > namespace = Namespace.get_instance () > func = namespace.register (area) > func (3,4) 12 V. Use decorator as hook
Now that we have defined a virtual namespace that can register a function, we also need a hook to call it during the function definition. Here, we will use the Python decorator.
In Python, decorators are used to encapsulate a function and allow us to add new functionality to it without modifying its structure. The decorator takes the decorated function fn as an argument and returns a new function for the actual call. The new function receives the args and kwargs of the original function and returns the final value.
The following is an example of a decorator that demonstrates how to add timing to a function.
Import timedef my_decorator (fn): "" this is a custom function that can decorate any function and print its execution time "def wrapper_function (* args, * * kwargs): start_time = time.time () # call the decorated function And get the return value value = fn (* args, * * kwargs) print ("the function execution took:", time.time ()-start_time, "seconds") # return the call result of the decorated function return value return wrapper_function@my_decoratordef area (l, b): return l * b > > area (3,4) the function execution took: 9.5367431640625e-07 seconds12
In the above example, we define a decorator called my_decorator, which encapsulates the function area and prints out the time it takes to execute area on the standard output.
Whenever the interpreter encounters a function definition, it calls the decorator function my_decorator (which encapsulates the decorated function and stores the encapsulated function in the local or global namespace of the Python), which is an ideal hook for us to register the function in the virtual namespace.
Therefore, we created a decorator called overload, which registers the function in the virtual namespace and returns a callable object.
Def overload (fn): "encapsulates the function and returns a callable object of the Function class" return Namespace.get_instance () .register (fn)
The overload decorator returns an instance of Function with the help of the. Register () function of the namespace. Now, whenever a function is called (decorated by overload), it calls an instance of Function, which is returned by the .register () function, and its call method is executed with the specified args and kwargs during the call.
Now all that's left is to implement the _ _ call__ method in the Function class so that it can call the corresponding function based on the parameters passed during the call.
Find the correct function from the namespace
In addition to the usual module, class and function names, we can also distinguish different functions according to the number of parameters of the functions. Therefore, we define a get method in the virtual namespace, which reads the functions and arguments to be distinguished from the Python namespace, and finally returns the correct function according to the different parameters. We have not changed the default behavior of Python, so there is only one function with the same name in the native namespace.
This get function determines which implementation of the function (if overloaded) will be called. The process of finding the correct function is simple-first use the key method, which uses functions and parameters to create a unique key (as you did when registering), then find out if the key exists in the function registry, and if so, get the implementation of its mapping.
Def get (self, fn, * args): "returns the matching function from the virtual namespace, or None" func = Function (fn) return self.function_map.get (func.key (args=args)) if no match is found.
The get function creates an instance of the Function class so that you can reuse the class's key function to obtain a unique key without having to write the logic to create the key. This key will then be used to get the correct function from the function registry.
Seventh, realize the call of the function
As mentioned earlier, every time a function decorated by overload is called, the _ _ call__ method in the Function class is called. We need to have the _ _ call__ method get the correct function from the get function in the namespace and call it.
The _ _ call__ method is implemented as follows:
Def _ _ call__ (self, * args, * * kwargs): "rewrite the _ _ call__ method that makes the instance of the class callable object" # get the function to be called from the virtual namespace fn = Namespace.get_instance (). Get (self.fn, * args) if not fn: raise Exception ("no matching function found.") # call the encapsulated function according to the parameter And return the result of the call return fn (* args, * * kwargs)
This method gets the correct function from the virtual namespace, and if no function is found, it throws an Exception, and if it does, it calls the function and returns the result of the call.
8. Use function overload
With all the code ready, we define two functions called area: one calculates the area of the rectangle and the other calculates the area of the circle. Two functions are defined below and decorated with the overload decorator.
Overloaddef area (l, b): return l * b@overloaddef area (r): import math return math.pi * r * * 2 > > area (3,4) 12 > area (7) 153.93804002589985
When we call area with one parameter, it returns the area of a circle, and when we pass two parameters, it calls the function that calculates the area of the rectangle, thus overloading the function area.
Note: starting with Python 3.4, Python's functools.singledispatch supports function overloading. Starting with Python 3.8, functools.singledispatchmethod supports overloading classes and instance methods. Thanks to Harry Percival for his correction.
IX. Summary
Python does not support function overloading, but by using its basic structure, we have a solution.
We use decorators and virtual namespaces to overload functions and use the number of arguments as a factor to distinguish functions. We can also distinguish functions according to the type of parameters (defined in the decorator)-- that is, overloading functions that have the same number of parameters but different types of parameters.
The extent to which overloading can be done is limited only by the getfullargspec function and our imagination. Using the ideas above, you may come up with a cleaner, cleaner, and more efficient approach, so try to do it.
This is the end of the text. The complete code is attached below:
# module: overload.pyfrom inspect import getfullargspecclass Function (object): "Function is a wrap over standard python function An instance of this Function class is also callable just like the python function that it wrapped. When the instance is" called "like a function it fetches the function to be invoked from the virtual namespace and then invokes the same." Def _ init__ (self, fn): self.fn = fn def _ call__ (self, * args, * * kwargs): "Overriding the _ _ call__ function which makes the instance callable."# fetching the function to be invoked from the virtual namespace # through the arguments. Fn = Namespace.get_instance (). Get (self.fn, * args) if not fn: raise Exception ("no matching function found.") # invoking the wrapped function and returning the value. Return fn (* args, * * kwargs) def key (self, args=None): "Returns the key that will uniquely identifies a function (even when it is overloaded)." If args is None: args = getfullargspec (self.fn). Args return tuple ([self.fn.__module__, self.fn.__class__, self.fn.__name__, len (args or []),]) class Namespace (object): "" Namespace is the singleton class that is responsible for holding all the functions. "" _ _ instance = None def _ _ init__ (self): if self.__instance is None: self.function_map = dict () Namespace.__instance = self else: raise Exception ("cannot instantiate Namespace again.") @ staticmethod def get_instance (): if Namespace.__instance is None: Namespace () return Namespace.__instance def register (self Fn): "" registers the function in the virtual namespace and returns an instance of callable Function that wraps the function fn. " Func = Function (fn) specs = getfullargspec (fn) self.function_map [func.key ()] = fn return func def get (self, fn, * args): "" get returns the matching function from the virtual namespace. Return None if it did not fund any matching function. "" Func = Function (fn) return self.function_map.get (func.key (args=args)) def overload (fn): "overload is the decorator that wraps the function and returns a callable object of type Function." Return Namespace.get_instance (). Register (fn) finally The demo code is as follows: from overload import overload@overloaddef area (length, breadth): return length * breadth@overloaddef area (radius): import math return math.pi * radius * * 2@overloaddef area (length, breadth, height): return 2 * (length * breadth + breadth * height + height * length) @ overloaddef volume (length, breadth, height): return length * breadth * height@overloaddef area (length, breadth, height): return length + breadth + height@overloaddef area (): return 0print (f "area of cuboid with dimension (4,3) 6) is: {area (4,3,6)} ") print (f" area of rectangle with dimension (7,2) is: {area (7,2)} ") print (f" area of circle with radius 7 is: {area (7)} ") print (f" area of nothing is: {area ()} ") print (f" volume of cuboid with dimension (4,3,6) is: {volume (4,3) 6)} ") the detailed process of decorator implementing Python function overloading" is introduced here. Thank you for your reading. If you want to know more about the industry, you can follow the website, the editor will output more high-quality practical articles for you!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.