Elegant way to add functionality to previously defined functions
如何将两个功能结合在一起
我有一个类控制一些硬件:
1 2 3 4 5 6 | class Heater() def set_power(self,dutycycle, period) ... def turn_on(self) ... def turn_off(self) |
以及一个连接到数据库并处理实验的所有数据记录功能的类:
1 2 3 4 5 | class DataLogger() def __init__(self) # Record measurements and controls in a database def start(self,t) # Starts a new thread to acquire and record measuements every t seconds |
号
现在,在我的program recipe.py中,我想做如下的事情:
1 2 3 4 5 6 7 8 9 10 11 12 | log = DataLogger() @DataLogger_decorator H1 = Heater() log.start(60) H1.set_power(10,100) H1.turn_on() sleep(10) H1.turn_off() etc |
其中h1上的所有操作都由数据记录器记录。我可以改变任何涉及的类,只是寻找一种优雅的方式来做到这一点。理想情况下,硬件功能与数据库和数据记录器功能保持分离。理想情况下,数据记录器可用于其他控制和测量。
您可以装饰加热器并向装饰师提供logger作为参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | # define the primitive logger class Logger(object): def log(self, func, args, kwargs): print"Logging %s %r %r" % (func, args, kwargs) # define the decorator # since it accepts an argument, it's essentially a decorator generator # which is supposed to return the actual decorator # which in turn adds a logger call to each method of the class def with_logger(logger): def method_wrapper(cls): def wrap(name, fun): def _(self, *a, **kw): logger.log(name, a, kw) return fun(self, *a, **kw) return _ for k in dir(cls): v = getattr(cls, k) if not k.startswith('__') and callable(v): setattr(cls, k, wrap(k, v)) return cls return method_wrapper # create a logger... my_logger = Logger() # ...and pass it to the decorator @with_logger(my_logger) class Heater(object): def set_power(self,dutycycle, period): pass def turn_on(self): pass def turn_off(self): pass # let's test! h = Heater() h.set_power(100, 200) h.turn_on() h.turn_off() |
。
在这个场景中,我更喜欢将数据记录器用作基类或其他类的混音器,而不是尝试执行某种装饰器魔术(这并不是真正将我作为使用装饰器的Python式方法来单击)。
例如。:
1 2 3 4 5 6 7 8 9 10 11 12 13 | class DataLogger(object): def __init__(self): # do init stuff def startlog(self, t): # start the log class Heater(DataLogger): def __init__(self): # do some stuff before initing your dataLogger super(Heater, self).__init__() # init the DataLogger #other functions |
这样你就可以做到:
1 2 3 | h1 = Heater() h1.startlog(5) h1.do_other_stuff() |
号
将其用作现有类的mixin的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class DataLoggerMixin(object): def __init__(self): # do your init things super(DataLogger, this).__init__() # this will trigger the next __init__ call in the inheritance chain (i.e. whatever you mix it with) class Heater(object): """ Here's a heater you have lying around that doesn't do data logging. No need to change it.""" # add a new child class with 2 lines, that includes the DataLoggerMixin as the first parent class, and you will have a new class with all the DataLogging functionality and the Heater functionality. class LoggingHeater(DataLoggerMixin, Heater): """ Now its a data logging heater""" pass # no further code should be necessary if you list DataLoggerMixin first in the base classes. >>> logging_heater = LoggingHeater() >>> logging_heater.start_log(5) >>> logging_heater.do_heater_stuff() |
在python中成功使用mixin的关键是理解方法解析顺序(mro),特别是对于super,如何在多重继承情况下工作。这是关于合作多重继承的。
1 | ____________________________________________________________________ |
。
替代方法:使用包装类
如果混合方法不适用于您的方案,另一个选项是使用数据记录器作为要记录的对象的包装器类。基本上,数据记录器会接受一个对象在其构造函数中进行登录,如下所示:
1 2 3 4 5 6 | class DataLogger(object) def __init__(self, object_to_log) self.object = object_to_log # now you have access to self.object in all your methods. # Record measurements and controls in a database def start(self,t) # Starts a new thread to aqcuire and reccord measuements every t secconds |
我不确定要进行哪种类型的日志记录或监视,也不确定您是否需要访问正在日志记录的对象,也不确定它是否独立。如果前者(可能是加热器、阀门等)都实现了数据记录器所关心的相同功能,那么您可以记录它们,而不管它们是什么类。(这是诸如python之类的动态语言的一个方便的核心特性,称为"duck-typing",在这里,只要类型实现了您所关心的函数或属性,就可以对不同的类型进行操作)。如果它像鸭子一样咯咯叫。……")
使用包装类方法,您的代码可能看起来更像这样:
1 2 3 4 5 6 7 | h1 = Heater() log = DataLogger(h1) log.start(60) h1.set_power(10,100) h1.turn_on() sleep(10) h1.turn_off() |
。
希望这有帮助!