python第十一课--函数的参数,位置参数,默认参数,可变长参数用法及注意点,名称空间及名字查找的顺序相关知识

昨日内容回顾

  • 作业讲解

    写代码可以先写思路、出错之后不要慌先看错在哪里然后针对性的校验
    
  • 文件内光标移动实战

    了解即可 后续会采用更加方便快捷的监控机制
    
  • 硬盘删除数据的原理

    占有态 自由态
    
  • 文件内数据修改原理

    1.覆盖写
    2.重命名
    
  • 函数前戏
    function /'fʌŋkʃən/ n. 功能;作用;职责;函数

    1.函数其实就类似于工具 提前定义好之后可以反复使用
    2.相同的代码需要在不同的地方反复执行
    
  • 函数语法结构
    definition /ˌdefɪˈnɪʃn/ 定义,释义(n)

    def 函数名(参数):
        '''函数注释'''
        函数体代码
        return 返回值
    
  • 函数的定义与调用

    1.函数必须先定义后调用
    2.函数定义需要使用关键字def 函数调用需要使用函数名加括号(可能好需要传参)
    3.函数在定义阶段只检测语法不执行代码 函数在调用阶段才会执行函数体代码
    
  • 函数的分类

    内置函数
    	解释器自带的函数
    自定义函数
    	空函数
    	无参函数
     	有参函数
    

函数的返回值

  1.调用函数之后返回给调用者的信息
  2.如何获取函数的返回值
	res = func()
  3.返回值多种情况
	1.函数体代码没有return关键字 默认返回None
	2.函数体代码有return关键字 后面如果没有东西 会返回None
    	3.函数体代码有return关键字 后面如果有东西 则返回
    	单个数据直接返回该数据
    	多个数据则自动组织成元组!!!!!!
		return在返回时会自动将返回的数据组织成元组,这样在接收返回值时,可以用解压赋值操作,用多个变量名分别接收元组里对应的数据。
	4.函数体代码遇到return会立刻结束!!!!!!
  • 函数参数的概念
    1.形式参数
    	函数定义阶段括号内填写的参数 形参
    2.实际参数
    	函数调用阶段括号内填写的参数 实参
    """
    形参类似于变量名
    实参类似于数据值
    形参与实参在函数调用阶段动态绑定 在函数结束阶段动态解除
    """
    

今日内容概要

  • 作业讲解

  • 函数参数

    位置参数、关键字参数、默认参数、可变长参数、命名关键字参数
    
  • 名称空间与作用域

  • 名字的查找顺序

  • global与nonlocal

  • 函数名的多种用法

今日内容详细

作业讲解

'''由于只讲了一点点函数的知识 所以只需要你们封装成函数就行'''
# 伪代码:主要用于表达逻辑 执行起来可能没有功能甚至报错

def register():
    注册相关代码
def login():
    登录相关代码
while True:
    print("""
    1.注册功能
    2.登录功能
    """)
    choice = input('>>>:').strip()
    if choice == '1':
        register()
    elif choice == '2':
        login()
    else:
        print('暂无该功能编号')

.
.
.
.
.
.

函数参数--- 1. 位置参数


位置形参:函数定义阶段括号内从左往右依次填写的变量名!!!

代码结构:
def func1(a, b, c):pass             # a,b,c都叫位置形参
# 补充:当子代码只有一行并且很简单的情况下 可以直接在冒号后编写 不用换行

位置实参:函数调用阶段括号内从左往右依次填写的数据值!!!
 func1(1, 2, 3)                    # 1, 2, 3都叫位置实参


----------------------------------------

位置参数应用注意点:

def func1(a, b):
    print(a, b)

func1(1, 2)       # 按照位置一一对应传值   结果是:1 2
func1(1)          # 少一个不行,会直接报错
func1(1, 2, 3)    # 多一个也不行,会直接报错

func1(b=1, a=2)   # 关键字传参(指名道姓的传)   结果是:2 1
func1(b=1, 2)     # 语法错误:位置参数跟在了关键字参数的后面,
# 如果想用关键字传参,一定要跟在位置参数的后面,不然用不了,会直接报错!!!
func1(2, b=1)     # 语法正确,结果是:2 1

func1(1, a=2)     # 语法错误:同一个形参a在调用的时候不能多次赋值
# 形参a先赋值给了1,后又赋值给了2,会直接报错

