【问题标题】:How to add pre/post methods to class. Python如何将前/后方法添加到类。 Python
【发布时间】:2015-05-26 18:26:19
【问题描述】:

假设我有一个 A 类,它有一堆方法,但我希望它在调用每个方法之前和之后运行某些行。 例如:我希望我的 Dog 类在每次调用 bark() 或 run() 时运行 before() 和 after()。

class Dog():
    def __init__(self, sound, speed):
        self.sound = sound
        self.speed = speed

    def before(self):
        check_some_things(self)

    def after(self):
        do_some_things(self)

    def bark(self):
        sound(self.sound)

    def run(self):
        move(self.speed)

【问题讨论】:

  • 如果这些要被硬编码到你的类中,为什么不把它们硬编码到方法中呢?所以在sound(self.sound) 之前有self.before()
  • 假设我有 20 个方法,我觉得在每个方法的第一行/最后一行运行相同的代码很愚蠢。更重要的是,让我们想象一下 MegaDog 是 dog 所固有的,我也希望对 MegaDog 的方法运行 before()/after()。
  • 我不确定,但我们可以在这里使用上下文管理器,因为它有一种干净的方式来执行任何方法的开始和结束方法。

标签: python oop polymorphism


【解决方案1】:

你可以把它封装在一个装饰器中;如果self 上可用,则以下装饰器将调用beforeafter

import inspect
from functools import wraps

def before_and_after(f):
    @wraps(f)
    def wrapper(self, *args, **kw):
        if hasattr(self, 'before') and inspect.ismethod(self.before):
            self.before()
        result = f(self, *args, **kw)
        if hasattr(self, 'after') and inspect.ismethod(self.after):
            self.after()
        return result
    return wrapper

然后简单地应用于应该被包装的方法:

class Dog():
    def __init__(self, sound, speed):
        self.sound = sound
        self.speed = speed

    def before(self):
        check_some_things(self)

    def after(self):
        do_some_things(self)

    @before_and_after
    def bark(self):
        sound(self.sound)

    @before_and_after
    def run(self):
        move(self.speed)

装饰器假定它用于方法,例如生成的包装器期望 self 作为第一个参数。

如果这需要应用于不是beforeafter所有 方法,则可能需要一个元类:

class BeforeAfterMeta(type):
    def __new__(mcs, classname, bases, body):
        for name, value in body.items():
            if not inspect.isfunction(value):
                continue
            if name in ('before', 'after') or name[:2] + name[-2:] == '_' * 4:
                # before or after hook, or a special method name like __init__.
                continue
            body[name] = before_and_after(value)
        return super(BeforeAfterMeta, mcs).__new__(mcs, classname, bases, body)

然后您可以将其应用于您的班级:

class Dog(metaclass=BeforeAfterMeta):
    def __init__(self, sound, speed):
        self.sound = sound
        self.speed = speed

    def before(self):
        check_some_things(self)

    def after(self):
        do_some_things(self)

    def bark(self):
        sound(self.sound)

    def run(self):
        move(self.speed)

【讨论】:

    【解决方案2】:

    如果 pre 和 post 方法存在,您还可以使用装饰器函数检查您的 class Dog 并覆盖 run 方法:

    def PrePostMethod(inputClass):
        mainRun    = inputClass.run
        beforeFunc = inputClass.before if "before" in inputClass.__dict__ else None
        afterFunc  = inputClass.after if "after" in inputClass.__dict__ else None
        def new_run(self, *args, **kwargs):
            # you could inspect the given arguments if you need
            # to parse arguments into before and the after methods
            if beforeFunc:
                self.before()
            mainRun(self)
            if afterFunc:
                self.after()
        inputClass.run = new_run
        return inputClass
    
    @PrePostMethod
    class Dog(object):
    
        def __init__(self, sound, speed):
            self.sound = sound
            self.speed = speed
    
        def before(self):
            print "Do stuff before"
    
        def after(self):
            print "Do stuff after"
    
        def run(self):
            print "Do main process"
    
    
    Dog(1,2).run()
    

    要将run 中的参数和关键字参数解析为beforeafter,请使用inspect 类并遍历args 和kwargs 以解析正确的参数。

    from inspect import getargspec
    def argHandler(method, *args, **kwargs):
        method  = getargspec(method)
        mArgs   = method.args
        mKwargs = method.keywords
        rArgs    =  args[:len(mArgs)-1]
        rKwargs  = { k:v for k,v in kwargs.iteritems() if k in mKwargs }
        leftArgs = len(mArgs)-len(rArgs)
        if len(rKwargs):
            rKwargs = [ rKwargs[k] for k in mArgs[:leftArgs-1]]
            rArgs  += rKwargs
        return rArgs
    
    def PrePostMethod(inputClass):
        mainRun    = inputClass.run
        beforeFunc = inputClass.before if "before" in inputClass.__dict__ else None
        afterFunc  = inputClass.after if "after" in inputClass.__dict__ else None
        def new_run(self, *args, **kwargs):
            if beforeFunc:
                nargs = argHandler(self.before, *args, **kwargs)
                if nargs: self.before( *nargs)
                else: self.before()
            nargs = argHandler(mainRun, *args, **kwargs)
            if nargs: mainRun(self, *nargs)
            else: mainRun(self)
            if afterFunc:
                nargs = argHandler(self.after, *args, **kwargs)
                if nargs:  self.after( *nargs)
                else: self.after()
        inputClass.run = new_run
        return inputClass
    

    【讨论】:

      【解决方案3】:

      您可以使用许多不同的方法来做到这一点。但我认为最好的方法是,用 Pre- 和 Post-Methods 定义一个类并重新定义它的对象隐藏方法:__enter____exit__。要使用它们,只需使用复合语句 with 调用类。

       class pre_post(object):
      
          def __enter__(self):
              print "Enter check method.."
      
          def __exit__(self, type, value, tb):
              print "Exit check method.."
      
      class dog(object):
      
          def run(self, checkups=True):
              if checkups:
                  with pre_post() as pp:
                      print "My stuff.."
              else:
                  print "My stuff.."
      
      dog().run(True)
      

      这将为您提供以下结果:

      Enter check method..
      My stuff..
      Exit check method..
      

      希望对你有帮助。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-06-02
        • 2020-11-08
        • 2013-01-16
        • 2012-08-21
        • 1970-01-01
        • 1970-01-01
        • 2011-07-28
        • 2016-04-04
        相关资源
        最近更新 更多