Python изменить __metaclass__ для всей программы - PullRequest
2 голосов
/ 02 декабря 2009

РЕДАКТИРОВАТЬ: Обратите внимание, что это действительно плохая идея, чтобы сделать в производственном коде. Это было просто интересно для меня. Не делай этого дома!

Можно ли изменить переменную __metaclass__ для всей программы (интерпретатора) в Python?

Этот простой пример работает:

class ChattyType(type):
    def __init__(cls, name, bases, dct):
        print "Class init", name
        super(ChattyType, cls).__init__(name, bases, dct)

__metaclass__= ChattyType


class Data:
    pass

data = Data() # prints "Class init Data"
print data

но я бы хотел иметь возможность изменять __metaclass__ для работы даже в подмодулях. Так, например (файл m1.py):

 class A:
       pass

 a=A()
 print a

файл main.py:

class ChattyType(type):
    def __init__(cls, name, bases, dct):
        print "Class init", name
        super(ChattyType, cls).__init__(name, bases, dct)

__metaclass__= ChattyType

import m1 # and now print "Class init A"

class Data:
    pass

data = Data() # print "Class init Data"
print data

Я понимаю, что глобальный __metaclass__ больше не работает в Python 3.X, но это не моя забота (мой код, если доказательство концепции). Так есть ли способ сделать это в Python-2.x?

Ответы [ 3 ]

7 голосов
/ 02 декабря 2009

Функция "global __metaclass__" в Python 2 предназначена для работы только для каждого модуля (просто подумайте, что это может привести к хаосу, в противном случае, навязывая свой собственный метакласс для всех импортированных вами библиотечных и сторонних модулей). с этого момента - дрожь!) Если для вас очень важно «тайно» изменить поведение всех модулей, которые вы импортируете, с определенной точки и далее, по какой-либо причине из-за клоаки и кинжала, вы можете сыграть очень грязные трюки с ловушкой импорта (в худшем случае сначала скопировать источники во временное местоположение, изменяя их ...), но усилия будут соразмерны огромности поступка, что представляется целесообразным; -)

4 голосов
/ 02 декабря 2009

Хорошо; ИМО это грубая, волосатая, темная магия. Вы не должны использовать это, возможно когда-либо, но особенно не в производственном коде. Однако это любопытно, просто ради любопытства.

Вы можете написать собственный импортер, используя механизмы, описанные в PEP 302 и более подробно обсуждаемые в PyMOTW Дуга Хеллмана: Модули и импорт . Это дает вам инструменты для выполнения задуманной вами задачи.

Я реализовал такого импортера только потому, что мне было любопытно. По сути, для модулей, которые вы задаете с помощью переменной класса __chatty_for__, он вставит пользовательский тип в качестве переменной __metaclass__ в импортированные модули __dict__, до кода, который будет оценен. Если рассматриваемый код определяет свой собственный __metaclass__, он заменит тот, который был предварительно вставлен импортером. Было бы нежелательно применять этот импортер к любым модулям, прежде чем тщательно продумать, что он с ними сделает.

Я не написал много импортеров, поэтому, возможно, я сделал одну или несколько глупостей, когда писал эту. Если кто-то заметит недостатки / угловые случаи, которые я пропустил в реализации, оставьте комментарий.

исходный файл 1:

# foo.py
class Foo: pass

исходный файл 2:

# bar.py
class Bar: pass

исходный файл 3:

# baaz.py
class Baaz: pass

и главное событие:

# chattyimport.py
import imp
import sys
import types

class ChattyType(type):
    def __init__(cls, name, bases, dct):
        print "Class init", name
        super(ChattyType, cls).__init__(name, bases, dct)

class ChattyImporter(object):

    __chatty_for__ = []

    def __init__(self, path_entry):
        pass

    def find_module(self, fullname, path=None):
        if fullname not in self.__chatty_for__:
            return None
        try:
            if path is None:
                self.find_results = imp.find_module(fullname)
            else:
                self.find_results = imp.find_module(fullname, path)
        except ImportError:
            return None
        (f,fn,(suf,mode,typ)) = self.find_results
        if typ == imp.PY_SOURCE:
            return self
        return None

    def load_module(self, fullname):
        #print '%s loading module %s' % (type(self).__name__, fullname)
        (f,fn,(suf,mode,typ)) = self.find_results
        data = f.read()
        if fullname in sys.modules:
            module = sys.modules[fullname]
        else:
            sys.modules[fullname] = module = types.ModuleType(fullname)

        module.__metaclass__ = ChattyType
        module.__file__ = fn
        module.__name__ = fullname
        codeobj = compile(data, fn, 'exec')
        exec codeobj in module.__dict__
        return module

class ChattyImportSomeModules(ChattyImporter):
    __chatty_for__ = 'foo bar'.split()

sys.meta_path.append(ChattyImportSomeModules(''))

import foo # prints 'Class init Foo'
import bar # prints 'Class init Bar'
import baaz
1 голос
/ 02 декабря 2009

Неа. (Это особенность!)

...