函数的定义

什么是函数

函数就是一种具备某一功能的工具,事先将工具准备好就是函数的定义,遇到场景拿来就用。

怎么使用函数

1
2
3
4
5
ef 函数名(等同于变量)():   # 定义函数
"""对函数的描述"""
代码块

函数名() # 函数调用

注册功能函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def register():
"""注册功能"""
count = 0
while count < 3:
username_inp = input('请输入你的用户名:').strip()
pwd_inp = input('请输入你的密码:').strip()
re_pwd_inp = input('请再次确认你的密码:').strip()

if re_pwd_inp != pwd_inp:
print('两次密码输入不一致!')
count += 1
continue

with open('userinfo.txt', 'a', encoding='utf-8') as fa:
fa.write(f'{username_inp}:{pwd_inp}\n')
print('注册成功')
break

register()

登录功能函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def login():
"""登录功能"""
username_inp = input('请输入你的用户名:').strip()
pwd_inp = input('请输入你的密码:').strip()

user_info = f'{username_inp}:{pwd_inp}'

with open('userinfo.txt', 'r', encoding='utf-8') as fr:
data = fr.read()
user_info_list = data.split('\n')

if user_info in user_info_list:
print('登录成功')
else:
print('登陆失败')

login()

函数的定义的三种形式

空函数

定义了一个函数,但啥都没有

1
2
def func():
pass

有参函数

1
2
3
4
5
def add(x, y):
"""有参函数"""
print(int(x) + int(y))

add(1, 2)

无参函数

1
2
def func():
print('hello world')

函数的调用

使用函数名()的形式就可以调用函数了

1
2
3
4
5
6
7
8
9
10
11
12
13
def func(x, y):
"""给定两个数, 打印较大的数"""
if x > y:
print(x)
else:
print(y)

print(func)
func(10, 20) # 加括号就能调用(执行内存中的代码)

# 打印结果:
<function func at 0x00000253DFEF0950> # 函数的地址
20

函数的返回值

  • return 返回一个值
1
2
3
4
5
6
7
8
9
10
def add(x, y):
return x + y



res = add(1, 2)
print(res)

# 打印结果
3
  • return没有返回值,默认返回None
1
2
3
4
5
6
7
8
9
10
11
12
def add(x, y):
print(x, y)
# return x + y



res = add(1, 2)
print(res)

# 打印结果:
1 2
None
  • return可以返回多个值,可以返回任意数据类型,默认用元组形式返回
1
2
3
4
5
6
7
8
9
10
11
def add(x, y):
print(2)
return x, y, x + y


res = add(1, 2)
print(res)

# 打印结果:
2
(1, 2, 3)
  • return会终止函数,不运行下面的代码,即使有多个return,只会执行第一个return,不会执行下一个
1
2
3
4
5
6
7
8
9
10
11
12
13
def add(x, y):
print(2)
return x, y, x + y # 遇到return就会终止函数
print('hello world')
return x


res = add(1, 2)
print(res)

# 打印结果:
2
(1, 2, 3)

函数的参数

形参和实参

形参

在函数定义阶段括号内定义的参数,称之为形式参数,简称形参,本质上就是变量名

1
2
3
def func(x, y):
print(x)
print(y)

实参

在函数调用阶段括号内传入的参数,称之为实参

1
func(1, 2)

位置参数

位置形参

在函数定义阶段,按照从左往右的顺序依次定义的参数,称之为位置形参

1
2
3
def func(x, y):
print(x)
print(y)

位置实参

在函数调用阶段,按照从左往右的顺序定义的参数,称之为位置实参,依次传值给位置形参

1
func(1, 2)

默认形参

在函数定义阶段,就已经被赋值

1
2
3
4
5
def func(x, y=10):
print(x)
print(y)

func(2)
  • 在定义阶段就已经赋值,在调用时可以不用传值;如果传值,就使用传的值
  • 默认形参必须在位置形参之后

关键字实参

在调用函数时,按照位置形参名传值

1
func(y=2, x=1)

关键字实参必须在位置实参后面

可变长参数

可变长形参之*

形参中的会将溢出的位置实参全部接收,然后存储元组的形式,然后把元组赋值给后的参数。需要注意的是:*后的参数名约定俗成为args。

1
2
3
4
5
6
7
8
def f1(*args):
print('args:', args)


f1(1, 23, 4) # args接收位置实参多余的参数

# 打印结果:
args: (1, 23, 4)

