python 可变与不可变变量

版权声明:本文为博主原创文章,未经博主允许下请随便转载。 https://blog.csdn.net/god_wen/article/details/78423621

不可变变量

操作某个对象时,重新开辟了内存,使其内存地址改变了,我们称其为可变对象,反之称为不可变变量

我们可以通过下面的例子来感受上面的话

x=1
print (id(x))
x+=1
print (id(x))
52454056
52454032

id()是一个内置函数,可以返回对象内存地址
同样的变量x经过加操作后地址改变了。

x=1
y=1
z=1
print (id(x))
print (id(y))
print (id(z))
56976040
56976040
56976040

可以发现虽然变量名不同但是地址确实一样的。

这种特性是不是很像某些语言中的字符串。我们称有这种特性的变量为不可变变量(值一旦改变就会占据新的内存空间)。python中的不可变变量有如下

不可变(immutable):int、字符串(string)、float、(数值型number)、元组(tuple),None

优点:
这样可以减少重复的值对内存空间的占用。
缺点:
操作之后就开辟内存的特性。执行效率会有一定程度的降低。

可变变量

我再来看看可变变量

x=[]
y=[]
z=[]
print (id(x))
print (id(y))
print (id(z))
62566472
62669960
62671368

可以看见对于可变变量 ,创建一次就开辟一次地址,不会引用同一块内存

x=[]
print (id(x))
x.append(1)
print (id(x))
49918024
49918024

而对同一个对象的操作。内存地址是不会改变的

可变(mutable)变量:字典型(dictionary)、列表型(list)

扫描二维码关注公众号,回复: 5476819 查看本文章

函数默认参数和None的作用

python 中不可变量为按值传递,可变变量按引用传递

def f(l=[]):
    l.append(1)
    return l
print (f())
print (f())
print (f())
[1]
[1, 1]
[1, 1, 1]

这是为什么呢?首先我们要知道python 你看见的任何东西都是对象。函数也不例外,默认参数有如下特点

  • 一个函数参数的默认值,仅仅在该函数定义的时候,被赋值一次。
  • 函数默认参数参数在每次调用时指向那个定义时创建的参数

现在我们来验证上上面的结果。
参数的默认值就存在__defaults__中。

def f(l=[]):

    l.append(1)
    return l
f()
print (f.__defaults__)
f()
print (f.__defaults__)
f()
print (f.__defaults__)

([1],)
([1, 1],)
([1, 1, 1],)

可以看见参数的默认值就在其中,但是是不是同一个呢,现在我们来看看地址

def f(i=1):
    print (id(i))
    i+=1
    print (id(i))
    return i

f()
print ('默认值地址:%s'%id(f.__defaults__[0]))
f()
print ('默认值地址:%s'%+id(f.__defaults__[0]))
f()
print ('默认值地址:%s'%+id(f.__defaults__[0]))
56976040
56976016
默认值地址:56976040
56976040
56976016
默认值地址:56976040
56976040
56976016
默认值地址:56976040

可以发现函数定义时默认值就已近存在在__defaults__中了。在调用函数时我们的参数变量就会被赋值为这个__defaults__[0]地址。当.__defaults__[0]是不可变变量时,我们对参数变量进行修改时我们的参数变量地址会变化。这个和我们的逻辑思维一致。
然而,可变变量就不一样了!

def f(l=[]):
    print id(l)
    l.append(1)
    print id(l)
    return l

f()
print ('默认值地址:%s'%id(f.__defaults__[0]))
f()
print ('默认值地址:%s'%id(f.__defaults__[0]))
f()
print ('默认值地址:%s'%id(f.__defaults__[0]))
58904648
58904648
默认值地址:58904648
58904648
58904648
默认值地址:58904648
58904648
58904648
默认值地址:58904648

因为可变变量的特性。我们在更改时时不会换地址的,所以我们一直都是在更改__defaults__[0] 。这种特性在默认参数中不是我们想要的。我们用如下的代码完成任务

def f(l=None):
    print id(l)
    l=[]
    l.append(1)
    print id(l)
    return l

