首页 > 技术文章 > 函数:函数的基本使用、函数参数的使用

gengfenglog 2020-12-25 22:20 原文

函数的基本使用

什么是函数

函数就类似于具备某一功能的工具,提前定义好之后可以反复使用(即:函数就是盛放代码和功能的容器)

函数的使用原则:

先定义 后调用

为何要用函数==>解决下述问题:

  • 1.代码组织结构不清晰、过于冗余、可读性差
  • 2.代码没有返回值(无法使用之后的返回结果)
  • 3.兼容性、可维护性、可扩展性差、修改繁杂等等

如何用函数

  • 先定义------> 三种定义方式
  • 后调用------> 三种调用方式
  • 返回值------> 三种返回值的形式

函数定义的语法:

def 函数名(参数1,参数2,参数3,...):
	'''函数的注释'''
    函数体代码
    return 返回值
'''
1.def: (必须)
  	定义函数的关键字;
2.函数名:(必须)
		函数名指向函数内存地址,是对函数体代码的引用。函数的命名应该反映出函数的功能;
3.括号冒号:(必须)
		括号后要加冒号,然后在下一行开始缩进编写函数体的代码;
4.参数:(可选)
		括号内定义参数,参数是可有可无的,且无需指定参数的类型;
5.函数注释: (可选)
  	描述函数功能,参数介绍,非必要,但是建议加上,从而增强函数的可读性;
6.函数体:(必须)
		由语句和表达式组成;
7.return值:(可选)
		定义函数的返回值,return是可有可无的。
'''

函数的定义与调用

定义函数

定义函数发生的事情

  • 1、在内存空间中申请一块内存空间存储函数体代码

  • 2、将函数体代码所在的内存空间地址绑定给函数名

强调:定义函数只检测语法,不执行代码

def login():  # login = 函数的内存地址
    print(111)  # 函数体代码
    print(222)

    
x = 10  # x = 10的内存地址
print(x)  # 10
'''
原则上打印应该返回内存地址,但是返回的是值。
变量名受到python解释器特殊照顾由内存地址直接找到返回值打印。
'''
print(login)  # <function login at 0x7fa136db91f0>
'''
但函数名不同,打印显示的是内存地址。定义函数的目的是为了把代码丢到容器,
只需要运行不需查看。则无需被照顾。
'''

调用函数

调用函数发生的事情:

  • 1、先通过函数名定位到函数的内存地址

  • 2、函数名加括号会触发函数体代码的运行

强调:调用函数才会执行函数体代码

# 示例:
def login(): 
    print('hello') 
   
login()  # hello 触发函数体代码的运行

# 语法错误
def func():
    print(111  # 无效语法,定义阶段都无法通过

# 逻辑错误
def func():
    print(111)
    x  # x 没有定义,变量名也需要先定义后引用
          
func()  # 调用肯定出错

# 示例:
def bar():
    print('from bar')

def foo():
    print('from foo')
    bar()
foo()
'''
from foo
from bar
'''
          
# 语法错误         
def foo():
    print('from foo')
    bar()
          
foo()  # bar没有定义,调用肯定出错
          
def bar():
    print('from bar')

定义函数的三种形式

1.无参函数

  • 函数在定义阶段括号内没有参数
# 函数体不需要外部传参数进行,它本身代码就是固定的,所以就是无参函数
def say():
    print('welcome to here')
    
say()

# 参数是函数的调用者向函数体传值的媒介,有参无参是根据函数体代码去走的,看函数体代码是否需要外部传参数
def login():
    inp_name = input("请输入用户名>>>: ").strip()  
    inp_pwd = input("请输入密码>>>: ").strip()
    if inp_name == "jack" and inp_pwd == "123":
        print('登录成功!')
    else:
        print("用户名或密码错误")

login()  # 直接调用无需传参数,input功能已经实现与用户交互

2.有参函数

  • 函数在定义阶段括号内有参数
# 函数体代码需要外部传参数进行,这种函数就需要定义为有参函数
def max2(x, y):  # 参数---》原材料 
    if x > y:
        print(x)
    else:
        print(y)
max2(10,20)

3.空函数

  • 函数在定义阶段括号内没有参数,并且函数体代码为空(pass)
# 函数体为pass代表什么都不做,称之为空函数。定义空函数通常是有用的,因为在程序设计的开始,往往是先想好程序都需要完成什么功能,然后把所有功能都列举出来用pass充当函数体“占位符”,这将使得程序的体系结构清晰且可读性强。例如要编写一个程序,我们可能想到的功能有用户认证,下载,上传,等功能,可以先做出如下定义:

def auth_user():
    """用户认证"""
    pass   # 代表占位符

def download_file():
    """下载文件"""
    pass

def upload_file():
    """上传文件"""
    pass

调用函数的三种形式

# 1.语句
len("hello")  # 单纯的调用,例如len统计hello的长度

# 2.表达式
res = len("hello")  # 将调用函数的返回值赋值给res

res = len("hello") * 10  # 将调用函数的返回值乘以10的结果赋值给res
print(res)

# 3 .可以当做参数传给另外一个函数
len("hello")  # 从形式上看就是将函数调用,从本质讲是跟2同理
print(len("hello"))  # 先调len的返回值然后再赋值给print

函数的返回值

return 详解

def max(x, y): 
    if x > y:
        return x
    else:
        return y
    
res = max(10, 20)
print(res)
  • 1、return是一个函数结束的标志,函数可以有多个return,但只要执行一次return,整个函数就立即结束,并且将return后的值当做本次调用的结果返回

    def func():
        print("=====")  # 只运行到了此行
        return 111  # return后的值当做本次调用的结果返回
        print("****")  # ruturn 下面的值不会被执行
        return 222
    
    res = func()
    print(res)
    '''
    =====
    111
    '''
    
  • 2、return后的返回值有三种情况

    • 1)函数内可以没有return 或者 return None 或者 return:返回的都是None
    • 2)return 值:返回的就是这一个值
    • 3)return 值1,值2,值3:会自动组织成一个元组返回
