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
的字典中。需要注意的是,每次类初始化的时候这个字典的内容会清空,所以我们要将类的父类中的字典和我们的字典合并,当然合并也还是有讲究的,就是子类的字典值不能被父类覆盖,否则程序运行会出现错误,比如类的方法只会调用父类不会调用子类中重写的方法。
当然如果字典不清空似乎可以解决这个问题,不过当字典不清空的时候,这个字典会注册所有子类中的命令,如果命令中出现重复就会相互覆盖,并且还会出现一个调用某个类不支持的方法(因为可以根据注册字典调用其它类的方法),这个并不是我们希望的。最后,因为类的初始化是从父类开始,慢慢到子类的,所以子类中可以清除字典而从父类中重新添加。
嗯,以上就是一个类方法的注册,总之功能实现就好,我也不想深究里面的东西,毕竟这玩意儿还是非常有深度的,我们来日有需求再见,👋👋
最后更新于 2020-02-17 01:13:33 并被添加「python 编程」标签,已有 3195 位童鞋阅读过。
本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。