今天突来兴趣,留档一些python常见的错误和思维
一图读懂:(2019元旦补:)
下面内容感兴趣可以看,不然就只看一图读懂:
在说错误之前,先来看一下在python中的哪些是可变数据和哪些是不可变数据吧
-
1.可变数据类型
- 列表list
- 字典dict 2.不可变数据类型
- 整型int
- 浮点型float
- 字符串型string
- 元组tuple。
下面我们以int和list为例,来说一下“可变数据类型”和“不可变数据类型”的区别
(1).不可变数据类型分析:
x=1
y=1
print(id(x))
print(id(y))
结果
1376539424
1376539424
可以看出,x和y绑定同一个地址(类比理解 用c的指针理解,p1和p2中的,指针变量p1和p2的值是一样,都是保存了同一个内存的地址,*p1意思就是":p1的内容(值)所指(代表)的地址的内容"),就是x和y其实是引用了同一个对象,即1,也就是说内存中对于1只占用了一个地址,而不管有多少个引用指向了它,都只有一个地址值,只是有一个引用计数器会记录指向这个地址的引用到底有几个而已。
(2).可变数据类型分析:
x=[2,3]
print(id(x))
x=[2,3]
print(id(x))
y=[2,3]
print(id(y))
结果
2285423966792
2285423964360
2285423964388
两次x变量绑定在不同的地址(即变量x(引用x)的值不同,有人说:python中 [变量]更准确叫法是[名字],赋值操作 = 就是把一个名字绑定到一个对象上。或者 类比理解(python中的变量是没有类型信息和不占内存区域的),c中一个指针变量(java php js中叫引用)指向了一个对象(内存空间)),也就是说其实创建了两个不同的对象,这一点明显不同于不可变数据类型,所以对于可变数据类型来说,具有同样值的对象是不同的对象,即在内存中保存了多个同样值的对象,地址是不同。
重点:
x=[2,3]
print(id(x))
x.append(4)
print(x)
x += [1]
print(x)
print(id(x))
结果
2655391136328
[2, 3, 4]
[2, 3, 4, 1]
2655391136328
我们对列表进行添加操作,分别x.append(4)和x += [1],发现这两个操作使得x引用的对象值变成了上面的最终结果,但是x变量绑定的地址依旧是2655391136328,也就是说对x引用指向的对象进行的操作,不会改变x变量的值(x引用指向的地址),只是在地址后面又扩充了新的地址,改变了地址里面存放的值,所以可变数据类型的意思就是说对一个变量进行操作时,其值是可变的,值的变化并不会引起新建对象,即地址是不会变的,只是地址中的内容变化了或者地址得到了扩充。
参考:http://blog.csdn.net/dan15188387481/article/details/49864613
开始常见错误
1.用一个可变的值作为默认值(可变数据类型作为函数定义中的默认参数)
def fn(var1,var2=[]):
var2.append(var1)
print(var2)
print(id(var2))
fn(1)
fn(2)
fn(3)
输出结果:
[1]
2275344529992
[1, 2]
2275344529992
[1, 2, 3]
2275344529992
在Python里,函数的默认值是在函数定义的时候实例化的,而不是在调用的时候。如同上面,当解析器运行到定义函数时,列表[]被实例化为函数定义的一部分。当函数运行时,它并不是每次都被实例化。这意味着,这个函数会一直使用完全一样的列表对象,除非我们提供一个新的对象传进去。
就是说 函数定义时,如果没有传进新对象,可变类型[]的地址一直没有变化,var2变量一直绑定在同一个地址上,这样就回到了上面可变数据类型分析问题上了
代码修正:
def fn(var1,var2=None):
if not var2:
#if var2 is None:
var2=[]
var2.append(var1)
print(var2)
fn(1)
fn(2)
结果
[1]
[2]
类似例子
import time
def print_now(now=time.time()):
print(now)
print_now()
print_now()
print_now()
结果
1513001005.1218028
1513001005.1218028
1513001005.1218028
跟上面一样,time.time() 的值是可变的 类似可变数据类型,那么它只会在函数定义的时候计算,所以无论调用多少次,都会返回相同的时间 — 这里输出的时间是程序被Python解释运行的时间。
2.可变数据类型作为类变量
class Url(object):
urls = []
def add_url(self,url):
self.urls.append(url)
print(id(self.urls))
a = Url()
a.add_url('www.baicai.com')
b = Url()
b.add_url('www.wljsb.com')
print(a.urls)
print(b.urls)
结果
2264790084168
2264790084168
[‘www.baicai.com’, ‘www.wljsb.com’]
[‘www.baicai.com’, ‘www.wljsb.com’]
这两个对象怎么会都有这两个元素呢?
这和第一个问题是类似的。创建类定义时,urls列表将被实例化。该类所有的实例使用相同的列表。在有些时候这种情况是有用的,但大多数时候你并不想这样做。
就是说,列表的地址一直没有变化,urls变量绑定在同一个地址上,最终还是回到上面的分析中
你希望每个对象有一个单独的储存。为此,我们修改代码为:
class Url(object):
def __init__(self):
self.urls = []
def add_url(self,url):
self.urls.append(url)
print(id(self.urls))
a = Url()
a.add_url('www.baicai.com')
b = Url()
b.add_url('www.wljsb.com')
print(a.urls)
print(b.urls)
结果
2019524248968
2019524233800
[‘www.baicai.com’]
[‘www.wljsb.com’]
3.理解python变量(也叫可变分配错误)
a = {'1': "one", '2': 'two'}
b = a
b['3'] = 'three'
print(a)
结果
{‘2’: ‘two’, ‘3’: ‘three’, ‘1’: ‘one’}
现在看这个很简单了, a 和 b 变量都是绑定在同一个对象上,就相当于看同一台电视,谁换台都受影响的。
结尾,根据这些也可以判断python中函数的参数 为啥有些人老是搞糊涂 是平常说的 传值还是传引用(地址), 其实 python中参数的传递本质上是一种 赋值操作
def foo(arg):
arg = 2
print(arg)
a = 1
foo(a)
print(a)
结果
2
1
上面根据不可变类型分析,a 和 arg 绑定的地址不同 类似传值操作
为啥有人会搞糊涂,说参数是传引用呢?看下面
def bar(args):
args.append(2)
b = [1]
print(b)
print(id(b))
bar(b)
print(b)
print(id(b))
结果
[1]
3001474107976
[1, 2]
3001474107976
看到这个结果,你思考一下,是不是很像把列表的地址传了进去,这就是c java php js的思维了,因为他们觉得调用函数后 再输出b列表是[1,2] ,函数内部修改了函数外部的变量,看地址输出都没有变化,所以认为是传引用。
这就是python变量和其他语言的不同之处了,上面根据可变类型分析,b和arg绑定在了同一个地址上,对同一个列表对象进行操作的。
python的变量和其他语言不同,可以说把传值传引用的说法统一成,说 赋值操作(绑定),根据不可变类型和可变类型分析,就理解到具体的操作变化了
以上内容总结成:变量当作标签贴在内存上,看是可变还是不可变,传参是时候,可变的话在内存基础上变,不可变的话,就开辟另外内存空间,把便签贴过来.
python常注意点:
a =(1,) #元组只有一个元素是后面要有逗号
b = a[::-1] #这是反向切片赋值
a[-1:6] #分片中最左边的索引比它右边的晚出现在序列中,结果就是一个空序列