Python基础之函数详解

一、函数的定义

到现在为止,我们已经掌握了Python的基本语法和数据类型等相关基础知识了,以进行一个项目的编写了,这个时候,就会发现,很多代码需要我们进行复制粘贴,这简直就是损害我们小文艺的脸面啊,这个时候,我们就需要向函数求助了。

函数的三大优点:

  1. 代码不冗余
  2. 可阅读性高
  3. 可维护性,可拓展性得到提升。

函数定义会发生什么呢?

  1. 先向内存申请一块空间将函数体的内容存放进去
  2. 将函数体的内容和函数名绑定一起
  3. 程序在执行到定义函数的阶段,只识别语法,不会执行函数体的内容

那么函数的语法是什么呢?

def 函数名(有无参数都可以):
"""描述性信息"""
代码块
return 值。
  1. def:这是定义函数的关键字
  2. 函数名:遵循变量名的定义,最好对函数有一个功能性介绍(动词为主)
  3. 括号:内部可以选填参数
  4. “”“描述性信息”“”:可有可无,但是推荐使用,对函数体的功能进行描述
  5. return:可有可无。给出返回值

二、函数的调用

使用函数要遵循一个原则:这个原则就表明了它的内部的一些现象:大家一定要记牢:先定义后使用。是不是很熟悉,没毛病,这就是变量的使用原则。

函数名实际上跟“变量名”有些相似,都是储存一个内存地址,当我们调用函数的时候,最最基本的方式就是函数名加括号的方式。

def func():
pass # 最最基本的调用方式。
func() def func(a,b):
pass # 函数体有参数的方式
func(1,2)

三、函数返回值

这时候就不得不讲一下return的作用了,如果我们把函数当做一个工厂,那么参数就是我们送到工厂的原材料,而工厂生产出来的东西就是我们return的值了。他可以是任意数据类型,当函数运行结束的时候,我们可以对其进行赋值,就可以得到我们的产品了。

# 当函数没有return 值的时候,默认返回值为None
def func():
pass
v= func()
print(v) # None # 当只有一个return,但是并没有写返回值的时候,默认返回值也是None
def func():
return v= func()
print(v) # None # 当函数有返回值的时候,对其调用进行赋值会得到返回值。
# 返回值可以是单个,也可以是多个
def num_sum(a,b):
return a+b sums = num_sum(1,2)
print(sums) # 3

return 的作用不止如此,他也是函数终结者。当函数体中运行到return的时候,就会终止函数的运行,不会执行return之下的代码。

当然,我们还可以将函数的返回值当做参数传给另外一个函数,因为返回值的本质就是一个变量值。

四、函数的参数

当我们在定义有参函数的时候,括号内部的参数相当于变量名,而在函数调用的时候,传进的值将内存地址绑定给定义阶段的参数。

在定义阶段,函数括号内的参数称为形参。

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

参数可以有好几种形式表现,比如单个数据类型、key=value、函数等,但是其核心概念就是传入的是个值。

4.1 位置参数

位置形参函数在定义的时候是按照从左到右依次定义的参数。特点是必须被传值,不能多也不能少。

位置实参:函数在调用的时候是按照从左到右依次定义的参数。特点是按顺序与形参一一对应。

# 特点:必须被传值,多少给参数都不行。

def func(x,y):  # 括号内就是位置形参
print(x,y) func(1,2,3) # 过多的位置实参被传
func(1,) # 太少的位置实参
func(1,2) # 完美

4.2 关键字参数

我们还可以利用key=value的形式进行传参,调用阶段的参数是关键字参数。关键字可以指定形参进行传值,可以不参照顺序。

# 关键字参数:按照key=value的形式传入当做参数。
def func(x,y=2): # 默认参数
print(x,y) # func(y=2,x=1) # 关键字实参
# func(1,2)

默认参数在定义阶段已经被赋值了,所以在调用阶段可以不必为该参数传值,当然,如果要是进行传值的话,会以传的值为准。

实参:位置实参和关键字参数的混合使用

混合使用的话,一定要注意一下两点:

  1. 位置实参必须放在关键字实参之前。
  2. 不能为同一个形参重复传参。

形参:位置形参和默认参数的混合使用

