Why doesn't my "formula" variable update automatically, like in a spreadsheet? How can I re-compute the value?

huangapple go评论79阅读模式
英文:

Why doesn't my "formula" variable update automatically, like in a spreadsheet? How can I re-compute the value?

问题

我注意到初学者常犯以下简单的逻辑错误。由于他们真的不理解问题,a) 他们的问题不能真正说是由于拼写错误(详细解释会有用); b) 他们缺乏创建适当示例、用适当术语解释问题并清晰提问所需的理解能力。因此,我代表他们提出这个问题,以创建一个经典的重复目标。

考虑这个代码示例:

x = 1
y = x + 2

for _ in range(5):
    x = x * 2 # 所以第一次是2,然后是4,然后是8,然后是16,最后是32
    print(y)

每次循环时,x 都会翻倍。由于 y 定义为 x + 2,为什么 x 变化时它不会改变?如何使值自动更新,以获得预期的输出

4
6
10
18
34
```?


<details>
<summary>英文:</summary>

I have noticed that it&#39;s common for beginners to have the following simple logical error. Since they genuinely don&#39;t understand the problem, a) their questions can&#39;t really be said to be caused by a typo (a full explanation would be useful); b) they lack the understanding necessary to create a proper example, explain the problem with proper terminology, and ask clearly. So, I am asking on their behalf, to make a canonical duplicate target.

Consider this code example:

x = 1
y = x + 2

for _ in range(5):
x = x * 2 # so it will be 2 the first time, then 4, then 8, then 16, then 32
print(y)

Each time through the loop, `x` is doubled. Since `y` was defined as `x + 2`, why doesn&#39;t it change when `x` changes? How can I make it so that the value is automatically updated, and I get the expected output

4
6
10
18
34

?

</details>


# 答案1
**得分**: 1

## 声明式编程

许多初学者期望Python能够按照这种方式工作,但实际情况并非如此。更糟糕的是,他们可能会不一致地期望它以这种方式工作。请仔细考虑一下示例中的这行代码:

```python
x = x * 2

如果赋值操作像数学公式一样,我们将不得不解出 xx 的唯一可能(数值)值将为零,因为任何其他数字都不等于两倍的那个数字。那么,我们应该如何解释代码之前说 x = 1 了呢?这不是矛盾吗?我们应该因为试图两种不同方式定义 x 而获得错误消息吗?还是期望 x 无限增长,因为程序不断尝试加倍旧值 x

当然,以上这些情况都不会发生。与大多数常见的编程语言一样,Python 是一种 声明式 语言,意味着代码的行描述了按照定义的顺序发生的 操作。在循环中,会重复执行循环内部的代码;在像 if/else 这样的情况下,某些代码可能会被跳过;但总体而言,在同一个“块”内部的代码只是按照它编写的顺序发生。

在示例中,首先发生了 x = 1,因此 x 等于 1。然后发生了 y = x + 2,这使得 y 目前等于 3。这是因为赋值操作,而不是因为 x 有一个值。因此,当代码中的 x 后来发生变化时,这不会导致 y 改变。

随着(控制)流程前进

那么,我们如何使 y 改变?最简单的答案是:与我们在第一次分配它值时一样 - 使用 = 进行 赋值。事实上,重新考虑 x = x * 2 代码,我们已经看到如何做到这一点。

在示例代码中,我们希望 y 多次更改 - 每次通过循环一次,因为那是 print(y) 发生的地方。应该分配什么值?这取决于 x - 在进程中的那个点上 x当前 值,这是通过使用... x 确定的。就像 x = x * 2 检查了 x 的现有值,将其加倍,并 x 更改为加倍后的结果 一样,我们可以写 y = x + 2 来检查 x 的现有值,加上两个,然后将 y 更改为新值。

因此:

x = 1

for _ in range(5):
    x = x * 2
    y = x + 2
    print(y)

唯一发生变化的是 y = x + 2 现在位于循环内部。我们希望在每次 x = x * 2 发生后立即发生此更新(即,以便在 print(y) 之前进行更改)。因此,这直接告诉我们代码需要去哪里。

定义关系

假设程序中有多个地方更改了 x 的值:

x = x * 2
y = x + 2
print(y)
x = 24
y = x + 2
print(y)

最终,记住在每行更改 x 后更新 y 将会很烦人。这也是潜在的错误源,随着程序的增长而变得更糟。

在原始代码中,编写 y = x + 2 的想法是为了 表达 xy 之间的关系:我们希望代码在任何出现 y 的地方都将其视为与 x + 2 相同的东西。在数学术语中,我们想将 y 视为 x函数

在Python中,与大多数其他编程语言一样,我们使用名为... 函数 的东西来表示函数的数学概念。在Python中具体来说,我们使用 def 函数来编写函数。它看起来像这样:

def y(z):
    return z + 2

我们可以在函数内部写任何代码,当函数被“调用” 时,该代码将运行,就像我们现有的“顶层”代码运行一样。但是当Python首次遇到以 def 开始的块时,它仅从该代码创建一个函数 - 还没有运行该代码。

