day10-函数的介绍,参数以及返回值

函数的介绍

1.什么是函数

​ ps : 函数就是存放代码的容器
​ 具备某一功能的工具-->函数
​ 事先准备工具的过程---> 函数的定义
​ 遇到应用场景拿来就用---> 函数的调用

所以函数的使用原则:
先定义
后调用

2.为何要用函数

​ 解决下述问题:
​ 1.代码组织结构不清晰,可读性差
​ 2.可扩展性差

3.如何使用函数

定义的语法

def 函数名(函数1,函数2,函数3...):
        # 函数名 和 变量名的定义方式格式一样,但最好使用动词,
        '''函数的文档注释'''
        代码1
        代码2
        代码3
        return 返回值

调用的语法

一.定义的阶段:

发生的事情

  1. 申请一个内存空间,将函数体代码放进去,

  2. 将函数内存地址绑定给函数名

    强调:只检测语法

在python中的变量名受到了特殊照顾,会自作主张的将变量名对应的内存地址的那个值返回,一般一个变量名返回的就是一个内存地址

所以调用函数名返回的是一个函数名对应的内存地址

例子:函数和变量返回的都是内存地址,但待遇不一样

def login():  # login = 函数的内存地址
    print(1111)
    print(2222)
    print(3333)


x = 10  # x = 10 的 内存地址
print(x)  # 10(本质是和函数一样的,返回内存地址,但python会自动给你将返回内存地址变成对应的那个值再返回)
print(login)  # 0x0000029F7BE4A0D8

二.调用函数

语法:变量名加括号===>变量名()
1.先通过函数名定位到函数的内存地址
2.函数内存地址() ===> 触发函数体代码的运行
强调:调用函数才会执行函数体代码

login()  # 执行一边函数体代码
login()  # 又执行一遍函数体代码

这俩个函数名加括号调用函数执行函数体代码,充分的说明了函数的作用.

例子说明:

例子1:定义阶段只检测语法

