【问题标题】:Elegant way to add functionality to previously defined functions向先前定义的函数添加功能的优雅方式
【发布时间】:2012-06-12 00:45:01
【问题描述】:

如何将两个函数组合在一起

我有一个类控制一些硬件:

class Heater()
    def set_power(self,dutycycle, period)
       ...
    def turn_on(self)
       ...
    def turn_off(self)

还有一个连接到数据库并处理实验的所有数据记录功能的类:

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

现在,在我的程序 recipe.py 中,我想做这样的事情:

        log = DataLogger()

        @DataLogger_decorator
        H1 = Heater()

        log.start(60)

        H1.set_power(10,100)
        H1.turn_on()
        sleep(10)
        H1.turn_off()
        etc

数据记录器记录 H1 上的所有操作。我可以更改任何涉及的类,只是寻找一种优雅的方式来做到这一点。理想情况下,硬件功能与数据库和 DataLogger 功能保持分离。理想情况下,DataLogger 可重复用于其他控制和测量。

【问题讨论】:

  • 您是否知道@decorator 只是非常简单的语法糖,它的意义何在?
  • 是的,我猜到一半了,我的印象是我仍然怀念可以用它完成的深层技巧。也许装饰器的事情根本没有帮助。

标签: python decorator


【解决方案1】:

对于这种情况,我更喜欢将 DataLogger 用作 BaseClass 或 Mixin 用于其他类,而不是尝试做某种装饰器魔术(这对我来说并没有真正点击作为使用装饰器的 Python 方式)

例如:

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

这样你就可以做到:

h1 = Heater()
h1.startlog(5)
h1.do_other_stuff()

将其用作现有类的 mixin 的示例:

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 中成功使用 mixins 的关键是了解方法解析顺序 (MRO),尤其是对于 super,如何在多重继承情况下工作。请参阅this 关于协作多重继承。

____________________________________________________________________

替代方法:使用包装类

如果 Mixin 方法不适用于您的方案,另一种选择是使用 DataLogger 作为要记录的对象的包装类。基本上,Data Logger 会接受一个对象在其构造函数中进行登录,如下所示:

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

我不确定执行了哪种类型的日志记录或监控,以及您是否需要访问正在记录的对象,或者它是否是独立的。如果前者,大概是 Heater、Valve 等都实现了 DataLogger 关心的相同功能,那么无论它们是什么类,您都可以为它们记录。 (这是 Python 等动态语言的一个方便的核心功能,称为“鸭子类型”,您可以在其中对不同的类型进行操作,只要这些类型实现了您关心的功能或属性。“如果它像鸭子一样嘎嘎叫...... ")

使用包装类方法,您的代码可能看起来更像这样:

h1 = Heater() 
log = DataLogger(h1)
log.start(60)
h1.set_power(10,100)
h1.turn_on()
sleep(10)
h1.turn_off()

希望这会有所帮助!

【讨论】:

  • 感谢您的帮助。此外,我们还有类:Valve()、Temperature()、Pressure() 等等。我认为如果我在添加它们时不必更改 Logger 和 Heater 会更好。看来“mixin”是我进一步研究的关键词,谢谢!
  • Mixin 只是类的另一种说法,它可以用作补充基类,为其他类添加额外的功能。根据定义,Mixins 意味着多重继承方案。使用我上面的 Mixin,您可以在不更改基类(阀门、温度、压力等)的情况下添加功能,方法是为每个基类创建一个两行子类,例如包含 Datalogger 的 LoggingHeater mixin 在继承列表中排在第一位。
  • 如果我上面描述的 Mixin 方法或他的回答中描述的装饰器方法 thg435 不合适,另一种选择是使用 DataLogger 作为要记录的对象的包装类。这可能是三种方法中最容易理解的。我在上面添加了代码来演示。我注意到您是 SO 新手,请务必标记一个接受的答案迟早,以获得良好的业力,因此您一定会得到未来问题的答案。
【解决方案2】:

您可以装饰 Heater 并将 Logger 作为参数提供给装饰器:

# 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()

【讨论】:

  • 感谢您的帮助。如果记录器的选择没有硬编码在加热器的硬件特定类中,那就太好了。
  • @Bastiaan,不是。装饰器是语法糖,可以在类定义之后应用。例如。如果Heater 在定义时没有被修饰,应用程序代码可以创建my_logger 和别名Heater = with_logger(my_logger)(Heater)。然后所有后来的Heater 对象都会有日志记录。
猜你喜欢
  • 1970-01-01
  • 2012-06-25
  • 1970-01-01
  • 2011-10-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-28
  • 2012-12-22
相关资源
最近更新 更多