首页>Program>source

我正在寻找有关可变性和类对象的一些说明.据我了解,Python中的变量是关于为对象分配变量名的。

如果该对象是不可变的,则当我们将两个变量设置为同一对象时,它将是两个单独的副本(例如a = b = 3,因此更改为4不会影响b,因为3是一个数字,例如 不变的物体)

但是,如果对象是可变的,则更改一个变量分配中的值自然会更改另一个变量中的值(例如a = b = []->a.append(1),因此现在a和b都将 请参阅" [1]")

与班级一起工作,似乎比我想象的还要流畅.我在下面写了一个简单的例子来说明差异.第一类是典型的Node类,带有一个下一个指针和一个值.将两个变量" slow"和" fast"设置为Node对象的同一实例(" head"),然后同时更改" slow"和" fast"的值不会影响其他变量.也就是说,"慢速","快速"和"头部"均指的是不同的对象(也通过检查其id()进行验证)。

第二个示例类没有下一个指针,仅具有self.val属性.这次更改两个变量" p1"和" p2"之一,这两个变量都设置为相同的实例" start",将影响另一个变量.尽管"开始"实例中的self.val是一个不变的数字。

'''
The below will have two variable names (slow, fast) assigned to a head Node.
Changing one of them will NOT change the other reference as well.
'''
class Node:
    def __init__(self, x, next=None):
        self.x = x
        self.next = next
    def __str__(self):
        return str(self.x)
n3 = Node(3)
n2 = Node(2, n3)
n1 = Node(1, n2)
head = n1
slow = fast = head
print(f"Printing before moving...{head}, {slow}, {fast}")  # 1, 1, 1
while fast and fast.next:
    fast = fast.next.next
    slow = slow.next
    print(f"Printing after moving...{head}, {slow}, {fast}") # 1, 2, 3
    print(f"Checking the ids of each variable {id(head)}, {id(slow)}, {id(fast)}") # all different
'''
The below will have two variable names (p1, p2) assigned to a start Dummy.
Changing one of them will change the other reference as well.
'''
class Dummy:
    def __init__(self, val):
        self.val = val
    def __str__(self):
        return str(self.val)
start = Dummy(100)
p1 = p2 = start
print(f"Printing before changing {p1}, {p2}")  # 100, 100
p1.val = 42
print(f"Printing after changing {p1}, {p2}")   # 42, 42

这对于我理解幕后的实际情况有些晦涩,我正在寻求澄清,这样我可以自信地将多个变量赋值设置给期望一个真实副本的同一对象(无需诉诸"导入" 复制; copy.deepcopy(x);")

感谢您的帮助

最新回答
  • 3天前
    1 #

    这不是不变性与可变性的问题.这是突变对象与重新分配引用的问题。

    If that object is immutable then when we set two variables to the same object, it'll be two separate copies

    这不是事实.无法复制.如果您有:

    a = 1
    b = a
    

    您有两个对同一个对象的引用,而不是该对象的副本.尽管因为整数是不可变的,但这很好.你不能变异 1 ,因此 ab 指向同一物体不会伤害任何东西。

    Python永远不会为您创建隐式副本.如果要复制,则需要自己明确复制(使用 copy.copy ,或其他类似切片的方法).如果您这样写:

    a = b = some_obj
    

    ab 将指向同一对象,无论类型为 以及它是否可变。


    那么您的示例之间有什么区别?

    在您的第一个 some_obj中 例如,您实际上从未更改过任何 Node 对象.它们可能也是不变的。

    Node
    

    最初的任务使两个 slow = fast = head 指向同一对象: slow .不过在那之后,您可以这样做:

    fast
    

    这重新分配了 head 引用,但从未真正改变对象 fast = fast.next.next 在看.您要做的就是更改 fastwhat对象 参考正在查看。

    但是,在第二个示例中,您直接对对象进行了突变:

    fast
    

    虽然这看起来像是重新分配,但不是.这实际上是:

    fast
    

    p1.val = 42 更改对象的内部状态。


    因此,重新分配会更改正在查看的对象.它将始终采用以下形式:

    p1.__setattr__("val", 42)
    

    与看起来像的重新分配相反,但实际上是对象的变异方法的调用:

    __setattr__
    

  • 3天前
    2 #

    您使事情复杂化了.基本的简单规则是:每次使用 a = b # Maybe chained as well. 要将对象分配给变量,只需使变量名称引用该对象即可.该对象可变与否无关紧要。

    l = [0] l[0] = 5 # Actually l.__setitem__(0, 5) d = Dummy() d.val = 42 # Actually d.__setattr__("val", 42) ,您将名称命名为 =a = b = 3 引用对象 a .如果然后让 b ,您将名称命名为 3 引用对象 a = 4 ,名称为 a 仍然指 4

    b ,您已经创建了两个名称 3a = b = [] 引用相同的列表对象.当做 a ,您追加 b 到这个清单.您尚未为 a.append(1)分配任何内容 或 1 在此过程中(您没有写任何 ab ).因此,是否通过名称 a = ...访问列表 或 b = ... ,它仍然是您操作的相同列表.可以用两个不同的名称来调用它。

    在带有类的示例中也会发生同样的情况:编写 a时 ,您可以使名称快速引用一个新对象。

    当你做 b ,你不做 fast = fast.next.next 引用一个新的不同实例,但是您更改了 p1.val = 42 此实例的属性. p1val 仍然是此唯一实例的两个名称,因此使用任何一个名称都可以引用同一实例。

    p1

  • python:pandasread_csv()不加载特殊字符
  • python:我无法删除整列-pandas