Python编程知识杂记

记录偶然遇到的Python知识和语言特性

Posted by tianhaoo on January 5, 2020 本文阅读量

传值还是传引用?

Python 中一切皆为对象,数字是对象,列表是对象,函数也是对象,任何东西都是对象。 而变量是对象的一个引用(又称为名字或者标签),对象的操作都是通过引用来完成的。 例如,在a = []中,[]是一个空列表对象,变量 a 是该对象的一个引用。

在 Python 中,「变量」更准确叫法是「名字」,赋值操作 = 就是把一个名字绑定到一个对象上。就像Java中的对象的引用一样。

这和C/C++中的变量有非常大的区别(C中的变量是一段内存区域,而python中的对象分配和回收是另外的机制)

Python 参数传递采用的是“传对象引用”的方式。总结来说:

  • 对于不可变对象作为函数参数,相当于C系语言的值传递。
  • 对于可变对象作为函数参数,相当于C系语言的引用传递。

其中不可变对象包括基本数据类型(int, float, bool, string等)和元组。

可变对象包括所有自己定义的class和内置的list, set, dict等。

那么如何在python中实现C/C++中传递基本数据类型的引用的效果呢?

  1. 使用可变数据类型代替,比如本来要用int &d, 可以换成传lst进去,在函数体里修改lst[0]的值,但这样应该是浪费了一定空间,而且感觉上怪怪的。
  2. 改变函数逻辑,把变了的数据return出来,但对某些递归算法这样做可能会增加增加实现的难度。
  3. 使用global, 即全局变量,但使用全局变量会带来额外的风险。

逻辑表达式的短路运算特性

  • a and b

    a==False时,不论b为何值,表达式结果为False

    a==True时,不论b为何值,表达式结果为b

  • a or b

    a==True时,不论b为何值,表达式结果为True

    a==False时,不论b为何值,表达式结果为b

  • 例如

    print(False or 'Y')的结果是Y,而不是True

    print(False or 0)的结果为0,而不是False

    print(True and 1)的结果是1,而不是True

python中的赋值、深拷贝和浅拷贝

在做搜做算法的相关题目时,发现结果总为空,原因是在函数体内将局部变量append到外部的res里之后,待到局部函数运行结束,清除了所有局部变量,那么res里面的元素就指向一个空地址了。

## 求子集
def func1(lst):
    res = []
    def backtrack(routes, start):  # routes记录路径(即子集),start记录开始位置(间接记录剩下的选择)
        res.append(routes)
        print(routes)
        for i in range(start, len(lst)):
            routes.append(lst[i])
            backtrack(routes, i+1)
            routes.pop()
    backtrack([], 0)
    return res

修改方法如下

## 求子集
def func1(lst):
    res = []
    def backtrack(routes, start):  # routes记录路径(即子集),start记录开始位置(间接记录剩下的选择)
        res.append(routes.copy())
        print(routes)
        for i in range(start, len(lst)):
            routes.append(lst[i])
            backtrack(routes, i+1)
            routes.pop()
    backtrack([], 0)
    return res

就只要在append的时候append一个列表的浅拷贝就好。

总结如下:

  • 赋值,即a = b

    传递对象的引用,赋值之后a 和 b 都指向同一个对象。

    给对象赋值有可变对象和不可变对象之分

    • 若是不可变对象,即内置类型(int,string,tuple等)则表现与浅拷贝一样。

    • 若是可变对象,即list, set, dict或自己定义的class等,这里需要注意,则a[0] = "#"等价于b[0]="#"

  • 浅拷贝,即a=b.copy()

    a 和 b 是一个独立的对象,但他们的子对象还是指向统一对象(是对象的引用)。

    • 若对不可变对象进行浅拷贝,则没什么意义,和用=赋值等价
    • 若是可变对象,相当于用一层循环创造一个副本,也就是深层的对象仍然有可能是赋值过来的,跟原来的有牵连。
        b = []
        for item in a:
            b.append(item)
      
  • 深拷贝,即a = copy.deepcopy(b)(需要import copy

    深度拷贝, a 和 b 完全拷贝了父对象及其子对象,两者是完全独立的。

    相当于递归的将每一层都完全拷贝一份,创造一份完全独立的副本

深拷贝&浅拷贝的参考

python中的++=

例子:

# 程序一
x=[1,2,3]
def f(x):
    x=x+[4]
f(x)
print(x)


# 程序二
x=[1,2,3]
def f(x):
    x+=[4]
f(x)
print(x)

两个程序的运行结果不一样,原因如下:

  • 对于可变类型:

    +: 代表连接操作,其结果会创建一个新的对象。

    +=: 代表追加操作,即 in-place 操作,在原地把另一个对象的内容追加到对象中。

  • 对于不可变类型: + 与 += 都代表连接或求和操作,两者没有什么区别,其操作的结果都会产生一个新的对象。