Python定义函数相关

blog 179

引入

函数就相当于我们一个工具包,里面存放了某些工具,存放工具就类似于我们函数的定义,当我们使用的时候,直接拿来就行了,而这个拿来就类似于我们的函数调用。函数一般都是集成了某些功能的,比如print()和open(),他们都是Python为我们提供的内置函数(事先被定义好)。

函数的定义

函数必须遵循一个规则,即先定义后调用,函数的定义相当于将函数里面的代码体(工具)打包起来,封装到一个函数(工具包)中。之后我们直接调用即可。

定义的语法:

def 函数名(参数1,参数2,...):
	command...
	return 值
  1. def为定义函数的关键字
  2. 函数名即函数的名称
  3. 参数1和2则是函数在调用的时候,可以传入的参数即值
  4. command则是我们函数体的代码。
  5. return 则是我们函数返回的值,该值可以被print出来。

提示: 函数被定义如果没有调用的时候,是不会执行函数代码体里面的代码的。它只看你函数里面有没有语法错误,如果代码题存在了语法等错误,则会报错。但像NameErrorTypeError这种错误是不会在函数定义不调用的时候报错。只有在函数被调用的时候,才会提示错误。

总结:函数使用def关键字进行调用,在定义的时候不会执行函数体里面的代码。只是会检查代码体里面的代码。

函数定义的三种形式

1、无参函数

# 无参函数的定义
def func():
	print('helloworld')
	print('helloworld')
	print('helloworld')
	print('helloworld')


# 函数的调用
func()

2、有参函数

# 函数的定义

def bar(x,y): # 类似定义了x和y两个变量,但是没有被赋值
	print(x + y)


bar(10,20)  # 函数在调用的时候,为其参数传入对应的值。注意这里的值不能多也不能少。类似于在调用的时候为x和y变量赋值分别是10和20。

3、空函数

def hello(x,y):
	pass


hello(10,20) # 函数的调用

三种函数的应用场景

1、无参函数的应用场景

# 无参函数适用于 一段代码完成了一个功能,但是这个功能后续可能还会使用,很显然复制粘贴的形式是比较冗余的,所以我们可以通过函数将其封装,后续直接调用该函数即可。
def input_info():
	in_name = input('input username >> ')
	in_age = input('input age >> ')
	in_gender = input('input gender >> ')

	print('My name is {},age is {},gender is {}'.format(in_name,in_age,in_gender))



input_info()

2、有参函数的应用场景

# 有参函数的使用场景:
''''
	如果你想要程序的开发者,可以通过传入参数动态的控制该参数的输出值的类型,那么就需要使用到有参参数了
	比如下面这个例子,我们要计算函数在调用时插入的参数总和,那么我们就需要使用到有参参数
'''

# 定义函数
def sum_number(x,y):
	print(x + y)

# 函数的调用

sum_number(30,70) # 输出100

3、空函数的应用场景

# 空函数的应用场景:
'''
	空函数往往在我们构思代码的时候使用,比如说我们这个程序需要有几个功能,那么你就可以事先定义空函数,有了思维的框架,进而再进行程序的开发
'''

def Down_file():
	# Download a file ...
	pass

def Up_file():
	# Upload a file ...
	pass

def Check_file():
	# Check a file 
	pass

调用函数的三种形式

1、语句形式的调用

# 1、语句的形式:只加括号调用函数,不执行任何操作
def bar(x,y):
	print(x + y)

bar(10,20) 

2、表达式形式

# 2、表达式形式:

def add(x,y): # 参数 -> 原材料
	res = x + y 
	return res  # 返回值 —> 产品

result = add(10,90) # 赋值表达式
print(result)
 
res = add(40,10) * 2 # 数学表达式: 将add(40,10)的结果*2之后的结果赋值给res变量
print(res)

3、把函数使用当做参数

# 3、函数的调用也可以当做参数

def add(x,y):
	res = x + y
	return res


sum = add(add(10,20),30)  # 将add(10,20)作为值赋值给参数x
print(sum)

函数的返回值

在每个函数执行完毕之后,都会返回一个值给当前的函数。在函数体中我们可以使用return关键字来指定函数的返回值,如果没有指定return或者return后没有值,以及return None,他们返回的都是None

