introduce
Based on ATM project, this paper introduces a practical programming technique, which uses decorator to add the specified functions in the project to the dictionary.
Using the characteristics of dictionary accessing value through key, the user can input the number, and directly obtain and call the function corresponding to the number through the dictionary.
#Goal: let the user input the number of the function to be used, and the program calls the corresponding function.
Basic version - if condition judgment
def login(): print('this is login function') def register(): print('this is register function') def transfer(): print('this is transfer function') # main program def atm(): desc = '1: Sign in\n2: register\n3: Transfer accounts' while 1: print(desc) cmd = input('Please select the function number you want to use:').strip() if cmd == '1': login() elif cmd == '2': register() elif cmd == '3': register() else: print('No. does not exist, please re select') if __name__ == '__main__': atm() # The basic version, each digit character corresponds to different functions, determines the user's choice through if-elif-else, and then calls. # Advantages: simple and bright, clear thinking # Disadvantages: repetitive code and overstaffed program
Enhanced function dictionary
# Function functions are omitted # main program def atm(): # Function dictionary cmd_func = { '1': ('Sign in', login), '2': ('register', register), '3': ('Transfer accounts', transfer), } while 1: for k, v in cmd_func.items(): print(f'({k}){v[0]}', end='\t') cmd = input('\n Please select the function number you want to use:').strip() if cmd not in cmd_func: print('No. does not exist, please select again') continue # Number in dictionary func = cmd_func.get(cmd)[1] # Func is the second element of value in the CMD func dictionary func() # Calling function if __name__ == '__main__': atm() # Improve the version, put each function in a dictionary, key is the number, and the value contains the function name. Enter key to get the function function in the value # Advantages: clear thinking, simple code, beautiful program # Disadvantage: need to create a function dictionary manually
Advanced - auto generate function dictionary
# Decorator def auto(desc): from functools import wraps def wrapper(func): @wraps(func) def inner(*args, **kwargs): index = len(cmd_func) + 1 # Call the global function dictionary (initially empty), and calculate the number automatically cmd_func[str(index)] = (desc, func) # Add numbers, descriptions and functions to the dictionary return inner return wrapper @auto('Sign in') def login(): print('this is login function') @auto('register') def register(): print('this is register function') @auto('Transfer accounts') def transfer(): print('this is transfer function') # To automatically call the function of the decorated function, you need to manually exclude the unnecessary names in the global namespace def auto_append(): my_func = [v for k, v in globals().items() if callable(v) if k not in ['atm', 'auto', 'auto_append']] for func in my_func: func() # Function dictionary cmd_func = {} # main program def atm(): # Call function add function dictionary automatically auto_append() while 1: for k, v in cmd_func.items(): print(f'({k}){v[0]}', end='\t') cmd = input('\n Please select the function number you want to use:').strip() if cmd not in cmd_func: print('No. does not exist, please re select') continue # Number in dictionary func = cmd_func.get(cmd)[1] # Func is the second element of value in the CMD func dictionary func() # Calling function if __name__ == '__main__': atm() # Advanced - write decorators, each decorated function call will be added to a dictionary, and then write a function to automatically discover these functions, automatically call # Advantage: dictionary functions do not need to be added manually # Disadvantages: the need dictionary is a global variable. cmd_func = {}. It can exclude unnecessary functions manually. When there are few function, it can't show its advantages # In the above case, all functions are in one file; # If the function function is in other files and the strokes are imported, the advantage of using the auto add function dictionary is obvious.
Advanced - auto add across files
Here, the simplified version of the software development directory specification is used to put functions of different functions in different files, and all files are in one folder.
ATM/ |-- run.py # Program startup file |-- atm.py # Main function file, i.e. atm() function |--funcs.py # The file that holds the login(), register(), transfer() functions |--tools.py # Store the files of decorator functions auto(), auto append()
run.py
from atm import atm # Import the atm() function under atm.py if __name__ == '__main__': atm() # Run the atm() function
atm.py
from tools import auto_append # Import auto append function from tools.py # Function dictionary. Add login, register and transfer to this dictionary automatically cmd_func = {} # main program def atm(): auto_append() # Call auto append function to add function dictionary automatically while 1: for k, v in cmd_func.items(): print(f'({k}){v[0]}', end='\t') cmd = input('\n Please select the function number you want to use:').strip() if cmd not in cmd_func: print('No. does not exist, please re select') continue # Number in dictionary func = cmd_func.get(cmd)[1] # Func is the second element of value in the CMD func dictionary func() # Calling function
funcs.py
from tools import auto # Import auto decorator from tools.py @auto('Sign in') # Decoration login def login(): print('this is login function') @auto('register') # Decoration register def register(): print('this is register function') @auto('Transfer accounts') # Decoration transfer def transfer(): print('this is transfer function')
tools.py
from functools import wraps def auto(desc): # Decorator from atm import cmd_func # Function import to avoid circular import def wrapper(func): @wraps(func) def inner(*args, **kwargs): index = len(cmd_func) + 1 # Access the CMD func Dictionary of the atm.py file cmd_func[str(index)] = (desc, func) # Add function customization to the dictionary return inner return wrapper def auto_append(): # Import the functions to be added to the dictionary in the current local namespace from funcs import login, register, transfer my_funcs = locals() for func in my_funcs.values(): func() # Auto execute, that is, execute the inner function in the decorator and add it to the dictionary automatically
Run run.py. The result interface is as follows. It's perfect, right. Through the above operations, we realize the automatic addition of function functions to the dictionary.
However, the following points should be noted:
-
We put the dictionary CMD func in the global namespace of the atm.py file. When the function decorator auto uses it, it needs to import CMD func from the atm.py file. What we need to pay attention to here is the circular import problem. In this case, there are two solutions to the problem of circular import. Scheme 1: import from ATM import CMD func in auto function; scheme 2: import atm at the top of tools.py file, and use atm.cmd func in auto function
-
If the dictionary CMD func is placed in a separate py file, rather than in the atm.py file, it is very convenient. As long as it is imported and referenced in the place where it is needed, it will not cause circular import problems.
-
The function auto append imports the login, register, transfer functions and the name of the local namespace.
supplement
That's the end of it, because the above operation is very smooth to achieve our goal. This kind of operation is very practical when there are many functions and cross file import.
At the end, I suddenly want to try. If I don't call the ATM function through the run.py file, but directly run the atm.py file and call the ATM function, is it OK? So, add if \\\\\\\\\\\
from tools import auto_append # Import auto append function from tools.py # Function dictionary. Add login, register and transfer to this dictionary automatically cmd_func = {} # main program def atm(): auto_append() # Call auto append function to add function dictionary automatically while 1: for k, v in cmd_func.items(): print(f'({k}){v[0]}', end='\t') cmd = input('\n Please select the function number you want to use:').strip() if cmd not in cmd_func: print('No. does not exist, please select again') continue # Number in dictionary func = cmd_func.get(cmd)[1] # Func is the second element of value in the CMD func dictionary func() # Calling function if __name__ == '__main__': # This judgment is added, which does not affect the import and execution of the file atm()
Execute the atm.py file, call the ATM function, and the result..... The dictionary is empty
That is to say, I did nothing in these operations. But I did it.
I have been studying this problem for a long time, but I don't understand it. After debugging with debug, I found that the reason is very simple.
When the atm function is executed in this way, the run.py file is used to import the atm function
The imported Module ATM will execute the atm.py file when importing the module. After the execution, there is a dictionary named CMD func in the name space of the atm.py module.
After executing the auto append function inside the ATM function, it will enter the decorator auto. When encountering the from ATM import CMD func inside the decorator, it will import the ATM module again. But because the ATM module has been loaded into the memory, it will not execute the atm.py file at this time, but directly find the name CMD func in the memory. The same dictionary is used inside the decorator, that is, the dictionary in the atm.py file.
The way to call the ATM function is different when executing the atm.py file directly.
Execute the atm.py file. When the program goes to cmd_func = {}, the program will open up a memory space for the dictionary.
Later, execute the atm function, enter the atm function, and when auto append() is encountered, it will jump to the auto append function.
In auto append, because of importing login, the program will enter funcs.py file. Because of the existence of the decorator, the program will enter the decorator auto function.
In auto, it is more critical, because to use the CMD func dictionary, the program needs to import the dictionary from the atm.py file again.
Now, there is no ATM module in memory (because the file is still executing). According to the search path (memory, built-in, sys.path) when importing the module, the atm.py file will be executed again. When encountering cmd_func = {}, a space will be opened in the memory to store the dictionary again.
So the dictionary we use in the decorator is the second generated dictionary, while the dictionary used inside the atm function is the first generated dictionary. They are in different scopes.
So the key of this problem lies in: the order of importing atm.py module in two ways is different, resulting in the first way that the whole program uses a dictionary; the second way opens up two dictionaries, we operate the second dictionary later, but the program uses the first one.
The core knowledge point is the search order of module import: memory, built-in, sys.path path