形参中的混合使用需要注意:

  1. 位置形参必须在默认参数之前。
  2. 默认参数是在定义阶段就被传入的值的内存地址
  3. 建议默认值不推荐使用可变数据类型

4.3 可变长度参数

4.3.1 可变长度形参

如果我们在调用函数的时候,如果传入的实参过多,就回出现错误,但是我们可以使用(*+形参名)或者(**形参名)来接受过多的实参。

原则上形参名可以是任意名,但是约定俗成通常为(*args)和(**kwargs)。

  1. *args。用来接受位置实参。多出来的实参会被保存成元祖。
  2. **kwargs。用来接受关键字参数。多出来的实参会被保存成字典。

当然,在函数内部使用的时候,是不带星号的。

在定义的时候请务必放到默认参数之后。*args在**kwargs之前。

# *args的使用
def foo(x,y,*args):
print(x,y,args) foo(1,2,3,4,5,6) # 1 2 (3,4,5,6) # **kwargs的使用
def foo(x,y,**kwargs):
print(x,y,kwargs) foo(1,2,a=3,c=4) # 1 2 {"a":3,"c":4}

4.3.2 星号在实参上的应用

多出来的实参有*args和**kwargs接受,那么如果在调用的时候就给实参加上星号会有什么结果呢?

如果我们给实参前加上星号,本质是将该实参进行循环,然后得出的值当做实参传入形参。当然*args后可以跟多个可循环的数据类型,如果是字典只传入key。**kwargs只能使用字典,代表的是将key=value的格式传入。

# 在字典上的应用。
def func(x,y,z):
print(x,y,z) func(*{'x':1,'y':2,'z':3}) # func('x','y','z')
func(**{'x':1,'y':2,'z':3}) # func(x=1,y=2,z=3)

当然,传入的实参数量要和形参数量相同,不然会出现错误。

4.3.3 星号的综合应用

def index(x,y,z):
print(x,y,z) def foo(*args,**kwargs): # (1,2)传给args,{"z":3}传给kwargs
# *args将(1,2)转成1,2。**kwargs将{"z":3}转成z=3,然后传入index函数做参数
index(*args,**kwargs) foo(1,2,z=3)

当函数定义的参数为*args,**kwargs时,代表函数可以接受任意符合格式的实参,上述的函数调用foo的本质是调用index函数。

4.4 命名关键字参数

这个参数使用的比较少,在定义形参的阶段,如果某些参数出现在星号之后,那么这些参数就被称为命名关键字参数。

# 命名关键字参数
def func(x,y,*,a,b): # a,b就是命名关键字参数
print(x,y,a,b) def foo(x,y,*,b=1,a): # 命名关键字参数是不用按key=value在单个值之后的顺序
print(x,y,a)

注意,由于命名关键字参数位于星号之后,所以如果不用关键字实参的话,其值是传不进去的,因此必须的用指名道姓的方法传参数,而且调用的时候还必须被传值。

4.5形参和实参的顺序

形参的顺序:位置形参、默认参数、*args、命名关键字参数、**Kwargs。

实参的顺序:位置实参、*args、关键字参数、**kwargs。

五、函数闭包函数

在了解闭包函数之前,我们是需要一些储备知识的。然后,你不知不觉就一边蒙逼一边明白什么是闭包函数了。

5.1 函数的应用。

# 1、函数可以赋值,因为函数名本身引用着函数体的内存地址。
def foo():
pass f = foo # 2、可以把函数当做参数传递给另外一个函数。
def func():
print() def foo(x):
print(x) foo(func) # 3、可以当作另一个函数的返回值。
def f1():
return foo # 4、可以当作容器类型的一个元素。
def foo():
pass l = [foo,1,a]

其实看着有这么多应用,但是大家发现没有,函数名其实就是一个变量名,凡是变量名能做到的它都能做到。这点很重要哦。

5.2 函数的嵌套

函数的嵌套共有两种。

# 1、函数的嵌套调用。即在调用一个函数的时候,在其内部又调用另外一个函数。
def foo():
pass
def func():
foo() # 2、函数的嵌套定义。即在定义一个函数的时候,在其内部又定义另外一个函数。 def func():
def foo():
print(1)

5.3 闭包函数

首先上面都是闭包函数的铺垫。