def bar():
	print('helloworld')

print(bar()) # None

注意:return之后的语句则不会再被执行。类似于我们for循环中的break,而我们return之后的代码则不会再次执行。

Python定义函数相关

可以发现,return之后print()语句,就没有做打印输出了。

return可以返回多个值,而这个类型则是我们基本数据类型中的tuple类型。

Python定义函数相关

形参与实参

形参与实参的介绍:

形参:在定义函数时定义的参数,我们称之为形式参数,简称为形参,类似于我们的变量名。

def add(x,y): # 定义变量x和y
	print(x + y)

实参:在调用函数时指定的参数,我们称之为实际参数,简称为实参,类似于我们变量的值。

# 定义
def add(x,y): # 定义变量x和y
	print(x + y)

# 调用
add(10,20)

形参与实参的关系:

1、在函数的调用阶段,实参(变量的值)会绑定给形参(变量名)

2、这种绑定关系,只能在函数体内使用

3、实参和形参的绑定关系在函数调用时生效,函数调用结束后解除绑定关系。

实参是传入的值,但是值可以是以下几种方式:

# 定义
def add(x,y): # 定义变量x和y
	print(x + y)

# 第一种方式
add(10,20)

# 第二种形式
a = 10
b = 20
add(a,b)

# 第三种形式(函数作为参数)
add(func1(1,2),func2(10,20))  # 换句话来说就是,只要最后的结果是一个值,都可以被当做实参

形参与实参的具体使用

一、位置参数:

按照从左到右的顺序依次定义的参数,我们称之为位置参数。

位置形参:

在函数定义阶段,按照从左到右的顺序直接定义的“变量名“

特点:必须被传值,少一个也不行,多一个也不行,需要一一对应。

位置实参:

在函数定义阶段,按照从左到右的顺序依次传入的值

特点:按照顺序传值与形参一一对应

def func(x,y):
	print(x + y)

# 调用

# func(1) #  TypeError: missing 1 required positional argument: 'y'
func(1,2) # 必须需要位置形参和实参一一对应即可。
# func(1,2,3) # TypeError: takes 2 positional arguments but 3 were given
二、关键字参数

关键字实参:在函数调用阶段,按照key=value的方式进行传值

特点:指名道姓的为某个形参传值,完全可以不按照先后顺序

def add(x,y,z):
	print(x + y + z)

add(z=90,y=10,x=100) # 关键字实参

混合使用:

1、位置实参必须放在关键字实参的前面。否则会报语法错误。

2、不能为同一个形参重复传值。

def add(x,y,z):
	print(x + y + z)

add(10,y=20,z=30) # 60
# add(y=30,20,10) # 语法错误,位置实参必须放在关键字实参的前面
# add(x=20,y=30,z=30,y=1000) # 不能为同一个形参重复传值
三、默认形参

在函数定义的时候,就已经被赋值的形参,我们称之为默认形参。

特点:在定义阶段就已经被赋值,就意味着在调用阶段可以不为其赋值。

def add(x,y=20):
	print(x,y)

add(10) # 10 20 

位置形参和默认形参混合使用的注意点:

1、不管是定义或者调用,位置形参都必须在默认形参的左边,不然会语法错误

def add(x,z,y=20,y): # Error 默认形参在位置形参的左边,所以会报错 
	print(x,z,y)

add(10,20,30)

2、不管是定义还是调用,默认参数的值都是在定义的时候被定义的。准确的来说,赋予的是内存地址。

3、函数的默认值虽说可以是任意数据类型,但是不推荐函数的默认值为可变数据类型作为函数的默认值。

# 示例一、
m = 123

def func(x,y=m): # y=123的内存地址
	print(x,y)

m = 100000000

func(10) # 10 123

# 示例二、
list1 = [11111,]

def bar(x,y=list1): # y=[11111,]的内存地址
	print(x,y)

list1.append(333333) 

bar(10) # 10 [11111, 333333]
可变长度的参数

可变长度指的是在调用函数的时候,传入的参数(实参)的长度不固定,可以是任意长度。而实参是用来对形参赋值的,所以针对溢出的实参必须有对应的实参进行接收。

可变长度的位置形参:

