【问题标题】:Are Python variables pointers? Or else, what are they?Python变量是指针吗?否则,它们是什么?
【发布时间】:2012-11-11 22:21:38
【问题描述】:

据我所知,Python 中的变量只是指针。

根据这个规则,我可以假设这段代码的结果是 sn-p:

i = 5
j = i
j = 3
print(i)

应该是3

但我得到了一个意想不到的结果,它是5

此外,我的 Python 书确实涵盖了这个示例:

i = [1,2,3]
j = i
i[0] = 5
print(j)

结果将是[5,2,3]

我理解错了什么?

【问题讨论】:

  • 我看不出i 应该如何等于3
  • @Tooniis j 现在指向不同的内存块,而不是 i 指向的内存块。你现在可能已经明白了,但以防万一其他人看到这个
  • 更有意义的测试用例应该是 i=5, j=i, i=3, print(j)

标签: python pointers variables identifier


【解决方案1】:

我们称它们为引用。他们是这样工作的

i = 5     # create int(5) instance, bind it to i
j = i     # bind j to the same int as i
j = 3     # create int(3) instance, bind it to j
print i   # i still bound to the int(5), j bound to the int(3)

小整数被实习,但这对这个解释并不重要

i = [1,2,3]   # create the list instance, and bind it to i
j = i         # bind j to the same list as i
i[0] = 5      # change the first item of i
print j       # j is still bound to the same list as i

【讨论】:

  • 嗨,约翰,你所说的“小整数被实习”是什么意思?谢谢!
  • @yuqli 在python中,一切都是对象,包括数字。由于经常使用小数字(-5,256),因此它们被“实习”或缓存在 CPython 中。因此,每次您输入40 时,您都在引用内存中的同一个对象。要查看此类型 a,b=256 并测试 a is b。现在用 a,b=257 试试。请参阅:stackoverflow.com/a/1136852/3973834codementor.io/python/tutorial/…
  • 根据我的经验,在 Python 开发人员中更常见的是称他们为 "names"。 “引用”一词带有不必要的 C 包袱,可能使 Python(语言)过于偏向 CPython(实现),而 CPython 恰好使用引用计数。
【解决方案2】:

变量不是指针。当你给一个变量赋值时,你就是绑定一个对象的名字。从那时起,您可以使用名称来引用对象,直到该名称被重新绑定。

在您的第一个示例中,名称 i 绑定到值 5。将不同的值绑定到名称ji 没有任何影响,因此当您稍后打印i 的值时,该值仍然是5

在第二个示例中,您将 ij 绑定到 same 列表对象。当您修改列表的内容时,无论您使用哪个名称来引用列表,都可以看到更改。

请注意,如果您说“两个列表都已更改”是不正确的。只有一个列表,但它有两个引用它的名称(ij)。

相关文档

【讨论】:

  • 您的回复没有明确解释什么是绑定。
【解决方案3】:

Python 变量是绑定到对象的名称

来自docs

名称指的是对象。名称由名称绑定操作引入。 程序文本中每次出现的名称都指向该名称的绑定,该绑定建立在包含使用的最内层功能块中。

当你这样做时

i = 5
j = i

这和做的一样:

i = 5
j = 5

j 不指向i,并且在赋值之后,j 不知道i 存在。 j 只是绑定到任何@987654329 @ 在分配时指向。

如果你在同一行做作业,它看起来像这样:

i = j = 5

结果将完全相同。

因此,以后做

i = 3

不会改变 j 指向的内容 - 您可以交换它 - j = 3 不会改变 i 指向的内容。

您的示例不会删除对列表的引用

所以当你这样做时:

i = [1,2,3]
j = i

和这样做是一样的:

i = j = [1,2,3]

所以ij 都指向同一个列表。然后你的例子改变了列表:

i[0] = 5

Python 列表是可变对象,因此当您从一个引用更改列表并从另一个引用查看它时,您会看到相同的结果,因为它是同一个列表。

