python(27) reflection mechanism

1. What is reflection?

Its core essence is actually a string-based event-driven operation, which manipulates the properties or methods of objects in the form of strings.
 

2. The advantages of reflection

A concept is proposed to understand what its advantages are, so that we can understand why reflection is used.

2.1 Scene Construction

Develop a website, which consists of two files, one is the file commons.py for specific operations, and the other is the entry file visit.py
Requirement: You need to set the entry file to let the user enter the url, and perform the corresponding operation according to the url entered by the user

# commons.py
def login():
    print("This is a landing page!")


def logout():
    print("This is an exit page!")


def home():
    print("This is the main page of the website!")
# visit.py
import commons


def run():
    inp = input("Please enter the page you want to visit url:   ").strip()
    if inp == 'login':
        commons.login()
    elif inp == 'logout':
        commons.logout()
    elif inp == 'index':
        commons.home()
    else:
        print('404')


if __name__ == '__main__':
    run()

After running the run method, the result is as follows:

Please enter the page you want to visit url:   login
 This is a landing page!

Question: The if judgment is used above, and the specified function is executed according to each url request. If there are 100 operations in commons.py, it is not appropriate to use the if judgment again.
Answer: Using the python reflection mechanism, the content of the commons.py file remains unchanged, modify the visit.py file, the content is as follows

import commons


def run():
    inp = input("Please enter the page you want to visit url:   ").strip()
    if hasattr(commons, inp):
        getattr(commons, inp)()
    else:
        print("404")


if __name__ == '__main__':
    run()

With these few lines of code, you can handle any number of page function calls in the ​​commons.py​​ file!
 

Built-in functions in reflection

 

getattr

def getattr(object, name, default=None): # known special case of getattr
    """
    getattr(object, name[, default]) -> value
    
    Get a named attribute from an object; getattr(x, 'y') is equivalent to x.y.
    When a default argument is given, it is returned when the attribute doesn't
    exist; without it, an exception is raised in that case.
    """
    pass

​​The first parameter of the getattr()​​​function needs to be an object. In the above example, I imported a custom commons module, and commons is an object; the second parameter is to specify a method name in the previous object .
​​getattr(x, 'y')​​​ is equivalent to executing ​x.y​​​. If the second parameter enters a method that does not exist in the previous object, the function will throw an exception and exit. So at this time, for the robustness of the program, we need to first determine whether the object has this method, so the ​​​hasattr()​​​function is used
 

hasattr

def hasattr(*args, **kwargs): # real signature unknown
    """
    Return whether the object has an attribute with the given name.
    
    This is done by calling getattr(obj, name) and catching AttributeError.
    """
    pass

The ​​hasattr()​​​function returns whether the object has an attribute with the specified name. The explanation of the source code also says that the implementation of this function actually calls the ​​​getattr()​​​function, but it catches the exception. So through this function, we can first determine whether there is this method in the object, and use ​​getattr()​​ to get the method.
 

setattr

def setattr(x, y, v): # real signature unknown; restored from __doc__
    """
    Sets the named attribute on the given object to the specified value.
    
    setattr(x, 'y', v) is equivalent to ``x.y = v''
    """
    pass

The ​​setattr()​​​ function is used to reassign the method in the specified object (assign the new function body/method body to the specified object name) and only take effect in the memory of this program running. ​​setattr(x, 'y', v)​​​ is equivalent to ​​x.y = v​
 

delattr

def delattr(x, y): # real signature unknown; restored from __doc__
    """
    Deletes the named attribute from the given object.
    
    delattr(x, 'y') is equivalent to ``del x.y''
    """
    pass

Delete the specified method in the specified object, special reminder: just delete the method in the memory of the running program this time, and it does not affect the content of the file
 

__import__ module reflection

Following the example of the above website, now a background file can no longer meet my needs. At this time, the background files need to be divided according to their functions. Now I have added a user.py file, which also needs to be imported to the home page to ready to call.
 

However, in the example of the above website, I have already written the method that can only specify the commons module to be called arbitrarily. Now that the user module has been added, then I have to use the if judgment again?
Answer: No, use the function __import__ that comes with Python
 

Since the import of the module also needs to use the Python reflection feature, the module name should also be added to the url, so now the url request becomes a form similar to ​​commons/visit​​​

# user.py
def add_user():
    print('Add user')


def del_user():
    print('delete users')
# commons.py
def login():
    print("This is a landing page!")


def logout():
    print("This is an exit page!")


def home():
    print("This is the main page of the website!")