'''
*形参名: 用来接受溢出的位置形参,溢出的位置实参将会被*保存,将其保存为一个元组类型,将其赋值给*后的形参名
*后的形参名可以是任意数据类型,但是出于规范性,约定俗成的,我们一般以*args形参名来作为接收溢出形参的形参名
'''

def bar(x,y,*z):
	print(x,y,z) # 1 2 (3, 4, 6, 7, 9, 9, 10)
	print(z) # (3, 4, 6, 7, 9, 9, 10) # 溢出的实参都由可变长度的形参*x保存,以元组的形式进行存储

bar(1,2,3,4,6,7,9,9,10) 


# 案例: 调用函数传入任意数量的实参,要求将其累加,将累加值返回

def sum(*args):
	res = 0
	for item in args:
		res += item

	return res
print(sum(100,200,300,400)) # 1000

注意:可变长度的位置参数必须位于位置参数的右边。

可变长度的位置实参:

# 可变长度的位置实参:

# 在函数调用的时候,我们可以使用*列表或*元组的方式进行传值,*会将我们列表或元组的值打散逐个传给我们的形参。
def demo(x,y,z):
	print(x,y,z) # 1 2 3 

demo(*[1,2,3])# 类似于遍历列表,将列表的值逐个赋值给我们的形参。

# 可变长度的位置实参和可变长度的位置形参混用

def wrapper(x,z,*args):
	print(x,z,args) # 1 2 (3, 4, 5, 6, 7, 8, 9)
	print(args) # (3, 4, 5, 6, 7, 8, 9)


wrapper(*[1,2,3,4,5,6,7,8,9]) #将该列表打散,将1赋值给x,将2赋值给z,溢出的3,4,5,6,7,8,9由*以元组的方式接收,赋值给args

可变长度的关键字形参:

'''
**形参名
可变长度的关键字形参必须位于函数参数的末尾,不然会报错。可变长度的关键字形参由**形参名定义
可变长度的关键字形参会接收溢出的关键字实参,会由**接收将其保存为字典的形式,以关键字作为字典的key,其值作为字典的value,赋予给**后的形参名,由于规范性,**后的形参名一般名为**kwargs
'''

def bar(x,**kwargs):
	print(x,kwargs) # 10 {'a': 20, 'b': 30, 'c': 40, 'd': 50, 'e': 60}
	print(kwargs) # {'a': 20, 'b': 30, 'c': 40, 'd': 50, 'e': 60}

bar(x=10,a=20,b=30,c=40,d=50,e=60) # 溢出的abcde对应的值都会由可变长度的关键字形参接收,将其转为字典类型

可变长度的关键字实参:

# 可变长度的关键字实参:

# 在函数调用的时候,以**字典的形式作为可变长度的关键字实参,之后会字典中的key作为关键字,将字典的value作为值传给函数
def wrapper(name,age,addr):
	print(name,age,addr) # x1ong 17 china

wrapper(name='x1ong',**{'age':17,'addr':'china'}) # 将字典中的age对应的值传给形参对应的参数age,将字典中的addr对应的值传给形参对应的参数addr

可变长度的位置形参和可变长度的关键字形参混用:

'''
注意点:
*args要在**kwargs的前面,不然会报语法错误
'''
def wrapper(x,*args,**kwargs):
	print(x,args,kwargs) # 10 (20, 30, 40, 50, 60) 
	print(args) # (20, 30, 40, 50, 60)
	print(kwargs) # {'z': 30, 'y': 40}


wrapper(10,20,30,40,50,60,z=30,y=40)

命名关键字参数

当我们定义了**kwargs之后,用户就可以传入任意的key=value,如果函数体的代码必须依靠冒个key值才能被执行,那么我们就需要使用if进行判断。

def register(name,age,addr,**kwargs):
	if 'gender' in kwargs:
		pass
		# 有gender要做的事情
	if 'tel' in tel:
		pass
		# 要tel要做的事情

如果要限定某个函数参数,在传入的时候,必须以key=value的方式传值,我们可以使用Python我们提供的新语法,在以*之后的所有形参在调用的时候都需要使用key=value的方式进行传值,不然会报TypeError错误,

def register(name,age,gender,*,tel,id):  # *后面的形参都为命名关键字参数,其必须通过key=value的方式传值
	print('my name is {},age is {},gender is {}'.format(name,age,gender))

