【问题标题】:Evaluation in python?python中的评估?
【发布时间】:2020-08-08 20:04:06
【问题描述】:
available_items = {"health potion": 10, "cake of the cure": 5, "green elixir": 20, "strength sandwich": 25, "stamina grains": 15, "power stew": 30}

health_points = 20

health_points += available_items.pop("stamina grains")

当我运行此代码时,会添加键“stamina grains”的值,并从字典 available_items 中删除 stamina grains。

但是 Python 从左到右计算表达式,因此它应该首先删除关键的“stamina grains”,因此应该没有任何内容添加到health_points

我对 Python 如何评估表达式感到困惑。有人可以澄清一下并给我一些关于 Python 如何评估表达式的资源吗?

【问题讨论】:

  • 字典的.pop() 方法显式返回与它删除的键对应的值 - 这就是该方法的重点。在这种情况下,返回值只是整数15,它不需要键的持续存在才能自身存在。
  • pop 的任务是返回与键关联的值并从字典中删除键。最后发生的事情是将结果(在右侧计算)对左侧变量的影响。见the doc on evaluation order
  • 顺便说一句,您的代码使用health_points += available_items.pop("stamina grains",0) 会更加健壮。使用 0 进行更新,而不是在出于任何原因没有该项目时抛出 KeyError

标签: python python-3.x expression-evaluation


【解决方案1】:

CPython 被实现为stack machine。如果您想确切了解子表达式的求值顺序,反汇编它们会很有帮助:

>>> from dis import dis
>>> dis('''
... available_items = {"health potion": 10, "cake of the cure": 5, "green elixir": 20, "strength sandwich": 25, "stamina grains": 15, "power stew": 30}
...
... health_points = 20
...
... health_points += available_items.pop("stamina grains")
... ''')
  2           0 LOAD_CONST               0 (10)
              2 LOAD_CONST               1 (5)
              4 LOAD_CONST               2 (20)
              6 LOAD_CONST               3 (25)
              8 LOAD_CONST               4 (15)
             10 LOAD_CONST               5 (30)
             12 LOAD_CONST               6 (('health potion', 'cake of the cure', 'green elixir', 'strength sandwich', 'stamina grains', 'power stew'))
             14 BUILD_CONST_KEY_MAP      6
             16 STORE_NAME               0 (available_items)

  4          18 LOAD_CONST               2 (20)
             20 STORE_NAME               1 (health_points)

  6          22 LOAD_NAME                1 (health_points)
             24 LOAD_NAME                0 (available_items)
             26 LOAD_METHOD              2 (pop)
             28 LOAD_CONST               7 ('stamina grains')
             30 CALL_METHOD              1
             32 INPLACE_ADD
             34 STORE_NAME               1 (health_points)
             36 LOAD_CONST               8 (None)
             38 RETURN_VALUE

虽然子表达式(大部分)从左到右计算,但运算符优先级和括号可以改变这一点。并且赋值是一个语句,而不是一个表达式。即使+= 位于.pop() 的左侧,您也可以看到调用发生在编译字节码中的赋值之前。

请注意,(pop) 调用在CALL_METHOD 指令处将其返回值压入堆栈,因此它可以被INPLACE_ADD 使用。那时,字典中没有引用该值,仅在堆栈上引用该值,但该值从未丢失。之后,加法的结果可用于STORE_NAME 指令。

【讨论】:

  • 谢谢兄弟,但你的答案很复杂,因为我是初学者
【解决方案2】:

first Google result搜索“Python+dictionary +pop”(重点是我的):

pop() 方法从具有给定键的字典中删除并返回一个元素。

所以,pop 动作确实是第一个执行的动作,它从字典中删除元素也是真的,但它也 返回那个值,即使用更新healt_points 变量。

详情,据evaluation order table

Python 从左到右计算表达式。请注意,在评估分配时,右侧会先于左侧进行评估。

所以,考虑到表达式

health_points = health_points  + available_items.pop("stamina grains")
  1. 我们有一个作业,所以首先评估右侧:health_points + available_items.pop("stamina grains")
  2. 右侧,从左到右进行求值:health_points首先求值,其值为20
  3. 现在available_items.pop("stamina grains") 被评估。为available_items字典调用pop方法
  4. 找到键"stamina grains",返回其值(15)
  5. 作为副作用pop 会从字典中删除"stamina grains"。无论如何都会返回值 15:它只是一个表示方法结果的整数。它不再受限于字典元素的存在
  6. 表达式现在是health_points = 20 + 15。右侧评估是整数 35,现在可以分配给 health_points。这样就完成了左侧评估

【讨论】:

  • @Muslim 欢迎您。无论如何,我用评估步骤的详细描述完成了我的回答。我希望它能帮助更多。
【解决方案3】:

如果你看这里:Operator precedence,你可以看到“call”在表格的底部附近,所以首先执行。执行完这第一步后,您可以将语句重写为: health_points += 15.

加法稍高一点,接下来会执行(+= 是加法的缩写:health_points = health_points + available_items.pop("stamina grains"))。顶部是赋值,所以最后执行。导致health_points == 35

【讨论】:

  • 谢谢,但是我没有回答你的问题的细节。当您评估一个语句时,您需要用它的返回值替换调用(正如我和其他人所说的那样)。 pop 方法的“奇怪”之处在于它有一个副作用:从字典中删除键。您可以在心理上将其视为同时发生的两件事:返回值和删除键。它们不会互相干扰(从外面看)。内部见@gilch 的回答,但你不应该关心这个。
  • 这不是 Python 运算符优先级的工作方式。首先,运算符优先级控制参数分组,而不是执行顺序。其次,+=中的+不能与=分开——+=中的+不具有加法的优先级。第三,表格顶部的:=条目是专门针对:=赋值表达式的,不是赋值语句或扩充的赋值语句; += 完全在表达式的优先级层次之外。
  • 嗨,您显然比我更了解这一点,并且可能更正确。赋值表达式与赋值不同。在我链接到 6.17 的答案中,在 6.16 中指出“在评估分配时,右侧在左侧之前进行评估”。所以这确实是最后一个任务的原因。我也可以按照你的第二点,+= 的分离只是精神上的。但是你能举个例子说明为什么我们不能使用 6.17 中的表格来在心理上遵循评估流程吗?
  • 运算符优先级不控制执行顺序。例如,即使函数调用的优先级高于除法,1 / 0 + f() 中的 the division executes firstf 也不会被调用。
  • 谢谢你的例子。你似乎对执行是正确的,但这是内部的东西。我认为 OP 对此不感兴趣(请参阅他对 gilch 答案的评论,该答案确实显示了内部情况)。如果我有无错误表达式1 / 1 + f(),它的计算结果为 3 而不是 1/3。它也不会引发错误,因为我尝试将函数对象 f 添加到 0 并调用结果,就像 (0 +f )() 一样。它评估为 3 的原因可以从我链接的表中得出。所以虽然我的解释不是很准确,但我相信它可以帮助 OP。
猜你喜欢
  • 1970-01-01
  • 2016-08-19
  • 1970-01-01
  • 1970-01-01
  • 2019-03-13
  • 1970-01-01
  • 1970-01-01
  • 2014-05-30
  • 1970-01-01
相关资源
最近更新 更多