你好,我是kelly。
kelly根据自己平时工作,总结9个易错知识点,希望对大家有用。
知识点1:is 和=
is比较是两个变量地址是否相同,==比较是两个变量的值(内容)是否相同。
示例:
In [92]: a = [1, 2, 3]
In [93]: b = [1, 2, 3]
In [94]: id(a)
Out[94]: 2799999236992
In [95]: id(b)
Out[95]: 2799997311872
In [98]: a is b
Out[98]: False
In [96]: a == b
Out[96]: True
a和b的内容相同,用id()函数可以查看变量地址,a和b的地址不同。
对上述例子做调整:
In [99]: a = [1, 2, 3]
In [100]: b = a
In [101]: id(a)
Out[101]: 2799998170496
In [102]: id(b)
Out[102]: 2799998170496
In [103]: a is b
Out[103]: True
In [105]: a == b
Out[105]: True
将一个变量赋值给另一个变量,本质上是起了另一个别名,前后2个变量指向同一个内存地址,地址和内容都相同。
知识点2:{}、{1,2,3}、set()的区分
{}创建的是空dic,{1,2,3}创建的是set,空set使用set()创建
示例:
In [39]: type({})
Out[39]: dict
In [40]: type({1,2,3})
Out[40]: set
In [41]: set()
Out[41]: set()
In [42]: dict()
Out[42]: {}
使用{1,2,3}初始化集合set是一个特殊写法,注意和{}创建空字典dict做区别。
知识点3:函数参数的默认值使用可变数据类型
默认参数值只会在函数被执行时被赋值一次,参数默认值在内存始终存在,直至程序运行结束。
使用可变数据类型作为函数参数默认值,该参数值“可能”会在函数运行过程不断发生变化。
示例:
def add_sequence(value, lst=[]):
lst.append(value)
return lst
In [57]: add_sequence(100)
Out[57]: [100]
In [58]: add_sequence(101)
Out[58]: [100, 101]
In [59]: add_sequence(102)
Out[59]: [100, 101, 102]
函数参数lst使用可变数据类型list作为默认参数,多次运行函数,每次的参数lst值都不同。当程序逻辑复杂时,会引发各种bug。
建议不要使用列表作为函数参数的默认值,再看另外一个示例:
In [66]: def cal_sum(summation=[]):
...: summation.append(1)
...: return summation
In [67]: cal_sum()
Out[67]: [1]
In [68]: cal_sum()
Out[68]: [1, 1]
In [69]: cal_sum()
Out[69]: [1, 1, 1]
再次强调,默认参数值只会在函数定义被执行时被赋值一次。
知识点4:深拷贝、浅拷贝
在Python中,对象赋值实际是对象引用,前后两个变量所指向的是同一个地址(内存空间)。
变量赋值基础:变量A赋值给变量B,只是将变量A的引用给了变量B,并没有将变量A的值真正给变量B。
深、浅拷贝在复杂变量(list、dict,或者list、dict相关的各种嵌套)赋值时会发生问题。
浅拷贝
示例:
In [70]: a = [100, 101]
...: b = a
...: a[0] = 200
In [71]: a
Out[71]: [200, 101]
In [72]: b
Out[72]: [200, 101]
变量a改变了,b也同步变化。
看下a和b的内存地址:
In [73]: id(a)
Out[73]: 2799976453696
In [74]: id(b)
Out[74]: 2799976453696
显然,a和b指向同个内存地址。
深拷贝
如果想要新变量的值不受赋值前的原变量的影响,需要对原变量执行深拷贝,这样可以创建一个完全新的变量,新变量会对原变量内部的对象进行级联拷贝。
深拷贝操作需要导入copy模块
示例:
In [75]: import copy
...: a = [100, 101]
...: b = copy.deepcopy(a)
...: b[0] = 200
In [76]: a
Out[76]: [100, 101]
In [77]: b
Out[77]: [200, 101]
In [78]: id(a)
Out[78]: 2799987225600
In [79]: id(b)
Out[79]: 2799987318464
对变量a进行深拷贝deepcopy得到变量b,变量a和b任一一方的改动不会影响到另一方。
知识点5:f(x)与f(*x)调用
f(x):直接将变量实参x赋值给函数f的指定形参。
f(*x):x一般为序列,调用时会按照函数f的参数顺序,将序列x元素依次赋值给函数f的各个参数。
示例:
def f1(x):
print("f1:", x)
def f2(x1, x2, x3):
print("f2:", x1, x2, x3)
f1(100)
f2(*(100, 101, 102))
输出结果:
f1: 100
f2: 100 101 102
说明:f(*x)调用时,序列x的元素个数必须和函数f的形参数目一致。
def f2(x1, x2, x3, x4):
print("f2:", x1, x2, x3)
f2(*(100, 101, 102))
抛出异常:
TypeError: f2() missing 1 required positional argument: 'x4'
知识点6:*args 和 **kwargs
*args:接受序列作为输入
**kwargs:接受字典作为输入
在很多情况下,定义函数时无法确定真正调用时所传入参数的数目。对于这种情况,可变参数的机制允许函数调用时接受可变数量的参数。
在函数定义中,*args表示可以接受任意数量的位置参数,使用时将传入的位置参数打包成一个元组赋值给args。
**kwargs表示可以接受任意数量的关键字参数,使用时将传入的关键字参数打包成一个字典,赋值给kwargs。
示例:
In [62]: def show_func1(*args):
...: return args
In [63]: show_func1("张三", "李四", "王五")
Out[63]: ('张三', '李四', '王五')
In [64]: def show_func2(**kwargs):
...: return kwargs
In [65]: show_func2(name="张三", sex="男", age=31)
Out[65]: {'name': '张三', 'sex': '男', 'age': 31}
知识点7:可迭代对象、迭代器、生成器
可迭代对象
实现了__iter__()方法的对象,可以通过调用iter()函数返回一个迭代器对象。
可迭代对象可以是Python内置的容器对象(如列表、元组、集合、字典等),也可以是自定义的对象。
自定义的可迭代对象示例
class CustomIterableObject(object):
def __init__(self):
self.name = ["张三", "李四", "王五"]
def __iter__(self):
pass
什么都不做,仅仅实现了__iter__方法。
Python内置的可迭代对象示例:
In [80]: a = [100, 101, 102, 103]
In [81]: hasattr(a, "__iter__")
Out[81]: True
In [82]: hasattr(a, "__next__")
Out[82]: False
上述列表a,是一个可迭代对象,但没有实现__next__方法,不是一个迭代器。
迭代器
Python中实现迭代协议的对象称为迭代器,本质上是一种数据结构。需要实现__next__()和__iter__()等方法。__iter__()返回迭代器自身,__next__()返回序列的下一个元素。
在每次迭代时,迭代器都会产生一个值,直到遍历完所有值。
可迭代对象和迭代器的区别:可迭代对象不是迭代器,可迭代对象可以通过iter()函数返回一个迭代器。
需要说明的是,可迭代对象不一定能被迭代,但迭代器一定是可迭代对象。
示例:
In [83]: a2 = iter(a)
In [86]: a2
Out[86]: <list_iterator at 0x28bebd10880>
In [84]: hasattr(a2, "__iter__")
Out[84]: True
In [85]: hasattr(a2, "__next__")
Out[85]: True
使用iter()函数得到一个迭代器a2,a2同时实现了__iter__()和__next__()两个方法。
生成器
是一种特殊类型的迭代器,它使用函数和yield关键字定义,可以像普通函数一样调用和执行。生成器在每次迭代时产生一个值,并在下一次迭代时恢复执行。
第一次调用生成器生成函数后,会返回一个生成器对象,可以在挂起和恢复状态中切换。生成器不会一次性生成整个序列,仅在每次调用时生成一个元素。生成器在内存使用和效率上更加优化,特别适合大型数据处理。
生成器和迭代器的区别:
1、实现方式不同:生成器用yield语句实现,创建迭代器需要实现__iter__()和__next__()方法。
2、生成数据方式不同:对于一个序列,生成器逐个生成元素,迭代器一次性生成整个序列,并存放在内存中。
3、执行方式不同:生成器使用函数方式调用,每次迭代时涉及到函数挂起和恢复;迭代器按照序列顺序,对各个元素依次执行。
知识点8:return、yield关键字
return是完全终止函数,并返回值。
yield是临时从函数内部返回值,得到生成器。
示例:
def get_return_value():
for v in range(100, 105):
return v
def get_yield_value():
for v in range(100, 103):
yield v
In [53]: value_yield = get_yield_value()
...: for value in value_yield:
...: print(value)
100
101
102
知识点9:函数使用没有定义的全局变量
函数只执行读操作时,会直接使用全局变量的值。
函数执行写操作时,会报错,提示局部变量未定义。
示例:
读取全局变量
SUMMATION= 100
def cal_sum1(a):
print(SUMMATION)
cal_sum1(10)
写全局变量
SUMMATION= 100
def cal_sum2(a):
SUMMATION += a
print(SUMMATION)
cal_sum2(10)
抛出错误:
UnboundLocalError: local variable 'SUMMATION' referenced before assignment
如何修改全局变量?使用global关键字,
SUMMATION= 100
def cal_sum2(a):
global SUMMATION
SUMMATION += a
print(SUMMATION)
cal_sum2(10)
本文原始版本发表链接:
kelly会在公众号「kelly学技术」不定期更新文章,感兴趣的朋友可以关注一下,期待与您交流。
--over--