函数基础
函数基础
1.函数体系
1.1什么是函数?
在程序中,函数就是具有某一功能的工具,事先将工具准备好就是函数的定义,遇到应用场景拿来就用就是函数的调用。
1.2为何用函数?
解决写程序的三个问题:1.程序冗长
2.程序的扩展性差
3.程序的可读性差
1.3如何用函数?
先定义函数,后调用
#注册功能函数
def register():
"""注册功能"""
username = input('username:').strip()
pwd = input('password:').strip()
with open('file path','a',encoding='utf8') as fa:
fa.write(f"{username}:{pwd}\n")
fa.flush()
register()
#复用
register()
register()
#登录函数
def login():
"""登录功能函数"""
inp_name = input('username:').strip()
inp_pwd = input('password:').strip()
user_dict=dict()
with open('file path','rt',encoding='utf8') as fr:
for user_info in fr:
user_info = user_info.strip('\n')
user_info_list = user_info.split(':')
user_dict[user_info_list[0]]=user_info_list[1]
if inp_pwd ==user_dict.get(inp_name):
print('login success!')
break
else:
print('failed')
login()#函数的调用
在函数定义的阶段中,语法错误会被检测出来,而代码逻辑错误只有执行时才会知道
2.定义函数
2.1无参函数
定义函数时,参数是函数体接受外部传值的一种媒介,其实就是一个变量名
在函数阶段括号内没有参数,成为无参函数。需要注意的是:定义无参时,意味着调用时也需传入参数。
如果函数体代码逻辑不需要依赖外部传入的值,必须得定义成无参函数。
def fuc():
print('hello world!')
fuc() # hello world!
2.2有参函数
在函数定义阶段括号内有参数,称为有参函数。需要注意的是:定义有参时,意味着调用时也必须传入参数。
如果函数体代码逻辑需要依赖外部传入的值,必须得定义成有参函数。
def sum_self(x,y):
"""求和"""
res = x+y
print(res)
2.3空函数
当你只知道你需要实现某个功能,但不知道如何用代码实现时,你可以暂时写个空函数,然后先实现其他的功能。
def fuc():
pass
3.函数的返回值
3.1什么是返回值?
函数内部代码经过一些列逻辑处理获得的结果。
3.2为什么要有返回值?
无return->None
return 1个值->返回1个值
return 逗号分隔多个值->元组
什么时候该有返回值?
调用函数,经过一系列的操作,最后要拿到一个明确的结果,则必须要有返回值
通常有参函数需要有返回值,输入参数,经过计算,得到一个最终的结果
什么时候不需要有返回值?
调用函数,仅仅只是执行一系列的操作,最后不需要得到什么结果,则无需有返回值
通常无参函数不需要有返回值
4.函数调用的三种形式
def max_self(x,y):
if x>y:
return x
return y
# 1.
max_self(1,2)
# 2.
res = max_self(1,2)*12
# 3.
max_self(max_self(20000,30000),40000)
5.函数的参数
5.1形参与实参
形参即变量名,实参即变量值。函数调用时,将值绑定到变量名上,函数调用时,解除绑定
5.2位置参数
位置形参:在函数定义节段,按照从左到右顺序依次定义的形参
def func(x,y):
print(x)
print(y)
#特点:按照位置定义的形参,都必须被传值
位置实参:在函数调用阶段,按照从左到右的顺序依次定义的实参,称之为位置实参。
func(1,2)
#特点:按照位置为对应的形参依次传值
关键字实参:在调用函数时,按照key=value的形式为指定的参数传值
func(y=2,x=1)
#特点:可以打破位置的限制,但仍能为指定的形参赋值
#注意:
1.可以混用位置实参和关键字实参,但位置实参必须在关键字实参的左边
func(x,y=2)
2.可以混用位置实参和关键字实参,但不能对一个形参重复赋值
默认形参:在定义阶段,就已经被赋值
def func(x,y=10):
print(x)
print(y)
func(9)
y = 9
func(y)
#特点:在定义阶段就已经被赋值,意味着调用时可以不用为其赋值
#注意:
1.位置形参必须放在默认形参的左边
2默认形参的值只在定义阶段赋值一次,也就是说默认参数的值在函数定义阶段就已经固定了。
总结
实参的应用:取决于个人习惯
形参的应用:
- 大多数情况的调用值一样,就应该将该参数定义成位置形参
- 大多数情况的调用值一样,就应该将该参数定义成默认形参
5.3可变长参数
可变长参数:指的是在调用函数时,传入的参数个数可以不固定
调用函数时,传值方式两种:位置实参和关键字实参;因此形参也必须得有两种解决方法,以此来分别接收溢出的位置实参(*)与关键字实参(**)
-
可变长形参的两种方式
1.#形参中的*会将溢出的位置实参全部接受,然后存成元组的形式,然后把元组赋值给*后的参数,需要注意的是:*的参数名约定俗成为args。 例如: def sum_self(*args): res = 0 from num in args: res+=num return res res = sum_self(1,2,3,4) print(res) 2.#形参中的**会将溢出的关键字实参全部接受,然后存储成字典的形式,然后把字典赋值给**后的参数,需要注意的是:**后的参数约定俗成为kwargs。 def func(**kwargs): print(kwargs) func(a=5)
-
可变长实参
```python
1.#实参中的*,*会将*后参数的值循环取出,打散成位置实参。以后但凡碰到实参中带*的,它就是位置实参,应该马上打散成位置实参去看。
def func(x,y,z,*args):
print(x,y,z,args)
func(1,*(1,2),3,4)
2.#实参中的**,**会将**后参数的值循环取出,打散成关键字实参。以后但凡碰到实参中带**的,它就是关键字实参,应该马上打散成关键字实参去看。
def func(x,y,z,**kwargs):
print(x,y,z,kwargs)
func(1,3,4,**{'a':1,'b':2})
```
3.可变长参数应用
```python
def index(name,age,sex):
print(f'name:{name},age:{age},sex:{sex}')
def wrapper(*args,**kwargs):
print(f'agrs:{args}')
print(f'kwargs:{kwargs}')
index(*args,**kwargs)
wrapper(name='nick',sex='male',age=19)
```
6.函数对象
函数是第一类对象,即函数可以当做数据进行传递
def func():
print('from func')
print(func)
-
可以被引用
f=func print(f)
-
当做参数传给一个函数
def func(): print('from func') def func1(x): x() func1(func) #结果为:from func
-
可以当做函数的返回值
def func(): print('from func') def func1(x): return x res=func1(func) res() #其实就是相当于func()
-
可以当做容器的元素
def func(): print('from func') def func1(): print('from func1') function_list = [func, func1] function_list[0]() #from func function_list[1]() #from func1
-
具体小案例:
def trans():
print('from trans')
def withdraw():
print('from withdraw')
function_print = {0:'trans',
1:'withdraw',
2:'quit'}
function_dict ={0:trans,
1:withdraw}
while 1:
print(function_print)
choice = input('请输入相应功能:').strip()
choice = int(choice)
if choice == 2:
break
elif choice in function_dict:
function_dict[choice]()
else:
print('dsb')
7.函数的嵌套定义
函数的嵌套,其实就是在函数内部定义函数,且函数外部无法使用内部定义的函数
#案例1:求圆的周长和面积
from math import pi
def circle(r,action):
if action =='p':
def perimeter():
return 2*pi*r
res=perimeter()
if action =='a':
def area():
return pi*r**2
res=area()
return res
while 1:
r_choice = input('半径>>>').strip()
if not r_choice.isdigit():
print('dsb')
continue
r_choice = int(r_choice)
break
while 1:
action_choice = input('请输入p或a:').strip()
if not action_choice in ['p','a']:
print('dashuaibi')
continue
break
res = circle(r_choice,action_choice)
print(res)
#案例2:四个数比大小
def max_num(x,y):
if x>y:
return x
return y
def max_num4(x,y,a,b):
res=max_num(x,y)
res1=max_num(res,a)
return max_num(res1,b)
max_num4(3,2,1,4)
8.名称空间与作用域
8.1名称空间
名称空间:就是存放变量名及函数名的内存空间。
-
内置名称空间:存放python解释器自带的名字,如:len,int
生命周期:在解释器启动时生效,在解释器关闭时失效
-
局部名称空间:用于存放函数调用函数体期间产生的名字
生命周期:在文件执行调用函数期间时生效,在函数执行结束后失效
-
全局名称空间:除了内置和局部之外,执行文件中存放的空间
生命周期:在文件执行时生效,在文件执行结束后失效
加载顺序:由于名称空间是用来存放变量名与值之间的绑定关系的,所以但凡要查找名字,一定是从三者之一找到,查找顺序为:
从当前的所在位置开始查找,如果当前所在的位置为局部名称空间,则查找顺序为:局部--》全局--》内置。
查找顺序:由于名称空间是用来存放变量名与值之间的绑定关系的,所以但凡要查找名字,一定是从三者之一找到,查找顺序为:
从当前的所在位置开始查找,如果当前所在的位置为局部名称空间,则查找顺序为:局部--》全局--》内置。也就是说只在当前名称空间及外层空间寻找,不会向里层寻找。
#例子:
x = 1
y = 2
len =100
def func():
y = 10
len = 1000
print(y) #y=10
print(x) #x=1 #由于x在局部空间未定义,所以就去全局空间里面找x值
print(len) #len = 1000
func()
#x = 15
def func():
print(x)
x = 10
func() #调用函数结果为10,说明x=10存在于局部空间
8.2作用域
作用域,即函数名或变量名适用于的范围
-
全局作用域:全局有效,作用范围包含内置名称空间和全局名称空间
-
局部作用域:局部有效,临时存储,只包含局部名称空间
# 局部作用域 def f1(): def f2(): def f3(): print(x) x = 2 f3() f2() f1() #函数f1调用的结果为2,其实也就从最里层到最外层找,谁先找到就先运行谁
注意点:作用域在函数定义阶段就被固定死了,与函数的调用无关
-
函数对象+作用域应用
#作用域应用 def f1(): def inner(): print('from inner') return inner f = f1() #f1()的返回结果为inner,f1()=inner,也就是把局部定义的函数名放在了全 局名称空间内 def bar(): f() #f()相当于调用了inner()函数 bar() #执行bar()函数就是调用了inner函数并输出了inner函数的执行结果
8.3补充知识点
-
global关键字
修改全局作用域中的变量
x = 1 def f1(): x = 2 def f2(): global x x = 3 f2() f1() print(x) #x=1的作用域应用于全局名称空间,由于global x的加入导致x=3了
-
nonlocal关键字
修改局部作用域中的变量
x = 1 def f1(): x = 2 def f2(): nonlocal x x=3 print(x) f2() print(x) f1() #结果本为2,由于nonlocal x的缘故,其实为3
-
注意点
- 在局部想要修改全局的可变类型,不需要任何声明,可以直接修改。
- 在局部如果想要修改全局的不可变类型,需要借助global声明,声明为全局的变量,即可直接修改。
9.闭包函数
9.1什么是闭包?
闭包:闭是封闭(函数内部函数),包是包含(该内部函数对外部作用域而非全局作用域的变量的引用)。闭包指的是:函数内部函数对外部作用域而非全局作用域的引用。
9.2函数传参的方式
-
使用参数的形式
def func(x): print(x) func(1) func(2) func(3) #单次传递单次接收
-
包给函数
def outter(x):
def inner():
print(x)
return inner
f = outter(1)
f()
#查看闭包的元素
print(f.__closure__[0].cell_contents)
9.3闭包函数的应用
闭包的意义:返回的函数对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,这使得,该函数无论在何处调用,优先使用自己外层包裹的作用域。
应用领域:延迟计算(原来我们是传参,现在我们是包起来)、爬虫领域。
import requests
def get(url):
response = requests.get(url)
data = response.text
print(data)
get('https://www.baidu.com')
get('https://www.baidu.com')
get('https://www.baidu.com') #多次爬取,劳动量大,人累得慌
上面的方式是极其复杂的,我们如果使用默认参数也只能解决一个网址,因此我们可以考虑使用闭包的方式。
import requests
def outter(url):
def get():
response = requests.get(url)
data = response.text
print(data)
return get
baidu=outter('https://www.baidu.com')
baidu()
baidu()
baidu() #这样就可以多次轻松的爬取了
10.装饰器
10.1无参装饰器
-
什么是装饰器?
器指的是工具,而程序中的函数就是具备某一功能的工具,所以装饰器指的是为被装饰器对象添加额外功能。因此定义装饰器就是定义一个函数,只不过该函数的功能是用来为其他函数添加额外的功能。
需要注意的是:
-
装饰器本身其实是可以任意可调用的对象
-
被装饰的对象也可以是任意可调用的对象
-
-
为什么要用装饰器?
因为软件的维护应该遵循开放封闭原则,即软件一旦上线运行后,软件的维护对修改源代码是封闭的,对扩展功能指的是开放的。
装饰器的实现必须遵循两大原则:
-
不修改被装饰对象的源代码
-
不修改被装饰对象的调用方式
装饰器其实就是在遵循以上两个原则的前提下为被装饰对象添加新功能。
-
-
怎么用装饰器?
如果我们定义一个index函数,我们需要给该函数增加一个计时功能,我们可以使用改变源代码的方式:
#原始土方法,只适合单次计时 import time def index(): start = time.time() print('from index') time.sleep(1) end = time.time() print(f'index run time:{end-start}') index()
第一种传参方式:改变调用方式
#还是麻烦的很 import time def index(): print('from index') time.sleep(1) def time_count(func): start = time.time() func() end = time.time() print(f'{func} time:{end-start}') time_count(index)
第二种传参形式: 包给函数—外包()
# import time def index(): print('from index') time.sleep(1) def time_count(func): def wrapper(): start = time.time() func() end = time.time() print(f'{func} time:{end-start}') return wrapper index = time_count(index) index()
-
完善装饰器
上述的装饰器,最后调用index()的时候,其实是在调用wrapper(),因此如果原始的index()有返回值的时候,wrapper()函数的返回值应该和index()的返回值相同,也就是说,我们需要同步原始的index()和wrapper()方法的返回值。
mport time def index(): print('welcome to index') time.sleep(1) return 123 def time_count(func): # func = 最原始的index def wrapper(): start = time.time() res = func() end = time.time() print(f"{func} time is {start-end}") return res return wrapper index = time_count(index) res = index() print(f"res: {res}") # 如果原始的index()方法需要传参,那么我们之前的装饰器是无法实现该功能的,由于有wrapper()=index(),所以给wrapper()方法传参即可。 import time def index(): print('welcome to index') time.sleep(1) return 123 def home(name): print(f"welcome {name} to home page") time.sleep(1) return name def time_count(func): # func = 最原始的index def wrapper(*args, **kwargs): start = time.time() res = func(*args, **kwargs) end = time.time() print(f"{func} time is {start-end}") return res return wrapper home = time_count(home) res = home('egon') print(f"res: {res}")
-
装饰器语法糖
在被装饰函数正上方,并且是单独一行写上
@装饰器名
import time def time_count(func): # func = 最原始的index def wrapper(*args,**kwargs): start = time.time() res = func(*args,**kwargs) end = time.time() print(f'{func} time:{end-start}') return res return wrapper @time_count # home = time_count(home) def home(name): print(f'welcome {name} to home page') time.sleep(1) return name @time_count #index = time_count(index) def index(): print('welcome to index') time.sleep(1) return 123 res = home('egon') print(f'res:{res}')
-
装饰器模板
def deco(func): def wrapper(*args,**kwargs): res = func(*args,**kwargs) return res return wrapper
10.2有参装饰器
无参装饰器其实就是一个套了两层的闭包函数,而有参装饰器是一个套了三层的装饰器
#用户登录的装饰器 import time current_user = {'username': None} def login(func): def wrapper(*args, **kwargs): if current_user['username']: res = func(*args, **kwargs) return res user = input('username:').strip() pwd = input('password:').strip() if user == 'wq' and pwd == '123': print('login success') res = func(*args, **kwargs) return res else: print('error') return wrapper @login def index(): time.sleep(1) print('are you kidding me?') return 'sanfrancisico' @login def home(name): time.sleep(1) print(f'welcome to {name} home!') return name res=home('wq') print(res) res=index() print(res)
对于上面的登录注册,我们把用户登录成功的信息写入内存当中。但是在工业上,用户信息可以存在文本中、mysql中、mongodb当中,但是我们只让用户信息来自于
file
的用户可以认证。因此我们可以改写上述的装饰器import time current_user = {'username':None} def login(func): def wrapper(*args,**kwargs): if current_user['username']: res = func[*args,**kwargs] return res user = input('username:').strip() pwd = input('password:').strip() engine = 'file' if engine == 'file': print('base of file') if user == 'wq' and pwd == '123': print('login success') current_user['username']=user res = func(*args,**kwargs) return res else: print('error') elif engine =='mysql': print('base of mysql') elif engine == 'mongodb': print('base of mongodb') else: print('default') return wrapper