Python 元编程实现类方法的注册

Python元编程,听起来就是一个非常高大上的名词,确实,元编程这个概念我研究了一天也没怎么研究明白。其实这个概念真的是在实际应用场景中很难用到,但是说巧不巧,这还真的就被我碰上了,无奈之下,只好研究一番。

首先我们还是来看看应用需求:我的程序中要实现一个类似命令分发器的功能,也就是根据不同的命令字符串调用相应的函数,这个功能一般都是通过函数的字典实现的,我在最开始也是通过这种方式实现的,但是感觉还是有点别扭,毕竟命令的注册和实现离得有点远,很容易忘记注册或者修改的时候漏掉某一部分,并且这种方式明显不够pythonic。一般这种命令分发器都是通过一个装饰器实现的,装饰器可以直接对函数进行提取,然后保存到相应的字典,比如像是如下的这种写法:

from functools import wraps
commands = {}
def reg (name):   # 进行柯里化
    @wraps
    def wrapper(func):
        print("registered function {}".format(name))
        commands[name] = func
        return func
    return wrapper

def default_func():
    print('unknown command')

def dispatcher():
    while True:
        cmd = input('>>')
        if cmd.strip()=="":  #  删除空格
            break
        commands.get(cmd, default_func)()

@reg('f1') #注册函数
def func1():
    print('func1')
@reg('f2')
def func2():
    print('func2')
dispatcher()

上述的写法就非常友好,我们定义新的命令的时候,只需要通过@reg注册一下就可以了,简单方便快捷,并且代码可读性也比较高。不过在我的代码中,这种方式的实现是不成功的。因为我的命令方法都是在类中,属于类的一个方法,简单的进行装饰器注册,装饰器并不能拿到相应的方法,而只能在相应的方法运行的时候才能拿到,这个确实有点奇怪,不过总有解决方法,当然解决方法就是通过元编程实现。

所谓的元编程,其实就是控制类的新建和初始化行为,本来类是通过type函数进行例化的,通过元编程,我们就可以自己定义一个类的type,通过新定义的type对我们自己的类进行初始化,在这个初始化的过程中我们就可以给类加一些特殊的操作,比如注册类中的方法,挺起来时复杂的。听起来负载,实际上写起来更为复杂,我也不想多说,直接看看代码好了:

class DeviceType(type):
    """ Create new DeviceType from type """
    def __init__(cls, name, bases, attrs):
        cls.actions = {} # reset actions dict
        for key, val in attrs.items():
            # check if class method has cmd property
            cmds = getattr(val, 'cmd', None)
            if cmds is not None:
                # register all commands with function
                for cmd in cmds:
                    cls.actions[cmd] = val
                    # print("Register ", cmd, key)
        # add commands from parent classes
        for base in bases:
            cls.actions = dict(base.actions, **cls.actions)
      
                    
def cmd(*args):
    """ Decorator to register new commands """
    def decorator(f):
        # add cmd property
        f.cmd = tuple(args)
        return f
    return decorator

class DeviceBase(metaclass=DeviceType):
    """ Device Base class """
    actions = {} # add actions dict to class
    def __init__(self, name):
        self.name = name
        
    def execute(self, cmd):
        if(cmd in self.actions):
            self.actions[cmd](self, cmd)
        else:
            print("Command not registered!")
            
    @cmd("open")
    def cmd_open(self, cmd):
        print("Executed: ", cmd)
        
class UartClass(DeviceBase):
    def __init__(self, name):
        self.name = name
          
    @cmd("write")
    def cmd_write(self, cmd):
        print(self.name, "Executed: ", cmd)
        
class JlinkClass(DeviceBase):
    def __init__(self, name):
        self.name = name
          
    @cmd("halt")
    def cmd_halt(self, cmd):
        print(self.name, "Executed: ", cmd)

上述就是类方法注册的一个实例,代码虽然进行简化,但是还是可以看出其中的原理,首先我们定义一个新的类型,名字叫做DeviceType这个类型会遍历类中的所有方法,将方法中有cmd属性的按照属性的值添加到类的一个名称为actions的字典中。需要注意的是,每次类初始化的时候这个字典的内容会清空,所以我们要将类的父类中的字典和我们的字典合并,当然合并也还是有讲究的,就是子类的字典值不能被父类覆盖,否则程序运行会出现错误,比如类的方法只会调用父类不会调用子类中重写的方法。

当然如果字典不清空似乎可以解决这个问题,不过当字典不清空的时候,这个字典会注册所有子类中的命令,如果命令中出现重复就会相互覆盖,并且还会出现一个调用某个类不支持的方法(因为可以根据注册字典调用其它类的方法),这个并不是我们希望的。最后,因为类的初始化是从父类开始,慢慢到子类的,所以子类中可以清除字典而从父类中重新添加。

嗯,以上就是一个类方法的注册,总之功能实现就好,我也不想深究里面的东西,毕竟这玩意儿还是非常有深度的,我们来日有需求再见,👋👋


本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。

相关文章

发表新评论