关于python:为以前定义的函数添加功能的优雅方式

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

希望这有帮助!