1. Case analysis

Consider a scenario where different functions need to be invoked to perform different operations based on the URL entered by the user, namely the URL routing function of a WEB framework. Routing is one of the core features of web frameworks such as Django’s urls.

First, there is a commons.py file with several functions for displaying different pages. This is essentially a View file for the Web service that handles the actual business logic.

# commons.py def login(): print(" this is a login page! ") ) def logout(): print(" This is an exit page! ") ) def home(): print(" this is the home page! ") )Copy the code

Second, there is a visit.py file that serves as the program entry, receives user input, and displays the page based on that input.

# visit.py import Commons def run(): inp = input(" please enter the url of the page you want to visit: ").strip() if inp == "login": commons.login() elif inp == "logout": commons.logout() elif inp == "home": commons.home() else: print("404") if __name__ == '__main__': run()Copy the code

Run visit.py, enter home, and the page looks like this:

Please enter the URL of the page you want to visit: Home this is the main page of the website!Copy the code

This implements a simple URL routing function that performs different functions to obtain different pages according to different urls.

However, let’s consider the question, what if there are hundreds or thousands of functions in the Commons file (which is common)? Do you want to put hundreds or thousands of elif in the visit module? Obviously this is impossible! So what to do?

If you look closely at the code in visit.py, you will see that the url string entered by the user looks like the name of the corresponding function called! If only I could call a function directly with this string! However, it has already been said that strings cannot be used to call functions. To solve this problem, Python provides a reflection mechanism to help us with this idea, mainly in the form of several built-in functions such as getattr()!

Now modify the previous visit.py to look like this:

# visit.py import commons def run(): Inp = input(" please enter the URL of the page you want to visit: ").strip() func = getattr(Commons,inp) func() if __name__ == '__main__': run()Copy the code

The func = getattr(Commons,inp) statement is the key, using the getattr() function from the Commons module, find the same function name as the inp string “appearance”, and return it, and then assign the value to the func variable. The variable func now refers to that function, and func() can call that function.

Getattr () takes two arguments, a class or module, and a string. It’s a string!

This process is equivalent to turning a string into a function name. This is a dynamic access process, everything is not written dead, all according to user input to change.

One small flaw in the previous code is that if the user enters an illegal URL, such as JPG, it will definitely produce a runtime error because there is no Commons function with the same name, as follows:

Please enter the URL of the page you want to visit: JPG Traceback (most recent call last): File "F:/Python/pycharm/s13/reflect/visit.py", line 16, in <module> run() File "F:/Python/pycharm/s13/reflect/visit.py", line 11, in run func = getattr(commons,inp) AttributeError: module 'commons' has no attribute 'jpg'Copy the code

So what do we do? Python provides a built-in hasattr() function that uses getattr() in much the same way as getattr(), which determines whether Commons hasa member and returns True or False. Now modify the code:

# visit.py import Commons def run(): inp = input(" please enter the url of the page you want to visit: ").strip() if hasattr(Commons,inp): func = getattr(commons,inp) func() else: print("404") if __name__ == '__main__': run()Copy the code

Now there’s no problem! Through the judgment of Hasattr (), you can prevent the error caused by illegal input and locate it to the error page uniformly.

Python’s four important built-in functions — getattr(), hasattr(), delattr(), and setattr() — fully implement string-based reflection. Delattr () and setattr() won’t go into much detail, but you should probably guess what they’re used for. They operate on modules in memory and do not modify the source file.

2. Dynamic import module

The previous example requires the Commons.py and visit.py modules to be in the same directory, and all page handling functions to be in the Commons module. The diagram below:

However, in the actual environment, page processing functions are often classified and placed in different modules in different directories, as shown in the figure below:

In principle, you just need to import each view module individually in the visit.py module. But what if there are a lot of modules? Do you want to add a bunch of import statements to visit to import account, Manage, Commons modules? What if there were 1000 modules?

You can solve this problem using Python’s built-in __import__(string argument) function. It allows you to implement reflection like getattr(). The __import__() method dynamically imports modules of the same name based on string arguments.

Modify the code for visit.py again.

# visit.py def run(): Inp = input(" please enter the url of the page you want to visit: ").strip() modules, func = inp.split("/") obj = __import__(modules) if hasattr(obj, func): func = getattr(obj, func) func() else: print("404") if __name__ == '__main__': run()Copy the code

It is important to provide both the module name and the function name, separated by a slash.

Let’s run this:

Please enter the URL of the page you want to visit: Commons /home this is the main page of the site! Please enter the URL of the page you want to visit: Account /find This is a find feature page!Copy the code

Again, there’s a small flaw! If our directory structure is such that visit.py and Commons. py are not in the same directory, we have a cross-package problem.

So in the call statement for visit, you have to make a change, and you might do something like this:

def run(): Inp = input(" Please enter the URL of the page you want to visit: "). Strip () modules, func = inp.split("/") obj = __import__("lib." + modules) func = getattr(obj, func) func() else: print("404") if __name__ == '__main__':Copy the code

This seems to work fine, and is similar to the traditional method of import lib.commons, but it actually runs with errors.

Please enter the URL of the page you want to visit: Commons /home 404 Please enter the URL of the page you want to visit: Account /find 404Copy the code

Why is that? For module import paths such as lib.xxx.xxx.xxx, __import__() only imports the directory to the left of the first dot, i.e. Lib. To test this, create a new file in the visit directory as follows:

obj = __import__("lib.commons")
print(obj)
Copy the code

How to solve this problem? Fromlist = True The complete code is as follows:

def run(): Inp = input(" Please enter the URL of the page you want to visit:  ").strip() modules, func = inp.split("/") obj = __import__("lib." + modules, If hasattr(obj, func): func = getattr(obj, func) else: print("404") if __name__ == '__main__': run()Copy the code