# 示例1:
def func():
    print(111)
    return
    print(2222) 

res = func()
print(res)

'''
111
None
'''

# 示例2:
def func():
    return 111,"xxx",[22,33]

res = func()
print(res)  # (111,"xxx",[22,33])返回的是元组




函数参数的使用

  • 函数参数的两大分类:

    • 1、形参:在函数定义阶段括号内指定的参数,称之为形式参数,简称“形参”

    • 2、实参:在函数调用阶段括号内传入的值,称之为实际参数,简称“实参”

形参与实参的关系:

我们可以将形参看成是变量名,实参看成是变量值,在调用函数时,实参值会绑定给形参名,在函数调用完毕后解除绑定

def func(x, y):  # x, y是形参,在定义阶段()内传值。形参指的是变量名。   
    print(x, y)
    
func(111, 222)  # 111,222是实参,在调用阶段传送的值,实参指的是变量值

"""
形参的表现形式只有一种就是变量名
实参的表现形式有很多种(但是把握核心就是:数据值)
"""

形参系列

位置形参

  • 在函数定义阶段按照从左往右的顺序依次定义的变量名
  • 特点:
    • 每次调用必须被赋值,按照位置一一对应
    • 位置参数在绑定的时候多一个不行少一个也不行
def func(x, y): # 定义位置形参:x,y,两者都必须被传值
    print(x)
    print(y)

func(1, 2)  # 可以正常赋值,必须刚刚好
func(1, 2, 3)  # 报错,多了一个值
func(1)  # 报错,少了一个值      

默认形参

  • 在函数定义阶段就已经为某个形参赋值了

  • 特点:

    • 在函数调用阶段,可以不用为其赋值,则使用默认的

    • 在函数调用阶段,也可以继续为其赋值, 则使用你给的

def func(name, age=18):  # age=18是默认形参,无需赋值,也可以被赋值
    print(name)
    print(age)
    
func("jack")  # name正常赋值
func("jack", 19)  # 正常赋值
  • 注意: 可以混用位置形参与默认形参,但是
    • 1、位置形参必须在默认形参的前面

    • 2、默认形参的值通常应该是不可变类型

    • 3、默认形参的值是在函数定义阶段赋值的

# 例一:
def func(name, hobby, hobbies = []):  # 三次调用都用的是同一列表
    hobbies.append(hobby)
    print('%s的爱好是%s'%(name,hobbies))
    
func('jack','read')  # jack的爱好是['read']
func('tom','play')  # tom的爱好是['read', 'play']
func('bob','movie')  # bob的爱好是['read', 'play', 'music']

'''格式越简单的越靠前 格式越复杂的越靠后'''

# 例二:
m = 111
def func(x, y ,z=m):  # 在定义阶段就把111赋值给了z
    print(x)
    print(y)
    print(z)
m = 666
func(1, 2)

实参系列

位置实参

  • 在函数调用阶段括号内按照从左往右的顺序依次传入的数据值
  • 特点:
    • 按照位置为形参赋值,必须一一对应
    • 位置参数在绑定的时候多一个不行少一个也不行
def func(name, age):  # 定义位置形参:name,age,两者都必须被传值
    print(name)
    print(age)
    
func("jack")  # 报错,少了一个值
func("jack",19)  # 正常传值

关键字实参

  • 在函数调用阶段,按照 形参名 = 数据值 的形式指名道姓的传值
    • 特点:可以打乱顺序,但是仍然能够指定道姓的为指定的形参赋值
def func(name, age):  # 定义位置形参:name,age,两者都必须被传值
    print(name)
    print(age)
    
func(age = 18,name = "jack")  # 正常传值
  • 注意:可以混用位置实参与关键字实参,但是

    • 1、位置实参必须在关键字实参的前面
    • 2、不能为同一形参重复赋值
