Python中的赋值


  • Python是一种动态类型编程语言,与C/C++等静态类型编程语言不同,Python中的变量没有固定的类型,变量也无需提前声明分配内存空间,因此我们可以给任何变量赋值任何符合Python规范的类型值,举个栗子:
    交互式Python代码:

    >>> var = 88
    >>> var = 'hello world'
    

    在C语言中,变量使用前需要提前声明并明确其类型(或声明和赋值可同一条语句执行),以便为其分配内存空间,举个栗子:
    C代码:

    int a;
    a = 100;
    

    但在C语言中一般不可直接将其他类型值赋值给不同类型的变量(可强制类型转换的除外),例如如下代码就会报错:
    C代码:

    int a;
    a = "hello world";
    
  • 在Python中将字面量或变量对另一个变量进行赋值可以理解为:给内存中的变量实体贴上一个标签。这个标签就是变量标识符,举个栗子:
    交互式Python代码:

    >>> a = 100
    >>> b = a
    >>> a is b
    True
    

    上述代码表明变量a与变量b是同一个对象,标识符a和标识符b都是内存中变量实体100的一个标签。在C语言中,因为每个变量都有自己的内存空间,变量赋值是将值复制到指定的内存中,因此进行赋值的两个变量只是值相同,但并不表示同一个对象。举个栗子:
    C代码:

    #include <stdio.h>
    int a = 100;
    int b;
    b = a;  
    if (&a == &b)
    {
        printf("yse\n");
    }
    else
    {
        printf("no\n");
    }
    // 输出:no
    
  • Python中函数的实参和形参之间以及返回值也是进行赋值操作的,举个栗子:
    交互式Python代码:

    >>> a = 3
    >>> b = 5
    >>> def func(m, n):
            print(m is a)
            print(n is b)
            r = m + n
            print('id(r):', id(r))
            return r
    >>> c = func(a, b)
    True
    True
    id(r): 10914624
    >>> id(c)
    10914624
    

    根据上述代码可知,Python函数调用时形参会被实参进行赋值操作,即在执行c = func(a, b)时其实相当于在函数内部执行了m = an = b。那么就会出现一个问题,当实参为可变对象时,在函数内部就存在改变实参的风险,举个栗子:
    交互式Python代码:

    >>> a = [1, 2]
    >>> def func(List):
            List.append(a[0] + a[1])
            return List
    >>> c = func(a)
    >>> print(c)
    [1, 2, 3]
    >>> print(a)
    [1, 2, 3]
    >>> id(a) == id(c)
    True
    

    上述代码中a为列表类型(可变对象),在函数内部List添加了一个元素并返回。aList和函数返回后的c表示同一个对象,这是我们之前讨论过的。现在我们讨论的重点是在函数内部改变了函数的实参,当然在有些情况下,我们正是需要借助这种特性完成某种功能,这种情况我们暂且不讨论。但在实际的工作中,更多的情况是我们不想函数更改实参,针对这种情况我们应该怎样规避实参被更改的风险呢?有三种方法:

    • 尽量使用不可变类型作实参
      当实参为不可变对象时,函数内部形参的操作不会影响实参,举个栗子:
      交互式Python代码:

      >>> a = 55
      >>> id(a)
      10916224
      >>> def func(value):
              print('id(value):', id(value), 'value: ', value)
              print(value is a)
              value = 99
              print('id(value):', id(value), 'value:', value)
              print(value is a)
      >>> func(a)
      id(value): 10916224 value:  55
      True
      id(value): 10917632 value:  99
      False
      >>> print('id(a):', id(a), 'a:', a)
      id(a): 10916224 a: 55
      

      上述代码中a为int类型(不可变类型),作为实参赋值给形参value,函数在执行value = 99之前valuea表示同一个对象,在执行之后valuea表示不同的对象,其实这很好理解就像我们前面讨论的,在执行赋值语句之后value标识符被“贴”到另一个内存变量实体99上了。

    • 使用拷贝

    • 以上两种都不想用?那就写代码的时候多注意吧

    下面我就来讨论今天的第二个重点:拷贝

Python中的拷贝