明确一个概念:闭包函数= 名称空间与作用域+函数嵌套+函数对象。

  1. 闭:该函数是内嵌函数。这字说明了该函数所处的位置,肯定是嵌套函数中的定义函数。

  2. 包:指该函数包含对外层函数作用域名字的引用。注意,自己本身不含有,只有外部函数有。

是不是看不懂,接下来看个例子、

# 闭函数:名称空间与作用域+函数嵌套
def outer():
x = 1111
def inter():
print(x)
inter()
x = 22222
outer()

当我们在外界去调用inter函数的时候,由于名称空间与作用域的查找关系(按照定义的时候的查找顺序)的原因,不管我们在外界定义的是否含有x ,他都会使用outer内定义的变量。

# 闭包函数:名称空间与作用域+函数嵌套+函数对象
def outer():
x = 1111
def inter():
print(x)
return inter
f1 = outer() f1()

注意,是把函数名当做变量来用,return后返回的inter是一个函数的内存地址,如果加上括号就是执行函数功能的意思。所以一定不能加括号。在运行Outer的时候,返回一个内部函数名,然后复制给一个全局变量,然后该全局变量就被绑定了inter函数的内存地址,当该函数名后加括号的话,就相当于执行了inter()。

这样我们就可以在任意地方调用函数的局部名称空间。

当然,你也可以把x当做参数进行传递。但是原理一定要搞清楚,因为这是接下来的重点内容的铺垫。

六、装饰器

当我们的软件已经在线上运行的时候,需要拓展新的功能,那么我们 应该怎么办呢?首先我们要提到开放封闭原则。

  1. 开放原则:对拓展功能是开放的。
  2. 封闭原则:对修改源代码则是封闭的。

也就是说我们既不能更改函数的源代码,也不能更改调用方式,这个时候,我们就要用到装饰器了。

装饰器:为函数增加额外的功能且不更改调用方式的函数。

6.1 无参装饰器

下面我们进行循序渐进的讲解,究竟是怎么用到装饰器的,他又是怎么做到的符合开放封闭原则的。我们以为一个函数增加一个计算运行时间的功能为例来进行推导。

import time

def foo(x):
print("其实我这里有很多代码,但是你看不到")
time.sleep(3)
return x # 1、我们直接在函数内部进行更改。
def foo(x):
start_time = time.time()
print("其实我这里有很多代码,但是你看不到")
time.sleep(3)
stop_time = time.time()
process_time = stop_time - start_time
print(process_time)
return x

这时候虽然我们增加了功能,但是我们已经违反了开放封闭原则,所以失败。

# 2、利用函数的嵌套定义来直接增加新功能

def outer(x):
start_time = time.time()
foo(x)
stop_time = time.time()
process_time = stop_time - start_time
print(process_time)
return x

我们同样实现了功能,调用方式改变,源代码没变。

# 3、利用闭包函数来修改

def outer(foo):
def inter(x):
start_time = time.time()
res = foo(x)
stop_time = time.time()
process_time = stop_time - start_time
print(process_time)
return res
return inter foo = outer(foo)
foo(x)

上述已经实现了不更改函数源码,不更改调用方式了,但是函数被写死了,如果用来修饰多个函数呢?这些函数又有不同个数的参数呢?

# 终极版装饰器的诞生。
def outer(func):
def inter(*args,**kwargs):
start_time = time.time()
res = func(*args,**kwargs)
stop_time = time.time()
process_time = stop_time - start_time
print(process_time)
return res
return inter foo = outer(foo)

这是利用星号在形参和实参不同的作用来实现的。

自此,装饰器就写好了,遵守开放封闭原则,让用户在不知情的情况下,完成了函数的更新。要想掌握装饰器,必须弄懂闭包函数这些前置只是,如果还是不太懂,就继续看看闭包函数板块和参数的知识。

# 装饰器的模板。
def outer(func):
def inter(*args,**kwargs):
res = func(*args,**kwargs)
return res
return inter

添加装饰器的时候,可以根据模板然后在inter函数里面添加相关功能。

当然,还有一点就是语法糖的使用,我们可以将装饰器的赋值转成在被修饰函数上一行添加@装饰器名。

