Python 学习 ---> 可变对象、不可变对象、深、浅 拷贝

1、Python 变量、对象

Python 变量

在 Python 中,变量都是指针;指针的内存空间与数据类型无关,其内存空间保存了指向数据的内存地址。

注意:在 C 中,当定义一个变量后,编译器就一定会给该变量分配内存,后续对该变量的读写是通过该内存地址实现的;而在 Python 中,只会给对象分配内存,。

Python 对象

在 Python 中,一切皆对象,主要由以下部分组成:

  • identity(ID):标识对象的 "内存地址",可使用 id(obj) 获取(唯一标识)
  • type(类型):标识对象的 "类型",可使用 type(obj) 获取
  • value(值):标识对象的 "",可使用 print(obj) 获取

注意对象的本质是一个内存块

注意:变量无类型,对象有类型;变量位于栈内存,对象位于堆内存

在 Python 中,对象可分为不可变对象可变对象,如下图所示:

不可变对象

包括:bool(布尔)、int(整数)、float(浮点数)、str(字符串)、tuple(元组)、frozenset(不可变集合),具有以下特性:

  • 可 hash(不可变长度)
  • 不支持新增
  • 不支持删除
  • 不支持修改
  • 支持查询
  • 可变对象

可变对象

包括:list(列表)、set(集合)、dict(字典),具有以下特性:

  • 不可 hash(可变长度)
  • 支持新增
  • 支持删除
  • 支持修改
  • 支持查询

tuple 不可变 之 可变

在 Python中,tuple 是不可变对象,但是这里的不可变指的是tuple这个容器总的元素不可变(确切的说是元素的id),但是元素的值是可以改变的。

总结:tuple 不可变是指 "里面元素的地址不能变",但是 "地址里面的内容是可以改变的"。

tpl = (1, 2, 3, [4, 5, 6])
print id(tpl)
print id(tpl[3])
tpl[3].extend([7, 8])
print tpl
print id(tpl)
print id(tpl[3])

代码结果如下,对于tpl对象,它的每个元素都是不可变的,但是tpl[3]是一个list对象。也就是说,对于这个tpl对象,id(tpl[3])是不可变的,但是tpl[3]确是可变的。

36764576
38639896
(1, 2, 3, [4, 5, 6, 7, 8])
36764576
38639896

2、什么是赋值引用?

拷贝对象的引用(即指针),使得两个变量指向同一个对象。

>>> a = {1: [1, 2, 3]}
>>> b = a
>>> print(a is b)
True
>>> print(id(a))
4468940880
>>> print(id(b))
4468940880

3、浅拷贝、深拷贝

浅拷贝

仅拷贝父对象,但不拷贝父对象中的子对象。

注意:子对象共享内存,指向同一个对象。

>>> a = {1: [1, 2, 3]}
>>> b = a.copy()
>>> print(a is b)
False
>>> print(id(a))
4468724912
>>> print(id(b))
4468689448

深拷贝(deepcopy)

拷贝父对象,同时递归拷贝所有子对象

注意如果子对象为不可变对象,则共享内存;如果子对象为可变对象,则不共享内存。

>>> a = {1: [1, 2, 3]}
>>> import copy
>>> b = copy.deepcopy(a)
>>> print(a is b)
False
>>> print(id(a))
140394609531776
>>> print(id(b))
140394611327680

使用 copy模块:深、浅 拷贝

在对Python对象进行赋值的操作中,一定要注意对象的深浅拷贝,一不小心就可能踩坑了。

当使用下面的操作的时候,会产生浅拷贝的效果:

  • 使用切片[:]操作
  • 使用工厂函数(如list/dir/set)
  • 使用copy模块中的copy()函数

使用copy模块里面的浅拷贝函数copy():

import copy

will = ["Will", 28, ["Python", "C#", "JavaScript"]]
wilber = copy.copy(will)

print id(will)
print will
print [id(ele) for ele in will]
print id(wilber)
print wilber
print [id(ele) for ele in wilber]

will[0] = "Wilber"
will[2].append("CSS")
print id(will)
print will
print [id(ele) for ele in will]
print id(wilber)
print wilber
print [id(ele) for ele in wilber]

