【问题标题】:Python __metaclass__ inheritance issuePython __metaclass__ 继承问题
【发布时间】:2011-12-04 22:30:05
【问题描述】:

我的问题是我使用元类将某些类方法包装在计时器中以用于记录目的。

例如:

class MyMeta(type):

    @staticmethod
    def time_method(method):
        def __wrapper(self, *args, **kwargs):
            start = time.time()
            result = method(self, *args, **kwargs)
            finish = time.time()
            sys.stdout.write('instancemethod %s took %0.3f s.\n' %(
                method.__name__, (finish - start)))
            return result
        return __wrapper

    def __new__(cls, name, bases, attrs):
        for attr in ['__init__', 'run']:
            if not attr in attrs:
                continue

            attrs[attr] = cls.time_method(attrs[attr])

        return super(MetaBuilderModule, cls).__new__(cls, name, bases, attrs)

我遇到的问题是我的包装器为每个 '__init__' 运行,即使我真的只希望它用于我正在实例化的当前模块。任何想要计时的方法也是如此。我不希望在任何继承的方法上运行时间,除非它们没有被覆盖。

class MyClass0(object):
    __metaclass__ = MyMeta
    def __init__(self):
        pass

    def run(self): 
        sys.stdout.write('running')
        return True


class MyClass1(MyClass0):
    def __init__(self): # I want this timed
        MyClass0.__init__(self) # But not this.
        pass

    ''' I need the inherited 'run' to be timed. '''

我已经尝试了一些东西,但到目前为止我都没有成功。

【问题讨论】:

    标签: python inheritance metaclass


    【解决方案1】:

    用属性保护计时码。这样,实际上只有对象上最外层的修饰方法才会被计时。

    @staticmethod
    def time_method(method):
        def __wrapper(self, *args, **kwargs):
            if hasattr(self, '_being_timed'):
                # We're being timed already; just run the method
                return method(self, *args, **kwargs)
            else:
                # Not timed yet; run the timing code
                self._being_timed = True  # remember we're being timed
                try:
                    start = time.time()
                    result = method(self, *args, **kwargs)
                    finish = time.time()
                    sys.stdout.write('instancemethod %s took %0.3f s.\n' %(
                        method.__name__, (finish - start)))
                    return result
                finally:
                    # Done timing, reset to original state
                    del self._being_timed
        return __wrapper
    

    仅计时最外层的方法与“除非没有被覆盖,否则不计时继承的方法”略有不同,但我相信它可以解决您的问题。

    【讨论】:

    • 感谢 Petr,效果很好。我是如此接近那个答案,我可以品尝到它。有时你需要另一双眼睛来理解我猜的疯狂。再次感谢! CB
    【解决方案2】:

    我不确定这与多重继承有关。

    问题在于MyClass0 的任何子类都必须是同一个元类的实例,这意味着MyClass1 是用MyMeta.__new__ 创建的,因此它的方法会被处理并包装在时序代码中。

    实际上,您需要的是 MyClass0.__init__ 在以下两种情况下以某种方式返回不同的东西:

    1. 直接调用时(直接实例化MyClass0,或者MyClass1不覆盖时),需要返回定时方法
    2. 在子类定义中调用时,需要返回原来的不定时方法

    这是不可能的,因为MyClass0.__init__ 不知道为什么会调用它。

    我看到三个选项:

    1. 使元类更复杂。它可以检查基类以查看它们是否已经是元类的实例;如果是这样,它可以制作它们的新副本,从正在构造的类中存在的方法中删除定时包装器。您不想直接改变基类,因为这会影响它们的所有使用(包括当它们被直接实例化时,或者当它们被覆盖不同方法的其他类子类化时) .这样做的一个缺点是它真的搞砸了 instanceof 关系;除非您通过创建基类的新子类(呃!)来构建基类的细微变化并缓存所有变化,以便您永远不会构造重复(呃!),否则您完全否定两个类共享一个基类的自然假设(它们可能只共享一个生成两个完全独立的基类的模板)。
    2. 使计时代码更复杂。有一个start_timingstop_timing 方法,如果在该方法已经计时时调用start_timing,您只需增加一个计数器,而stop_timing 只是减少一个计数器,并且仅在计数器达到零时停止计时。注意调用其他定时方法的定时方法;您需要为每个方法名称设置单独的计数器。
    3. 放弃元类,只在要显式计时的方法上使用装饰器,并通过某种方式获取未装饰的方法,以便覆盖定义可以调用它。这将涉及每次使用几行样板;与其他两个选项相比,这很可能会减少代码行数。

    【讨论】:

    • 感谢您的帮助,我将使用 2 号。我想让这段代码尽可能容易让人们阅读,但仍然强制计时/记录发生,所以我觉得元类路线是最好的方法。使用装饰器依赖于继承/抽象类的人来实际装饰他们的方法,而且我不得不假设只有基本的 Python 技能,让它尽可能自动化是我最好的方法感觉。再次感谢! CB
    猜你喜欢
    • 2011-08-25
    • 2019-04-10
    • 2010-10-30
    • 1970-01-01
    • 2012-08-08
    • 2021-05-08
    相关资源
    最近更新 更多