Сохранить лямбда-выражения в файл - PullRequest
0 голосов
/ 19 октября 2018

Я реализую алгоритм стратегии развития в Python 3. Я создал класс под названием Individual, который считывает конфигурацию из файла (формат YAML), который выглядит следующим образом:

num_of_genes: 3
pre_init_gene:
    gene1: 1.0
    gene2: 1.0
    gene3: 1.0
metrics:
    - metric1
    - metric2
obj_funcs:
    obj_fun1: 'lambda x,y: x+y'
    obj_fun2: 'lambda x: (x**3)/2'

Идея состоит в том,что человек будет читать этот файл, чтобы получить его конфигурацию.Я знаю, что могу сохранить свое лямбда-выражение в виде строки, а затем вызвать на нем eval.

Однако, есть ли более Pythonic решение для этой проблемы?Мне не очень комфортно с ОО в Python, но я открыт для предложений.

Ответы [ 2 ]

0 голосов
/ 19 октября 2018

Я бы придерживался Zen of Python , в частности, "Явное лучше, чем неявное" и "Количество читабельности".

Так что хорошо бы иметь ваши функции в виде читаемых строк, определяющих лямбда-выраженияИдея, хотя, из соображений безопасности, вызов eval для загруженного строкового представления лямбда может не быть.Это опять-таки зависит от того, у кого есть доступ к файлу изменений и в какой системе они запущены.

В общем случае вам не нужно слишком сильно беспокоиться, если кто-то может (не случайно) внедрить что-либо, что приведет к рекурсивному удалению всех файлов насистема, если у них есть права доступа для входа в систему, с которыми они могут сделать это в любом случае.Однако, если, например, программное обеспечение работает в удаленной системе, и эти файлы можно редактировать через какой-либо веб-интерфейс, или если изменения в файле могут быть сделаны кем-то, кроме человека, использующего эти файлы, это то, что вы должны принять во внимание.

Если лямбды приходят из фиксированного набора, вы можете просто использовать их строковое представление в качестве поиска:

lambdas = {}
for l in [
   'lambda x,y: x+y',
   'lambda x: (x**3)/2',
   # some more
]:
   lambdas[l] = eval(l)

Затем вы можете использовать строку, загруженную из вашей конфигурации YAML, чтобы получить реальную лямбду иэту строку нельзя изменить, так как она должна соответствовать доступному набору лямбд, которые вы указали.Конечно, вы можете загрузить реальные лямбда-строки из файла, который можете изменить только вы, вместо того, чтобы жестко кодировать их в исходном коде.

Это IMO более явный, чем дамп реальной лямбды, в результате чего YAML выглядит следующим образом:

!!python/name:__main__.%3Clambda%3E

, что в любом случае требует небезопасной загрузки документа YAML.

Есливам нужно быть более гибким, чем использовать предопределенные лямбды, но вы не хотите небезопасно использовать eval, тогда другой возможностью является использование модуля AST в Python.Этот модуль позволяет безопасно вычислять унарные и двоичные операторы, но может быть расширен для обработки только тех функций (например, некоторых математических функций), которые вы хотите разрешить в своей лямбде.Я проделал аналогичное расширение в своем модуле нотации объектов Python ( PON ), добавив datetime и возможности отступления к оцененному входу AST.


Что-то ещеИМО должен улучшить свой YAML.Вместо использования gene1, gene2 в качестве ключей в отображении используйте последовательность и отметьте элементы:

pre_init_gene:
    - !Gene 1.0
    - !Gene 1.0
    - !Gene 1.0

или, альтернативно, пометьте последовательность:

pre_init_gene: !Genes
    - 1.0
    - 1.0
    - 1.0

Ваши лямбдыу меня та же «проблема», и я бы сделал что-то вроде:

obj_funcs:
   - !Lambda 'x, y: x+y'
   - !Lambda 'x: (x**3)/2'

, где объект, реализующий from_yaml classmethod для тега !Lambda, прозрачно выполняет оценку eval или AST.

0 голосов
/ 19 октября 2018

С cloudpickle вы можете сбросить лямбду до bytes.Затем вам нужно преобразовать bytes в str для записи в файл.

import cloudpickle
import base64


def lambda2str(expr):
    b = cloudpickle.dumps(expr)
    s = base64.b64encode(b).decode()
    return s


def str2lambda(s):
    b = base64.b64decode(s)
    expr = cloudpickle.loads(b)
    return expr


e = lambda x, y: x + y
s = lambda2str(e)      
print(s)           # => gASVNAEAAAAAAACMF2Nsb3VkcGlja2xlLmNsb3VkcGlja2xllIwOX2ZpbGxfZnVuY3Rpb26Uk5QoaACMD19tYWtlX3NrZWxfZnVuY5STlGgAjA1fYnVpbHRpbl90eXBllJOUjAhDb2RlVHlwZZSFlFKUKEsCSwBLAksCS0NDCHwAfAEXAFMAlE6FlCmMAXiUjAF5lIaUjCovVXNlcnMvYmxvd25oaXRoZXJtYS9wcm9qZWN0cy90ZXN0L3Rlc3QucHmUjAg8bGFtYmRhPpRLEUMAlCkpdJRSlEr/////fZSHlFKUf

# store s in file, read s from file

e2 = str2lambda(s)
print(e2(1, 1))    # => 2

Обратите внимание, что base64 избегает таких вещей, как \n в зашифрованной строке, которые могут испортить структуру файла.decode() просто преобразует bytes в str, чтобы его можно было записать в файл.

Это не краткое представление, а безопасное.Если ваша рабочая среда безопасна, не стесняйтесь использовать читаемую версию!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...