# visit.py
def run():
    inp = input("Please enter the page you want to visit url:   ").strip()
    # modules represents the imported module, and func represents the method in the imported module
    modules, func = inp.split('/')
    obj_module = __import__(modules)
    if hasattr(obj_module, func):
        getattr(obj_module, func)()
    else:
        print("404")


if __name__ == '__main__':
    run()

Finally, the run function is executed, and the result is as follows:

Please enter the page you want to visit url:   user/add_user
 Add user

Please enter the page you want to visit url:   user/del_user
 delete users

Now we can understand the role of __import__, which is to import strings as modules.
 

But if my site structure becomes the following

|- visit.py
|- commons.py
|- user.py
|- lib
    |- __init__.py
    |- connectdb.py

Now I want to call the method in the ​​connectdb​​ module under the ​​lib​​​package in the ​​visit​​​page, or can I call it in the previous way?

# connectdb.py
def conn():
    print("connected mysql")
# visit.py
def run():
    inp = input("Please enter the page you want to visit url:   ").strip()
    # modules represents the imported module, and func represents the method in the imported module
    modules, func = inp.split('/')
    obj_module = __import__('lib.' + modules)
    if hasattr(obj_module, func):
        getattr(obj_module, func)()
    else:
        print("404")


if __name__ == '__main__':
    run()

Run the run command and the results are as follows:

Please enter the page you want to visit url:   connectdb/conn
404

The result shows that it cannot be found. In order to test the module under the call lib, I have abandoned support for all modules in the same level directory, so we need to view the __import__ source code

def __import__(name, globals=None, locals=None, fromlist=(), level=0): # real signature unknown; restored from __doc__
    """
    __import__(name, globals=None, locals=None, fromlist=(), level=0) -> module
    
    Import a module. Because this function is meant for use by the Python
    interpreter and not for general use, it is better to use
    importlib.import_module() to programmatically import a module.
    
    The globals argument is only used to determine the context;
    they are not modified.  The locals argument is unused.  The fromlist
    should be a list of names to emulate ``from name import ...'', or an
    empty list to emulate ``import name''.
    When importing a module from a package, note that __import__('A.B', ...)
    returns package A when fromlist is empty, but its submodule B when
    fromlist is not empty.  The level argument is used to determine whether to
    perform absolute or relative imports: 0 is absolute, while a positive number
    is the number of parent directories to search relative to the current module.
    """
    pass

​​__import__​​​There is a​​fromlist​​parameter in the function. The source code explains that if a module is imported in a package, if this parameter is empty, the package object will be return ed. If this parameter is not empty, then Returns the module object specified under the package, so we returned the package object above, so the result of 404 will be returned, now modified as follows:

# visit.py
def run():
    inp = input("Please enter the page you want to visit url:   ").strip()
    # modules represents the imported module, and func represents the method in the imported module
    modules, func = inp.split('/')
    # Only added fromlist=True
    obj_module = __import__('lib.' + modules, fromlist=True)
    if hasattr(obj_module, func):
        getattr(obj_module, func)()
    else:
        print("404")


if __name__ == '__main__':
    run()

Running the run method yields the following results:

Please enter the page you want to visit url:   connectdb/conn
 connected mysql

It was successful, but I wrote the lib prefix, which is equivalent to abandoning the two import functions of commons and user, so the above code is not perfect. After the requirements are complicated, it is still necessary to judge the requested url.

def getf(module, func):
    """
    take out the public part
    """
    if hasattr(module, func):
        func = getattr(module, func)
        func()
    else:
        print('404')


def run():
    inp = input("Please enter the page you want to visit url:   ").strip()
    if len(inp.split('/')) == 2:
        # modules represents the imported module, func represents the method in the imported module
        modules, func = inp.split('/')
        obj_module = __import__(modules)
        getf(obj_module, func)
    elif len(inp.split("/")) == 3:
        p, module, func = inp.split('/')
        obj_module = __import__(p + '.' + module, fromlist=True)
        getf(obj_module, func)


if __name__ == '__main__':
    run()

Running the run function yields the following results:

Please enter the page you want to visit url:   lib/connectdb/conn
 connected mysql

Please enter the page you want to visit url:   user/add_user
 Add user

Of course, you can also continue to optimize the code. Now only the ones with 1 slash and 2 slashes are judged. If there are more directory levels, this will not be considered for the time being. This time is to understand the reflection mechanism of python

Posted by sajidfiaz on Wed, 09 Nov 2022 05:18:12 +0300