python - 将 Python 复杂字符串输出(如 (-0-0j))转换为等效的复杂字符串

标签 python python-3.x complex-numbers

在Python中,我想要一种好方法将其复数字符串输出转换为等效的字符串表示形式,当由Python解释时,它给出相同的值。

基本上,我希望函数 complexStr2str(s: str): str 具有 eval(complexStr2str(str(c))) 无法区分的属性code>c,对于任何值为复数类型的 c。但是,complexStr2str() 只需处理 str()repr() 为复数值输出的字符串模式类型。请注意,对于复杂值,str()repr() 执行相同的操作。

我所说的“无法区分”并不是Python意义上的==;您可以定义(或重新定义)它以表示您想要的任何内容; “无法区分”意味着如果程序中有字符串 a 代表某个值,并将程序中的字符串替换为字符串 b (可能正是 a ),那么没有办法区分Python程序和替换程序的运行之间的差异,缺乏对程序的自省(introspection)。

请注意,(-0-0j)-0j 不同,尽管前者是 Python 为 str(-0j) 输出的内容repr(-0j)。如下面的交互式 session 所示,-0j 具有实部和虚部 float 部分 -0.0,而 -0-0j 具有实部和虚部 float 部分0.0。

如果存在 naninf 等值,问题会变得更加困难。尽管在 Python 3.5+ 中,您可以从数学中导入这些值,但由于各种原因,我想避免这样做。但是使用 float("nan") 是可以的。

考虑这个 Python session :

>>> -0j
(-0-0j)
>>> -0j.imag
-0.0
>>> -0j.real
-0.0
>>> (-0-0j).imag
0.0  # this is not -0.0
>>> (-0-0j).real
0.0  # this is also not -0.0
>>> eval("-0-0j")
0j # and so this is -0j
>>> atan2(-0.0, -1.0)
-3.141592653589793
>>> atan2((-0-0j).imag, -1.0)
3.141592653589793
>>> -1e500j
(-0-infj)
>>> (-0-infj)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'infj' is not defined

附录:

这个问题引起了一些轰动(例如,这个问题及其接受的解决方案有很多投反对票)。而且对该问题进行了大量编辑,因此某些评论可能已经过时。

批评的主旨是人们不应该想要这样做。从某些现有程序的文本中解析数据是一件经常发生的事情,有时您只是无法控制生成数据的程序。

一个相关的问题是,人们可以控制输出程序,但需要让它出现在文本中,这是编写一个更好的 repr() 函数,该函数更适合 float 和复数,并遵循原理见最后。做到这一点很简单,即使它有点难看,因为要完全做到这一点,您还需要处理复合类型(如列表、元组、集合和字典)中的 float /复数。

最后,我要说的是,Python 的 str()repr() 对于复杂值的输出似乎没有帮助,这就是为什么这个问题更加具体与支持复数作为基本数据类型或通过库的其他语言相比,Python 更受欢迎。

这是一个显示这一点的 session :

>>> complex(-0.0, -0.0)
(-0-0j)  # confusing and can lead to problems if eval'd
>>> repr(complex(-0.0, -0.0))
'(-0-0j)' # 'complex(-0.0, -0.0)' would be the simplest, clearest, and most useful

请注意,在通过 print() 进行输出时,会调用 str()repr() 是此类使用的首选方法,但这里它与 str() 相同,并且都存在诸如 inf 之类的问题> 和 nan

