函数声明、调用的过程

【 一 】函数声明、调用的过程详述

1. 函数必须先定义后调用,没有定义函数是一定不能够调用的
2. 函数在定义阶段,只检测语法是否错误,不检测逻辑是否有问题
3. 逻辑上的错误只会在调用阶段检测
4. 函数一定是被调用之后才会执行函数内部的代码块,不调用函数一定不会执行函数的
    # 如何调用函数
    函数名() # 只要函数名加括号一定会执行函数体代码
    函数如果在定义阶段有参数,那么,在调用的时候,连参数一块写上
    
**************************************************************************************

函数调用的内部原理:
1. 先在内存空间中申请一块空间地址来存储函数体代码
2. 把函数名和函数体所在的空间地址绑定在一起
3. 以后只需要通过函数名()来访问函数体代码即可

        def用来声明一个函数,python的函数包括函数名称、参数、函数体、函数体中涉及到的变量、返回值。

        实际上,函数名称其实是一个变量名,def表示将保存在某块内存区域中的函数代码体赋值给函数名变量。例如:

def myfunc(x,y,z):
    ...CODE...

上面表示将函数体赋值给变量名myfunc。如下图:

函数声明、调用的过程_第1张图片

既然是变量,就可以进行输出:

def myfunc(x):
    return x+5

print(myfunc)

输出结果:


由于python是解释型语言,所以必须先定义函数,才能调用函数。

        如果导入一个模块文件,导入的时候会解释、执行文件中的代码,包括def语句,也就是说,导入文件时会先声明好函数。

【  二 】 函数变量的细节

        请一定理解本节内容,也许细节方面可能会有些不准确,但对于深入理解函数来说(不仅限于python语言),是非常有帮助的,特别是理解作用域规则的时候。

        python是解释性语言,读一行解释一行,解释一行忘记一行。而函数是一种代码块,代码块是一个解释单元,是一个整体。在代码块范围内不会忘记读取过的行,也不会读一行就立即解释一行,而是读取完所有代码块内的行,然后统筹安排地进行解释。关于这一点,在后面的文章代码块详述中有非常详细的解释,建议一读。

        当python读取到def所在行的时候,知道这是一个函数声明语句,它有一个属于自己的代码块范围,于是会读完整个代码块,然后解释这个代码块。在这个解释过程中,会记录好变量以及该变量的所属作用域(是全局范围内的变量还是函数的本地变量),但一定注意,def声明函数的过程中不会进行变量的赋值(参数默认值除外,见下文),只有在函数调用的时候才会进行变量赋值。换句话说,在def声明函数的过程中,在函数被调用之前,函数所记录的变量一直都是变量的地址,或者通俗一点理解为记录变量的名称,而不会进行变量的赋值替换

        实际上,变量的明确的值会当作常量被记录起来。如a=10的10被作为常量,而变量a赋值给变量b时b=a,a显然不会作为常量。

如下函数:

x=3
def myfunc(a,b):
    c=10
    print(x,a,b,c)

myfunc(5,6)

输出结果为:"3 5 6 10"。

上面的函数涉及到了4个变量:a、b、c、x。其中:

  • 全局变量x
  • 本地变量a、b、c,其中本地变量a和b是函数的参数

        在def的过程中,会完完整整地记录好这些变量以及所属作用域,但只会记录,不会进行变量的赋值。如下图:

函数声明、调用的过程_第2张图片

        然后函数被调用,这时候才会开始根据记录的作用域搜索变量是否存在,是否已经赋值(非本地变量),并对需要赋值的变量赋值:

  • 查找全局变量变量x,它在全局作用域内已经赋值过了,所以只需找到这个全局变量即可
  • 查找本地变量a、b、c,它们是属于函数myfunc的本地变量,而a和b是参数变量,所以最先对它们进行赋值a=5,b=6,然后赋值普通的本地变量c=10

如图:

函数声明、调用的过程_第3张图片

最后执行print(x,a,b,c)输出这些变量的值。

        还需注意,python是读一行解释一行的,在函数调用过程中,因为c=10print()的前面,所以是先赋值c=10,再执行print,如果print在c=10前面,则先执行print,再赋值,这显然是错误的,因为print()中使用了变量c,但目前还没有对其赋值。这和其它语言可能有些不同(特别是编译型语言),它们可能会无视变量赋值以及变量使用的位置前后关系。