def func():
    print(1111  # 这是在定义阶段就检测的语法问题:SyntaxError: invalid syntax

print('我还没执行到这呢')
func()

例2:调用阶段,才执行函数体代码

def func():
    print(1111)
    x
    print(123)

print('如果我执行了就代表函数定义阶段没问题')
func()  # 报错的位置是line 76 NameError: name 'x' is not defined

例3和例4都是在强调函数分为俩个阶段,定义阶段和调用阶段

例3:
# =====按顺序先将bar的内存地址放到内存,再将foo的内存地址放到内存
def bar():
    print('from bar')

def foo():
    print('from foo')
    bar()  # 执行到这是前面bar的内存地址已经有了

foo()  # 所以调用的时候是没有问题的

例4:
==== 将 foo的内存地址先放到内存,再将bar的内存地址放到内存,再执行foo

def foo():
    print('from foo')
    bar()


def bar():
    print('from bar')


foo()

你会发现也是没有问题的.按道理来说不应该是会报错嘛?因为foo内的bar前面还没被定义呢?
但你忘了一件事,那就是函数是分俩个阶段的,一是调用,二才是执行.此时你看看.你执行foo()前,是不是已经将bar内存地址申请好了,因为定义的时候就已经申请好了,所以你在之后调用才不会有问题
除非你在调用foo之后,再定义的bar,它才会找不到对应的内存地址

三.定义函数的三种方式

3.1无参

概念:无参表示的就是在括号后面没有的"变量名"或者对应的"变量值"的

例子:

def func():
    print('xxxx')
    
func()

3.2 有参

概念:有参表示的就是在括号后面有"变量名"或者对应的"变量值"的

例子:

def func1(x):
    print(x)
func1(1)

3.3 空

概念:表示的是函数体代码为pass或者...的,只是保证执行时不会报语法错误,一般用于程序的设计搭建

def login():
    pass  # 也可以是 ...
login()

应用场景:

其实一个函数你要不要定义成一个无参的还是有参的,完全取决与你的函数体代码,里面的功能是不是要"写死"还是要"写活",参数就是一个可以变化的值,相当于变量,你不想写死,就给他一个可以变化的地方,而参数就是这么一个东西
所以说定义一个函数是不是有参无参,完全看函数体代码的需求
空函数表示的就是一个占位,表示这个函数定义阶段不会报错,并且可以执行,一般用于框架搭载时,先用pass占位,后面再来填代码的具体功能

例子:登入功能

def login():
    inp_name = input("username>>>: ").strip()
    inp_pwd = input("password>>>: ").strip()
    if inp_name == "egon" and inp_pwd == "123":
        print('login successful')
    else:
        print("username or password error")

login()

四:调用函数的三种形式

4.1 语句

len('hello')

4.2 表达式

res = len('hello') * 10  # 本质是函数的返回值进行运算
print(res)

4.3 可以当做参数传给另外一个函数

print(len('hello'))

像len()这种有返回值的函数一般都是作为具体的功能的作用,而print()这种没有返回值的一般都是作为语句的作用

函数的参数

总体分为俩大类:

1.形参:在函数定义阶段括号内指定的参数,称之为形式参数,简称形参--> 相当于变量名
2.实参:在函数调用阶段括号内指定的参数,称之为实际参数,简称实参 ---> 相当于变量的值

形参与实参的关系是:
在调用函数时,实参值会绑定给形参名,在函数调用完毕后解除绑定

细分的话

形参系列:

一:位置形参:在定义函数时,按照左到右的顺序依次定义的变量名,称之为位置形参

特点: 每次调用,必须被赋值

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

func(1,2,3)
func(1)

二:默认形参:在定义函数时,就已经为某个形参赋值了.称之为默认形参

特点:调用函数时,可以不为默认形参赋值

注意:可以混用位置形参与默认形参,但是

  1. 位置形参必须在默认形参的前面

  2. 默认形参对应的值一般是一个不可变的类型

  3. 不管是位置形参还是默认形参都是在定义阶段就放到内存的

例子:当形参为一个可变类型时,我该怎么处理这个可变类型

def func(name, hobby, hobbies=None):
    if hobbies == None:
        hobbies = []
    hobbies.append(hobby)
    print(f'{name}:{hobbies}')


func('jkey', 'eat')

func('song', 'play', ['music'])

例子2:当一个默认形参的值为一个变量时,值会怎么变化

m = 1111


def func2(x, y, z=m):
    print(x, y, z)


m = 2222
func2(1, 2)

是不是发现它不会变成2222,哈哈哈,就是因为在定义函数的时候就已经将z=m赋好值了,这又充分说明了函数的形参在定义阶段就已经绑定到内存空间了.而m=2222 的时候,函数以及定义好了,所以z=m还是=1111

二:位置实参:在调用函数时,按照左到右的顺序依次传入的值,称之为位置实参

特点:按照位置未形参赋值,一一对应

例子:

def func(name, age):
    print(name)
    print(age)


func('egon', 17)

三.关键字实参:在调用函数时,按照key=value的形式传值,称之为关键字实参

特点:可以打乱顺序,但是仍然能够指名道姓的为指定的形参赋值

def func(name, age):
    print(name)
    print(age)

func(age=18, name='jkey')

注意:可以混用位置参数与关键字参数,但是

  1. 位置实参必须在关键字实参的前面
  2. 不能为同一形参重复赋值

例子解释:

func('egon', age=18)


# func(age=18,'egon')  # 报语法错误 line 41 error SyntaxError: positional argument follows keyword argument

def foo(x, y, z):
    pass


# foo(1,y=2,3)  # 语法错误 位置实参必须在关键字实参前面
# foo(1, y=2, z=3, x=4)  # 不能给同一个形参重复赋值,TypeError: foo() got multiple values for argument 'x'
foo(1, y=2, z=3)

可变长参数系列

概念:可变长参数指的是在调用函数时,传入的实参个数不固定,对应着必须有特殊形式的形参来接收移除的实参

> * 和 ** 在 形参中 其实就是一种汇总,合并功能=============
而实参无非俩种,所以它对应的特殊形式的形参应该是:
1.溢出位置实参 ---> *
2.溢出的关键字形参 ====> **

1.在形参种的应用:会将溢出的位置实参合并成一个元组,然后赋值给紧跟其后的那个形参名-- 这个形参名一般为args

def func(x, *args):  # *的功能是 将 溢出的 位置实参(也就是值) 绑定个args
    print(x)  # 1
    print(args)  # (2, 3, 4, 5)

func(1,2,3,4,5)

例子: 计算n个数之和

def my_sum(*args):
    sum_num = 0
    for i in args:
        sum_num += i

    print(sum_num)

my_sum(1,2,3,4,5)

2.**在形参种的应用:**会将溢出的关键字实参合并成一个字典,然后赋值给紧跟其后的那个形参名 -- 这个形参名一般为 kwargs

def func(x, **kwargs):  # **的功能是将溢出的关键字实参合并成一个字典
    print(x)  # 1
    print(kwargs)  # {'a': 2, 'c': 3}


func(1, a=2, c=3)

========= * 与 ** 在实参中是一种打散功能==========

* 在实参中的应用: * 后可以跟被for 循环遍历的所有类型,*会将紧跟其后的那个值打散成位置实参

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

func(*(11,22,33))
# func(*"hello")  # TypeError: func() takes 3 positional arguments but 5 were given
func(*{'x':1,"y":2,'z':3})  # x y z for遍历出来的默认是key

** 在实参中的应用: ** 只能跟字典类型,**会将字典打散成关键字实参

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


# func(**{'k1': 111, 'k2': 222})  # 相当于func(k2=222,k1=111) 报错:TypeError: func() got an unexpected keyword argument 'k1'
func(**{"x": 111, "y": 222, "z": 333})  # 111\n222\n333

* 和 ** 在形参和实参中的应用

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


def wrapper(*args, **kwargs):  # 这里的*和**是在形参中,功能是将wrapper的位置实参和关键字实参给汇和,合并
    index(*args, **kwargs)  # 而这里的* 和 ** 是在实参中,表示的功能是将汇合合并后的形参打散. 会将wrapper中传入的参数原封不动的传入到 index函数


# wrapper(1, 2, 3, 4, 5, a=1, b=2, c=3)  # 所以该wrapper的实参参照的形参其实是index的形参规范.  所以这样传会报错:TypeError: index() got an unexpected keyword argument 'a'
# 所以要想调用wrapper就必须满足index的形参规范
wrapper(1, 2, 3)  # 1 2 3
wrapper(1, y=2, z=3)  # 1 2 3

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

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

例子:

def func(x, m=1, *args, y=222, z, **kwargs):
    print(x)
    print(m)
    print(args)
    print(y)
    print(z)
    print(kwargs)


func(1, 2, 3, z=4, a=1, b=2, c=3)
# func(1, 2, 3, 4, 5, 6)  # 多余的 2, 3, 4, 5, 6 全部给了* 绑定给了args

# 所以要想该z的值就必须得以关键字实参的形式来传
func(1, 2, 3, 5, 6, z=4)

函数的返回值

函数的返回值可以放在 return关键字的后面,后面表示的就是该函数的返回值

返回值可以是任何数据类型

返回值的应用:计算俩个人月薪高的年薪

def max2(x, y):
    """
    一般的函数就应该让对应的返回值表示的有意义,本函数就是一个例子
    :param x:
    :param y:
    :return:
    """
    if x > y:
        return x
    else:
        return y


res = max2(10, 20)
print(res * 12)

return关键字的作用:当一个函数体代码在执行的时候(即被调用后),只要执行到return就会立即结束函数体的代码,并且将之后的值返回

例子:

def func():
    """
    当函数体内出现了 return时,执行到return时函数体代码会立即结束(不会往下执行了)
    但是你在return之后还有代码也没有关系,不会报语法错误,但是并不会执行
    :return: 111
    """
    print('=====>')  # =====>
    return 111  # 从这之后的函数体代码就不会被执行了
    print('=====>2')
    return 222


func()

返回值的三种类型

def func1():
    """
    当返回值 只有一个的情况下,返回的就是这个值的本身
    :return: 111
    """
    print(111)
    return 111


def func2():
    """
    当返回值 有多个的情况下, 返回的就是小元组
    :return: ('name', 111, True)
    """
    print(222)
    return 'name', 111, True


def func3():
    """
    当没有返回值 或者 只有一个 return 或者 return None 的情况下, 返回的都是None
    :return:
    """
    print(333)
    return

本章主要主要介绍了函数的基本使用

需要注意的是函数的参数部分 记住形参有俩种加上俩个特殊形式(位置形参,默认形参,*和**),对应的实参也有俩种加上俩个特殊形式(位置实参,关键字实参,*和** ), 在形参时的 *和 ** 的的功能是汇合,而在实参时的 * 和 **表示的是打散.

posted on 2020-12-28 20:27  Jkeykey  阅读(205)  评论(0编辑  收藏  举报