我们目前为止,已经可以完成一些软件的基本功能了,现在我们自己来实现一个len,但是不能使用len
a = "sadsfsfg"
count = 0
for i in a:
count += 1
print(count)
我们现在实现了一个求长度,我还想让你们求一下列表和元组的长度 是不是就要将我们写的内容再次拿过来
我们在求一个字典的长度,也需要将我们写好的内容拿过来使用 好像程序中好多都是一样的,所以可以将它封装起来,多次重复使用
def 是python中关键字主要用来定义函数的
len这个是函数的名字
()这个括号是个神奇的东西,用来定义参数
: 冒号是表示咱们这个语句写完了
函数体就是有4个空格的缩进
def len():
a = "sadassb"
count = 0
for i in a:
count += 1
print(count)
我们来看一下函数的定义在内存空间发生了什么:
内存开辟了一个空间,但是里边存放是代码.
这样我就将咱们写的代码封装起来了,我们现在执行一下程序看看它会不会进行求长度,它并没有执行,我们来看看怎么能够执行上呢?
使用函数名加小括号就可以调用了 写法:函数名() 这个时候函数的函数体会被执行
def len():
a = "alexdsb"
count = 0
for i in a:
count += 1
print(count)
len() # 函数的调用
当我们调用执行的时候,才会执行func这个空间里的代码,执行的时候在开辟空间,这次是在func里边开辟的空间
现在就实现了求一次长度,我想要多求几次我就只需要
len()
len()
len()
直接调用定义好的函数就可以了
当我们执行完函数后,函数里开辟的空间就销毁了,我们如果想用函数里的值就需要从函数中传递出来
参数,也就是函数括号里的内容 函数在调用的时候指定一个具体的变量的值 就是参数.写法如下:
def 函数名(参数):
函数体
我们应该把代码改一下,能够实现我刚说的那种效果
def yue(app):
print("拿出⼿手机")
print("打开"+app)
print("搜一下宋冬野")
print("戴上耳机听一听")
yue("网易云")
yue("酷我")
yue("酷狗")
搞定了. 我们在调用yue的时候给app一个值. 然后再执行函数体.
如果我们再定函数的时候写了形参,在调用函数的时候没有传递值,调用的时候右边括号会发黄,所以我们必须要传递参数,参数要一一对应,不能多不能少.
1.形参
写在函数声明的位置的变量叫形参,形式上的一个完整.表示这个函数需要xxx
2.实参
在函数调用的时候给函数传递的值.加实参,实际执行的时候给函数传递的信息.表示给函数xxx
3.传参
从调用函数的时候将值传递到定义函数的过程叫做传参
def yue(app): # app 形参
print("拿出⼿手机")
print("打开"+app)
print("搜一下宋冬野")
print("戴上耳机听一听")
yue("酷我") # "酷我"在这里就是实参
len("酷狗") # "字符串"在这里就是实参
print("网易云") # "麻花藤"就是实参
def yue(chat,addr,age): # chat 形参
print("拿出手机")
print("打开"+chat)
print("找个" + addr +"附近漂亮的" + str(age) + "岁妹子")
print("约不约")
yue("陌陌","北京",18) # 实参
结果:
拿出手机
打开陌陌
找个北京附近漂亮的18岁妹子
约不约
上述代码分析: 在访问yue()的时候,我们按照位置的顺序分别把"陌陌","北京",18赋值给了chat,addr,age,在传参过程中.系统会按照位置把实参赋值到形参.
形参就是一个变量名,实参就是值 传参就是在赋值
def func(addr,age):
addr = "北京"
age = 18 # 从实参到形参的过程中,函数体内部帮我做了变量的赋值
print(addr)
print(age)
func("北京",18)
练习
编写函数,给函数传递两个参数a,b a,b相加 返回a参数和b参数相加的和
def f(a,b):
c = a+b
return c
num_sum = f(5,8)
print(num_sum)
结果: 13
编写函数,给函数传递两个参数a,b 比较a,b的大小 返回a,b中最大的那个数
def f(a,b):
if a>b:
return a
else:
return b
num_sum = f(5,8)
print(num_sum)
结果:8
比较大小的这个写法有点麻烦,我们在这里学一个三元运算符
def f(a,b):
c = a if a > b else b #当a>b就把a赋值给c,否则就把b赋值给c
return c
msg = f(5,7)
print(msg)
结果:
7
位置参数好不好呢? 如果是少量的参数还算OK, 没有问题. 但是如果函数在定义的时候参数非常多怎么办? 程序员必须记住, 我有哪些参数, 而且还有记住每个参数的位置, 否则函数就不能正常调用了. 那则么办呢? python提出了一种叫做关键字参数. 我们不需要记住每个参数的位置. 只要记住每个参数的名字就可以了
def yue(chat, address, age):
print("拿出手机")
print("打开"+chat)
print("找个"+address+"附近漂亮的"+str(age)+"岁妹子")
print("约不约")
yue(chat="微信", age=18, address="北京") # 关键字参数.
结果:
拿出手机
打开微信
找个北京附近漂亮的18岁妹子
约不约
搞定, 这样就不需要记住繁琐的参数位置了.
可以把上面两种参数混合着使用. 也就是说在调用函数的时候即可以给出位置参数, 也可以指定关键字参数.
# 混合参数
yue("微信", age=18, address="上海") # 正确.第一个位置赋值给chat, 后面的参数开始指定关键字.
yue(age="18", "微信", address="广州") # 错误, 最开始使用了关键字参数, 那么后面的 微信的位置就串了, 容易出现混乱
注意: 在使用混合参数的时候, 关键字参数必须在位置参数后面
综上: 在实参的⾓角度来看. 分为三种:
位置参数:
位置参数,按照位置来赋值,到目前为止,我们编写的函数都是这种
def yue(chat, address, age):
print("拿出手机")
print("打开"+chat)
print("找个"+address+"附近漂亮的"+str(age)+"岁妹子")
print("约不约")
默认值参数:
在函数声明的时候, 就可以给出函数参数的默认值. 在调用的时候可以 给出具体的值, 也可以不给值, 使⽤用默认值. 比如, 我们录入咱们班学生的基本信息. 通过调查发现. 我们班大部分学生都是男生. 这个时 候就可以给出⼀一个sex='男'的默认值.
def stu_info(name, age, sex='男'):
print("录入学生信息")
print(name, age, sex)
print("录入完毕")
stu_info("张强", 18)
注意:必须先声明在位置参数,才能声明关键字参数
形参: 函数的定义中括号里是形参
实参: 函数的调用括号里是实参
位置传参时 形参和实参必须一一对应
传参: 将实参传递给形参的过程就是传参
函数的参数:
# 形参: 函数定义的时候叫做形参
# 位置参数
# 默认参数
# 混合参数
# 实参: 函数调用的时候叫做实参
# 位置参数
# 关键字参数
# 混合参数
# 传参: 将实参传递给形参的过程叫做传参
动态参数分为两种:动态接受位置参数 *args,动态接收关键字参数**kwargs.
动态接收位置参数:*args
我们按照上面的例子继续写,如果我请你吃的内容很多,但是我又不想用多个参数接收,那么我就可以使用动态参数*args
def eat(*args):
print('我请你吃:',args)
eat('蒸羊羔儿','蒸熊掌','蒸鹿尾儿','烧花鸭','烧雏鸡','烧子鹅')
# 运行结果:
#我请你吃: ('蒸羊羔儿', '蒸熊掌', '蒸鹿尾儿', '烧花鸭', '烧雏鸡', '烧子鹅')
解释一下上面参数的意义:首先来说args,args就是一个普通的形参,但是如果你在args前面加一个,那么就拥有了特殊的意义:在python中除了表示乘号,他是有魔法的。+args,这样设置形参,那么这个形参会将实参所有的位置参数接收,放置在一个元组中,并将这个元组赋值给args这个形参,这里起到魔法效果的是 而不是args,a也可以达到刚才效果,但是我们PEP8规范中规定就使用args,约定俗成的。
练习:传入函数中数量不定的int型数据,函数计算所有数的和并返回。
def my_max(*args):
n = 0
for i in args:
n += i
return n
动态接收关键字参数: kwargs**
实参角度有位置参数和关键字参数两种,python中既然有*args可以接受所有的位置参数那么肯定也有一种参数接受所有的关键字参数,那么这个就是kwargs,同理这个是具有魔法用法的,kwargs约定俗成使用作为形参。举例说明:**kwargs,是接受所有的关键字参数然后将其转换成一个字典赋值给kwargs这个形参。
def func(**kwargs):
print(kwargs) # {'name': '太白金星', 'sex': '男'}
func(name='太白金星',sex='男')
我们看一下动态参数的完成写法:
def func(*args,**kwargs):
print(args) # ('蒸羊羔儿', '蒸熊掌', '蒸鹿尾儿')
print(kwargs) # {'name': '太白金星', 'sex': '男'}
func('蒸羊羔儿', '蒸熊掌', '蒸鹿尾儿',name='太白金星',sex='男')
如果一个参数设置了动态参数,那么他可以接受所有的位置参数,以及关键字参数,这样就会大大提升函数拓展性,针对于实参参数较多的情况下,解决了一一对应的麻烦。
刚才我们研究了动态参数,其实有的同学对于魔法用法 * 比较感兴趣,那么那的魔性用法不止这么一点用法,我们继续研究:
函数中分为打散和聚合。
函数外可以处理剩余的元素。
函数的打散和聚合
刚才我们研究了,在函数定义时,如果我只定义了一个形参称为args,那么这一个形参只能接受几个实参? 是不是只能当做一个位置参数对待?它只能接受一个参数:
def eat(args):
print('我请你吃:',args) # 我请你吃: 蒸羊羔儿
eat('蒸羊羔儿')
但是如果我给其前面加一个* 那么args可以接受多个实参,并且返回一个元组,对吧? (*kwargs也是同理将多个关键字参数转化成一个字典返回)所以在函数的定义时: 起到的是聚合的作用。
此时不着急给大家讲这个打散,而是出一个小题:你如何将三个数据(这三个数据都是可迭代对象类型)s1 = 'alex',l1 = [1, 2, 3, 4], tu1 = ('武sir', '太白', '女神',)的每一元素传给动态参数*args?(就是args最终得到的是 ('a','l','e','x', 1, 2, 3, 4,'武sir', '太白', '女神',)?有人说这还不简单么?我直接传给他们不就行了?
s1 = 'alex'
l1 = [1, 2, 3, 4]
tu1 = ('武sir', '太白', '女神',)
def func(*args):
print(args) # ('alex', [1, 2, 3, 4], ('武sir', '太白', '女神'))
func(s1,l1,tu1)
这样肯定是不行,他会将这个三个数据类型当成三个位置参数传给args,没有实现我的要求。
好像你除了直接写,没有别的什么办法,那么这里就得用到我们的魔法用法 :*
s1 = 'alex'
l1 = [1, 2, 3, 4]
tu1 = ('武sir', '太白', '女神',)
def func(*args):
print(args) # ('a', 'l', 'e', 'x', 1, 2, 3, 4, '武sir', '太白', '女神')
func(*s1,*l1,*tu1)
你看此时是函数的执行时,我将你位置参数的实参(可迭代类型)前面加上,相当于将这些实参给拆解成一个一个的组成元素当成位置参数,然后传给args,这时候这个好像取到的是打散的作用。所以在函数的执行时:,**起到的是打散的作用。
dic1 = {'name': '太白', 'age': 18}
dic2 = {'hobby': '喝茶', 'sex': '男'}
def func(**kwargs):
print(kwargs) # {'name': '太白', 'age': 18, 'hobby': '喝茶', 'sex': '男'}
func(**dic1,**dic2)
*处理剩下的元素
*除了在函数中可以这样打散,聚合外,函数外还可以灵活的运用:
# 之前讲过的分别赋值
a,b = (1,2)
print(a, b) # 1 2
# 其实还可以这么用:
a,*b = (1, 2, 3, 4,)
print(a, b) # 1 [2, 3, 4]
*rest,a,b = range(5)
print(rest, a, b) # [0, 1, 2] 3 4
print([1, 2, *[3, 4, 5]]) # [1, 2, 3, 4, 5]
到目前为止,从形参的角度我们讲了位置参数,默认值参数,动态参数*args,**kwargs,还差一种参数,需要讲完形参顺序之后,引出。先不着急,我们先看看已经讲的这些形参他的排列顺序是如何的呢?
首先,位置参数,与默认参数他两个的顺序我们昨天已经确定了,位置参数必须在前面,即 :位置参数,默认参数。
那么动态参数*args,**kwargs放在哪里呢?
动态参数*args,肯定不能放在位置参数前面,这样我的位置参数的参数就接收不到具体的实参了:
# 这样位置参数a,b始终接收不到实参了,因为args全部接受完了
def func(*args,a,b,sex='男'):
print(args)
print(a,b)
func(1, 2, 3, 4, 5)
那么动态参数必须在位置参数后面,他可以在默认参数后面么?
# 这样也不行,我的实参的第三个参数始终都会将sex覆盖掉,这样失去了默认参数的意义。
def func(a,b,sex='男',*args,):
print(args) # (4, 5)
print(sex) # 3
print(a,b) # 1 2
func(1, 2, 3, 4, 5)
所以*args一定要在位置参数与默认值参数中间:位置参数,*args,默认参数。
那么我的kwargs放在哪里?kwargs可以放在默认参数前面么?
# 直接报错:因为**kwargs是接受所有的关键字参数,如果你想改变默认参数sex,你永远也改变不了,因为
# 它会先被**kwargs接受。
def func(a,b,*args,**kwargs,sex='男',):
print(args) # (4, 5)
print(sex) # 3
print(a,b) # 1 2
print(kwargs)
func(1, 2, 3, 4, 5)
所以截止到此:所有形参的顺序为:位置参数,*args,默认参数,**kwargs。
仅限关键字参数是python3x更新的新特性,他的位置要放在*args后面,kwargs前面(如果有kwargs),也就是默认参数的位置,它与默认参数的前后顺序无所谓,它只接受关键字传的参数:
# 这样传参是错误的,因为仅限关键字参数c只接受关键字参数
def func(a,b,*args,c):
print(a,b) # 1 2
print(args) # (4, 5)
# func(1, 2, 3, 4, 5)
# 这样就正确了:
def func(a,b,*args,c):
print(a,b) # 1 2
print(args) # (3, 4)
print(5)
func(1, 2, 3, 4, c=5)
这个仅限关键字参数从名字定义就可以看出他只能通过关键字参数传参,其实可以把它当成不设置默认值的默认参数而且必须要传参数,不传就报错。
所以形参角度的所有形参的最终顺序为:*位置参数,args,默认参数,仅限关键字参数,kwargs。
课间考一道题:
def foo(a,b,*args,c,sex=None,**kwargs):
print(a,b)
print(c)
print(sex)
print(args)
print(kwargs)
# foo(1,2,3,4,c=6)
# foo(1,2,sex='男',name='alex',hobby='old_woman')
# foo(1,2,3,4,name='alex',sex='男')
# foo(1,2,c=18)
# foo(2, 3, [1, 2, 3],c=13,hobby='喝茶')
# foo(*[1, 2, 3, 4],**{'name':'太白','c':12,'sex':'女'})
接下来我们讲的内容,理论性的偏多,就是从空间角度,内存级别去研究python。首先我们看看什么是全局名称空间:
在python解释器开始执行之后, 就会在内存中开辟一个空间, 每当遇到一个变量的时候, 就把变量名和值之间的关系记录下来, 但是当遇到函数定义的时候, 解释器只是把函数名读入内存, 表示这个函数存在了, 至于函数内部的变量和逻辑, 解释器是不关心的. 也就是说一开始的时候函数只是加载进来, 仅此而已, 只有当函数被调用和访问的时候, 解释器才会根据函数内部声明的变量来进行开辟变量的内部空间. 随着函数执行完毕, 这些函数内部变量占用的空间也会随着函数执行完毕而被清空.
我们首先回忆一下Python代码运行的时候遇到函数是怎么做的,从Python解释器开始执行之后,就在内存中开辟里一个空间,每当遇到一个变量的时候,就把变量名和值之间对应的关系记录下来,但是当遇到函数定义的时候,解释器只是象征性的将函数名读如内存,表示知道这个函数存在了,至于函数内部的变量和逻辑,解释器根本不关心。
等执行到函数调用的时候,Python解释器会再开辟一块内存来储存这个函数里面的内容,这个时候,才关注函数里面有哪些变量,而函数中的变量会储存在新开辟出来的内存中,函数中的变量只能在函数内部使用,并且会随着函数执行完毕,这块内存中的所有内容也会被清空。
我们给这个‘存放名字与值的关系’的空间起了一个名字-------命名空间。
代码在运行伊始,创建的存储“变量名与值的关系”的空间叫做全局命名空间;
在函数的运行中开辟的临时的空间叫做局部命名空间也叫做临时名称空间。
现在我们知道了,py文件中,存放变量与值的关系的一个空间叫做全局名称空间,而当执行一个函数时,内存中会临时开辟一个空间,临时存放函数中的变量与值的关系,这个叫做临时名称空间,或者局部名称空间。
其实python还有一个空间叫做内置名称空间:内置名称空间存放的就是一些内置函数等拿来即用的特殊的变量:input,print,list等等,所以,我们通过画图捋一下:
那么这就是python中经常提到的三个空间。
总结:
\1. 全局命名空间--> 我们直接在py文件中, 函数外声明的变量都属于全局命名空间
\2. 局部命名空间--> 在函数中声明的变量会放在局部命名空间
\3. 内置命名空间--> 存放python解释器为我们提供的名字, list, tuple, str, int这些都是内置命名空间
所谓的加载顺序,就是这三个空间加载到内存的先后顺序,也就是这个三个空间在内存中创建的先后顺序,你想想他们能是同时创建么?肯定不是的,那么谁先谁后呢?我们捋顺一下:在启动python解释器之后,即使没有创建任何的变量或者函数,还是会有一些函数直接可以用的比如abs(-1),max(1,3)等等,在启动Python解释器的时候,就已经导入到内存当中供我们使用,所以肯定是先加载内置名称空间,然后就开始从文件的最上面向下一行一行执行,此时如果遇到了初始化变量,就会创建全局名称空间,将这些对应关系存放进去,然后遇到了函数执行时,在内存中临时开辟一个空间,加载函数中的一些变量等等。所以这三个空间的加载顺序为:内置命名空间(程序运行伊始加载)->全局命名空间(程序运行中:从上到下加载)->局部命名空间(程序运行中:调用时才加载。
取值顺序就是引用一个变量,先从哪一个空间开始引用。这个有一个关键点:从哪个空间开始引用这个变量。我们分别举例说明:
# 如果你在全局名称空间引用一个变量,先从全局名称空间引用,全局名# 称空间如果没有,才会向内置名称空间引用。
input = 666
print(input) # 666
# 如果你在局部名称空间引用一个变量,先从局部名称空间引用,
# 局部名称空间如果没有,才会向全局名称空间引用,全局名称空间在没有,就会向内置名称空间引用。
input = 666
print(input) # 666
input = 666
def func():
input = 111
print(input) # 111
func()
所以空间的取值顺序与加载顺序是相反的,取值顺序满足的就近原则,从小范围到大范围一层一层的逐步引用。
作用域就是作用范围, 按照生效范围来看分为全局作用域和局部作用域
全局作用域: 包含内置命名空间和全局命名空间. 在整个文件的任何位置都可以使用(遵循 从上到下逐⾏执行).
局部作用域: 在函数内部可以使用.
作⽤域命名空间:
1. 全局作用域: 全局命名空间 + 内置命名空间
2. 局部作⽤域: 局部命名空间
这两个内置函数放在这里讲是在合适不过的,他们就直接可以反映作用域的内容,有助于我们理解作用域的范围。
globals(): 以字典的形式返回全局作用域所有的变量对应关系。
locals(): 以字典的形式返回当前作用域的变量的对应关系。
这里一个是全局作用域,一个是当前作用域,一定要分清楚,接下来,我们用代码验证:
# 在全局作用域下打印,则他们获取的都是全局作用域的所有的内容。
a = 2
b = 3
print(globals())
print(locals())
'''
{'__name__': '__main__', '__doc__': None, '__package__': None,
'__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000001806E50C0B8>,
'__spec__': None, '__annotations__': {},
'__builtins__': <module 'builtins' (built-in)>,
'__file__': 'D:/lnh.python/py project/teaching_show/day09~day15/function.py',
'__cached__': None, 'a': 2, 'b': 3}
'''
# 在局部作用域中打印。
a = 2
b = 3
def foo():
c = 3
print(globals()) # 和上面一样,还是全局作用域的内容
print(locals()) # {'c': 3}
foo()
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。