补充知识点
-
函数形参中默认参数的陷阱
-
针对不可变数据类型,它是没有陷阱的
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) # 3a + 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 方法取值,但是不直观,操作方法比较单一的数据集。
应用:
当你的数据量过大,大到足以撑爆你的内存或者你以节省内存为首选因素时,将数据集设置为迭代器是一个不错的选择。