register('x1ong',17,'male',tel='177********',id='3214**************') #  正确调用
register('x1ong',17,'male','177********','3214**************') # TypeError

# 命名关键字参数也可以有默认值
def register(name,age,*,gender='male'):
	print('my name is {},age is {},gender is {}'.format(name,age,gender))

register('x1ong',17)

需要说明的是,gender不是默认参数,tel也不是关键字参数,因为他们都在星号的后面,所以我们称之为命名关键字参数。如果 函数的参数中存在*args,那么我们就不需要再次使用星号进行分割。

def register(name,age,*args,id,tel):
	print('my name is {},age is {}'.format(name,age))


register('x1ong','17',id='41****************',tel='178********') # 正确调用
register('x1ong','17','41****************','178********') # TypeError错误
register('x1ong','17') # TypeError错误

组合使用

综合上诉之后,有的小伙伴会将他们组合使用,但是他们组合使用的时候,需要参照一定的顺序,不然会报语法错误。

他们的顺序为:位置参数、默认参数、*args、命名关键字参数、**kwargs

可变长度位置参数*args和可变长度关键字参数**kwargs一般都是组合使用,组合使用之后的效果就为,该函数可以接收任意长度的位置参数和任意长度的关键字参数。但是要注意顺序。

def demo(*args,**kwargs):
	pass

其实函数里面还可以调用另一个函数,而该函数的形参作为另外一个函数的实参传递,之后运行外层函数传值。

def wrapper(a,b,c):
	print(a,b,c)


def content(*args,**kwargs):
	wrapper(*args,**kwargs)


content(1,b=100,c=300)

content(1,b=100,c=300)调用content()函数并传入值。由于content()的参数有可变长度的位置形参和可变长度的关键字形参,那么实参1被传进去之后被*接收,将其转为元组之后成为(1,)又将该元组赋值给args。所以args的值为(1,)b和c位置参数对应的key=value会被**转换为字典类型{'a':100,'b':300}之后将该字典赋值给kwargs

之后又在函数的内部调用wrapper()函数,并将其传值wrapper(*args,**kwargs),而此时传入的为

wrapper(*(1,),**{'a':100,'b':300}),元组1会被形参a接受,而字典对应的keyvalue,会被ab两者接收。所以最后的结果为1 100 300

函数的类型提示

我们在定义函数的参数时,要想让调用者为每个参数传入正确的数据类型,那么我们就可以使用函数的类型提示了。

# 函数类型提示:
              string    int      tuple  返回值为string
def register(name:str,age:int,hobby:tuple)->str:
	print(name)
	print(age)
	print(hobby)
        return 'over'

register('x1ong',17,('study','music')) # 在调用的时候,为每个值正确传参。

注意,这只是提示性文字,如果调用者在调用的时候,不遵守此规范,那么程序也不会报错:

res = register(1,2,3)
print(res)

此语法只在python3.5以后有效。

函数提示语法也可以这么写,其实可以把形参:后面的当做是一个描述性信息。

# 函数类型提示:
def register(name:"给我传一个字符串",age:"给我一个int",hobby:"给我一个元组")->str:
	print(name)
	print(age)
	print(hobby)
	return 'over'
res = register(1,2,3)
print(res)

该描述性信息可以通过函数的内置属性__annotations__查看,但是此属性只支持自定义函数。

def register(name:"给我传一个字符串",age:"给我一个int",hobby:"给我一个元组")->str:
	print(name)
	print(age)
	print(hobby)
	return 'over'
res = register(1,2,3)

print(register.__annotations__) # {'name': '给我传一个字符串', 'age': '给我一个int', 'hobby': '给我一个元组', 'return': <class 'str'>}

如果为一个函数参数设置了函数提示,那么为该函数的某个参数设置默认值的方式,有所改变:

# 函数类型提示:
              # 为形参name设置默认值为'x1ong'
def register(name:str='x1ong',age:int=17,hobby:tuple=('play','music'))->str:
	print(name)
	print(age)
	print(hobby)
	return 'over'
res = register()

print(register.__annotations__) # {'name': '给我传一个字符串', 'age': '给我一个int', 'hobby': '给我一个元组', 'return': <class 'str'>}
print(res)

分享