print f()
print ('默认值地址:%s'%id(f.__defaults__[0]))
print f()
print ('默认值地址:%s'%id(f.__defaults__[0]))
print f()
print ('默认值地址:%s'%id(f.__defaults__[0]))

1792584792
47296584
[1]
默认值地址:1792584792
1792584792
47296584
[1]
默认值地址:1792584792
1792584792
47296584
[1]
默认值地址:1792584792

因为None是不可变变量,所以我对他的所有操作都是在新的内存中完成的。这其实也就是None的作用

类中局部变量的注意事项

class b:
    x = []
    def set(self):
        self.x.append(1)
    def get(self):
        return self.x
for i in range(3):
    a = b()
    print (b.__dict__)
    a.set()
    a.get()

{'x': [], '__module__': '__main__', 'set': <function set at 0x0000000002DE5AC8>, '__doc__': None, 'get': <function get at 0x0000000002DE5B38>}
{'x': [1], '__module__': '__main__', 'set': <function set at 0x0000000002DE5AC8>, '__doc__': None, 'get': <function get at 0x0000000002DE5B38>}
{'x': [1, 1], '__module__': '__main__', 'set': <function set at 0x0000000002DE5AC8>, '__doc__': None, 'get': <function get at 0x0000000002DE5B38>}

可以发现每次创建类b时,其中的x都是不一样的。这是因为类的变量被所有实例共享,而变量又是可变变量,所有x都是指向同一个地址。类似c++类中的静态变量,所有类的实例共享一个。而对于不可变变量就不一样了。


class b:
    x = 1
    def set(self):
        self.x+=1
    def get(self):
        return self.x
for i in range(3):
    a = b()
    a.set()
    print("---------------------------")
    print("%d对象地址:%s"%(i,id(a)))
    print ("x的值:%d"%a.get())
    print("x的地址:%d" % id(a.x))
    print("---------------------------")

---------------------------
0对象地址:1632001448536
x的值:2
x的地址:1896902144
---------------------------
---------------------------
1对象地址:1632001471096
x的值:2
x的地址:1896902144
---------------------------
---------------------------
2对象地址:1632001448536
x的值:2
x的地址:1896902144
---------------------------

可以发现,类的”全局变量“是不可变变量,被操纵之后重新占据内存空间。如果想把可变变量变成静态变量(所有类的实例共享一个),我们可以有两个方法。

  1. 使用不可变变量包装一下,比如在原来的代码上进行如下的修改:
class b:
    x = [1]
    def set(self):
        self.x[0]+=1
    def get(self):
        return self.x[0]
for i in range(3):
    a = b()
    a.set()
    print("---------------------------")
    print("%d对象地址:%s"%(i,id(a)))
    print ("x的值:%d"%a.get())
    print("x的地址:%d" % id(a.x))
    print("---------------------------")
D:\Python\learningExampleForPython\venv\Scripts\python.exe D:/Python/learningExampleForPython/example/test.py
---------------------------
0对象地址:2433377426008
x的值:2
x的地址:2433377460872
---------------------------
---------------------------
1对象地址:2433377448568
x的值:3
x的地址:2433377460872
---------------------------
---------------------------
2对象地址:2433377426008
x的值:4
x的地址:2433377460872
---------------------------

Process finished with exit code 0

  1. 使用global关键字,操作全局变量
x=1
class b:
    def set(self):
        global x
        x+=1
    def get(self):
        global x
        return x
for i in range(3):
    a = b()
    a.set()
    print("---------------------------")
    print("%d对象地址:%s"%(i,id(a)))
    print ("x的值:%d"%a.get())
    print("x的地址:%d" % id(x))
    print("---------------------------")

---------------------------
0对象地址:1739802401032
x的值:2
x的地址:1896902144
---------------------------
---------------------------
1对象地址:1739802401368
x的值:3
x的地址:1896902176
---------------------------
---------------------------
2对象地址:1739802401032
x的值:4
x的地址:1896902208
---------------------------

Process finished with exit code 0

猜你喜欢

转载自blog.csdn.net/god_wen/article/details/78423621
今日推荐