Создание динамического / динамического метода (генерация кода) в 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