位置参数应用的变型情况:
def func1(a, b):
    print(a, b)
name = 'jason'
pwd = 123
func1(name, pwd)      # 语法正确  实参没有固定的定义,可以传数据值,也可以传绑定了数据值的变量名
func1(a=name, b=pwd)  # 语法正确  实参没有固定的定义 可以传数据值 也可以传绑定了数据值的变量名

小规律总结:
越短的越简单的越靠前
越长的越复杂的越靠后
但是遇到下列的情况除外:
  同一个形参在调用的时候不能多次赋值

.
.
.
.

函数参数--- 2. 默认参数(就是位置参数添加了默认值)


无论是行参还是实参,一定遵循,位置参数在前,默认参数在后!!!


# 默认参数:提前就已经给了 用户可以不传 ,也可以传,你传了就用你传的值

# 规律总结:默认参数的定义也遵循短的简单的靠前,长的复杂的靠后


案例:
# gender='male'    这里面默认参数在括号里面摆放的顺序,只能放最后,不能往前放!!
def register(name, age, gender='male'):   # 在函数定义阶段直接给形参赋值,这个整体就叫默认参数或关键字行参 
    print(f"""
    --------学员信息----------
    姓名:{name}
    年龄:{age}
    性别:{gender}
    -------------------------
    """)



register('kevin', 28)             # 在函数调用阶段可以不对默认参数传值
--------学员信息----------
    姓名:{kevin}
    年龄:{28}
    性别:{male}
-------------------------



register('lili', 28, 'female')    # 不传值就用默认参数,但是传了,就用传的值
--------学员信息----------
    姓名:{lili}
    年龄:{28}
    性别:{female}
-------------------------


.
.
.
.

函数参数---3. 可变长形参 重要!!!


总结1:* 号在形参中用于接收实参中多余的位置参数!!!组织成元组赋值给*号后面的变量名!!!
可以理解为* 号后面的变量名是一个元组用来接收实参中多余的位置参数

总结2:** 号在形参中,用于接收多余的关键字参数!!! 组织成字典的形式赋值给** 号后面的变量名!!!
可以理解为** 号后面的变量名是一个字典用来接收实参中多余的关键字参数

注意:
接收多余的参数与组织成元组或字典这两步是独立的,就算没有接收到数据,还是可以形成元组或字典,
只不过为空元组或空字典!!!

--------------------------------------------------------------------------

# *号后面随便写一个变量名,此处*a不是一个完整的变量,*号是一部分,a是一部分
def func1(*a):
    print(a)

func1()          # ()
func1(1)         # (1,)
func1(1,2)       # (1, 2)


def func2(b, *a):
    print(a, b)
func2()               # 代码报错,函数定义阶段给了一个位置形参b,调用阶段就需要一个参数给到位置行参b
func2(1)              # 位置参数1给了形参b,没有多余的位置参数给*号接收,并组成元组赋值给a ,所以结果为  () 1
func2(1, 2, 3, 4)     # 同理,1给了形参b,2,3,4给*号接收,并组成元组赋值给a ,所以 结果为 (2, 3, 4) 1

总结:*号在形参中,用于接收实参种多余的位置参数!!!组织成元组赋值给*号后面的变量名!!!


注意!!:
这种很少用:def func2(a,b ): 中a不要放在前面,如果非要放在前面,在调用函数的时候,括号里面的实参一定要有个b=xxx的默认参数,不然电脑不知道该把一堆位置参数中的哪一个给b,会报错!!!
image
.
image
.
.
.
.
.
.


# **号后是一个变量名,想写啥就写啥,括号内除了**K,没有其他的普通行参了,所以在调用这个函数的时候可以不传参。
def func3(**k):
    print(k)


func3()                 # {}
func3(a=1)              # 函数在定义阶段没有形参a,所以调用阶段时a=1是多余的关键字参数,
                        # 被**号接收,组织成字典的形式赋值给了K 所以结果为   {'a': 1}


func3(a=1, b=2, c=3)    # 同理函数在定义阶段没有形参a,b,c,所以调用阶段时a=1, b=2, c=3都是多余的关键字参数,
                        # 被**号接收,组织成字典的形式赋值给了K   所以结果为{'a': 1, 'b': 2, 'c': 3}

---------------------------------------------------

def func4(a, **k):
    print(a, k)

