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