【问题标题】:How can I add a delay to every method in a Python sub-class when I don't want to replicate every method in the parent class当我不想复制父类中的每个方法时,如何为 Python 子类中的每个方法添加延迟
【发布时间】:2012-01-26 22:30:09
【问题描述】:

如果这个问题已经在某处得到回答,我深表歉意,但如果有,我无法找到答案。

我想创建一个父类的子类,在每次调用相应的父类方法之前会有一个延迟(例如time.sleep())。我想这样做,我不需要在子类中复制每个父类方法。事实上,我希望有一个几乎可以与任何父类一起使用的通用方法——这样我什至不需要知道所有的父类方法。

实例化子类时会指定延迟。

例如:

class Parent():
    ....
    def method1(self):
        ....
    def method2(self):
        ....


class Child(Parent):
    def __init__(self, delay)
        self.delay = delay
        ....

child = Child(1)

调用child.method1() 会导致在调用Parent.method1() 之前延迟1 秒。

【问题讨论】:

  • 可以编辑父类来实现这种行为吗?

标签: python dynamic methods


【解决方案1】:

真的,你这里的设计涉及到一个策略对象。

您最好的方法是修复父类以包含对“延迟对象”的调用。默认延迟对象什么都不做。

这违反了“让我什至不需要知道所有父类的方法”所希望的功能集。

方法查找没有一个方便的__getmethod__ 对应于__getattribute__;这种差距使得很难利用 Python 的内部结构进行方法调用。

class Parent( object ):
    delay= ZeroDelay()
    def method1(self):
        self.delay()
        ....
    def method2(self):
        self.delay()
        ...

class ZeroDelay( object ):
    def __call__( self ):
        pass

class ShortDelay( ZeroDelay ):
    def __init__( self, duration=1.0 )
        self.duration= duration
    def __call__( self ):
        time.sleep( self.duration )

class Child( Parent ):
    delay= ShortDelay( 1 )

编辑:当然,你也可以装饰每个方法。

def delayed( delayer ):
    def wrap( a_method ):
        def do_delay( *args, **kw ):
            delayer()
            return a_method( *args, **kw )
        return do_delay
    return wrap


class Parent( object ):
    delay= ZeroDelay()
    @delayed( self.delay )
    def method1(self):
        self.delay()
        ....
    @delayed( self.delay )
    def method2(self):
        self.delay()
        ...

【讨论】:

  • 您提到缺少方便的__getMethod__。你能解释一下为什么__getattribute__ 不合适吗?我想这是有原因的,因为我的回答没有得到支持 ;-)
【解决方案2】:

你可以使用__getattribute__的方法实现你想要的

class Child(Parent):
    def __init__(self, delay):
        self.delay = delay
    def __getattribute__(self, name):
        attr = object.__getattribute__(self, name)
        if hasattr(attr, '__call__'):
            def proxFct(*args, **kwargs):
                time.sleep(object.__getattribute__(self, "delay"))
                return attr(*args, **kwargs)
            return proxFct
        else:
            return attr

更新:根据delnan的评论更新

更新2:根据delnan的第二条评论更新

【讨论】:

  • 这会延迟属性访问,而不是方法调用。即使您将其限制为方法访问,它仍然无法按预期使用绑定方法 (f = obj.method; for x in xs: f(xs))。
  • 我修复了我的Child-class 实现,以便仅在方法调用上使用sleep。我不明白你的边界方法是什么意思......你能更详细地解释一下吗?
  • 我看不出您的更新如何解决任何问题。如果检索到的值是真实的,它会休眠,如果它不可调用,它会在之后崩溃。至于绑定方法:考虑我在第一条评论中给出的示例,f = obj.method; for x in xs: f(xs)。尽管该方法被调用 len(xs) 次,但它在循环之前休眠一次。
  • @delnan 要么我错了你的意思,要么你错了。当我执行f = obj.method; for i in range(3): f() 时,程序会休眠 3 次......(我修复了可调用问题)
  • 对不起,我错了。我只浏览了您更新的代码。没想到你当时已经解决了这个问题。