func4()           # 代码报错,函数在定义阶段有形参a,在调用阶段就需要传一个参数给到a
func4(a=1)       # 函数在定义阶段有形参a,所以调用阶段时a=1不是多余的关键字参数!!
                 # 所以没有多余关键字参数,被**号接收,组织成字典的形式赋值给了K ,所以结果为 1 {}

func4(a=1, b=2, c=3)  # 同理b=2, c=3是多余的关键字参,被**号接收,组织成字典的形式赋值给了K ,
                      # 所以结果为 1 {'b': 2, 'c': 3}

func4(a=1, b=2, c=3, x='jason', y='kevin')
# 同理结果为 1 {'b': 2, 'c': 3, 'x': 'jason', 'y': 'kevin'}

总结2:**号在形参中,用于接收多余的关键字参数!!! 组织成字典的形式赋值给**号后面的变量名!!!

.
.
.
.
.
.


def func5(*a, **k):        # 括号里面没有任何普通行参,两个都是可变长参数
    print(a, k)

func5()        # 定义阶段无普通形参,所以调用阶段可以不需要传参数,也没有多余的位置参数与关键字参数,*号与**号什么都接收不到,所以形成空的元组与字典赋值给对应的变量名 所以结果为 () {}

func5(1, 2, 3)  # 同理,位置参数被*号接收组织成元组赋值给了a,没有关键字参数被**号接受,所以组织成的字典为空字典    所以结果为(1, 2, 3) {}

func5(a=1, b=2, c=3)  # 同理关键字参数被**号接收组织成字典赋值给了K,没有位置参数,所以结果为 () {'a': 1, 'b': 2, 'c': 3}

func5(1, 2, 3, a=1, b=2, c=3)  # 同理结果为 (1, 2, 3) {'a': 1, 'b': 2, 'c': 3}

-----------------------------------------------------

def func5(n, *a, **k):
    print(a, k)

同理不难想出
func5()       # 代码报错,函数在定义阶段有位置参数n,在调用阶段就需要传一个参数给到a
func5(1, 2, 3)  # (2, 3) {}
func5(111,a=1, b=2, c=3)        # () {'a': 1, 'b': 2, 'c': 3}
func5(n=111,a=1, b=2, c=3)      # () {'a': 1, 'b': 2, 'c': 3}
func5(a=1, b=2, c=3, n=111)     # () {'a': 1, 'b': 2, 'c': 3}
func5(1, 2, 3, a=1, b=2, c=3)   # (2, 3) {'a': 1, 'b': 2, 'c': 3}

"""
由于*和**在函数的形参中使用频率很高 后面跟的变量名推荐使用
    *args
    **kwargs
def index(*args,**kwargs):pass
"""

.
.
.
.
.
.

函数参数---4. 可变长实参


* 号与** 号 在函数的定义阶段的形参中与在函数调用阶段的实参中的作用完全不一样!!!

* 号与** 号的作用就是用在一些列表与字典里面数据比较多的时候,一个一个的取比较烦是就用它们把列表与字典打散了传给函数。


总结1:* 号 在实参中类似于for循环!!! 将所有循环遍历出来的数据按照位置参数一次性传给函数!!!能够将列表打散成位置参数的形式传给函数。
总结2:** 号在实参中,将字典打散成关键字参数的形式传递给函数


def index(a, b, c):
    print(a, b, c)
l1 = [11, 22, 33]
t1 = (33, 22, 11)
s1 = 'tom'
se = {123, 321, 222}
d1 = {'username': 'jason', 'pwd': 123, 'age': 18}

需求:将列表中三个数据值取出来传给函数的三个形参
方法1    index(l1[0], l1[1], l1[2])   # 如果列表里面的数据很多,这个操作就不方便了
方法2    index(*l1)                   # 相当于index(11, 22, 33)

index(*t1)    # 相当于index(33, 22, 11)
index(*s1)    # 相当于index('t','o','m')
index(*se)    # 相当于index(321 123 222)注意集合里的数据是无序的所以赋值给abc的数据也是随机取的
index(*d1)    # 相当于index('username','pwd','age') 只拿键传给形参

# 总结1:* 号 在实参中类似于for循环!!! 将所有循环遍历出来的数据按照位置参数一次性传给函数!!!
# 能够将列表打散成位置参数的形式传给函数。





