Строка с не-литеральными комплексными числами в массиве numpy - PullRequest
1 голос
/ 24 апреля 2020

Я хотел бы взять такую ​​строку

A = '[[complex(1,-1), complex(1,1)], [1, 1]]'

и преобразовать ее в этот массив numpy

array([[1.-1.j, 1.+1.j],
   [1.+0.j, 1.+0.j]])

Я пытался использовать этот код

string = a.replace("[","").replace("]","").replace("\"",  "").replace(" ","")
np.fromstring(string, dtype=complex, count=-1, sep=',')

, но вывод такой:

array([], dtype=complex128)

, если есть хотя бы простой способ поместить матрицу A в эту форму

A = '1-1j, 1+1j, 1, 1'

код выше работает. Мне нужно сделать это с большим количеством матриц, импортированных из .csv

1 Ответ

0 голосов
/ 24 апреля 2020

Нужно сопротивляться искушению использовать 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), а не давать неожиданные или вредные результаты .

...