使用 copy模块里面的深拷贝函数 deepcopy():

import copy

will = ["Will", 28, ["Python", "C#", "JavaScript"]]
wilber = copy.deepcopy(will)

print id(will)
print will
print [id(ele) for ele in will]
print id(wilber)
print wilber
print [id(ele) for ele in wilber]

will[0] = "Wilber"
will[2].append("CSS")
print id(will)
print will
print [id(ele) for ele in will]
print id(wilber)
print wilber
print [id(ele) for ele in wilber]

4、Python 参数传递是值传递还是引用传递?

在 Python 参数传递中,不存在所谓值传递和引用传递的区分,其本质都是拷贝对象的引用(指针)传递。

def fun(a):
    print(id(a))
    return a


b = 1
print(id(b))
print(fun(b))

在执行 fun(b) 代码时、其等价执行的是 fun(a=b),其本质是一种赋值引用;所以***在 Python 中,对象只存在引用传递。***

不可变对象_外部永远不随内部变

# 不可变对象
>>> def fun(b):
...    b = -1
...    return b
>>> a = 1
>>> print(fun(a))
-1
>>> print(a)
1

可变对象_外部跟随内部变

# 可变对象
>>> def fun(b):
...    b.append(-1)  # 修改原对象
...    return b


>>> a = [1]
>>> print(fun(a))
[1, -1]
>>> print(a)
[1, -1]

可变对象_外部不随内部变

# 可变对象>>> def fun(b):
...    b = [-1]  # 产生新对象
...    return b
>>> a = [1]
>>>> print(fun(a))
>[-1]
>>>> print(a)
>[1]

5、避免 "可变对象" 作为默认参数

在使用函数的过程中,经常会涉及默认参数。在Python中,当使用可变对象作为默认参数的时候,就可能产生非预期的结果。

下面看一个例子:

def append_item(a = 1, b = []):
    b.append(a)
    print b
    
append_item(a=1)
append_item(a=3)
append_item(a=5)  

结果为:

[1]
[1, 3]
[1, 3, 5]

从结果中可以看到,当后面两次调用append_item函数的时候,函数参数b并没有被初始化为[],而是保持了前面函数调用的值。

之所以得到这个结果,是因为在Python中,一个函数参数的默认值,仅仅在该函数定义的时候,被初始化一次

下面看一个例子证明 Python 的这个特性:

class Test(object):  
    def __init__(self):  
        print("Init Test")  
          
def arg_init(a, b = Test()):  
    print(a)  

arg_init(1)  
arg_init(3)  
arg_init(5)

结果为:

Init Test
1
3
5

从这个例子的结果就可以看到,Test类仅仅被实例化了一次,也就是说默认参数跟函数调用次数无关,仅仅在函数定义的时候被初始化一次。

正确使用:可变对象 作为 默认参数

对于可变的默认参数,我们可以使用下面的模式来避免上面的非预期结果:

def append_item(a = 1, b = None):
    if b is None:
        b = []
    b.append(a)
    print b
    
append_item(a=1)
append_item(a=3)
append_item(a=5) 

结果为:

[1]
[3]
[5]


 

6、模块循环依赖

在Python中使用import导入模块的时候,有的时候会产生模块循环依赖,例如下面的例子,module_x模块和module_y模块相互依赖,运行module_y.py的时候就会产生错误。

# module_x.py
import module_y
    
def inc_count():
    module_y.count += 1
    print module_y.count
    
    
# module_y.py
import module_x

count = 10

def run():
    module_x.inc_count()
    
run()            

其实,在编码的过程中就应当避免循环依赖的情况,或者代码重构的过程中消除循环依赖。

当然,上面的问题也是可以解决的,常用的解决办法就是把引用关系搞清楚,让某个模块在真正需要的时候再导入(一般放到函数里面)。

对于上面的例子,就可以把module_x.py修改为如下形式,在函数内部导入module_y

# module_x.py
def inc_count():
    import module_y
    module_y.count += 1
    print module_y.count

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值