Python中的拷贝分为浅拷贝深拷贝

  • 浅拷贝

    Python中的默认拷贝都是浅拷贝,这里我们就拿列表类型举个栗子,先看看列表有哪些拷贝方法,此处参考Python语言及其应用
    交互式Python代码:

    >>> a = [1, 2, 3]
    >>> b = a.copy()
    >>> a
    [1, 2, 3]
    >>> b
    [1, 2, 3]
    >>> b is a
    False
    >>> c = list(a)
    >>> c
    [1, 2, 3]
    >>> c is a
    False
    >>> d = a[:]
    >>> d is a
    False
    

    上述代码中显示出三种列表拷贝方法:

    • 使用列表对象的.copy()方法
    • 使用list()转换函数
    • 使用列表分片[:]

    我们也可看到拷贝出来的对象与被拷贝对象不再是同一个对象,这也是拷贝的根本目的。当使用可变对象的拷贝进行函数传参可以避免函数内部修改实参,举个栗子:
    交互式Python代码:

    >>> a = [1, 2, 3]
    >>> def func(List):
            List.append(100)
            List[0] = 'hello'
            return List
    >>> b = func(a.copy())
    >>> b
    ['hello', 2, 3, 100]
    >>> a
    [1, 2, 3]
    

    好像一切都很完美,达到了我们的目的。于是我们编写了下面这样的代码:
    交互式Python代码:

    >>> a = [1, 'hello', 'world', [88, 99], 22]
    >>> def func(List):
            List.append('shawn')
            List[0] = 'simon'
            List[3][0] = 100
            return List
    >>> b = func(a.copy())
    >>> b
    ['simon', 'hello', 'world', [100, 99], 22, 'shawn']
    >>> a
    [1, 'hello', 'world', [100, 99], 22]
    

    有瑕疵啊,这运行结果和我们想象的不一样,我们想象着变量a索引为3的元素值仍为[88, 99],但输出并不是这样,问题出在了哪里?原因在于我们前面所说的Python中的默认拷贝均为浅拷贝,如果我们想要输出达到我们的预期,则需要深拷贝

  • 深拷贝
    深拷贝就是在每个层次都对可变对象进行拷贝,浅拷贝只对最外层可变对象进行拷贝。
    我们先来看看赋值的情况即b = a,如下图所示:
    enter image description here
    示意图ab指向同一个对象,我们通过代码验证其正确性:
    交互式Python代码:

    >>> a = [1, 2, [3, 4]]
    >>> b = a
    >>> id(b) == id(a)
    True
    >>> id(a[0]) == id(b[0])
    True
    >>> id(a[1]) == id(b[1])
    True
    >>> id(a[2]) == id(b[2])
    True
    >>> id(a[2][0]) == id(b[2][0])
    True
    >>> id(a[2][1]) == id(b[2][1])
    True
    

    再来看看浅拷贝的情况,先上图:
    enter image description here
    上图中为b = a[:]示意图,ba的浅拷贝,浅拷贝会将最外层可变对象进行拷贝,下面进行代码验证:
    交互式Python代码:

    >>> a = [1, 2, [3, 4]]
    >>> b = a[:]
    >>> id(b) == id(a)
    False
    >>> id(a[0]) == id(b[0])
    True
    >>> id(a[1]) == id(b[1])
    True
    >>> id(a[2]) == id(b[2])
    True
    >>> id(a[2][0]) == id(b[2][0])
    True
    >>> id(a[2][1]) == id(b[2][1])
    True
    

    最后来看看深拷贝,深拷贝我们需要用到copy库的deepcopy,示意图如下:
    enter image description here 接下来进行代码验证:
    交互式Python代码:

    >>> from copy import deepcopy
    >>>
    >>> a = [1, 2, [3, 4]]
    >>> b = deepcopy(a)
    >>> id(a) == id(b)
    False
    >>> id(a[0]) == id(b[0])
    True
    >>> id(a[1]) == id(b[1])
    True
    >>> id(a[2]) == id(b[2])
    False
    >>> id(a[2][0]) == id(b[2][0])
    True
    >>> id(a[2][1]) == id(b[2][1])
    True
    

    因此当实参为可变对象且其中含有多层嵌套的可变对象时,如果我们不想因为我们的粗心让函数更改实参,那么我们就应该使用深拷贝,看代码:
    交互式Python代码:

    >>> a = [1, 2, [3, 4]]
    >>> def func(List):
            List.append('hello world')
            List[0] = 'shawn'
            List[2][0] = 'simon'
            List[2][1] = 199
            return List
    >>> from copy import deepcopy
    >>> b = func(deepcopy(a))
    >>> a
    [1, 2, [3, 4]]
    >>> b
    ['shawn', 2, ['simon', 199], 'hello world']
    

    Note: Python中的深拷贝和浅拷贝只是针对可变对象而言,而对于不可变对象无所谓深拷贝还是浅拷贝,因为对于不可变对象两种拷贝和变量之间赋值没什么两样。
    交互式Python代码:

    >>> from copy import deepcopy
    >>>
    >>> a = 'hello world'
    >>> b = deepcopy(a)
    >>> c = a
    >>> id(a) == id(b) == id(c)
    True
    

总结


  • Python中变量之间的赋值,相当于给变量多贴了一个“标签”,或者说给变量起了个别名

  • 深拷贝、浅拷贝是针对可变对象说的,不可变对象无所谓什么拷贝

  • 对于可变对象,深拷贝对每层可变对象都进行拷贝,而浅拷贝只对最外层进行拷贝

  • 当函数实参为可变对象且我们不想函数改变实参本身,应尽量使用深拷贝,尤其在实参中嵌套了可变对象时