【讨论】:

    【解决方案4】:

    TLDR:Python names 的工作方式类似于具有自动取消/引用的指针,但不允许显式指针操作。其他目标表示间接,其行为类似于指针。


    Python 语言规范并没有定义实际的名称等,只定义了它们的行为方式。不过,这种行为可以用指针来解释。

    CPython 实现在底层使用pointers of type PyObject*。因此,可以将名称语义转换为指针操作。关键是将 names 与实际的 objects 分开。

    示例 Python 代码包括名称 (i) 和对象 (5)。

    i = 5  # name `i` refers to object `5`
    j = i  # ???
    j = 3  # name `j` refers to object `3`
    

    这可以粗略地翻译成具有独立名称和对象的C代码。

    int three=3, five=5;  // objects
    int *i, *j;           // names
    i = &five;   // name `i` refers to position of object `5`
    j = i;       // name `j` refers to referent of `i`
    j = &three;  // name `j` refers to position of object `3`
    

    重要的部分是“名称作为指针”不存储对象!我们没有定义*i = five,而是i = &five 。名称和对象相互独立存在。

    名称仅指向内存中的现有对象。

    当从一个名称分配到另一个名称时,不会交换任何对象!当我们定义j = i 时,这相当于j = &fiveij 都没有相互连接。

    +- name i -+ -\
                   \
                    --> + <five> -+
                   /    |        5 |
    +- name j -+ -/     +----------+
    

    因此,changing the target of one name does not affect the other。它只会更新该特定名称所指向的内容。


    Python 还有other kinds of name-like elements:属性引用(i.j)、订阅(i[j])和切片(i[:j])。与直接引用对象的名称不同,所有三个都间接引用对象的元素

    示例代码包括名称 (i) 和订阅 (i[0])。

    i = [1,2,3]  # name `i` refers to object `[1, 2, 3]`
    j = i        # name `j` refers to referent of `i`
    i[0] = 5     # ???
    

    CPython list 在底层使用 PyObject* 指针的 C 数组。这可以再次粗略地转换为具有单独名称和对象的 C 代码。

    typedef struct{
        int *elements[3];
    } list;  // length 3 `list` type
    
    int one = 1, two = 2, three = 3, five = 5;
    list values = {&one, &two, &three};  // objects
    list *i, *j;                         // names
    i = &values;             // name `i` refers to object `[1, 2, 3]`
    j = i;                   // name `j` refers to referent of `i`
    i->elements[0] = &five;  // leading element of `i` refers to object `5`
    

    重要的是我们没有更改任何名称!我们确实更改了i-&gt;elements[0],我们的名字都指向的对象的元素。

    现有复合对象的值可能会改变。

    通过名称更改对象的值时,名称不会更改。 ij 仍然引用同一个对象,我们可以更改其值。

    +- name i -+ -\
                   \
                    --> + <values> -+
                   /    |  elements | --> [1, 2, 3]
    +- name j -+ -/     +-----------+
    

    中间对象的行为类似于指针,我们可以直接更改它所指向的内容并从多个名称中引用它。

    【讨论】:

    • 我真的很喜欢这个答案,但我认为您在示例中颠倒了 ij 分配。您从i = 5j = 3 开始,然后在您的帖子的其余部分中反转它们。再说一遍,这是 imo 对 OP 中的问题公正的唯一答案,并真正解释了幕后发生的事情。
    • @jeremyradcliff 感谢您的提醒。现在应该修好了。如果我错过了更多,请告诉我。
    • 这应该是公认的答案!它正面解决了 C 问题。作为一个 C 程序员,这是我唯一能真正理解的答案。
    【解决方案5】:

    它们并不完全是指针;它们是对对象的引用。对象可以是可变的,也可以是不可变的。不可变对象在修改时会被复制。可变对象就地更改。整数是一个不可变对象,您可以通过 i 和 j 变量引用它。列表是一个可变对象。

    在你的第一个例子中

    i = 5
    # The label i now references 5
    
    j = i
    # The label j now references what i references
    
    j = 3
    # The label j now references 3
    
    print i
    # i still references 5
    

    在你的第二个例子中:

    i = [1, 2, 3]
    # 'i' references a list object (a mutable object)
    j = i
    # 'j' now references the same object as 'i' (they reference the same mutable object)
    
    i[0] = 5
    # Sets first element of references object to 5
    
    print j
    # Prints the list object that 'j' references. It's the same one as 'i'.
    

    【讨论】:

    • “一个不可变的对象在被修改时被复制。”这有点自相矛盾。
    【解决方案6】:

    赋值不会修改对象;它所做的只是改变变量指向的位置。改变一个变量点的位置不会改变另一个变量点的位置。

    您可能会想到列表和字典是可变类型这一事实。有操作符可以就地修改实际对象,如果您使用其中之一,您将看到指向同一对象的所有变量的变化:

    x = []
    y = x
    x.append(1)
    # x and y both are now [1]
    

    但赋值仍然只是移动指针:

    x = [2]
    # x now points to new list [2]; y still points to old list [1]
    

    与字典和列表不同,数字是不可变的。如果你这样做x = 3; x += 2,你并没有将数字 3 转换为数字 5;您只是将变量 x 改为指向 5。 3 仍然没有改变,任何指向它的变量仍然会将 3 视为它们的值。

    (在实际实现中,数字可能根本不是引用类型;变量实际上更可能直接包含值的表示,而不是指向它。但是该实现细节不会改变不可变的语义类型有关。)

    【讨论】:

    • 这不是值类型的意思。值类型正是您在上一段中描述的内容(值被传递/复制而不是对对象的引用),它 not 在内部(在 CPython 和 PyPy sans 中) JIT 编译器 - 每个整数都是堆分配的对象)。坚持不可变,这正是你需要的词。
    【解决方案7】:

    当您设置j=3 时,标签j 不再适用(指向)i,它开始指向整数3i这个名字还是指你原来设置的值,5

    【讨论】:

      【解决方案8】:

      '=' 符号左侧的任何变量都分配有 '=' 右侧的值

      i = 5

      j = i --- j 有 5 个

      j = 3 --- j 有 3(覆盖 5 的值)但 i 没有任何改变

      print(i)-- 所以这会打印 5

      【讨论】:

        【解决方案9】:

        在 Python 中,一切都是对象,包括返回的内存片段本身。这意味着,当创建新的内存块时(无论您创建了什么:int、str、自定义对象等),您都有一个新的内存对象。在您的情况下,这是对 3 的分配,它创建一个新的(内存)对象,因此具有一个新地址。

        如果你运行以下命令,你很容易明白我的意思。

        i = 5
        j = i
        print("id of j: {}", id(j))
        j = 3
        print("id of j: {}", id(j))
        

        IMO,内存方面,这是 C 和 Python 之间的关键理解/区别。在 C/C++ 中,您将返回一个内存指针(当然,如果您使用指针语法)而不是内存对象,这为您在更改引用地址方面提供了更大的灵活性。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-10-05
          • 2011-05-19
          • 1970-01-01
          • 2010-11-26
          相关资源
          最近更新 更多