可变长形参之**

1
2
3
4
5
6
7
8
9
10
11
12
'''
形参中的**会将溢出的关键字实参全部接收,然后存储字典的形式,然后把字典赋值给**后的参数。需要注意的是:**后的参数名约定俗成为kwargs。
'''

def f2(a,**kwargs):
print('kwargs:', kwargs)


f2(x=12, a=10, b=9) # kwargs接收关键字实参多余的参数

# 打印结果:
kwargs: {'x': 12, 'b': 9}

可变长实参之*

1
2
3
4
5
def func1(a,b,c,d):
print(a,b,c,d)

lt = [1,2,3,4]
func1(*lt) # *lt列表中的元素打散成位置实参依次传给位置形参

可变实参之**

1
2
3
4
5
6
def func(a, b):
print(a, b)


dic = {'a': 1, 'b': 2}
func(**dic) # **dic把字典打散成关键字实参然后传给函数func

函数对象的四大功能

在python 中一切皆对象

引用

1
2
3
4
5
6
7
8
9
10
def f1():
print('from f1')

func = f1
print('f1:', f1)
print('func', func)

# 打印结果:
f1: <function f1 at 0x000002110991D268>
func <function f1 at 0x000002110991D268>

作为函数参数

1
2
3
4
5
6
7
8
9
10
11
12
13
def f1():
print('from f1')


def f2(f2_f1):
print('f2_f1:', f2_f1)

f2(f1)
print('f1:', f1)

# 打印结果:
f2_f1: <function f1 at 0x000001E47029D268>
f1: <function f1 at 0x000001E47029D268>

作为函数返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def f1():
print('from f1')


def f2(f2_f1):
return f2_f1

res = f2(f1)
print('res:', res)
print('f1:', f1)

# 打印结果:
res: <function f1 at 0x0000019F13E7D268>
f1: <function f1 at 0x0000019F13E7D268>

作为容器类元素

1
2
3
4
5
6
def f1():
print('from f1')


lt = [f1, 12, 3, 4, 5]
lt[0]()

小练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def pay():
print('支付1e成功')

def withdraw():
print('提现2e成功')

dic = {
'1': pay,
'2': withdraw
}

while True:
msg = '''
'1': 支付,
'2': 提现,
'3': 退出,
'''
print(msg)
choice = input('>>: ').strip()
if choice == '3':
break

elif choice in dic:
dic[choice]()

函数的嵌套

函数里面嵌套函数

1
2
3
4
5
6
7
8
def f1():
print('from f1')

def f2():
print('from f2')
f2()

f1()

名称空间和作用域

名称空间

内置名称空间

存储了内置方法的空间

数据类型内置方法;python解释器自带的方法(print / len / list / str / dict)

全局名称空间

除了内置和局部都叫全局

局部名称空间

函数内部定义的都叫局部

名称空间的执行(生成)的顺序

  1. 内置名称空间:python解释器启动的时候就有了
  2. 全局名称空间:执行文件代码的时候才会有全局
  3. 局部名称空间:函数调用的时候才会有局部

搜索顺序

从当前位置查找,找不到按照这种顺序,不会逆向寻找:局部 > 全局 > 内置 > 报错

作用域

全局作用域

内置名称空间 +全局名称空间 —> 全局作用域

局部作用域

局部名称空间 —> 局部作用域

  • 全局作用域的 x 和局部作用域 x 没有任何关系
  • 局部作用域1 的 x 和局部作用域2 的 x没有任何关系,即使局部作用域1和局部作用域2在同一个局部作用域下

global关键字

1
2
3
4
5
6
7
x = 10
def func():
global x
x = 20

func()
print(x) # 打印20

nonlocal关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
x = 10
def f1():
x = 2

def f2():
nonlocal x # nonlocal让x成为额顶层函数的局部,不是让它成为全局
x = 30

f2()
print(x)

f1()
# 打印30

注意点

所有可变数据类型均可打破上述一切规则

1
2
3
4
5
6
7
8
9
10
lt = [10]

def f1():
lt.append(20)

f1()
print(lt)

# 打印结果:
[10,20]

闭包函数

什么是闭包函数

闭包函数把 闭包函数内的变量 + 闭包函数内部的函数, 这两者包裹起来,然后通过返回值的形式返回出来。

  • 定义在函数的内函数
  • 该函数体代码包含对该函数外层作用域中变量的引用
  • 函数外层指的不是全局作用域