需求:将字典里面的数据变成关键字产数的形式传给函数。
def index(username, pwd, age):
    print(username, pwd, age)
d1 = {'username': 'jason', 'pwd': 123, 'age': 18}
方法1: index(username=d1.get('username'), pwd=d1.get('pwd'), age=d1.get('age'))
# 用字典取值这个方法比较烦

方法2: index(**d1)        # index(username='jason',pwd=123,age=18)
总结2:** 号在实参中,将字典打散成关键字参数的形式传递给函数

例题:
def index(*args, **kwargs):
    print(args)  # (11, 22, 33, 44)
    print(kwargs)  # {}
index(*[11, 22, 33, 44])  # 相当于 index(11, 22, 33, 44)
index(*(11, 22, 33, 44))  # 相当于 index(11, 22, 33, 44)

函数定义阶段没有普通的行参,所以函数调用阶段里面所有的位置参数都是多余的,
会被*号接受组织成元组赋值给args。没有多余的关键字参数被**号接收,所以组织成的字典为一个空字典赋值给了变量名kwargs

.
.
.
.
.

命名关键字参数(比较冷门,了解一下就好)

形参必须按照关键字参数传值,这种行参就叫作:命名关键字参数

def index(name, gender='male',*args, **kwargs):   #复杂的而靠后,短的靠前
    print(name, args, gender, kwargs)

index('jason',female,1,2,3,a=111)
这种情况下代码运行没有问题

------------------------------------------------------------

def index(name, gender='male',*args, **kwargs):
    print(name, gender,args, kwargs)
index('jason',1,2,3,a=111)
这种情况下就不对了结果为:
默认参数的传值是有就用传的值,没有就用默认参的值,此时会把1当成gender需要的值直接赋值给它了,
我们想的是不传值给默认形参的。


这时候index('jason',gender='male',1,2,3,a=111)   # 语法错误,因为这时候位置参数在后面了,语法还是有问题。

index('jason',1,2,3,gender='male',a=111)
# 还是不行,1直接赋值给了默认形参了,而且gender='male'相当于被赋值两次了,还是语法有问题。


这时候把定义阶段的默认参数位置换一下,这时候可以理解为*args的优先级在默认参数前面了,
当第一个'jason'传给name以后,系统就认为没有普通行参了,
开始执行:*号接收实参中多余的位置参数组成元组赋值给后面的变量名。*args执行完了,
发现还有默认参数时,此时1, 2, 3, 4已经被接收了,没有位置参数给他传值了,
所以只能用自己的默认参数了。这种情况下gender='male'就叫作命名关键字参数,
也就是说你在调用函数时,输入的参数里面要想给形参中的默认参数gender='male'传值,必需时gender=XXXX
这种形式指名道姓的告诉形参中的默认参数,我要传值给你了,否则就无法将值传给默认参数。

def index(name, *args, gender='male',**kwargs):     # gender='male'就是命名关键字参数
	print(name, gender,args, kwargs)
index('jason', 1, 2, 3, 4, a=1, b=2)
这样就行了,调用阶段会用gender的默认参数结果为:
jason(1,2,3,4)male {'a':1,'b':2 }
如果想改gender的默认值就只能这样写
index('jason', 1, 2, 3, 4,gender='female' , b=2)

代码如果这样写,把gender去掉就出问题了,'female'会被当做多余的位置参数被*号给接收了
index('jason', 1, 2, 3, 4,'female' , b=2)

.
.
.
.
.

名称空间 理论知识 这个重要!!!


下面这行代码底层发生了什么?
name = 'jason'
1.申请内存空间存储jason
2.给jason绑定一个变量名name
3.后续通过变量名name就可以访问到jason

定义: 名称空间就是用来存储变量名!与数据值绑定关系(绑定的内存地址)!的地方
(我们也可以简单的理解为就是存储变量名的地方)

名称空间在python里面有3个地方存变量名:内置 全局 局部 !!!
1. 内置名称空间:就是程序自带的,解释器运行,自动产生的名称空间 里面包含了很多名字
比如:len print input 等程序自带的方法的名字,程序一运行,就自动产生的名称空间


2. 全局名称空间:解释器种的.py文件运行时产生,里面存放文件级别的名字
比如每个.py文件运行时,就会在内存里面产生一个全局名称空间。
文件级别的名字有哪些了?一般就是文件里面代码中的变量名
name = 'jason'  # name