# 语法糖:在被修饰函数上一行添加@装饰器名。
@outer
def foo(x):
print(x)

6.2 有参装饰器

当用户进行身份验证时,有的函数要从info文件里得到信息,有的则是从数据库等等,那么如何让让一个装饰器实现那么多功能呢?我们都能想到,那就再给装饰器一个状态参数呗,要知道,之前为了伪装成被装饰参数,内部函数的参数已经固定,外部参数虽然也可以进行参数赋值,但是由于语法糖的存在,导致我们如果在外部函数增加参数之后,语法糖就不能使用了。

综上,我们还可以再次利用给函数传参的两种方式中的第二种,在函数外在套一层,此时的炸U那个时期已经是三个函数定义了。

# 语法糖的实际效果相当于执行了这一步:与被修饰函数名相同的全局变量 = 装饰器(被装饰函数名)
@outer # foo = outer(foo)
def foo():
print("1") # 有参装饰器的模板 def auth(参数):
def outer(func):
def wrapper(*args,**kwargs):
res = func(*args,**kwargs)
return res
return wrapper
return outer @auth("参数")
def foo():
print(123)

当程序运行到语法糖时,由于函数名加括号,开始执行函数返回outer的内存地址,所以实际上语法糖依然是@outer。不过是多执行了一步函数将参数传到内部函数的名称空间而已。

6.3多个装饰器的运行原理

def outer1(func):
def wrapper1(*args,**kwargs):
res = func(*args,**kwargs)
return res
return wrapper def outer2(func):
def wrapper2(*args,**kwargs):
res = func(*args,**kwargs)
return res
return wrapper2 def outer3(func):
def wrapper3(*args,**kwargs):
res = func(*args,**kwargs)
return res
return wrapper3 @outer1 # foo = outer1(foo)= wrapper1的内存地址
@outer2 # foo = outer2(foo)= wrapper2的内存地址
@outer3 # foo = outer3(foo) = wrapper3的内存地址
def foo():
print("123")

加载顺序:outer3>outer2>outer1

执行顺序:outer1==>outer2>outer3

七、递归函数

递归函数的本质就是循环,在运行函数体期间一直在不停地调用自己。在Python解释器中,递归有一个最大限制,超过这个限制就会报错。

# 递归的直接调用
def foo():
print("一直循环我会报错")
foo() # 递归的间接调用
def foo1():
print("我是正常的函数,调用了foo我就不正常了")
foo()

递归函数在调用自己的时候又两个过程

  1. 回溯:在函数中不停地调用自己的过程
  2. 递推:在函数满足条件退出函数之后,不停地将已打开的函数结束的过程。
# while循环
count = 0 while count <5:
print(count)
count += 1 # 改成递归函数之后 def cou(count):
if count > 4:
return
print(count)
count += 1
cou(count) cou(0)

据我观察,递归函数应该是主要应用在需要参数且多次循环的场景之上。

八、函数的类型提示

这是一个可用可不用的一个提示。

def foo(x:str,y:"这里面应该写整数",z:list=[1,2,3]):
pass

形参后面加个冒号然后可以写一些提示性信息,声明一下数据类型等,但是如果你不按照规定传参,函数也不会报错,因为这个规定本来就是自己定的 ,但是你打破了这个自己提示的类型,那你声明干嘛呢,,就像调皮的用户,哈哈哈

