These two days, we are optimizing Tencent's cloud migration platform. SmartMS When using the middleware (go2cloud_api). Some of the interfaces involved multiple server system types and the judgment of migration type. As a result, an interface tends to run on 70-80 lines. Modifying an interface, debugging, and changing parameters will take several minutes to unravel the logic in the middle. Adding the same interface, different system types (mainly windows, will take several minutes to unravel.Linux) involves different stacks of technologies, and there are two different code styles of people writing on the same interface. Ultimately, the code structure, functions, and classes are messy. Just during the epidemic, people in Hubei (want to cry without tears), have plenty of time, and want to do a code refactoring. First, look at the original code.
The above code is just part of the code for windows. Because it also involves checking the common parameters of interfaces, different migration system types call different interfaces of agent s. The same interface uses a lot of if...else.
First of all, the interface itself is not complex. Core technologies are not covered by this article. If you are interested in researching code or trying out the Tencent Cloud Migration Platform (SmartMS), you can leave a message. Free experience.
1. Eliminate if...else
Because there's too much if involved in the code...Others. Originally intended to use strategy mode + factory mode ( Eliminate if...else However, since the code design pattern was not taken into account at the beginning of the project, changes can now take a lot of work. You can only think of other options.
When you redeveloped jumpserver before, you studied the core code. You found that a lot of getattrs were used in the code, where getattrs were mainly used in function calls. For example, let me intercept some of the code below
def run_command(self, func_name, args): """ Attempts to run the given command. If the command does not execute, or there are any problems validating the given GET vars, an error message is set. func: the name of the function to run (e.g. __open) command_variables: a list of 'name':True/False tuples specifying which GET variables must be present or empty for this command. """ if not self.check_command_args(args): self.response['error'] = 'Invalid arguments' print("++++++++++++++++++++++++++++++++ not valid") return func = getattr(self, '_' + self.__class__.__name__ + func_name, None) if not callable(func): self.response['error'] = 'Command failed' return try: func() except Exception as e: self.response['error'] = '%s' % e logger.error("Error occur ------------------------------") logger.exception(e) logger.error("Error end ------------------------------")
Getattr is a built-in function in python. In python's official documentation, getattr() is interpreted as:
getattr(object, name[, default])
Return the value of the named attribute of object. name must be a string. If the string is the name of one of the object's attributes, the result is the value of that attribute. For example, getattr(x, 'foobar') is equivalent to x.foobar. If the named attribute does not exist, default is returned if provided, otherwise AttributeError is raised.
The primary function of getattr() is to implement the reflection mechanism.That is, you can get a method instance through a string.This is what python calls introspection.
- You can put a method that a class might call into a configuration file and load it dynamically when needed.
There are hasattr,setattr,delattr related to getattr in python, so let's go into more detail about their usage through the following examples.
2. Reflection mechanism
bool determines if an object has a name attribute, such as:
foo = test() hasattr(foo,'setName') #Determines whether setName exists, and returns True if it exists.
Returns the value of name (method address) if the name attribute (method) exists, otherwise returns the default value.
# foo.py def name(): return "hello" # main.py import foo getattr(foo,'name',"default value") # The name attribute exists, so its value is returned # "hello" getattr(foo,'test',None) # None
The way the function is called here.
Set a new property for the class
class Foo(object): def __init__(self,sex): self.sex = sex foo = Foo('man') # instantiate setattr(Foo,'age',18) # Set a new property foo.age # 18
Changing the value of the original class attribute
class Foo(object): def __init__(self,sex): self.sex = sex foo = Foo('man') # instantiate setattr(Foo,'sex','woman') # Set a new property foo.sex # woman
Delete Class Properties
delattr(foo,'sex') #Delete attribute sex, original value is `woman` getattr(foo,'sex','not find') #'not find''
3. getattr Details
This article focuses on the use of getattr
#!/usr/bin/env python # ~*~ coding: utf-8 ~*~ import time def hello(a, b): print('hello') return a, b def test_sleep(a, b): print('test_sleep') time.sleep(3) return a, b
#!/usr/bin/env python # ~*~ coding: utf-8 ~*~ import multiprocessing import demo def run(func, *args): print(getattr(demo, func)(*args)) if __name__ == "__main__": run('hello', "a", "b") pool = multiprocessing.Pool(processes=4) for i in range(10): pool.apply_async(run, ('hello', 'a', 'b')) pool.apply_async(run, ('test_sleep', 'a', 'b')) pool.close() pool.join() print("END ")
Define a string and execute it with getattr, which is somewhat like python's celery use of globals.Pass the name of a function, of course a string, and call this method with a multiprocessing multiprocess.
2. Use in Classes
This method optimizes the code provided at the beginning of the article as follows
class SrcAgentClient(CommAgentClient): ... @property async def system_disk_sync_status(self, action="system_migration_heartbeat"): ''' Get rsync server service status. :param action: a string,match the desAgent api:rsync_status_check :return: ''' url = self._api_base_url + action if await self._agent_status: response = await self.client.fetch(url, method="GET") data = json.loads(response.body.decode()) return data else: return self.response @property async def data_disk_sync_status(self, action="data_migration_heartbeat"): ''' Get rsync server service status. :param action: a string,match the desAgent api:rsync_data_status_check :return: ''' url = self._api_base_url + action if await self._agent_status: response = await self.client.fetch(url, method="GET") data = json.loads(response.body.decode()) return data else: return self.response
The simplified code uses getattr to determine the incoming disk_type parameter, map the corresponding attributes in the map, and optimize the code.
The getattr built-in attribute plus map simplifies if...else logic code. This code does not take into account the performance of the code. Take time to compare.