记录我的 python 学习历程-Day11 两个被忽视的坑、补充知识点、函数名的应用、新版格式化输出、迭代器
Dylan_Yu 人气:0补充知识点
函数形参中默认参数的陷阱
针对不可变数据类型,它是没有陷阱的
def func(name, sex='男'): print(name) print(sex) func('Dylan') # 输出结果: Dylan 男
陷阱只针对默认参数是可变的数据类型
def func(name, a_list=[]): # a_list=[]不在全局名称空间里,也不在局部名称空间里,它在一个特殊的内存空间中 a_list.append(name) # 将实参传进来的 name 增加到 a_list 这个空列表里. return a_list # 返回 a_list 这个列表 ret = func('Dylan') print(ret, id(ret)) # ['Dylan'] 4344392712 ret2 = func('xiaobai') print(ret2, id(ret2)) # ['Dylan', 'xiaobai'] 4344392712 # 如果你的默认参数指向的是可变的数据类型,那么你无论调用多少次这个默认参数,都是同一个.
面试题-1 ***
def func(a, list=[]): list.append(a) return list print(func(10,)) # [10] print(func(20, [])) # [20] print(func(100,)) # [10, 100] # 在传参的时候,如果使用默认参数传参,那么无论你调用多少次,使用的都将是同一个 id,如果你将默认参数覆盖传入,将会重新创建另一个 id.
面试题-2 ***
def func(a, list=[]): list.append(a) return list ret1 = func(10,) ret2 = func(20, []) ret3 = func(100,) # ret1 和 ret2 和 ret3 都执行完毕后,其 ret1和 ret3其实是同一个列表,那当然就是 ret3改变 ret1也跟着改变了. print(ret1) # [10, 100] print(ret2) # [20] print(ret3) # [10, 100]
局部作用域当中的坑
先引用,后定义的坑
# 在函数中,如果定义了一个变量,但是在定义这个变量之前对其引用了,那么解释器就会认为这是一个语法问题. # 你应该在使用之间先定义. cont =1 def func(): print(cont) cont = 3 func() # 此时就会报错了,因为你是先引用后定义的
globlal 和 nonlocal 关键字
global
在局部作用域声明一个全局变量
def func(): global name # 在局部作用域中声明一个全局变量 name = 'Dylan' print(name) # Dylan func() print(name) # Dylan # 这样用 global 在函数里声明一下,那么在全局作用域中就可以找到局部作用域中的变量了
修改一个全局变量
cont = 1 def func(): global cont cont += 1 print(cont) # 1 这之所以是1,是因为函数还没有执行,此时打印的是全局作用域中原有的 cont func() print(cont) # 2 这之所以是2,是因为,函数执行了,在函数里定义了全局变量,函数里把 cont 自加了个1,所以此时打印的 cont 就变成了2.函数内的自加改变了函数外的变量.就是因为 golbal在函数中定义了全局变量.
nonlocal
不能够操作全局变量
cont = 1 def func(): nonlocal cont cont += 1 func() print(cont) # 报错了 SyntaxError: no binding for nonlocal 'cont' found
局部作用域:内层函数对外层函数的局部变量进行修改
def wrapper(): cont = 1 def inner(): nonlocal cont cont += 1 print(cont) # 1 inner() print(cont) # 2 wrapper()
函数名的运用
函数名的定义和变量的定义几乎一致,在变量的角度,函数名其实就是一个变量,具有变量的功能:可以赋值;但是作为函数名他也有特殊的功能就是加上()就会执行对应的函数,所以我们可以把函数名当做一个特殊的变量,那么接下来,我们就来研究一下这个特殊的变量。
函数的内存地址
函数名指向的是这个函数的内存地址
def func(): print('Dylan') print(func) # 结果:<function func at 0x102020e18>
其实深一步理解可得知,与其说函数名()可以执行这个函数,不如说是函数的内存地址()才是执行这个函数的关键,就好比:
a = 1 b = 2 c = a + b print(c) # 3
a + b 并不是变量的相加,而是 两个变量指向的int对象的相加。
函数名可以赋值给其它变量
def func(): print("Dylan") print(func) a = func # 把函数当成一个变量赋值给另一个变量 a() # 函数调用 func()
通过变量的赋值,变量a,和变量func都指向的这个函数的内存地址,那么a() 当然可以执行这个函数了。
函数名可以当做容器类的元素
函数名就是一个变量,我的变量是可以当做容器类类型的元素的:
def func1(): print("in func1: 嘻嘻") def func2(): print("in func2: 哈哈") def func3(): print("in func3: 咯咯") def func4(): print("in func4: 吱吱") lst = [func1, func2, func3, func4] for i in lst: i() # 结果: # in func1: 嘻嘻 # in func2: 哈哈 # in func3: 咯咯 # in func4: 吱吱
函数名可以当做函数的参数
变量可以做的,函数名都可以做到
def func1(): print('in func1') def func2(f): # 这相当于 func1 print('in func2') # in func2 f() #这相当于func1() # in func1 func2(func1)
函数名可以作为函数的返回值
def func1(): print('in func1') def func2(f): print('in func2') return f ret = func2(func1) ret() # ret, f, func1 都是指向的func1这个函数的内存地址
小结:函数名是一个特殊的变量,他除了具有变量的功能,还有最主要一个特点就是加上() 就执行,其实他还有一个学名叫第一类对象。
Python新特性:f-strings格式化输出
f-strings 是python3.6开始加入标准库的格式化输出新的写法,这个格式化输出比之前的%s 或者 format 效率高并且更加简化,非常的好用,学完这个之后,以后再用格式化输出这就是你们唯一的选择。
简单举例
他的结构就是F(f)+ str的形式,在字符串中想替换的位置用{}展位,与format类似,但是用在字符串后面写入替换的内容,而他可以直接识别。
ame = 'Dylan' age = 18 sex = '男' msg = f'姓名:{name}, 年龄:{age}, 性别:{sex}' # f不区分大小写 print(msg) # 输出结果: 姓名:Dylan, 年龄:18, 性别:男
任意表达式
他可以加任意的表达式,非常方便:
print(f'{3*21}') # 63 name = 'barry' print(f"全部大写:{name.upper()}") # 全部大写:BARRY # 字典也可以 teacher = {'name': 'Dylan', 'age': 18} msg = f"The teacher is {teacher['name']}, aged {teacher['age']}" print(msg) # The comedian is Dylan, aged 18 # 列表也行 l1 = ['Dylan', 18] msg = f'姓名:{l1[0]},年龄:{l1[1]}.' print(msg) # 姓名:Dylan,年龄:18.
插入表达式
可以用函数完成相应的功能,然后将返回值返回到字符串相应的位置
def sum_a_b(a,b): return a + b a = 1 b = 2 print('求和的结果为:\t' + f'{sum_a_b(a,b)}') # 求和的结果为: 3
多行 f(不常用)
name = 'barry' age = 18 ajd = 'handsome' # speaker = f'''Hi {name}. # You are {age} years old. # You are a {ajd} guy!''' speaker = f'Hi {name}.'\ f'You are {age} years old.'\ f'You are a {ajd} guy!' print(speaker) # Hi barry.You are 18 years old.You are a handsome guy!
其他细节
这里有一些注意的细节,了解一下就行。
```python
print(f"{{73}}") # {73}
print(f"{{{73}}}") # {73}
print(f"{{{{73}}}}") # {{73}}
m = 21
# ! , : { } ;这些标点不能出现在{} 这里面。
# print(f'{;12}') # 报错
# 所以使用lambda 表达式会出现一些问题。
# 解决方式:可将lambda嵌套在圆括号里面解决此问题。
x = 5
print(f'{(lambda x: x*2) (x)}') # 10
```
**总结**:f-string的格式化输出更加简洁,方便,易读。而且他的处理速度对之前的%s 或者format 有了较高的提升,所以以后尽量使用此种格式化输出。
可迭代对象和迭代器
可迭代对象
可迭代对象的定义
字面意思:
对象:python 中一切皆对象。一个实实在在存在的值,对象。
可迭代:更新迭代。重复的,循环一个过程,更新迭代每次都有新的内容,可以进行循环更新的一个实实在在的值(或对象)。
专业角度:在python中,但凡内部含有
__iter__
方法的对象,都是可迭代对象。目前学过的可迭代对象有:srt / list / tuple / dict / set / range / 文件句柄
查看对象的内部方法 dir()
该对象内部含有什么方法除了看源码,还可以通过 dir() 去判断一个对象具有什么方法。
s1 = 'Dylan' print(dir(s1)) # 结果: ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
dir() 会返回一个列表,这个列表中含有该对象的以字符串的形式所有方法名。
这样我们就可以判断python中的一个对象中是不是有
__iter__
方法,从而就可以判断这个对象是不是可迭代对象了:s1 = 'alex' i = 100 print('__iter__' in dir(i)) # False print('__iter__' in dir(s1)) # True
小结:
从字面意思来说:可迭代对象就是一个可以重复取值的实实在在的东西。
从专业角度来说:但凡内部含有
'__iter__'
方法的对象,都是可迭代对象。判断一个对象是不是可迭代对象:
'__iter__'
in dir(对象)优点:
- 存储的数据直接能显示,比较直观。
- 拥有的方法比较多,操作方便。
缺点:
占用内存。
可迭代对象是不能迭代取值的(除去索引,key 以外)
那么这个缺点有人就提出质疑了,即使抛去索引,key以外,这些我可以通过for循环进行取值呀!对,他们都可以通过for循环进行取值,其实for循环在底层做了一个小小的转化,就是先将可迭代对象转化成迭代器,然后在进行取值的。
迭代器
迭代器的定义
- 字面意思:更新迭代,器:工具,可更新迭代的工具。
- 专业角度:迭代器是这样的对象:实现了无参数的__next__方法,返回序列中的下一个元素,如果没有元素了,那么抛出StopIteration异常.python中的迭代器还实现了__iter__方法,因此迭代器也可以迭代。 ——出自《流畅的python》
- 简单来说:在 python 中,内部含有
'__iter__'
方法,并且含有'__next__'
方法的对象,就是迭代器。
判断一个对象是否是迭代器
# 方法如下: # print('__iter__' in dir(对象) and '__next__' in dir(对象)) # 例子1: with open('文件', encoding='utf-8',mode='w') as f: print('__iter__' in dir(f) and '__next__' in dir(f)) # 输出结果: True # 这说明,文件句柄是一个迭代器。 # 例子2: o1 = 'alex' o2 = [1, 2, 3] o3 = (1, 2, 3) o4 = {'name': '太白','age': 18} o5 = {1, 2, 3} with open('文件', encoding='utf-8',mode='w') as f: print('__iter__' in dir(o1)) # True print('__iter__' in dir(o2)) # True print('__iter__' in dir(o3)) # True print('__iter__' in dir(o4)) # True print('__iter__' in dir(o5)) # True print('__iter__' in dir(f)) # True # print('__next__' in dir(o1)) # False print('__next__' in dir(o2)) # False print('__next__' in dir(o3)) # False print('__next__' in dir(o4)) # False print('__next__' in dir(o5)) # False print('__next__' in dir(f)) # True # 这说明,上面这些数据类型(或者对象),除了文件句柄外,其它的都不是迭代器,只是可迭代对象。
可迭代对象如何转化成迭代器
iter(可迭代对象)或者 可迭代对象.__iter__()
s = 'Dylan' obj = s.__iter__() # 或者 obj = iter(s) print(obj) # <str_iterator object at 0x102894a90>
迭代器取值
l1 = [11, 22, 33, 44, 55, 66] obj = iter(l1) # 把 l1列表转换为迭代器赋值给变量 obj。 print(obj) # <list_iterator object at 0x1032949e8> print(next(obj)) # 11 print(next(obj)) # 22 print(next(obj)) # 33 print(next(obj)) # 44 print(next(obj)) # 55 print(next(obj)) # 66 print(next(obj)) # 再多一个就会报错了。StopIteration 停止迭代 # 迭代器利用 next 取值:一个 next 取对应的一个值,如果迭代器里面的值取完了,还要 next,那么就会报'StopIteration' 停止迭代的错误
while 循环模拟 for 循环机制
l1 = [1, 2, 3, 4, 5, 6] # 1,将可迭代对象转化成迭代器。 obj = iter(l1) # 2,利用 while 循环,next 进行取值。 while 1: # 3,利用异常处理,终止循环。 try: print(next(obj)) except StopIteration: break
小结:
从字面意思来说:迭代器就是可以迭代取值的工具。
从专业角度来说:在 python 中,内部含有
'__iter__'
方法并且含有'__next__'
方法的对象就是迭代器。迭代器的优点:
节省内存。
迭代器在内存中相当于只占一个数据的空间,因为每次取值都会将上一条数据在内存中释放,加载当前的此条数据。
惰性机制。
next 一次,取一个值。
(有一个迭代器模式可以很好的解释上面这两条:迭代是数据处理的基石。扫描内存中放不下的数据集时,我们要找到一种惰性获取数据项的方式,即按需一次获取一个数据项。这就是迭代器模式。)
迭代顺的缺点:
速度慢,不能直观的查看里面的数据。
取值时不走回头路,只能一直向下取值。
l1 = [1, 2, 3, 4, 5, 6] obj = iter(l1) for i in range(2): print(next(obj)) for i in range(2): print(next(obj)) # 迭代器会记住当前程序最后一次取值的位置,下次取值时,会继续上次的位置进行取值。
可迭代对象与迭代器的对比
可迭代对象:
是一个操作方法比较多,比较直观,存储数据相对少(几百万个对象,8G 内存是可以承受的)的一个数据集。
应用:
当你侧重于数据可以灵活处理,并且内存空间足够,将数据集设置为可迭代对象是明确的选择。
迭代器:
是一个非常节省内存,可以记录取值位置,可以直接通过循环+next 方法取值,但是不直观,操作方法比较单一的数据集。
应用:
当你的数据量过大,大到足以撑爆你的内存或者你以节省内存为首选因素时,将数据集设置为迭代器是一个不错的选择。
加载全部内容