python基础之函数详解的更多相关文章

  1. Python内置函数详解

    置顶   内置函数详解 https://docs.python.org/3/library/functions.html?highlight=built#ascii https://docs.pyth ...

  2. python pandas字符串函数详解(转)

     pandas字符串函数详解(转)——原文连接见文章末尾 在使用pandas框架的DataFrame的过程中,如果需要处理一些字符串的特性,例如判断某列是否包含一些关键字,某列的字符长度是否小于3等等 ...

  3. Python基础之数据类型详解(2)

    今天继续昨天的python基本数据类型详解,按照上一篇博文的格式,接下来讲解列表.元组.字典以及集合. 列表 1.用途按位置存放多个值2.定义在[]内用逗号分割开多个任意类型的元素 # 定义列表 # ...

  4. Python之print函数详解

    输出的 print 函数总结: 1. 字符串和数值类型可以直接输出 >>> print(1) 1 >>> print("Hello World" ...

  5. python中groupby函数详解(非常容易懂)

    一.groupby 能做什么? python中groupby函数主要的作用是进行数据的分组以及分组后地组内运算! 对于数据的分组和分组运算主要是指groupby函数的应用,具体函数的规则如下: df[ ...

  6. python基础知识——字符串详解

    大多数人学习的第一门编程语言是C/C++,个人觉得C/C++也许是小白入门的最合适的语言,但是必须承认C/C++确实有的地方难以理解,初学者如果没有正确理解,就可能会在使用指针等变量时候变得越来越困惑 ...

  7. Python内置函数详解——总结篇

    2个多月来,将3.5版本中的68个内置函数,按顺序逐个进行了自认为详细的解析,现在是时候进行个总结了.为了方便记忆,将这些内置函数进行了如下分类:     数学运算(7个)     类型转换(24个) ...

  8. python 内置函数详解

    懒得写了  参考1:https://www.cnblogs.com/xiao1/p/5856890.html 参考2:https://www.runoob.com/python/python-buil ...

  9. 5.python内置函数详解

    内置函数 声明,转载至这位大哥,感谢之至 http://blog.csdn.net/oaa608868/article/details/53506188 关于分类 数学运算(7个) 类型转换(24个) ...

随机推荐

  1. 扯一扯基于4046系IC的锁相电路设计

             4046系IC(下简称4046),包括最常见的CD4046(HEF4046),可以工作在更高频的74(V)HC4046,以及冷门而且巨难买到的74HC(T)7046和74HCT904 ...

  2. CDNbest-设置不缓存

    写在开始之前 有时候根据业务需求,我们网站不需要缓存,这时候就需要设置下 让网站不走缓存 步骤一 登录平台找到我们的站点->站点设置->缓存设置 如图 备注:填写需要操作的域名,时间为&q ...

  3. In Triangle Test / To Left Test

    2020-01-09 14:51:29 如何高效的判断一个点是否是包含在一个三角形的内部是计算几何里的一个基础问题. In Triangle Test问题也可以用来解决计算几何里的一个基础问题就是 凸 ...

  4. logstash设置从文件读取的重要参数说明及如何强置重新读取

    问题描述: 如果运行logstash时从文件读取数据时,就会遇到一个问题,如果读取的目标文件未经修改,而仅修改了conf文件,则即使重新运行logstash,或是执行时使用-r时输出也无法更新. 解决 ...

  5. 十个python图像处理工具

    介绍 如今的世界存在了大量的数据,图像数据是重要的组成部分.如果要利用这些图片,需要对图像进行处理,提高图片质量或提取图片内容信息. 图像处理的常见操作包括图像显示,基本操作如裁剪,翻转,旋转等,图像 ...

  6. 瀑布流vue-waterfall的高度设置

    最近用vue做项目,用到了瀑布流vue-waterfall,其中遇到高度的设置问题,大概介绍下,希望可以帮到一些人 1.安装 npm install --save vue-waterfall 2.引入 ...

  7. springboot集成通用mapper详细配置

    通常,我们利用mybatis写持久层方法.要么按照传统定义mapper方法,定义xml文件的方式,全部手写.要么需要通过mybatis-generator逆向工程插件生成大量的xxxExample文件 ...

  8. codeforces 466c(暴力枚举)

    题目链接 思路如下 *题意: 给定一个序列,问有多少种方案可以将此序列分割成3个序列元素和完全相同的子序列.(子序列不能为空).即问有多少个点对(i,j)满足a[1]+-+a[i-1]=a[i]+a[ ...

  9. 多线程学习笔记(四)---- Thread类的其他方法介绍

    一.wait和 sleep的区别 wait可以指定时间也可以不指定时间,而sleep必须指定时间: 在同步中时,对cpu的执行权和锁的处理不同: wait:释放执行权,释放锁:释放锁是为了别人noti ...

  10. Level Up - ICPC Southeastern Europe Contest 2019(简单DP)

    题意:Steve玩魔兽世界要做任务升两级,任务在你不同的等级给的经验不同,输入任务数量和升第一级和升第二级需要的经验,接着输入每个任务第一级完成给的经验和花费的时间.第二级级完成给的经验和花费的时间. ...