Как внедрить код в класс и присвоить значения полям класса во время выполнения?

#python #python-3.x #metaclass #python-internals

#python #python-3.x #метакласс #python-внутренности

Вопрос:

У меня есть файл спецификации BatchJob (batch.spec), что-то вроде приведенного ниже:

 python = <<EOF

def foo():
    return f"foo: I am {self.name}"

def bar():
    return f"bar: I am {self.name}"

EOF

<batch>
  name pamp;l_calculator
  exec echo %foo()%
</batch>
  

Я конвертирую этот файл в python dict с помощью https://github.com/etingof/apacheconfig

 from apacheconfig import *

with make_loader() as loader:
    config = loader.load('batch.spec')

# Print content of python dict
for key, value in config.items():
    print(key, value)

# output from print statement above
# python 
# def foo():
#     return f"foo: I am {self.name}"
#
# def bar():
#     return f"bar: I am {self.name}"
# batch {'name': 'pamp;l_calculator', 'exec': 'echo %foo()%'}
  

Теперь я преобразую этот python dict в объект BatchJobSpec, как показано ниже:

 class BatchJobSpec:

    def __init__(self, pythoncode , batch):
        self.pythoncode = pythoncode
        self.name = batch.get("name")
        self.exec = batch.get("exec")

batchjobspec = BatchJobSpec(config.get("python"), config.get("batch"))
  

Если я напечатаю поля batchjobspec, то я получу что-то вроде приведенного ниже

 print(batchjobspec.pythoncode)
print(batchjobspec.name)
print(batchjobspec.exec) 

# Output from above print statements
# def foo():
#     return f"foo: I am {self.name}"
#
# def bar():
#     return f"bar: I am {self.name}"
# pamp;l_calculator
# echo %foo()%
  

Проблема: я хочу, чтобы значение «batchjobspec.exec» интерполировалось при попытке доступа к нему, т. Е. оно не должно быть «echo %foo()%», а должно быть «echo foo: I am p amp; l_calculator».

т. е. каким-то образом в получателе полей я хочу проверить, есть ли синтаксис «% %». Если да, и если содержимое внутри «% %» содержит вызываемый объект, то я хочу вызвать этот вызываемый объект и добавить значение, возвращаемое из callable, к значению соответствующего поля. Я предполагаю, что мне также придется сделать эти вызываемые объекты доступными в BatchJobSpec dict .

Комментарии:

1. Выглядит как простой string.replace('%...%', name)

2. @stovfl да, заменяющая часть проста, но вызываемая (функция foo и bar в примере выше) не будет доступна непосредственно в классе BatchJobSpec dict. Вызываемые объекты представлены в виде форматированной строки внутри поля «pythoncode».

Ответ №1:

Комментарий: У меня есть foo, а также bar. exec(str) выполнит оба

В вашем self.pythoncode у вас есть определения таких функций, как def foo(): . Поэтому exec не выполняется, но определяет эти функции в локальном пространстве имен. Чтобы выполнить эти функции как a class methode , вы должны создать attribute , ссылающийся на эти локальные функции, в class самом.

  1. В этом примере имена функций известны заранее

     class BatchJobSpec:
        def __init__(self, pythoncode , batch):
            self.pythoncode = pythoncode
            self.name = batch['name']
            self._exec = ['for', 'bar']
            self.exec()
      
  2. Чтобы сделать self видимым в локальном пространстве имен, определите locals_ dict.
    exec строка self.pythoncode приводит к вставке ссылки на функции в locals_ . Чтобы использовать эти локальные ссылки как class methode и сделать их постоянными, используйте self.__setattr__(... .

         def exec(self):
            locals_ = {'self': self}
            exec(self.pythoncode, locals_)
            for attrib in self._exec:
                print('__setattr__({})'.format(attrib))
                self.__setattr__(attrib, locals_[attrib])
      
  3. Использование: Другой синтаксис python format , поскольку я использую python 3.5

     python = """
    def foo():
        return 'foo: I am {name}'.format(name=self.name)
    
    def bar():
        return 'bar: I am {name}'.format(name=self.name)
    """
    
    if __name__ == "__main__":
        batchjobspec = BatchJobSpec(config.get("python"), config.get("batch"))
        print('echo {}'.format(batchjobspec.foo()))
        print('echo {}'.format(batchjobspec.bar()))
      
  4. Вывод:

     __setattr__(foo)
    __setattr__(bar)
    echo foo: I am pamp;l_calculator
    echo bar: I am pamp;l_calculator
      

Вопрос Я хочу, чтобы значение «batchjobspec.exec» интерполировалось при попытке доступа к нему


Измените свой class BatchJobSpec на:

 class BatchJobSpec:

    def __init__(self, pythoncode , batch):
        self.pythoncode = pythoncode
        self.name = batch.get("name")
        self._exec = batch.get("exec")

    @property
    def exec(self):
        to_exec = 'foo'
        self.__setattr__(to_exec, lambda : exec('print("Hello world")'))

        self.foo()
        return None
  

Комментарии:

1. Спасибо за ответ, но, пожалуйста, обратите внимание, что функции «foo» и «bar» отсутствуют в классе. Они находятся внутри поля «self.pythoncode», как я могу внедрить эти функции в класс? Нужно ли мне добавить параметр setter для поля pythoncode и обновить class dict оттуда?

2. foo уже есть в self.pythoncode, я не хочу создавать никакого нового foo.

3. @LokeshAgrawal: Нет , в .pythoncode есть str , созданное self.foo является ссылкой на эту строку, выполняемую через exec(<code>)

4. exec выполнит весь код в str. В str может быть много функций. В качестве примера проверьте в моем вопросе, что у меня есть foo, а также bar. exec(str) выполнит оба.

5. Не могли бы вы, пожалуйста, обобщить это решение, не предполагая, что будут только функции foo и bar.