【解决方案3】:

我认为之前给出的答案并没有真正解决您延迟父类中的所有方法的具体需求,并且不一定必须去装饰它们。您说您不想为了延迟它们而必须在子类中复制父类方法。这个答案使用来自 S.Lott 的相同延迟包装器,但也使用元类 (http://www.voidspace.org.uk/python/articles/metaclasses.shtml)

#!/usr/bin/env python

from types import FunctionType
import time


def MetaClassFactory(function):
    class MetaClass(type):
        def __new__(meta, classname, bases, classDict):
            newClassDict = {}
            for attributeName, attribute in classDict.items():
                if type(attribute) == FunctionType:
                    attribute = function(attribute)

                newClassDict[attributeName] = attribute
            return type.__new__(meta, classname, bases, newClassDict)
    return MetaClass


def delayed(func):
    def wrapped(*args, **kwargs):
        time.sleep(2)
        func(*args, **kwargs)
    return wrapped

Delayed = MetaClassFactory(delayed)

class MyClass(object):
    __metaclass__ = Delayed 

    def a(self):
        print 'foo'

    def b(self):
        print 'bar'

MetaClassFactory 将每个函数包装在延迟装饰器中。如果您想确保像 init 函数这样的某些内置函数没有延迟,您可以在 MetaClassFactory 中检查该名称并忽略它。

【讨论】:

  • 只延迟某些方法可能是明智的。例如,不以下划线开头的“公共”方法。否则,您可能会遇到严重的嵌套延迟。比如说,方法 a 经常调用琐碎的方法 b,例如 len。我不会采用“延迟所有方法”的方法,但我会给出一个明确的装饰方法列表。但是,我真的很喜欢这种元类方法,因为您不必为要延迟的每个方法编写高度冗余的代码。
  • 即我会将其增强为__metaclass__ = Delayed("abc", "def", "ghi")
  • @Anonymous - 是为了包装多个装饰器吗?是的,您可以让 MetaClassFactory 采用 *args 而不是单个函数,然后循环并将它们包装在 if type(attribute) == FunctionType:
【解决方案4】:

S.Lott 解决方案是一个很好的解决方案。如果您需要更多粒度(即仅延迟某些方法,而不是所有方法),您可以使用装饰器:

from time import sleep

def delayed(func):
    '''This is the decorator'''
    def wrapped(*args, **kwargs):
        sleep(2)
        func(*args, **kwargs)
    return wrapped

class Example(object):

    @delayed
    def method(self, str):
        print str

e = Example()
print "Brace! I'm delaying!"
e.method("I'm done!")

想法是在要删除的那些方法的定义之前添加@delayed


编辑:更精细:设置任意延迟:

from time import sleep

def set_delay(seconds):
    def delayed(func):
        '''This is the decorator'''
        def wrapped(*args, **kwargs):
            sleep(seconds)
            func(*args, **kwargs)
        return wrapped
    return delayed

class Example(object):

    @set_delay(1)
    def method(self, str):
        print str

    @set_delay(2)
    def method_2(self, str):
        print str

e = Example()
print "Brace! I'm delaying!"
e.method("I'm done!")
e.method_2("I'm also done!")

【讨论】:

  • 不知装饰器方案是否满足do this in such a way that I do not need to replicate each parent class method in the child class的要求?
  • @gecco - 当然不是。正如我所指定的,S.Lott 的解决方案是一个很好的解决方案,如果 OP 想要获得更多粒度,我的解决方案是一种替代方案。 :)
  • 哦,我的错...我以某种方式错过了您指定的这一点:-p 我已经对 S.Lott 的帖子投了赞成票,但我想知道为什么我的不是更好的解决方案?!
  • 我认为第一个 delayed 装饰器中缺少返回,应该是 return func(*args, **kwargs)
猜你喜欢
  • 1970-01-01
  • 2012-07-06
  • 1970-01-01
  • 1970-01-01
  • 2017-03-12
  • 1970-01-01
  • 1970-01-01
  • 2012-09-22
  • 1970-01-01
相关资源
最近更新 更多