如果上面的示例中,函数myfunc调用之前,将变量x赋值为另一个值:

x=3
def myfunc(a,b):
    c=10
    print(x,a,b,c)

x=33
myfunc(5,6)

        这时将输出:"33 5 6 10"。因为x是全局变量,只有在函数调用的时候才会去找到变量x对应的值,而这时全局变量的值已经是33。

【  三  】 定义函数

  1. 定义函数 -- 封装独立的功能
  2. 调用函数 -- 享受封装的结果

【1】打印九九乘法表

# 我们将以下代码复制到python文件中,保存为 九九乘法表.py
def multiple_table():
    for i in range(1,10):
        for j in range(1,10):
            if j<=i:
                print(f"{i} * {j} = {i*j}",end='\t')
        print("")
multiple_table()  # 调用该函数
  • 函数的文档注释
# 给函数添加注释,应该定义在函数下方,在连续的三对引号中编写对函数的说明文字
def multiple_table():
"""这是一个生成九九乘法表的函数"""
    for i in range(1,10):
        print('\n')
        for j in range(1,10):
            if j<=i:
                print("%d * %d = %d" % (i,j,i*j),end = '\t')

【 2 】函数的声明和调用: 

def 函数名(形参1,形参2,...,形参n):   #函数名的命名符合标识符的命名规则
   "函数_文档字符串"
    函数体
    return 语句    #不带表达式的 return 相当于返回 None,None的逻辑值恒为False

# 例1
def sumup(x,y):
	"两数之和"
	return x+y
sumup(3,4)

( 1 )函数中参数的传递:

函数参数的作用:增加函数的通用性

位置参数:所谓位置参数,指的是当调用一个函数时,实参按位置依次传递

def sumup(x,y)
	return x+y

关键字参数:所谓关键字参数,指的是当调用一个函数的时候,可以用key=value的方式指定给某个参数赋值,这样就不一定严格遵守函数声明里的参数顺序。

def myfunc(a,b,c):
    print(f"a:{a},b:{b},c:{c}")
myfunc(c=3,b=4,a=2)

# 同时使用位置参数和关键字参数时,位置参数必须在关键字参数前
def myfunc(a,b,c):
    print(f"a:{a},b:{b},c:{c}")
myfunc(3,c=4,b=2)

默认参数(缺省参数):默认参数就是在声明函数的时候使用一些包含默认值的参数,函数声明时,普通参数在前,默认参数在后

# 函数调用时,若没有给默认参数传递参数,则该形参直接取默认参数值,如果调用时给默认参数传递了参数,那么该形参的值应该等于外部传递的参数
def func(x,y,z=1,w=3):
    return x+y+z+w
test1 = func(1,2,3)
test2 = func(1,4,w=4)

# 例1:
def print_info(name,gender=True):
    gender_text = "男生"
    if not gender:
        gender_text = "女生"
    print("{}是{}".format(name,gender_text))
print_info("小明")
print_info("小美",False)

默认参数必须指向不变对象,可以参考这篇文章

# 使用help()函数查看函数文档的时候,经常会发现在函数的原型中,会看到一个斜杠,该斜杠表示,其左边的参数必须传递位置参数而不能是关键字参数
In [10]: help(abs)
Help on built-in function abs in module builtins:
abs(x, /)
    Return the absolute value of the argument.

In [11]: help(sum)
Help on built-in function sum in module builtins:
sum(iterable, /, start=0)
    Return the sum of a 'start' value (default: 0) plus an iterable of numbers
    When the iterable is empty, return the start value.
    This function is intended specifically for use with numeric values and may
    reject non-numeric types.

# 上面help(abs)之后,我们看到里面的形参为x,我们调用abs(x=-1.5),Python会报错
In [12]: abs(-1.5) #只能以位置参数传入
Out[12]: 1.5

# 上面help(sum)之后,我们看到里面斜杠右边有个start,从而start参数可以选择以位置参数或者关键字参数传入
In [14]: sum([1,2,4,5],start=2)
Out[14]: 14

In [15]: sum([1,2,4,5],2)
Out[15]: 14

# 同样我们自己创建函数时,也可以利用这个"/"号
def myfunc(a,b,/,c,d):
    print(f"a:{a},b:{b},c:{c},d:{d}")
