函数基本知识一
Posted jiangxianseng
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了函数基本知识一相关的知识,希望对你有一定的参考价值。
一、灵魂三问
1.什么是函数
在程序中具备某种功能的“工具”就是函数,并且函数可以像工具一样重复调用
2.为什么要用函数
防止代码冗余和程序的可读性变差
3.怎么用函数
函数遵循先定义再调用的规则
即:制造工具----》定义函数
使用工具----》调用函数
二、定义函数
1.定义规则
函数的定义就相当于事先将函数体代码保存起来,然后将内存地址赋值给函数名,函数名就是对这段代码的引用,这和变量的定义是相似的。没有事先定义函数而直接调用,就相当于在引用一个不存在的’变量名’。
注意:定义函数时只检测语法,不执行函数体代码,只有调用函数时才会执行
定义函数的语法
def 函数名(参数1,参数2,...):
"""文档描述"""
函数体
return 值
-
def: 定义函数的关键字;
-
函数名:函数名指向函数内存地址,是对函数体代码的引用。函数的命名应该反映出函数的功能;
-
括号:括号内定义参数,参数是可有可无的,且无需指定参数的类型;
-
冒号:括号后要加冒号,然后在下一行开始缩进编写函数体的代码;
-
"""文档描述""": 描述函数功能,参数介绍等信息的文档,非必要,但是建议加上,从而增强函数的可读性;
-
函数体:由语句和表达式组成;
-
return 值:定义函数的返回值,return是可有可无的。
2.函数类型
-
有参函数
参数是函数的调用者像函数体传值的媒介,若函数体代码逻辑依赖外部传来的参数时则需要定义为有参函数
def index(x,y) # x,y即为参数
if x >y:
print(x)
else:
pringt(y)
?
index(1,2)
-
无参函数
若函数体代码逻辑不依赖外部传来的参数时则需要定义为无参函数
def logon():
username=input(‘user>>: ‘).strip()
password=input(‘password>>: ‘).strip()
return (username,password)
-
空函数
函数体为pass代表什么都不做,称之为空函数。当我们设计程序的时候需要刚开始需要建立程序的体系结构,但是又无法写出具体程序,因此通过空函数的pass来充当函数体“占位符”,后面再将其补上,这样能有效提高开发效率。
示例:
def auth_user():
"""user authentication function"""
pass
?
def download_file():
"""download file function"""
pass
?
def upload_file():
"""upload file function"""
pass
?
def ls():
"""list contents function"""
pass
?
def cd():
"""change directory"""
pass
三、函数的调用和返回值
1.函数的调用
当函数定义完之后我们就可以使用函数了,使用函数就是指函数的调用
调用函数的三种形式:
#1、语句形式:
index()
?
#2、表达式形式:
m=my_min(1,2) #将调用函数的返回值赋值给m
n=10*my_min(1,2) #将调用函数的返回值乘以10的结果赋值给n
?
#3、函数调用作为参数的形式:
# my_min(2,3)作为函数my_min的第二个参数,实现了取1,2,3中的较小者赋值给m
m=my_min(1,my_min(2,3))
2.函数的返回值
若需要将函数体代码执行的结果返回给调用者,则需要用到return。return后无值或直接省略return,则默认返回None,return的返回值无类型限制,且可以将多个返回值放到一个元组内。
return是一个函数结束的标志,函数内可以有多个return,但只执行一次函数就结束了,并把return后定义的值作为本次调用的结果返回。
1、不写return:默认返回None
2、只写return:只有结束函数体代码的效果,返回None
3、写return None :与只写return的效果相同
4、return返回一个值: 可以将返回的结果,当做一个变量值来使用
5、return返回多个值:
1、将返回的多个值,默认存入元组返回
2、函数的返回值不想被修改
3、可以自己指定返回的数据类型
return:它是一个函数结束的标志,函数体代码只要执行到return,函数执行结束
四、函数的参数
1.形参和实参
形参:即在定义函数时,括号内声明的参数。形参本质就是一个变量名,用来接收外部传来的值。
实参:即在调用函数时,括号内传入的值,值可以是常量、变量、表达式或三者的组合:
#1:实参是常量
res=my_min(1,2)
?
#2:实参是变量
a=1
b=2
res=my_min(a,b)
?
#3:实参是表达式
res=my_min(10*2,10*my_min(3,4))
?
#4:实参可以是常量、变量、表达式的任意组合
a=2
my_min(1,a,10*my_min(3,4))
注意:在调用有参函数时,实参(值)会赋值给形参(变量名)。在Python中,变量名与值只是单纯的绑定关系,而对于函数来说,这种绑定关系只在函数调用时生效,在调用结束后解除。
2.参数的使用方式:
-
位置参数
位置参数指的是按照位置顺序定义的参数
》在定义函数时,按照从左到右依次定义形参,称为位置形参, 是按照这种形式定义的形参都必须被传值
def register(name,age,sex): #定义位置形参:name,age,sex,三者都必须被传值
print(‘Name:%s Age:%s Sex:%s‘ %(name,age,sex))
?
register() #TypeError:缺少3个位置参数
》在调用函数时,按照从左到右的顺序依次定义参数,称为位置实参,凡按照这种形式定义的实参会按照从左到右的顺序与形参意义对应
register(‘egon‘,18,‘male‘) #对应关系为:name=’egon’,age=18,sex=’male’
Name:egon Age:18 Sex:male
-
关键字参数
在调用函数时,实参可以是key=value的形式,称为关键字参数,这种模式下可以不按照顺序而是通过指定的形式为形参赋值
register(‘tank‘,sex=‘male‘,age=18) #正确使用
register(name=‘tank‘,18,sex=‘male‘) #SyntaxError:关键字参数name=‘lili’在位置参数18之前
>>> register(‘tank‘,sex=‘male‘,age=18,name=‘jack‘) #TypeError:形参name被重复赋值
注意:如果位置参数和关键字参数混合用,则优先让位置参数在前,关键字参数在后
register(‘tank‘,sex=‘male‘,age=18) # tank在关键字参数之前优先被赋值
-
默认参数
在定义函数时,就已经给形参赋值,这种称之为默认参数,这种情况适用于此形参的实参重复出现次数过多时使用,如果传入一个新值会将形参改成新参。
def register(name,age,sex=‘male‘): #默认sex的值为male
print(‘Name:%s Age:%s Sex:%s‘ %(name,age,sex))
register(‘tom‘,17) #大多数情况,无需为sex传值,默认为male
Name:tom Age:17 Sex:male
register(‘Lili‘,18,‘female‘) #少数情况,可以为sex传值female
Name:Lili Age:18 Sex:female
注意:
1.默认参数也需要在位置参数之后
2.默认参数的值仅在函数定义阶段被赋值一次
3.默认参数通常应该设为不可变类型,例如设为列表时每次调用都会在上一次基础上向同一列表怎加值
-
可变长度参数(*与**的用法)
参数的长度可变指的是在调用函数时,实参的个数可以不固定,而在调用函数时,实参的定义无非是按位置或者按关键字两种形式,这就要求形参提供两种解决方案来分别处理两种形式的可变长度的参数
》可变长度的位置参数
如果在最后一个形参名前加上*号,那么在调用函数时,一处的位置实参都会被最后一个形参以元组的形式接受(官方默认最后一个形参为*args)
def foo(x,y,z=1,*args): #在最后一个形参名args前加*号
... print(x)
... print(y)
... print(z)
... print(args)
...
foo(1,2,3,4,5,6,7) #实参1、2、3按位置为形参x、y、z赋值,多余的位置实参4、5、6、7都被*接收,以元组的形式保存下来,赋值给args,即args=(4, 5, 6,7)
?
1
2
3
(4, 5, 6, 7)
如果我们事先生成了一个列表,仍然是可以传值给*args的,此时在实参中*表示将列表打散传入形参,如果没有*则列表会以一个整体传入
def foo(x,y,*args):
... print(x)
... print(y)
... print(args)
...
L=[3,4,5]
foo(1,2,*L) # *L就相当于位置参数3,4,5, foo(1,2,*L)就等同于foo(1,2,3,4,5)
1
2
(3, 4, 5)
注意:
如果我们想要求多个值的和,*args就派上用场了
>>> def add(*args):
... res=0
... for i in args:
... res+=i
... return res
...
>>> add(1,2,3,4,5)
15
》可变长度的关键字参数
如果在最后一个形参名前加**号,那么在调用函数时,溢出的关键字参数,都会被接收,以字典的形式保存下来赋值给该形参 (官方默认为**kwargs)
>>> def foo(x,**kwargs): #在最后一个参数kwargs前加**
... print(x)
... print(kwargs)
...
>>> foo(y=2,x=1,z=3) #溢出的关键字实参y=2,z=3都被**接收,以字典的形式保存下来,赋值给kwargs
1
{‘z‘: 3, ‘y‘: 2}
如果我们事先生成了一个字典,仍然是可以传值给**kwargs的
>>> def foo(x,y,**kwargs):
... print(x)
... print(y)
... print(kwargs)
...
>>> dic={‘a‘:1,‘b‘:2}
>>> foo(1,2,**dic) #**dic就相当于关键字参数a=1,b=2,foo(1,2,**dic)等同foo(1,2,a=1,b=2),**表示打散,和*类似
1
2
{‘a‘: 1, ‘b‘: 2}
-
命名关键字参数
在定义了**kwargs参数后,函数调用者就可以传入任意的关键字参数key=value,如果函数体代码的执行需要依赖某个key,必须在函数内进行判断
>>> def register(name,age,**kwargs):
... if ‘sex‘ in kwargs:
... #有sex参数
... pass
... if ‘height‘ in kwargs:
... #有height参数
... pass
...想要限定函数的调用者必须以key=value的形式传值,Python3提供了专门的语法:需要在定义形参时,用作为一个分隔符号,号之后的形参称为命名关键字参数。对于这类参数,在函数调用时,必须按照key=value的形式为其传值,且必须被传值
>>> def register(name,age,*,sex,height): #sex,height为命名关键字参数
... pass
...
>>> register(‘lili‘,18,sex=‘male‘,height=‘1.8m‘) #正确使用
>>> register(‘lili‘,18,‘male‘,‘1.8m‘) # TypeError:未使用关键字的形式为sex和height传值
>>> register(‘lili‘,18,height=‘1.8m‘) # TypeError没有为命名关键字参数height传值。命名关键字参数也可以有默认值,从而简化调用
>>> def register(name,age,*,sex=‘male‘,height):
... print(‘Name:%s,Age:%s,Sex:%s,Height:%s‘ %(name,age,sex,height))
...
>>> register(‘lili‘,18,height=‘1.8m‘)
Name:lili,Age:18,Sex:male,Height:1.8m需要强调的是:sex不是默认参数,height也不是位置参数,因为二者均在后,所以都是命名关键字参数,形参sex=’male’属于命名关键字参数的默认值,因而即便是放到形参height之前也不会有问题。另外,如果形参中已经有一个args了,命名关键字参数就不再需要一个单独的*作为分隔符号了
>>> def register(name,age,*args,sex=‘male‘,height):
... print(‘Name:%s,Age:%s,Args:%s,Sex:%s,Height:%s‘ %(name,age,args,sex,height))
...
>>> register(‘lili‘,18,1,2,3,height=‘1.8m‘) #sex与height仍为命名关键字参数
Name:lili,Age:18,Args:(1, 2, 3),Sex:male,Height:1.8m
3.组合使用
综上所述所有参数可任意组合使用,但定义顺序必须是:位置参数、默认参数、*args、命名关键字参数、kwargs**
可变参数args与关键字参数**kwargs通常是组合在一起使用的,如果一个函数的形参为args与**kwargs,那么代表该函数可以接收任何形式、任意长度的参数
在该函数内部还可以把接收到的参数传给另外一个函数(这在4.6小节装饰器的实现中大有用处)
>>> def func(x,y,z):
... print(x,y,z)
...
>>> def wrapper(*args,**kwargs):
... func(*args,**kwargs)
...
>>> wrapper(1,z=3,y=2)
1 2 3
按照上述写法,在为函数wrapper传参时,其实遵循的是函数func的参数规则,调用函数wrapper的过程分析如下:
-
位置实参1被*接收,以元组的形式保存下来,赋值给args,即args=(1,),关键字实参z=3,y=2被**接收,以字典的形式保存下来,赋值给kwargs,即kwargs={‘y‘: 2, ‘z‘: 3}
-
执行func(args,kwargs),即func(*(1,),** {‘y‘: 2, ‘z‘: 3}),等同于func(1,z=3,y=2)
提示: *args、**kwargs中的args和kwargs被替换成其他名字并无语法错误,但使用args、kwargs是约定俗成的。
以上是关于函数基本知识一的主要内容,如果未能解决你的问题,请参考以下文章
Android 逆向加壳技术简介 ( 动态加载 | 第一代加壳技术 - DEX 整体加固 | 第二代加壳技术 - 函数抽取 | 第三代加壳技术 - VMP / Dex2C | 动态库加壳技术 )