# 例一:
def func(name, age):  # 定义位置形参:name,age,两者都必须被传值
    print(name)
    print(age)
    
func("jack",age = 18)  # 正常传值
func(age = 18,"jack")  # 报错,语法错误

'''格式越简单的越靠前 格式越复杂的越靠后'''

# 例二:
def foo(x, y, z):
    pass
foo(1, y=2,3)  # 报错,语法错误
foo(1, y=2, z=3, x=4)  # 报错,语法错误

可变长参数

  • 可变长参数指的是在函数调用阶段,传入的实参个数不固定,对应着必须有特殊形式的形参来接收多余的实参

  • 实参无非两种形式

    • 多余的位置实参 *
    • 多余的位置关键字实参 **

*与**在形参中是一种汇总行为

* 在形参中的使用

  • 用于接收多余的位置实参,并组织成元组的形式赋值给紧跟其后的形参名

# 函数无论传入多少位置参数都可以正常运行
def func(x, *y):  # *是接收多余位置实参的功劳,不是y变量名的功劳。*后面可以跟任意变量名。
    print(x)
    print(y)
    
func(1, 2, 3, 4, 5)  # x = 1 , y = (2,3,4,5)
'''
1
(2,3,4,5)
'''

def func(x, *args):  # args变量名通常搭配*使用
    print(x)
    print(args)

func(1, 2, 3, 4, 5)  # 同上是一个道理


def my_sum(*args):  # 求和运算的函数
    res = 0
    for i in args:
        res += i
    print(res)

my_sum(1, 2)  # 3

** 在形参中的使用

  • 用于接收多余的位置实参的关键字实参,并组织成字典的形式赋值给紧跟其后的那个形参名
# 函数无论传入多少关键字参数都可以正常运行
def func(x, **kwargs):  # kwargs变量名通常搭配**使用
    print(x)
    print(kwargs)

func(1,a=2,b=3,c=4)
'''
1
{'a': 2, 'b': 3, 'c': 4}
'''

# 定义一个函数无论传入多少位置参数和关键字参数都可以正常运行
def index(*a,**b):
    print(a,b)
index()  # () {}
index(1,2,3,4)  # (1, 2, 3, 4) {}
index(a=1,b=2,c=3)  # () {'a': 1, 'b': 2, 'c': 3}
index(1,2,3,4,a=1,b=2,c=3)  # (1, 2, 3, 4) {'a': 1, 'b': 2, 'c': 3}
"""
墨守成规
    可变长形参 *与**后面的变量名其实是可以随便定义的
    但是python中推荐使用
        *args  
        **kwargs 
def index(*args, **kwargs):
    pass
"""

*与**在实参中是一种打散行为

* 在实参中的使用

  • 会将列表、元组内元素打散成位置实参的形式一一传值

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

func([11, 22, 33])  # 报错
func(*[11,22,33])  # func(11,22,33) *会把列表这种实参打散成位置实参
func(*"hello")  # func("h","e","l","l","o") 报错,但能证明*的打散行为
func(*{"k1":111,"k2":2222})  # func("k1","k2") 报错,但证明*能把字典打散成key

** 在实参中的使用

  • 会将字典键值对打散成关键字实参传入

# ** 只能跟字典类型
def func(x, y, z):
  	print(x)
    print(y)
    print(z)
func(**{"k1": 111, "k2": 2222})  # func(k2=2222,k1=111) 报错,没有k关键字,打散后对应不上

func(**{"x": 111, "y": 2222, "z": 333})  # **将字典打散成关键字实参
'''
111
2222
333
'''

# 例1:
def wrapper(*args, **kwargs):  # 形参中带*和**是一种汇总行为
    print(args)
    print(kwargs)

wrapper(1, 2, 3, 4, 5, 6, a=1, b=2, c=3)
'''
(1, 2, 3, 4, 5, 6)
{'a': 1, 'b': 2, 'c': 3}
'''

# 例2:
def index(x, y, z):
    print(x, y, z)

def wrapper(*args, **kwargs):  
    index(*args, **kwargs)   
    
wrapper(1, y=2, z=3)  # 1 2 3

'''
实参中带 * 和 ** 是一种打散行为
index(*(1, 2, 3, 4, 5, 6), **{"a": 1, "b": 2, "c": 3})
index(1, 2, 3, 4, 5, 6, a=1, b=2, c=3)
'''

命名关键字形参: 在*与**中间的形参称之为命名关键字形参(了解)

特点: 必须按照key=value的形式传值

def register(name,age,*,sex,height):
       pass

# register('jason',18,'male',183)
register('lili',18,sex='male',height='1.8m') #正确使用

"""
sex height在传入实参的时候必须以关键字参数的形式
ps:该类型的参数几乎不用 也几乎很少能碰到
"""  

推荐阅读