所以,现在我们有了一个名为 y 的东西,它是一个函数,接受某个 z 值并返回(即,return)计算 z + 2 结果的结果。我们可以通过编写 y(x) 这样的东西来调用它,这将为它提供我们现有的 x 值并 计算出 将2添加到该值的结果。

请注意,这里的 z函数自己 对传入值的名称,它 不必与 我们对该值的名称匹配。实际上,我们不必为该值自己命名:例如,我们可以编写 y(1),该函数将计算 3

通过“评估为”,或“返回”,或“return”,我们是

英文:

Declarative programming

Many beginners expect Python to work this way, but it does not. Worse, they may inconsistently expect it to work that way. Carefully consider this line from the example:

x = x * 2

If assignments were like mathematical formulas, we'd have to solve for x here. The only possible (numeric) value for x would be zero, since any other number is not equal to twice that number. And how should we account for the fact that the code previously says x = 1? Isn't that a contradiction? Should we get an error message for trying to define x two different ways? Or expect x to blow up to infinity, as the program keeps trying to double the old value of x

Of course, none of those things happen. Like most programming languages in common use, Python is a declarative language, meaning that lines of code describe actions that occur in a defined order. Where there is a loop, the code inside the loop is repeated; where there is something like if/else, some code might be skipped; but in general, code within the same "block" simply happens in the order that it's written.

In the example, first x = 1 happens, so x is equal to 1. Then y = x + 2 happens, which makes y equal to 3 for the time being. This happened because of the assignment, not because of x having a value. Thus, when x changes later on in the code, that does not cause y to change.

Going with the (control) flow

So, how do we make y change? The simplest answer is: the same way that we gave it this value in the first place - by assignment, using =. In fact, thinking about the x = x * 2 code again, we already have seen how to do this.

In the example code, we want y to change multiple times - once each time through the loop, since that is where print(y) happens. What value should be assigned? It depends on x - the current value of x at that point in the process, which is determined by using... x. Just like how x = x * 2 checks the existing value of x, doubles it, and changes x to that doubled result, so we can write y = x + 2 to check the existing value of x, add two, and change y to be that new value.

Thus:

x = 1

for _ in range(5):
    x = x * 2
    y = x + 2
    print(y)

All that changed is that the line y = x + 2 is now inside the loop. We want that update to happen every time that x = x * 2 happens, immediately after that happens (i.e., so that the change is made in time for the print(y)). So, that directly tells us where the code needs to go.

defining relationships

Suppose there were multiple places in the program where x changes:

x = x * 2
y = x + 2
print(y)
x = 24
y = x + 2
print(y)

Eventually, it will get annoying to remember to update y after every line of code that changes x. It's also a potential source of bugs, that will get worse as the program grows.

In the original code, the idea behind writing y = x + 2 was to express a relationship between x and y: we want the code to treat y as if it meant the same thing as x + 2, anywhere that it appears. In mathematical terms, we want to treat y as a function of x.

In Python, like most other programming languages, we express the mathematical concept of a function, using something called... a function. In Python specifically, we use the def function to write functions. It looks like:

def y(z):
    return z + 2

We can write whatever code we like inside the function, and when the function is "called", that code will run, much like our existing "top-level" code runs. When Python first encounters the block starting with def, though, it only creates a function from that code - it doesn't run the code yet.

So, now we have something named y, which is a function that takes in some z value and gives back (i.e., returns) the result of calculating z + 2. We can call it by writing something like y(x), which will give it our existing x value and evaluate to the result of adding 2 to that value.

Notice that the z here is the function's own name for the value was passed in, and it does not have to match our own name for that value. In fact, we don't have to have our own name for that value at all: for example, we can write y(1), and the function will compute 3.

What do we mean by "evaluating to", or "giving back", or "returning"? Simply, the code that calls the function is an expression, just like 1 + 2, and when the value is computed, it gets used in place, in the same way. So, for example, a = y(1) will make a be equal to 3:

  • The function receives a value 1, calling it z internally.
  • The function computes z + 2, i.e. 1 + 2, getting a result of 3.
  • The function returns the result of 3.
  • That means that y(1) evaluated to 3; thus, the code proceeds as if we had put 3 where the y(1) is.
  • Now we have the equivalent of a = 3.

For more about using functions, see https://stackoverflow.com/questions/3052793/.

Going back to the beginning of this section, we can therefore use calls to y directly for our prints:

x = x * 2
print(y(x))
x = 24
print(y(x))

We don't need to "update" y when x changes; instead, we determine the value when and where it is used. Of course, we technically could have done that anyway: it only matters that y is "correct" at the points where it's actually used for something. But by using the function, the logic for the x + 2 calculation is wrapped up, given a name, and put in a single place. We don't need to write x + 2 every time. It looks trivial in this example, but y(x) would do the trick no matter how complicated the calculation is, as long as x is the only needed input. The calculation only needs to be written once: inside the function definition, and everything else just says y(x).

It's also possible to make the y function use the x value directly from our "top-level" code, rather than passing it in explicitly. This can be useful, but in the general case it gets complicated and can make code much harder to understand and prone to bugs. For a proper understanding, please read https://stackoverflow.com/questions/423379/ and https://stackoverflow.com/questions/291978.

huangapple
  • 本文由 发表于 2023年1月6日 12:01:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/75026816.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定