对于任何 built-in type (eval(repr(c)) 应该与 c 无法区分。

最佳答案

这个问题是基于错误的前提。要在使用复数时正确保留有符号零、nan 和无穷大,您应该使用函数调用而不是 binops:

complex(real, imag)

应该用两个 float 调用它:

>>> complex(-0., -0.)  # correct usage
(-0-0j)
>>> complex(-0, -0j)  # incorrect usage
-0j

尝试使用 eval 文字时遇到的问题是 -0-0j 实际上并不是一个复杂的文字。它是一个二元运算,即整数 0 与复数 0j 的减法。该整数首先有 unary sub应用,但对于整数零来说这是一个无操作。

解析器将揭示这一点:

>>> ast.dump(ast.parse("-0-0j"))
'Module(body=[Expr(value=BinOp(left=UnaryOp(op=USub(), operand=Constant(value=0, kind=None)), op=Sub(), right=Constant(value=0j, kind=None)))], type_ignores=[])'

如果您了解 tokenizer 的原理,Python 在这里的选择将会更有意义。有效,它不想回溯:

$ echo "-0-0j" > wtf.py
$ python -m tokenize wtf.py
0,0-0,0:            ENCODING       'utf-8'        
1,0-1,1:            OP             '-'            
1,1-1,2:            NUMBER         '0'            
1,2-1,3:            OP             '-'            
1,3-1,5:            NUMBER         '0j'           
1,5-1,6:            NEWLINE        '\n'           
2,0-2,0:            ENDMARKER      ''

但是您也可以根据数据模型 Hook 和运算符优先级轻松地自己推理:

>>> -0-0j  # this result seems weird at first
0j
>>> -(0) - (0j)  # but it's parsed like this
0j
>>> (0) - (0j)  # unary op (0).__neg__() applies first, does nothing
0j
>>> (0).__sub__(0j)  # left-hand side asked to handle first, but opts out
NotImplemented
>>> (0j).__rsub__(0)  # right-hand side gets second shot, reflected op works
0j

同样的推理也适用于-0j,它实际上是一个否定,并且实部也隐式否定:

>>> -0j  # where did the negative zero real part come from?
(-0-0j)
>>> -(0j)  # actually parsed like this
(-0-0j)
>>> (0j).__neg__()  # so *both* real and imag parts are negated
(-0-0j)

我们来谈谈这部分,它把责任指向了错误的方向:

Python's str() representation for complex numbers with negative real and imaginary parts is unhelpful

不,这里 __str__ 的实现没有任何问题,并且您使用 complex(-0,-0j) 让我怀疑您没有完全了解首先了解发生了什么。首先,没有理由写 -0 因为整数没有符号零,只有 float 。正如我上面所解释的,虚部 -0j 仍然被解析为复数上的 USub 。通常,您不会将虚数本身作为虚部传递,调用 complex 的正确方法就是使用两个 float :complex(-0., -0.).这里没有什么惊喜。

虽然我同意复杂表达式的解析/评估是违反直觉的,但我不同意它们的字符串表示中有任何问题。 “改进”表达式 eval 的建议可能是可能的,目标是使 eval(repr(c)) 准确地往返 - 但这意味着你不能使用 Python 的 left - 不再向右咀嚼解析器。该解析器快速、简单且易于解释。 为了使涉及复杂零的表达式表现得不那么奇怪而使解析树变得非常复杂,这不是一个公平的权衡,因为没有人需要关心这些细节应该选择repr (c) 首先作为它们的序列化格式。

请注意ast.literal_eval只允许它作为一种方便。 ast.literal_eval("0+0j") 尽管不是是一个文字,但仍然可以工作,反之亦然会失败:

>>> ast.literal_eval("0+0j")
0j
>>> ast.literal_eval("0j+0")
ValueError: malformed node or string: <_ast.BinOp object at 0xcafeb4be>

总之,复数的字符串表示形式很好。重要的是创建数字的方式。 str(c) 用于人类可读的输出,如果您关心保留有符号的零、nan 和无穷大,请使用机器友好的序列化格式。

关于python - 将 Python 复杂字符串输出(如 (-0-0j))转换为等效的复杂字符串,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59384884/

相关文章:

python - 在列表中使用 .index 时,仅返回它第一次出现在数组中的时间

pytorch - PyTorch 中复数的矩阵乘法

python - 通过 SSH 登录思科无线 Controller

python - 在 sklearn 中使用数据标签自定义 transformerMixin

python - 列表的浅拷贝[:]

python - 将 2 列类似计数器的 csv 文件转换为 Python collections.Counter?

python - 将字符串拆分为列表并将项目转换为 int

python - 'krb5-config' 未被识别为内部或外部命令

java - Java中的复数加法

python - 复数的 `j` 后缀如何工作?我可以创建自己的后缀吗?