知识点
首先,我们需要知道,Python中的变量类型是分为可变类型和不可变类型
可变类型:列表、字典、集合
不可变类型:数字、字符串、元组
碰到不可变类型,会在函数内部新开辟一个内存存放变量,修改的结果不会影响函数外面。如果要修改外部变量,则需要return或者使用global
def func(a):
print('--func----')
a = a + 1
print(id(a))
a = 10
print(id(a))
drawPic(a)
print(a)
输出:
1582984544
--def----
1582984576
10
对于可变类型,直接修改,结果影响外面
def drawPic(b):
print('--def----')
print(id(b))
b.append(5)
a = [1,2,3,4]
print(id(a))
drawPic(a)
print(a)
输出:
2788961292360
--def----
2788961292360
[1, 2, 3, 4, 5]
其实,这种机制存在于整个python环境中,而不仅仅是参数传递中,我们看下面的例子:
list1 = [1,2]
list2 = list1
list1[0] = 3
print list1
print list2
输出:
[3, 2]
[3, 2]
在上述示例中,令list2 = list1,因为根据之前的理论,list2和list1指向同一个对象,所以修改list1时同时也会修改list2,这种机制在一定程度上可以提高资源的重复利用,但是对C/C++程序员来说无疑算是一个陷阱。
知识点以上是知识点,下面是我曾经遇到的坑
对于python而言,一切皆对象,python为每个对象分配内存空间,但是并非为每个变量分配内存空间,因为在python中,变量更像是一个标签,就像在现实生活中,一个人可以有多种身份标签,比如:XX的父亲,XX的儿子,XX的工程师,X地志愿者等等,但对应的实体都是同一个人,只占同一份资源。
为了验证这个问题,我们可以做下面的实验:
x = 1
print(id(x))
print(id(1))
print(id(5))
print('-------------------')
x= 5
print(id(x))
print(id(1))
print(id(5))
输出:
1582984256
1582984256
1582984384
-------------------
1582984384
1582984256
1582984384
从输出可以看出,当我们将x变量的值由1变成5时,x的地址刚好从对象1的内存地址变成了对象5的内存地址。
Tips:
对于C/C++程序员来说,这段代码并不好理解,变量的值被改变时,竟然是变量的地址变化而不是原变量地址上的值变化!而且,为什么系统会为字面值1和5分配内存空间,这在C/C++中是不存在的!
所以我们要从python变量内存角度来理解:对象1和对象5早就在内存中存在,而变量x先是指向1的标签,在赋值后变成了指向5的标签。
那么,意思就是,只要你的内容是相同的,就只有一份内存空间,一改全改,一变全变
如下图,我已经新建了一个dataframe,id也是不一样的,但是修改其中一个dataframe,另一个还是跟着变了
def drawPic(df):
print('df的内存地址----' + str(id(df)))
#已经新建一个dataframe,id也不一样了
data = pd.DataFrame(data = df, columns = df.columns, index = df.index)
print('data的内存地址--' + str(id(data)))
#但是一个修改了,另一个也跟着变化了
data['CALL_DUR1'] = 0
print('---------------data-----------------')
print(data.head(5))
print('---------------df-----------------')
print(df.head(5))
df = conn_sql()
df = df[['USER_ID', 'TOTAL_FLUX1', 'CALL_DUR1']]
df.set_index('USER_ID', inplace=True)
drawPic(df)
输出:
df的内存地址----2788975571744
data的内存地址--2788976475944
---------------data-----------------
TOTAL_FLUX1 CALL_DUR1
USER_ID
1500000262321 0.000029 0
1500000289552 0.153896 0
1500000310606 0.000010 0
1500000317566 0.000000 0
1500000455809 0.524805 0
---------------df-----------------
TOTAL_FLUX1 CALL_DUR1
USER_ID
1500000262321 0.000029 0
1500000289552 0.153896 0
1500000310606 0.000010 0
1500000317566 0.000000 0
1500000455809 0.524805 0
但是,我只要稍微修改data的一个值,让两个dataframe的数据不一样,data的修改就不会影响到df
def drawPic(df):
print('df的内存地址----' + str(id(df)))
#稍微修改data的一个值
data = df.iloc[1:,:]
print('data的内存地址--' + str(id(data)))
#就不会跟着变化了
data['CALL_DUR1'] = 0
print('---------------data-----------------')
print(data.head(5))
print('---------------df-----------------')
print(df.head(5))
df = conn_sql()
df = df[['USER_ID', 'TOTAL_FLUX1', 'CALL_DUR1']]
df.set_index('USER_ID', inplace=True)
drawPic(df)
输出:
df的内存地址----2788976475888
data的内存地址--2788976549840
---------------data-----------------
TOTAL_FLUX1 CALL_DUR1
USER_ID
1500000289552 0.153896 0
1500000310606 0.000010 0
1500000317566 0.000000 0
1500000455809 0.524805 0
1500000513925 0.000156 0
---------------df-----------------
TOTAL_FLUX1 CALL_DUR1
USER_ID
1500000262321 0.000029 45.416667
1500000289552 0.153896 19.950000
1500000310606 0.000010 354.833333
1500000317566 0.000000 0.000000
1500000455809 0.524805 118.883333
总结来说,就是,只要内容一样,修改就会跟着变化。我们已经知道,如何在函数中修改一个不可变对象的实参(使用return或者global),那么,如何在函数中不修改可变类型的实参?
这个时候就需要引用另一个模块:copy,就是重新复制出另一个可变类型参数
def drawPic(df):
print('df的内存地址----' + str(id(df)))
#拷贝
data = copy.copy(df)
print('data的内存地址--' + str(id(data)))
data['CALL_DUR1'] = 0
print('---------------data-----------------')
print(data.head(5))
print('---------------df-----------------')
print(df.head(5))
df = conn_sql()
df = df[['USER_ID', 'TOTAL_FLUX1', 'CALL_DUR1']]
df.set_index('USER_ID', inplace=True)
drawPic(df)
输出:
df的内存地址----2788973730560
data的内存地址--2788975492512
---------------data-----------------
TOTAL_FLUX1 CALL_DUR1
USER_ID
1500000262321 0.000029 0
1500000289552 0.153896 0
1500000310606 0.000010 0
1500000317566 0.000000 0
1500000455809 0.524805 0
---------------df-----------------
TOTAL_FLUX1 CALL_DUR1
USER_ID
1500000262321 0.000029 45.416667
1500000289552 0.153896 19.950000
1500000310606 0.000010 354.833333
1500000317566 0.000000 0.000000
1500000455809 0.524805 118.883333
copy复制依然可能存在问题
在可变类型和不可变类型混合的情况下,我们知道了,一个变量很可能并非仅仅指向一个完整的对象,变量的子元素依然存在引用的情况,比如在lst = [[1,2],3)]中,lst[0]就是引用了别处的对象,而非在内存中完全存在一个单独的[[1,2],3]对象,那么,如果使用copy对lst进行复制,对于lst[0],是仅仅复制了引用,还是复制了整个对象呢?我们可以看下面的例子:
import copy
lst = [[1,2],3]
lst_cp = copy.copy(lst)
print id(lst)
print id(lst_cp)
print id(lst[0])
print id(lst_cp[0])
输出:
3072042988
3072043404
3072043020
3072043020
从结果可以看出,对于lst列表,仅仅是复制了lst对象,lst[0]仅仅是复制了引用。这其实违背了我们的本意,如果我们要完整地复制整个对象,最稳妥的办法是使用deepcopy
使用copy模块的deepcopy方法,相对于copy方法(通常被称为浅拷贝),deepcopy(通常被称为深拷贝),浅拷贝就像上述的例子一样,复制出一个新的对象,但是目标对象中的子对象可能是引用。而deepcopy则是完全复制一个对象的所有内容。
参考:
https://www.cnblogs.com/downey-blog/p/10483216.html