【问题标题】:In Python runtime, is there a way to distinguish literal string instances from dynamically created ones?在 Python 运行时,有没有办法区分文字字符串实例和动态创建的实例?
【发布时间】:2018-01-27 02:54:18
【问题描述】:

在 Python 运行时,有没有办法区分文字字符串实例和动态创建的实例?

例如,我希望能够区分这两个值:

val1 = "Foo"
var2 = "%s" % "Foo"

此检查的一个示例用例是保护类似string.Template 的函数免受任何攻击,例如暴露局部变量的值。

如果不可能,有什么好的理由吗?


还有一个旁注......

PEP 498 -- Literal String Interpolation 引入了 f-strings,它们是字符串文字,可以在标记化时拆分为文字和表达式。

F-strings 的工作方式与 string.Template() 非常相似,但强制输入为文字字符串,代价是语言的语法更新。

如果这种检查在运行时可用,则 f-strings 可以作为函数实现。


更新 1

正如@kevin 在他的回答中指出的那样,CPython 进行了优化,允许它在不需要创建新实例时重用现有实例。在我的第一个示例中,"%s" % "Foo" 被跳过,仅链接到现有的 "Foo" 实例。

但这不是语言要求,事实上并不总是这样。除了一些明显的字符串格式之外的任何字符串格式都会导致创建一个新实例。

在下面的例子中,你可以看到虽然字符串值相等,但它们不是同一个对象。不过,使用 sys.intern() 会给我们同样的实例。

In [1]: import dis
   ...: import sys
   ...:
   ...: def foo():
   ...:     var1 = "Foo Bar"
   ...:     var2 = "%s %s" % ("Foo", "Bar")
   ...:     print(f'plain eq: {var1 == var2}')
   ...:     print(f'plain is: {var1 is var2}')
   ...:     print(f'intern is: {sys.intern(var1) is sys.intern(var2)}')
   ...:
   ...: dis.dis(foo)
   ...: foo()
   ...:
  5           0 LOAD_CONST               1 ('Foo Bar')
              2 STORE_FAST               0 (var1)

  6           4 LOAD_CONST               9 ('Foo Bar')
              6 STORE_FAST               1 (var2)

  7           8 LOAD_GLOBAL              0 (print)
             10 LOAD_CONST               5 ('plain eq: ')
             12 LOAD_FAST                0 (var1)
             14 LOAD_FAST                1 (var2)
             16 COMPARE_OP               2 (==)
             18 FORMAT_VALUE             0
             20 BUILD_STRING             2
             22 CALL_FUNCTION            1
             24 POP_TOP

  8          26 LOAD_GLOBAL              0 (print)
             28 LOAD_CONST               6 ('plain is: ')
             30 LOAD_FAST                0 (var1)
             32 LOAD_FAST                1 (var2)
             34 COMPARE_OP               8 (is)
             36 FORMAT_VALUE             0
             38 BUILD_STRING             2
             40 CALL_FUNCTION            1
             42 POP_TOP

  9          44 LOAD_GLOBAL              0 (print)
             46 LOAD_CONST               7 ('intern is: ')
             48 LOAD_GLOBAL              1 (sys)
             50 LOAD_ATTR                2 (intern)
             52 LOAD_FAST                0 (var1)
             54 CALL_FUNCTION            1
             56 LOAD_GLOBAL              1 (sys)
             58 LOAD_ATTR                2 (intern)
             60 LOAD_FAST                1 (var2)
             62 CALL_FUNCTION            1
             64 COMPARE_OP               8 (is)
             66 FORMAT_VALUE             0
             68 BUILD_STRING             2
             70 CALL_FUNCTION            1
             72 POP_TOP
             74 LOAD_CONST               0 (None)
             76 RETURN_VALUE
plain eq: True
plain is: False
intern is: True

sys.intern() 中所述,“通常,Python 程序中使用的名称是自动实习的,用于保存模块、类或实例属性的字典具有实习键。”换句话说,通常情况下,运行时字符串实例不会被实习。

【问题讨论】:

  • string.Template 只会填写你传递给.substitute() 的内容,你能演示一下你担心的攻击吗?
  • 使用locals()(或*locals())将所有局部变量传递给. substitute().format()是很常见的。
  • 套用我的医生的话,“如果您这样做时它会泄露信息,请不要这样做。”

标签: python string python-3.x


【解决方案1】:

不,你不能那样做。或者至少,你不能在运行时这样做。如果您愿意接受编译时分析的限制,您可以使用 ast 解析和检查 Python 代码,但这可能是一个比您正在寻找的工具更复杂的工具,当然不会让您“将 f 字符串实现为函数。”

对于您的示例的特定情况,Python 语言规范允许 var1var2 指向同一个对象(如果您将它们都传递给 sys.intern() 函数并比较结果)。由于符合标准的 Python 实现可以给它们起别名,因此没有可靠的方法来区分它们。事实上,当我在 CPython 3.6.1 中尝试时,它们被别名了:

import dis

def foo():
    var1 = "Foo"
    var2 = "%s" % "Foo"
    return var1 is var2

dis.dis(foo)
print(foo())

输出:

  4           0 LOAD_CONST               1 ('Foo')
              2 STORE_FAST               0 (var1)

  5           4 LOAD_CONST               3 ('Foo')
              6 STORE_FAST               1 (var2)

  6           8 LOAD_FAST                0 (var1)
             10 LOAD_FAST                1 (var2)
             12 COMPARE_OP               8 (is)
             14 RETURN_VALUE
True

请注意,它甚至没有浪费时间计算 var2。它将constant-folded 转换为文字值'Foo',然后与函数已用于var1 的另一个'Foo' 进行重复数据删除。

(一个更积极的优化器可以传播这些常量并将var1 is var2转换为True,但CPython没有这样做(还没有?),可能是因为很少使用is来处理字符串等不可变值. 大多数可能从持续传播中受益的其他操作都受制于各种monkey patching,这在绝大多数实际用例中阻止了这种优化。因此,我认为它不值得实现。 )

如果不可能,有什么好的理由吗?

因为 Python 和大多数命令式语言一样,使用 eager evaluation,它会立即丢弃这些信息。使用惰性评估语言,这个问题至少是合理的,但我不相信他们中的大多数人也保留了这些信息。在大多数处理字符串的编程语言中,字符串是文字还是非文字的问题根本不被视为字符串值的一部分。

【讨论】:

  • 感谢@kevin 的深刻回答。我已经用更多细节更新了问题,以更好地显示解释器的当前状态。
  • 关于热切评估,恕我直言,仍然没有理由区分文字字符串与格式化字符串。是的,如果不同,它可能会导致解释器使用更多内存,但这并不意味着 Python 这种语言不具备该功能。
  • 你没有抓住重点。如果语言允许给 A 和 B 起别名,那么就没有可靠的方法来区分它们,即使它们在运行时恰好是单独的实例。
  • 对。对。该评论针对问题的第二部分:“为什么不呢?”。看起来语言可以有区分它们的选项,尽管它目前没有。
  • 一种语言可以做几乎任何事情。是否值得在每个字符串上存储一个额外的标志(其中有很多,因为 Python 的变量和其他名称在运行时以字符串的形式存在)所需的工程努力、CPU 周期和 RAM 是真正的问题。大多数编程语言选择不这样做,既因为它似乎不值得,也因为它部分地破坏了我上面描述的字符串实习优化。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-11
  • 2011-12-11
  • 2011-10-15
  • 1970-01-01
  • 2022-01-09
相关资源
最近更新 更多