Создание динамического/времени выполнения (генерация кода) в Python
Мне нужно сгенерировать код для метода во время выполнения. Важно иметь возможность запускать произвольный код и иметь docstring.
Я придумал решение, объединяющее exec
и setattr
, здесь пример фиктивного примера:
class Viking(object):
def __init__(self):
code = '''
def dynamo(self, arg):
""" dynamo 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__
Есть ли лучший/более безопасный/более идиоматический способ достижения того же результата?
Ответы
Ответ 1
На основе кода Theran, но распространяя его на методы в классах:
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
Ответ 2
Функции docstrings и names являются изменяемыми свойствами. Вы можете делать все, что хотите во внутренней функции, или даже иметь несколько версий внутренней функции, которую выбирает 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
Ответ 3
Python позволит вам объявить функцию в функции, поэтому вам не нужно делать трюк exec
.
def __init__(self):
def dynamo(self, arg):
""" dynamo 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 a dynamic method!
"""
self.weight += i
return arg * self.weight
setattr(self.__class__, 'dynamo_'+i, dynamo)
self.weight = 50
(Я знаю, что это не отличный код, но он имеет смысл). Что касается установки docstring, я знаю это возможно, но мне придется искать его в документации.
Изменить. Вы можете установить docstring с помощью dynamo.__doc__
, чтобы вы могли сделать что-то подобное в своем теле цикла:
dynamo.__doc__ = "Adds %s to the weight" % i
Другое Редактирование. С помощью @eliben и @bobince проблема закрытия должна быть решена.
Ответ 4
Извините меня за плохой английский.
Мне недавно нужно создать динамическую функцию для привязки каждого элемента меню, чтобы открыть определенный фрейм на 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