Создание динамического / динамического метода (генерация кода) в Python - PullRequest
41 голосов
/ 10 февраля 2009

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

Я придумала решение, сочетающее 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 ]

70 голосов
/ 11 февраля 2009

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



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

12 голосов
/ 10 февраля 2009

Строки документации функций и имена являются изменяемыми свойствами. Вы можете делать все что угодно во внутренней функции или даже иметь несколько версий внутренней функции, которые 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
8 голосов
/ 10 февраля 2009

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, проблема закрытия должна быть решена.

0 голосов
/ 17 февраля 2018
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()
0 голосов
/ 29 марта 2010

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

Недавно мне нужно было сгенерировать динамическую функцию для привязки каждого пункта меню для открытия определенного кадра на 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
...