if name:
	age = 18   # age

while True:
	gender = 'male'   # gender

def index():          # index
	pass

class MyClass(object):    # MyClass
pass

name\age\gender\index\MyClass 等等


3. 局部名称空间:  函数体代码运行\类体代码运行 产生的空间!!!
比如函数体代码中的变量名就是在局部名称空间里,在外部代码中调用函数体代码中的变量名,是调用不出来该变量名对应的数据的!!!

image

名称空间存活周期及作用范围(域)

存活周期:
	内置名称空间
		python解释器启动则创建 关闭则销毁
	全局名称空间
		py文件执行则创建 运行结束则销毁
	局部名称空间
		函数体代码运行创建 函数体代码结束则销毁(类暂且不考虑)

作用域
	内置名称空间
		解释器级别的全局有效!!  就是在任何一个地方都可以从内置名称空间拿到你想要的名字
	全局名称空间
		py文件级别的全局有效!!也就是说如果有多个.py文件,此时你无法从一个.py文件里面调用另一个.py文件里的变量名的。
	局部名称空间
		函数体代码内有效!!只要不在函数体代码内,都无法调用该变量名!!

image

名字的查找顺序

注意:涉及到名字的查找 一定要先搞明白自己在哪个空间!!!
比如不同名称空间下打印len,看len的值

1.当我们在局部名称空间中的时候
查找顺序为:局部名称空间 >>> 全局名称空间 >>> 内置名称空间

2.当我们在全局名称空间中的时候
查找顺序为:全局名称空间 >>> 内置名称空间

ps:其实名字的查找顺序是可以打破的 

在全局空间下打印len的结果,先在全局里面找,找不到在到内置里面找
image
.
在局部空间下打印len的结果,先在局部里面找到就直接打印了
image
.
在局部空间下打印len的结果,局部里面没找到,再到全局空间里面找,找到也就直接打印了
image
.
在全局空间里面打印len的结果,不会到局部里面找,先在全局里面找,找到就直接打印了。
image

image

查找顺序案例

1.相互独立的局部名称空间默认不能够互相访问!!!!
 def func1():
    name = 'jason'
    print(age)

def func2():
   age = 18
   print(name)
func1()
func2()
直接报错!!!!

image


2. 局部名称空间嵌套
先从自己的局部名称空间查找,之后由内而外依次查找!!!!!

x = '干饭了'
def func1():
    x = 1
    def func2():
        x = 2
        def func3():
            print(x)
            x = 3
        func3()
    func2()

func1()
"""

image
.
image

注意:函数体代码中名字的查找顺序在函数定义阶段就已经固定死了!!!
所以下面的代码报错!!函数体代码在在执行时也是从上到下执行,此时执行打印功能,但是此时func3的函数体代码中X还没有被定义,虽然下一行就定义X了,但是顺序不对,不会直接认为局部空间里面没有,再跳到外面去找,而是直接报错了。
image

但是这种把下面X=3这行代码删掉后,执行代码,这个时候X在函数体代码的局部空间没找到后,就会去它的外部空间找,就能找到X=2,这个代码就可以执行!!
image

注意:也就是说在嵌套的名称空间里面可能会同时存在一个变量名,每一个变量名绑定不一样的值!!比如下图中,你可以理解在电脑的内存里的不同空间里总共有4个X,每个X绑定的值都不一样!!!
image

运行代码可以从外往里看,首先看func1函数调用后,就会打印它对应的函数体里面对应的被赋值的X的值,然后以此类推往里看,func1()下面的print(X)也就是最后一行,可以理解为func1函数定义到调用整个代码看成一个整体,运行最下面一行print(X)时,与这一整块的func1函数定义与调用的代码是无关的,所以只会在局部空间里面找X的对应值'干饭了'
image
image

作业

1.整理内容及博客
	仔细整理!!!
    
2.判断下列money的值是多少并说明理由 思考如何修改而不是新增绑定关系
	money = 100
	def index():
        money = 666	
 	print(money)
    
   

	money = 100
 	def func1():
        money = 666
        def func2():
            money = 888
        func2()
   print(money)
   
3.预习明日内容
	闭包函数 装饰器
posted @   tengyifan  阅读(137)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
点击右上角即可分享
微信分享提示