Создание динамического / динамического метода (генерация кода) в Python

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

Я придумал решение, объединяющее exec а также setattrВот пример:

class Viking(object):
    def __init__(self):
        code = '''
            def dynamo(self, arg):
                """ dynamo's a dynamic method!
                """
                self.weight += 1
                return arg * self.weight
            '''
        self.weight = 50

        d = {}
        exec code.strip() in d
        setattr(self.__class__, 'dynamo', d['dynamo'])


if __name__ == "__main__":
    v = Viking()
    print v.dynamo(10)
    print v.dynamo(10)
    print v.dynamo.__doc__

Есть ли лучший / безопасный / более идиоматический способ достижения того же результата?

5 ответов

Решение

Основано на коде Терана, но распространяется на методы классов:



class Dynamo(object):
    pass

def add_dynamo(cls,i):
    def innerdynamo(self):
        print "in dynamo %d" % i
    innerdynamo.__doc__ = "docstring for dynamo%d" % i
    innerdynamo.__name__ = "dynamo%d" % i
    setattr(cls,innerdynamo.__name__,innerdynamo)

for i in range(2):
    add_dynamo(Dynamo, i)

d=Dynamo()
d.dynamo0()
d.dynamo1()

Который должен напечатать:


in dynamo 0
in dynamo 1

Строки документов и имена функций являются изменяемыми свойствами. Вы можете делать все что угодно во внутренней функции или даже иметь несколько версий внутренней функции, которые makedynamo() выбирает между. Нет необходимости строить код из строк.

Вот фрагмент из интерпретатора:

>>> def makedynamo(i):
...     def innerdynamo():
...         print "in dynamo %d" % i
...     innerdynamo.__doc__ = "docstring for dynamo%d" % i
...     innerdynamo.__name__ = "dynamo%d" % i
...     return innerdynamo

>>> dynamo10 = makedynamo(10)
>>> help(dynamo10)
Help on function dynamo10 in module __main__:

dynamo10()
    docstring for dynamo10

Python позволит вам объявить функцию в функции, поэтому вам не нужно делать exec обман.

def __init__(self):

    def dynamo(self, arg):
        """ dynamo's a dynamic method!
        """
        self.weight += 1
        return arg * self.weight
    self.weight = 50

    setattr(self.__class__, 'dynamo', dynamo)

Если вы хотите иметь несколько версий функции, вы можете поместить все это в цикл и изменить то, что вы называете их в setattr функция:

def __init__(self):

    for i in range(0,10):

        def dynamo(self, arg, i=i):
            """ dynamo's a dynamic method!
            """
            self.weight += i
            return arg * self.weight

        setattr(self.__class__, 'dynamo_'+i, dynamo)
        self.weight = 50

(Я знаю, что это не очень хороший код, но в нем есть смысл). Что касается настройки строки документации, я знаю, что это возможно, но мне придется искать это в документации.

Редактировать: Вы можете установить строку документации через dynamo.__doc__так что вы можете сделать что-то вроде этого в вашем теле цикла:

dynamo.__doc__ = "Adds %s to the weight" % i

Другое редактирование: с помощью @eliben и @bobince, проблема закрытия должна быть решена.

class Dynamo(object):
    def __init__(self):
        pass

    @staticmethod
    def init(initData=None):
        if initData is not None:
            dynamo= Dynamo()
            for name, value in initData.items():
                code = '''
def %s(self, *args, **kwargs):
%s
                            ''' % (name, value)
                result = {}
                exec code.strip() in result
                setattr(dynamo.__class__, name, result[name])

            return dynamo

        return None

service = Dynamo.init({'fnc1':'pass'})
service.fnc1()

Немного более общее решение:

Вы можете вызвать любой метод экземпляра класса Dummy. Строка документации создается на основе имени метода. Демонстрируется обработка любых входных аргументов путем их простого возврата.

Код

class Dummy(object):

    def _mirror(self, method, *args, **kwargs):
        """doc _mirror"""
        return args, kwargs

    def __getattr__(self, method):
        "doc __getattr__"

        def tmp(*args, **kwargs):
            """doc tmp"""
            return self._mirror(method, *args, **kwargs)
        tmp.__doc__ = (
                'generated docstring, access by {:}.__doc__'
                .format(method))
        return tmp

d = Dummy()    
print(d.test2('asd', level=0), d.test.__doc__)
print(d.whatever_method(7, 99, par=None), d.whatever_method.__doc__)

Выход

(('asd',), {'level': 0}) generated docstring, access by test.__doc__
((7, 99), {'par': None}) generated docstring, access by whatever_method.__doc__

Извините за мой плохой английский.

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

Во-первых, я создаю список отображения между пунктом меню и фреймом.

menus = [(self.menuItemFile, FileFrame), (self.menuItemEdit, EditFrame)]

первый элемент в отображении - это элемент меню, а последний элемент - открываемая рамка. Затем я связываю событие wx.EVT_MENU от каждого пункта меню до определенного кадра.

for menu in menus:
    f = genfunc(self, menu[1])
    self.Bind(wx.EVT_MENU, f, menu[0])

Функция genfunc - это динамический построитель функций, вот код:

def genfunc(parent, form):
    def OnClick(event):
        f = form(parent)
        f.Maximize()
        f.Show()
    return OnClick
Другие вопросы по тегам