myfunc(3,3,4,d=2)

# 上面我们知道使用"/"可以限制左边的参数只能以位置参数传入,当我们使用"*"时,就可以限制右边的参数只能以关键字参数传入
def myfunc(a,b,*,c,d):
    print(f"a:{a},b:{b},c:{c},d:{d}")
myfunc(3,c=4,b=3,d=2)

( 2 )收集参数

       收集参数就是说只指定一个参数,然后允许调用函数时传入任意数量的参数。

       元组参数:在某个参数前加一个 * 号,将前面必须赋值的参数赋值完之后,剩下的打包成一个元组接收(可以接收空元组)。

# 函数定义时,*参数收集所有未匹配的位置参数组成一个tuple对象,局部变量tup指向此tuple对象,局部变量tup指向该tuple对象
def myfunc(d,*tup):
    print(f'必须参数:{d}')
    print(f'非必须参数:{tup}')
myfunc(10)
myfunc(10,20,30,'test')

必须参数:10
非必须参数:()
必须参数:10
非必须参数:(20, 30, 'test')

# 函数调用时,*参数用来解包tuple对象中的每个元素,作为一个个位置参数传入到函数中
def myfunc(name,age,*interest):
    print('name:{},age:{},interest:{}'.format(name,age,interest))
tuple1 = ('football','tourist','eating')
myfunc('xiyangyang','17',*tuple1)

tuple2 = ('meiyangyang','15','eating','swimming','sleeping')
myfunc(*tuple2)

# 如果在收集参数后面还需要指定其他参数,那么在调用函数的时候,就应该使用关键字参数来指定后面的参数,否则Python就会把实参纳入收集参数中
def myfunc(*arg,x,y):
    print(f"arg:{arg},x:{x},y:{y}")
myfunc(3,4,3,2)
TypeError: myfunc() missing 2 required keyword-only arguments: 'x' and 'y' #报错信息

def myfunc(*arg,x,y):
    print(f"arg:{arg},x:{x},y:{y}")
myfunc(3,4,y=3,x=2)
arg:(3, 4),x:2,y:3

字典参数:元组参数允许你传入0个或者任意个参数,这些可变参数在函数调用时会自动组装成为一个tuple,而关键字参数允许你传入0个或者任意个关键字参数,这些关键字参数会在函数内部自动组装成为一个dict

# 函数定义时,**参数收集所有未匹配的关键字参数组成一个dict对象,局部变量dict_interest指向此dict对象
def student(name,age,**dict_interest):
    print('name:{},age:{},interest:{}'.format(name,age,dict_interest))
student('luxiaofeng',18,sport = 'basketball',food = 'humberger')
name:luxiaofeng,age:18,interest:{'sport': 'basketball', 'food': 'humberger'}

# 函数调用时,**参数用于解包dict对象的每个元素,作为一个一个的关键字参数传入到函数中
def student(name,age,**dict_interest):
    print('name:{},age:{},interest:{}'.format(name,age,dict_interest))
dict1 = {'sports':'football','food':'humberger'}
student('zouhongwei',19,**dict1)

在来看一个例子:

def demo(*args,**kwargs):
    print(args)
    print(kwargs)
gl_nums = (1,2,3)
gl_dict = {"name":"小明","age":18}
demo(*gl_nums,**gl_dict)

(1, 2, 3)
{'name': '小明', 'age': 18}
  • 函数返回多个值

前面我们讲过元组的打包:

tuple = "a","b","c","d" # 这样创建元组的方式叫做元组打包

利用这个,我们可以在函数中同时返回多个值:

def measure():
    temperature = 23
    wetness = 20
    return temperature,wetness
a = measure()  # 这里的a就是一个元组

再利用元组解包,就能单独拿到每个值了:

def measure():
    temperature = 23
    wetness = 20
    return temperature,wetness
temperature,wetness = measure()

练习题1:定义一个函数 sum_numbers ,能够接收一个 num 的整数参数,计算 1~num的整数和

# 递归
def sum_numbers(num):
    if num == 1:
        return 1    # 设定一个出口
    temp = num + sum_numbers(num-1)
    return temp
print(sum_numbers(100))

你可能感兴趣的:(python,开发语言)