python中getattr详解

getattr详解

前言

这两天在优化腾讯云迁移平台(SmartMS)的中间件(go2cloud_api)时. 其中某些接口由于涉及多种服务器系统类型, 迁移类型的判断.导致往往一个接口动辄70-80行. 随便进行一个接口的修改, 调试, 参数的变更. 都将花费好几分钟的时间去缕缕中间的逻辑.加上同一个接口, 不同系统类型(主要是windows, Linux)涉及的技术栈不一样,同一个接口就有两个不同的代码风格的人进行编写. 最终导致代码结构, 函数,类乱七八糟. 刚好疫情期间, 人在湖北(欲哭无泪呀),时间充裕, 想着来进行一次代码重构. 先看一下最初的代码.

上面的代码:只是windows的部分代码. 因为还涉及到接口的公共参数校验, 不同迁移系统类型调用agent不同接口. 同一个接口使用了大量的if…else.

首先说明,接口本身并不复杂. 核心技术不在本篇范畴内. 有兴趣研究代码的或试用腾讯云迁移平台的(SmartMS),可以留言. 免费体验.

一.消除if…else

因为代码中涉及到太多的if…else. 本来想使用策略模式 + 工厂模式 (消除if…else). 但是由于项目起始没有考虑代码设计模式, 导致现在改动会花费增加很大的工作量. 所以只能想其他的办法.

  • 之前对jumpserver 进行二次开发时, 研究过核心代码. 发现代码中使用了大量的getattr, 里面getattr主要是用在函数调用上. 比如下面我截取部分代码

    
    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是python里的一个内建函数,在python的官方文档中:getattr()的解释:

    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.

    getattr()这个方法最主要的作用是实现反射机制。也就是说可以通过字符串获取方法实例。这也是python中的所说的自省这样,

    • 你就可以把一个类可能要调用的方法放在配置文件里,在需要的时候动态加载。
  • python里面跟getattr相关的有hasattr,setattr,delattr ,那么我们通过下面的例子,来详细的说说他们的用法。

二. 反射机制

  1. hasattr(object,name)

    • bool 判断object中是否具有name属性,例如:

      foo = test()
      
      hasattr(foo,’setName’) #判断setName是否存在,存在则返回True。
      

2.getattr(object,name,default)

  • 如果存在name属性(方法)则返回name的值(方法地址)否则返回default值。

    # foo.py
    
    def name():
        return "hello"
    
    # main.py
    import foo
    
    getattr(foo,'name',"default value") # 存在name属性,所以返回其value
    # "hello"
    
    getattr(foo,'test',None)
    # None
    
  • 这里函数的调用方式.

3.setattr(object,name,default)

  • 为类设置一个新的属性

    class Foo(object):
        def __init__(self,sex):
            self.sex = sex
            
    foo = Foo('man')      # 实例化类  
    setattr(Foo,'age',18) # 设置一个新的属性
    
    foo.age
    # 18
    
  • 改变原有的类属性的值

    class Foo(object):
        def __init__(self,sex):
            self.sex = sex
            
    foo = Foo('man')           # 实例化类  
    setattr(Foo,'sex','woman') # 设置一个新的属性
    
    foo.sex
    # woman
    

4. delattr(object,'name')

  • 删除类属性

    delattr(foo,'sex')  #删除属性sex,原值为`woman`
    
    getattr(foo,'sex','not find')
    #'not find''
    

三.getattr 详解

本篇重点是讲述getattr的用法

1. 函数

  • demo.py

    #!/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
    
  • main.py

    #!/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 ")
    

定义一个字符串,然后用getattr去执行,这样调用效果有些类似python的celery那种用globals的用法。 传递一个函数的名字,当然是字符串,用multiprocessing多进程调用这个方法。

2.类中使用

通过此方法优化文章开头提供的代码如下

类代码

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

after.png

简化后的代码,通过getattr判断传入的disk_type参数, 映射map中对应的 属性. 从而优化代码.

总结

getattr 内置属性加map方式可以简化if…else逻辑代码. 本代码并未考虑代码的性能因素. 抽时间比较一下.

原文地址:https://www.cnblogs.com/failymao/p/12490113.html