1
2
3
4
5
6
7
8
9
10
11
12
13
def outter():
x = 10
def inner():
print(x)
return inner

f = outter() # f=inner
f()
print(f)

# 打印结果:
10
<function outter.<locals>.inner at 0x00000201011A7840>

上述代码中,f是一个全局的名字,但f拿到了inner的内存地址。将一个定义在内部的函数返回出来,在全局拿到f,这个f是全局变量,这样就打破了函数的层级限制,能在任意位置使用内部的函数

闭包函数的应用

以参数的方式传值

1
2
3
4
5
6
7
import requests

def get(url):
response = requests.get(url)
print(response.text)

get('https://www.baidu.com')

这里写了一个爬虫函数,爬取百度的首页。但这样的问题就是每次想爬百度,或者其他网站都要传一堆网址,比较麻烦,所以可以用闭包函数解决。

传值另一方式: 包给函数

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests

def spider(url):
def get():
response = requests.get(url)
print(response.text)
return get

baidu = spider('https://www.baidu.com')
baidu()

taobao = spider('https://www.taobao.com')
taobao()

这样就很方便,以后调baidu,直接baidu()就行了

装饰器

什么是装饰器

装饰器就是 为需要装饰的函数新增了一个额外的功能,装饰器的本质就是一个 给函数增加功能的函数。

为什么要装饰器

装饰器,增加功能需要注意以下几点:

  • 不改变原函数的原代码
  • 不改变原函数的调用方式

使用无参装饰器

1
2
3
4
5
6
7
8
import time

def index():
'''被装饰函数'''
time.sleep(1)
print('welcome to index')

index()

需要为上面的函数新增加一个功能,能够统计函数运行的时间

在原代码上修改

1
2
3
4
5
6
7
8
9
10
import time

def index():
start = time.time()
time.sleep(1)
print('welcome to index')
end = time.time()
print(f'run time is {end - start}')

index()

这样就违反了不能修改原代码这一原则

1
2
3
4
5
6
7
8
9
10
import time

def index():
time.sleep(1)
print('welcome to index')

start = time.time()
index()
end = time.time()
print(f'run time is {end - start}')

这样写就不是装饰器,因为装饰器是一个函数

利用函数传参方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import time

def index():
'''被装饰函数'''
time.sleep(1)
print('welcome to index')

def time_count(func):
start = time.time()
func()
end = time.time()
print(f'run time is {end - start}')

time_count(index)

虽然实现了,但改变了函数调用方式

利用闭包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import time

def index():
'''被装饰函数'''
time.sleep(1)
print('welcome to index')

def deco(func): # func = index 最初的index
def time_count():
start = time.time()
func()
end = time.time()
print(f'run time is {end - start}')
return time_count

# f = deco(index)

index = deco(index) # index = time_count
index()

这样就简单实现了一个装饰器函数,调用index不是调用最初的index了,而是调用time_count函数,但用户不知道,看起来就和原来使用一样

装饰器完善

上述的装饰器,最后调用index()的时候,其实是在调用time_count(),因此如果原始的index()有返回值的时候,time_count()函数的返回值应该和index()的返回值相同,也就是说,我们需要同步原始的index()和time_count()方法的返回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import time

def index():
'''被装饰函数'''
time.sleep(1)
print('welcome to index')
return 1234

def deco(func):
def time_count():
start = time.time()
res = func()
end = time.time()
print(f'run time is {end - start}')
return res

return time_count

# index = deco(index) # index = time_count
# index()
res = index()
print(res)

给原始index传参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import time

def index(x):
'''被装饰函数'''
time.sleep(1)
print('welcome to index')
return 1234

def deco(func):
def time_count(*args,**kwargs):
start = time.time()
res = func(*args,**kwargs)
end = time.time()
print(f'run time is {end - start}')
return res

return time_count

index = deco(index) # index = time_count
index(10)

使用装饰器语法糖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import time


