Нужно сопротивляться искушению использовать eval
, потому что eval
небезопасно и плохая практика . Это действительно опасно . Если вы можете быть уверены, что ввод безопасен, потому что вы управляете вводом: измените вывод предыдущего шага, чтобы вам не приходилось использовать eval
во-первых.
Так как ваш случай использования очень конкретен c (complex(a, b)
, где ни a
, ни b
не могут содержать запятые или скобки) легко написать шаблон регулярного выражения, который поможет заменить ваши сложные вызовы сложными литералами. Получив это, мы можем использовать ast.literal_eval
, чтобы выполнить безопасное преобразование из строки в числа, которые вы можете ввести numpy.
См. Следующие несколько определений :
import ast
import re
pattern = re.compile(r'complex\(([^)]+),([^)]+)\)')
def complex_from_match(match):
"""Converts a regex match containing 'complex(a, b)' to a complex number"""
re = float(match.group(1))
im = float(match.group(2))
return re + 1j*im
def literalify_complex(s):
"""Converts a string with a number of the form complex(a, b) into a string literal"""
return pattern.sub(lambda match: repr(complex_from_match(match)), s)
Здесь pattern
- это шаблон регулярного выражения, соответствующий подстроке формы complex(a, b)
. Для каждого совпадения match.group(1)
дает нам a
, а match.group(2)
дает b
.
Функция literalify_complex(s)
вызывает pattern.sub
для входной строки s
, которая будет вызывать переданный вызываемый ( лямбда) при каждом совпадении по шаблону pattern
в s
. Лямбда, которую я определил, использует функцию complex_from_match
, которая берет соответствующую строку complex(a,b)
и эффективно превращает ее в собственное python комплексное число a + 1j*b
. Затем это комплексное число преобразуется в repr
во время замены, эффективно превращая такие вещи, как complex(1, 3.4)
в 1+3.4j
. Вот как выглядит вывод:
>>> literalify_complex('[[complex(1,-1), complex(1,1)], [1, 1]]')
'[[(1-1j), (1+1j)], [1, 1]]'
>>> literalify_complex('[[complex(0,-3.14), complex(1.57337537832783243243,1e-100)], [1, 1]]')
'[[-3.14j, (1.5733753783278324+1e-100j)], [1, 1]]'
Теперь это строки, которые можно напрямую преобразовать во вложенные списки python, используя literal_eval
:
>>> ast.literal_eval(literalify_complex('[[complex(1,-1), complex(1,1)], [1, 1]]'))
[[(1-1j), (1+1j)], [1, 1]]
И тогда эти списки могут быть передано в numpy:
def crazy_complex_string_to_ndarray(s):
return np.array(ast.literal_eval(literalify_complex(s)))
# >>> crazy_complex_string_to_ndarray('[[complex(1,-1), complex(1,1)], [1, 1]]')
# array([[1.-1.j, 1.+1.j],
# [1.+0.j, 1.+0.j]])
#
# >>> crazy_complex_string_to_ndarray('[[complex(0,-3.14), complex(1.57337537832783243243,1e-100)], [1, 1]]')
# array([[-0. -3.14e+000j, 1.57337538+1.00e-100j],
# [ 1. +0.00e+000j, 1. +0.00e+000j]])
Хорошая особенность этого подхода заключается в том, что любой вид искаженного или злонамеренного ввода будет громко терпеть неудачу (на этапе ast.literal_eval
), а не давать неожиданные или вредные результаты .