Python __metaclass__ проблема наследования - PullRequest
4 голосов
/ 11 октября 2011

Моя проблема в том, что я использую метакласс, чтобы обернуть определенные методы класса в таймер для целей регистрации.

Например:

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. '''

Я пробовал несколько вещей, но до сих пор не добился успеха.

Ответы [ 2 ]

4 голосов
/ 11 октября 2011

Защитите временной код с атрибутом. Таким образом, только самый внешний декорированный метод на объекте будет фактически синхронизирован.

@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

Синхронизация только самого внешнего метода немного отличается от «синхронизации не унаследованных методов, если они не переопределяются», но я считаю, что это решает вашу проблему.

1 голос
/ 11 октября 2011

Я не уверен, что это как-то связано с множественным наследованием.

Проблема в том, что любой подкласс MyClass0 должен быть экземпляром того же метакласса, что означает, что MyClass1 создается с MyMeta.__new__, поэтому его методы обрабатываются и заключаются в код синхронизации.

По сути, вам нужно, чтобы MyClass0.__init__ каким-то образом возвращал что-то другое в двух следующих обстоятельствах:

  1. При прямом вызове (создание экземпляра MyClass0 напрямую или когда MyClass1 не переопределяет его), он должен возвращать синхронизированный метод
  2. При вызове в определении подкласса он должен возвращать исходный несвязанный метод

Это невозможно, поскольку MyClass0.__init__ не знает , почему он вызывается.

вижу три варианта:

  1. Сделать метакласс более сложным. Он может проверить базовые классы, чтобы увидеть, являются ли они уже экземплярами метакласса; если это так, он может создать новую их копию, которая удалит временную оболочку из методов, присутствующих в создаваемом классе. Вы не хотите напрямую изменять базовые классы, так как это повлияет на все их использования (в том числе, когда они создаются непосредственно или когда они подклассируются другими классами, которые переопределяют другие методы). Недостатком этого является то, что действительно портит отношения экземпляра; если вы не создадите небольшие вариации базовых классов, создав их новые подклассы (тьфу!) и кэшируя все вариации, чтобы никогда не создавать дубликаты (тьфу!), вы полностью аннулируете естественные предположения, что два класса совместно используют базовый класс ( использовать только шаблон, из которого были сгенерированы два полностью независимых базовых класса).
  2. Сделать временной код более сложным. Есть метод start_timing и stop_timing, и если start_timing вызывается, когда метод уже рассчитан, вы просто увеличиваете счетчик, а stop_timing просто уменьшает счетчик и останавливает синхронизацию только тогда, когда счетчик достигает нуля. Будьте осторожны с синхронизированными методами, которые вызывают другие синхронизированные методы; вам нужно иметь отдельные счетчики для каждого имени метода.
  3. Откажитесь от метаклассов и просто используйте декоратор для методов, которые вы хотите явно синхронизировать, с некоторым способом получить недекорированный метод, чтобы переопределенные определения могли его вызывать. Это будет включать в себя пару линий котельной плиты на использование; вполне возможно, что это приведет к меньшему количеству строк кода, чем любой из двух других вариантов.
...