def deco(func):
def time_count(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
end = time.time()
print(f'run time is {end - start}')
return res

return time_count


@deco # 这里就相当于 index = deco(index)
def index(x):
'''被装饰函数'''
time.sleep(1)
print('welcome to index')
return 1234


index(10)

装饰器模板

1
2
3
4
5
6
7
def deco(func):
def wrapper(*args,**kwargs):
# 这里加功能
res = func(*args,**kwargs)
return res

return wrapper

装饰器小练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# 写一个登录装饰器,装饰猜年龄,登录了才能玩猜年龄

username_list = []

def guess(age):
print('welcome to guess age')
age_inp = input('请猜年龄').strip()
age_inp = int(age_inp)

if age_inp == age:
print('bingo')
elif age_inp < age:
print('猜小了')
else:
print('猜大了')


def login(func):
def wrapper(*args,**kwargs):
if username_list:
print('已登录,请勿重复登录')
res = func(*args, **kwargs)
return res

username_inp = input('请输入用户名:').strip()
pwd_inp = input('请输入密码:').strip()

with open('user_info.txt', 'r', encoding='utf-8') as fr:
for user_info in fr:
user_info = user_info.strip()
username, pwd = user_info.split(':')

if username_inp == username and pwd_inp == pwd:
print('登录成功')
username_list.append(username_inp)
res = func(*args, **kwargs)
return res

else:
print('登录失败')

res = func(*args,**kwargs)
return res

return wrapper

guess = login(guess)
guess(19)

有参装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import time

def outter(age):
def deco(func):
def wrapper(*args,**kwargs):
if age >= 18:
print('成年了')
else:
print('未成年')

start = time.time()
res = func(*args,**kwargs)
end = time.time()
print(f'run time is {end - start}')
return res

return wrapper

return deco

@outter(19) # 相当于 deco = outter(19) index = deco(index)
def index():
'''被装饰函数'''
time.sleep(1)
print('welcome to index')

index()

迭代器

可迭代对象

python中一切皆对象

凡是有__iter__方法的对象,都是可迭代对象

可迭代的对象:Python内置str、list、tuple、dict、set、file都是可迭代对象。

1
2
3
4
5
lt = [1,2,3,4]
print(lt.__iter__())

# 打印结果:
<list_iterator object at 0x00000211EB7565C0>

迭代器对象

含有__iter____next__方法的对象就是迭代器对象

为什么要有迭代器对象:提供了 不依赖索引取值的手段

1
2
3
4
5
dic = {'a':1, 'b':2, 'c':3}
dic_iter = dic.__iter__()
print(dic_iter.__next__())
print(dic_iter.__next__())
print(dic_iter.__next__())

如果值取干净了,就会报错StopIteration

总结:

迭代器对象:执行可迭代对象的__iter__方法,拿到的返回值就是迭代器对象。

特点:

  1. 内置__next__方法,执行该方法会拿到迭代器对象中的一个值
  2. 内置有__iter__方法,执行该方法会拿到迭代器本身
  3. 文件本身就是迭代器对象。

缺点:

  1. 取值麻烦,只能一个一个取,并且只能往后取,值取了就没了
  2. 无法使用len()方法获取长度

for循环原理

for循环称为迭代器循环,in后面必须是可迭代对象

1
2
3
4
5
6
7
dic = {'a':1, 'b':2, 'c':3}
dic_iter = dic.__iter__()
while True:
try:
print(dic_iter.__next__())
except Exception:
break

总结:

  • 含有__iter__方法的就是可迭代对象,除了数字类型,其它都是可迭代对象。可迭代对象使用__iter__就会变成迭代器。
  • 含有__next__方法的就是迭代器对象,只有文件是迭代器对象。迭代器使用__iter__依然是迭代器

其他推导式

三元表达式

语法:条件成立时的返回值 if 条件 else 条件不成立时的返回值

1
2
3
4
5
6
7
8
9
10
11
x = 10
y = 20

if x > y:
print(x)
else:
print(y)



print(x if x > y else y) # 三元表达式

列表推导式

1
2
lt = [i for i in range(10)]
print(lt)

字典生成式

1
2
dic = {i:i for i in range(10)}
print(dic)

zip()方法

1
2
3
4
5
6
7
8
9
res = zip([1,2,3],['a','b','c'])    # res是一个迭代器,__next__返回元组
print(res.__next__())
print(res.__next__())
print(res.__next__())

# 打印结果:
(1, 'a')
(2, 'b')
(3, 'c')

利用两个列表快速生成一个字典

1
2
3
4
5
6
7
8
lt1 = ['a', 'b', 'c']
lt2 = [1, 2, 3]
res = zip(lt1, lt2)
dic = {k: v for k, v in res}
print(dic)

# 打印结果:
{'a': 1, 'b': 2, 'c': 3}

生成器

生成器:

  • 含有yield关键字的函数叫生成器。
  • 生成器本质上就是一个自定义的迭代器
1
2
3
4
5
6
7
def f():
yield 1 # 一个yield相当于一个next,可以暂停函数
yield 2

f = f()
print(f.__next__())
print(f.__next__())

生成器表达式

1
2
3
f = (i for i in range(10))
for i in f:
print(i)

生成器表达式节省内存,能一个个取出值—-> 相当于老母鸡下蛋

列表推导式,一下子就取出——> 相当于一筐鸡蛋

yield与return的比较:

  • yield可以暂停函数,通过next取值
  • return可以终止函数,通过调用函数拿到值

递归

函数f内部直接或间接调用函数f本身

1
2
3
4
5
def f():   # 死递归
a = 1
print(a)
f()
f()

每一次递归,不会结束函数,并且每一次递归都会开辟内存空间,如果一直开辟内存空间,内存就炸掉了,最多递归1000次。

真正的递归必须要有退出条件

1
2
3
4
5
6
7
8
9
10
count = 0
def func():
global count
count += 1
print(count)
if count == 100:
return
func()

func()

总结:递归必须要有两个明确的阶段

  1. 递推:一层一层递归调用下去,进入下一层递归的问题规模都将会减小
  2. 回溯:递归必须要有一个明确的结束条件,在满足该条件开始一层一层回溯。

递归的精髓在于通过不断地重复逼近一个最终的结果。

匿名函数

有名函数

1
2
3
4
def func():
print('from func')

func()

这是一个有名函数,func就是它的名字

匿名函数

1
lambda x,y: x+y

匿名函数没有名字,也不能调用函数,一般与内置方法联用

1
lambda 参数: 返回值

与内置函数联用

max()方法

1
2
3
4
5
6
salary_dict = {
'nick': 3000,
'jason': 100000,
'tank': 5000,
'sean': 2000
}

如果想取上述薪资最多的名字,可以

1
2
print(max(salary_dict))
# 打印结果:tank

这里是按照字典的key比较大小,但我们要的是value比较大小,返回key

1
2
3
4
5
6
def func(name):
return salary_dict[name]

print(max(salary_dict, key=func))

# 打印结果:jason

用匿名函数

1
print(max(salary_dict, key=lambda name: salary_dict[name]))

sorted()

对薪资进行排序, 可以用sorted方法

sorted()工作原理:

  1. 首先将可迭代对象变成迭代器对象
  2. res=next(迭代器对象),将res当做参数传给第一个参数指定的函数,然后将该函数的返回值当做判断依据。
1
2
3
4
5
6
def func(item):
return salary_dict[item]

print(sorted(salary_dict, key=func, reverse=True)) # reverse是反转

# 打印结果: ['jason', 'tank', 'nick', 'sean']

匿名函数:

1
print(sorted(salary_dict, key=lambda name: salary_dict[name]))

map()

map()工作原理:

  1. 首先将可迭代对象变成迭代器对象
  2. res=next(迭代器对象),将res当做参数传给第一个参数指定的函数,然后将该函数的返回值作为map()方法的结果之一。

map()方法 就是一种映射,可以给可迭代对象一种对应关系

1
2
3
4
res = map(lambda x: x + 1, [1, 2, 3])
print(list(res))

# 打印结果:[2,3,4]

filter()

filter()工作原理:

  1. 首先将可迭代对象变成迭代器对象
  2. res=next(迭代器对象),将res当做参数传给第一个参数指定的函数,然后filter会判断函数的返回值的真假,如果为真则留下。

filter可以删选

1
2
3
4
res = filter(lambda x: x > 3, [1, 2, 3, 4, 45, 5, 0])
print(list(res))

# 打印结果:[4,45,5]

内置函数

掌握

方法 用法
bytes() 解码字符:print(bytes('你好', encoding='utf-8'))
chr() / ord() chr()参考ASCII码表将数字转成对应字符;ord()将字符转换成对应的数字
divmod() print(divmod(10, 3)) 打印结果:(3, 1)
enumerate() 带索引的迭代
eval() 把字符串的引号去掉,留下来的是什么就是什么
hash() 是否哈希

了解

方法 作用
abs 绝对值
all 如果全为真则为True,否则为False
any 只有有一个为真,则为真,否则为假
bin 转换为二进制
oct 八进制
hex 十六进制
dir 列出模块所有方法
frozenset 不可变集合
globals/loals 列出所有全局变量/当前位置所有变量
pow
round 四舍五入
slice 切片
sum 求和
__import__ 通过字符串导入模块