Python基础进阶:9个易错知识点

2023-12-29 15:34:06

你好,我是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)


本文原始版本发表链接:

https://mp.weixin.qq.com/s?__biz=MzI2Mjg3NTY5MQ==&mid=2247484561&idx=1&sn=b4b6213f8f9b2ca4a21b68ab8a02b4de&chksm=ea453bd5dd32b2c3aedc2058eebbad0204e0675b0642687234082bd8820483bed3e02e2cfbb6#rd

kelly会在公众号「kelly学技术」不定期更新文章,感兴趣的朋友可以关注一下,期待与您交流。
--over--

文章来源:https://blog.csdn.net/qm5132/article/details/135290550
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。