Создание динамического метода/метода времени выполнения (генерация кода) в Python
-
22-08-2019 - |
Вопрос
Мне нужно сгенерировать код для метода во время выполнения.Важно иметь возможность запускать произвольный код и иметь строку документации.
Я придумал решение, объединив 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__
Есть ли лучший/более безопасный/более идиоматический способ достижения того же результата?
Решение
На основе кода Терана, но с расширением его на методы классов:
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()
Простите меня за мой плохой английский.
Недавно мне нужно было создать динамическую функцию для привязки каждого пункта меню к открытию определенного кадра на 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 function — это построитель динамических функций, вот код:
def genfunc(parent, form):
def OnClick(event):
f = form(parent)
f.Maximize()
f.Show()
return OnClick