函数的定义解构及销毁过程
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了函数的定义解构及销毁过程相关的知识,希望对你有一定的参考价值。
函数主要作用为了复用
函数中的return定义
函数中,所有的语句都是有retrun操作,如果函数没有自定义的return,则默认retrun None值
形参和实参
参数中,是一种形式的表示,一种符号的表达简称形参;而调用时所传达的参数列表为实实在在传入的值,简称为实参
def fn(xx): #形参
return xx
print(fn(‘hello‘)) #实参数
callable
使用callbale可以看其是否是可被调用对象
例:
def add(x,y):
return 1
print(callable(add))
True
函数参数
测定函数需要匹配定义个数相匹配(可变参数例外)
位置参数
调用使用:按照要求是按照顺序去定义传入实参
In [1]: def f(x,y):
...: print(x,y)
...:
In [2]: f(‘a‘,‘ccc‘)
a ccc
关键字参数
调用使用对应的形参明确指定要传入的参数
使用形式参数名字传入实参的方式,顺序是可以和定义的顺序不一样,因为是通过名称去定义
In [3]: f(y=‘python‘, x=‘hello‘)
hello python
关键字传参
In [5]: def f(x,y,z):
...: print(x,y,z)
调用
In [7]: f(z=None,y=10,x=[1])
返回值:
[1] 10 None
In [8]: f((1,),z=6,y=4.1)
返回值:
(1,) 4.1 6
位置参数必须在关键字参数之前传入,否则会被误认
In [13]: f((1,),z=6,1)
File "<ipython-input-13-452adbde2693>", line 1
f((1,),z=6,1)
^
SyntaxError: positional argument follows keyword argument
def add(x,y):
result = x + y
print(result)
add(6,7)
13
#将关键字参数先传进会提示报错
add(x=6,7)
add(x=6,7)
^
SyntaxError: positional argument follows keyword argument
总结:简单来讲,有默认值的参数,需要在后面进行调用
一般流程来讲,用户必须要填写的参数,直接定义为位置参数
例:
def add(x,y=9):
result = x + y
print(result)
add(6) #这样x就被定义为必须定义的对象
定义一个login函数,参数命名为host,port,username,password
def login(host=‘127.0.0.1‘,port=‘8080‘,username=‘chaoye‘,password=‘q1w2e3‘):
print(‘{}:{}:{}/{}‘.format(host,port,username,password))
login()
127.0.0.1:8080:chaoye/q1w2e3
login(‘192.168.0.1‘,9909,username=‘haha‘)
192.168.0.1:9909:haha/q1w2e3
login(‘192.168.0.1‘,9909,password=‘this‘)
192.168.0.1:9909:chaoye/thi
*name 可变参数
传递参数,可以匹配任意个(0个到n个)参数
def fn(*args):
print(type(args))
print(args)
fn(1,2,3,4,88)
<class ‘tuple‘>
(1, 2, 3, 4, 88)
fn()
<class ‘tuple‘> # 可变参数在封装过程中,将其以元组的方式进行传递
()
def fn(*nums):
sum = 0
for x in nums:
sum+=x
print(sum)
fn(10)
10
add(1,2,3,4,88) 这里定义表示可以接收多个参数,这里形参可以接受0个到多个实参
在传递的过程中,可变参数在封装过程中,将其以元组的方式进行传递,因为传递的同时已知其需要传递的个数,因为内部是不允许改变nums
*传递是一个不可变的类型(元组)
验证return
In [5]: def add(*nums):
...: sum = 0
...: for x in nums:
...: sum += x
...: print(sum)
...: #retrun sum
...:
In [6]: val = add(3,5,7)
15
In [8]: print(val)
None
这里没有定义return,则解释器直接调用隐含的return None进行返回
自定义return
In [12]: def add(*nums):
...: sum = 0
...: for x in nums:
...: sum += x
...: return(sum)
In [14]: add(0)
0
Out[14]: 0
**kwargs 可变关键字参数
**kwargs 将传递进来的值封装为字典类型,格式如下:
def showconfig(**name):
print(name)
showconfig(a=5,b=32,hj=90)
调用:
调用的时候需要对关键字进行指定值的赋予
{‘a‘: 5, ‘b‘: 32, ‘hj‘: 90}
例:
def showconfig(**kwagrs):
for k,v in kwagrs.items():
print(k,v)
showconfig(hostname=‘127.0.0.1‘,port=‘8080‘,username=‘chaoye‘,passwd=‘134qwe‘)
hostname 127.0.0.1
username chaoye
port 8080
passwd 134qwe
以上,传递的信息是未知的,可能是n个,所以需要使用可变类型去接收
我们需要确定这个值传递给那些kv,所以需要key=value 这样进行赋值
传递之后会形成一个字典,我们只需要对字典进行操作即可
混合使用
将关键字参数提取,比如username,passwd ,因为有些参数为用户必填,所以将其单独分离出来,其他全部给予默认值
有时我们会忘记关键字参数,那么可以只写敏感部分,比如监控端口,用户密码等
def showconfig(username,password,**kwagrs):
print(username)
print(password)
for k,v in kwagrs.items():
print(‘{} = {}‘.format(k,v))
showconfig(‘wangchao‘,‘1234‘,host=‘127.0.0.1‘)
wangchao
1234
host = 127.0.0.1
改进:
def showconfig(username,*args,**kwargs):
print(username)
print(args)
for k,v in kwargs.items():
print(k,v)
showconfig(‘chaoye‘)
定义时,第一个是必须需要定义的,其他都是可变类型,可变类型支持0到n个
定义位置的先后顺序
*args 一般需要定义在**kwargs 之前的位置,以复杂度进行排放
def showconfig(username,**kwargs,*args):
print(username)
print(args)
for k,v in kwargs.items():
print(k,v)
如果定义的顺序不当,则会直接告警
File "E:\python_project\a.py", line 3
def showconfig(username,**kwargs,*args):
^
SyntaxError: invalid syntax
[Finished in 0.1s with exit code 1]
总结:
·有位置可变的参数和关键可变的参数,*args和**kwargs
·位置可变参数和关键字可变参数(*args和**kwargs都可以收集若干个实例,位置可变参数(*args)收集实参封装为一个元组,关键字可变参数(**kwargs)则封装为一个字典
·混合使用参数的过程,可变参数要放到参数的列表最后,普通参数需要放到参数列表前,位置参数*args需放置**kwargs关键字参数前
即: 位置参数 --> 关键字参数 -->*args --> **kwargs
例:
以下语法没有问题
def fn(*args,x,y,**kwargs):
print(x)
print(y)
print(args)
print(kwargs)
首先对位置参数进行赋值
fn(1,2,3)
提示:
TypeError: fn() missing 2 required keyword-only arguments: ‘x‘ and ‘y‘
只对关键字参数进行赋值,但是没有对位置参数进行传递,所以此类语法无法识别
fn(3,5,a=1,b=‘py‘)
以下语句通过,首先对args进行了传递,x和y分别以关键字形式进行传递,b以kw的方式传递
fn(3,5,y=1,x=2,b=‘python‘)
2
1
(3, 5)
{‘b‘: ‘python‘}
关键字的传参方式依然是将x,y进行对应,原则上没有更变,只是区别当前的传参的方式
keyword-only 关键字参数形参(强制参数)
keyword-only 在python3种加入,如在一个星号形参后,或一个位置可变参数后,则是kw-only参数类型
def fn(*args,x):
print(x)
print(args)
fn(3,5,1,x=9)
args可以手机所有的位置参数,x不能使用关键字参数就不可能拿到实参
如果kw放在后面是kw-only参数,kw为可变kw,x也同样,所以区分不出来,本身kw就跟顺序无关,其本意都是收集到一个字典
*号特殊意义 后期详细看
加上*号之后,*号将后面所有参数都强行转为keyword-only参数
def fn(*,x,y):
print(x,y)
fn(x=1,y=2)
这样的定义在py3种的函数库大量存在
例:
def fn(z,*,x,y):
print(z)
print(x,y)
fn(9,x=1,y=3)
单星号之后的参数都是kw-only参数,需要指定绝对的缺省值
一般都会给一个默认值,但是必须提醒其存在
def fn(*args,x=1):
print(x)
print(args)
可变的本身就是0个,唯一需要确定的是在args星号后,不是普通参数,而是变身为kw-only类型,如果需要传参的话,至少传3个
def connect(host=‘localhost‘,port=3306,user=‘admin‘,**kwargs):
print(host,port)
print(user)
print(kwargs)
connect(db=‘cmdb‘)
localhost 3306
admin
{‘db‘: ‘cmdb‘}
connect(host=‘123‘,db=‘cmdb‘)
123 3306
admin
{‘db‘: ‘cmdb‘}
函数解构
例:
def add(x,y):
print(x+y)
return x+y
add((1,2)[0],(3,4)[1])
使用*号对数据结构进行解析
def add(x,y):
print(x+y)
return x+y
t = (3,4)
add(*t)
当前变量t为记忆参数,对应的是元组,但是需要将元组拆解
def add(x,y):
print(x,y)
t = [1,2]
add(*t)
1 2
这里必须将其对应参数位置,否则会报错,也就是说形参和实参数必须对应一致
t = [1,2,3,4]
add(*t)
add(*t)
TypeError: add() takes 2 positional arguments but 4 were given
使用可迭代对象
add(*range(1,3))
1 2
解构字典
如解构字典类型必须使用**kwargs的形参即可
def fn(**kwargs):
for k,v in kwargs.items():
print(k,v)
d = {‘a‘:1,‘b‘:2}
fn(**d)
在解构的过程,必须对应函数的位置参数
使用字典方法进行传参:这样就并非是字符串传参,而是直接将其付给形参
可以对其进行遍历或者取key或value进行解构
可变的类型:
def add(*iterable):
result = 0
for x in iterable:
result += x
return result
print(add(1,2,3))
print(add(*[1,2,3]))
add(*range(10))
函数返回值
return和print
return的作用是直接出栈,写在return之后会被认为是废语句
print 会作为隐含类型进行转换并输出
隐式调用
return = return None
def showlist():
return [1,2,3]
print(type(showlist()))
showlist返回的为一个值,这里为一个列表,但是列表中是存在元素的
def showlist():
return 1,2,3
print(type(showlist()))
当没有定义返回的数据类型,则默认将其封装为元组并进行返回
所以,在return的时候都是返回一个值,最后一个以元组方式进行封装并返回
函数嵌套
内部函数不能被外部直接使用,会直接抛NameError异常
如下
def outer():
def inner():
print(‘inner‘)
print(‘outer‘)
outer()
inner()
inner()
NameError: name ‘inner‘ is not defined
函数外是不可看到函数内部,也就是说在函数外是找不到内部定义的
作用域
函数作为一个标识符的可见范围,被称为作用域,一般来讲指的是变量
例:
def fn():
out = 123 #这个变量也算为一个标识符
print(‘outer‘)
fn()
在函数内定义的变量,在函数外是不可被调用,因为不可以超越函数
目前我们在全局定义变量,在函数内是可以的
x = 50
def show():
print(x)
show()
在函数中将变量进行更改
x = 50
def show():
x += 1
print(x)
show()
x += 1
UnboundLocalError: local variable ‘x‘ referenced before assignment
在函数本地变量中,不允许对全局变量进行操作
作用域
全局作用域
在整个程序中运行环境都可见
局部作用域
在函数内定义的变量,被称为本地变量,只限局部使用范围,其他范围不可以被调用
函数嵌套结构
def outer1():
o = 65
def inner():
print(‘inner‘,o)
print(‘outer‘,o)
inner()
outer1()
outer 65
inner 65
def outer2():
o = 65
def inner():
o = 97 #当进入嵌套函数中,o已经被重新定义为一个新的变量
print(‘inner‘,o) #其打印的变量为上行重新定义的值
print(‘outer‘,o)
inner()
outer2()
outer 65
inner 97
原因在于赋值的定义,在外部的变量o与inner函数中的变量o没有任何关系
外部对内部是可见的,但是内部对外部则为不可见,在动态语言中,赋值既定义 所以都是在局部作用域中生效
x = 5
def foo():
y = x + 1
x += 1
print(x)
foo()
y = x + 1
UnboundLocalError: local variable ‘x‘ referenced before assignment
在函数本地变量中,不允许对其进行操作,看似是y赋值时错误,但实际为x+=1的时候出现错误
以上信息为没有绑定变量本地错误
至于x += 1为何报错的解释如下:
因为在本地语句块定义的x,在等式中先进行右运算,右边有x存在,那么被认为
x没有被赋值的空标识符,所以都被认为是局部变量
全局变量global
通过global对其进行全局变量声明
z = 1
def xx():
global z
z += 21
print(z)
xx()
22
工作流程是先调用内部,再调用外部;在函数体内通过golbal 关键字声明变量;
将xx内的z使用外部的全局作用域中进行定义
赋值既定义的理念
回到之前的例子中,
x = 5
def foo():
y = x + 1
x += 1
print(x)
foo()
y = x + 1
UnboundLocalError: local variable ‘x‘ referenced before assignment
当x = 10,在x内部作用域作为一个外部作用域的变量赋值,所以在x+=1 的时候不会报错,这里的x作用域还是全局的x+=1,先引用后赋值
而python中,赋值才算真正的定义,才能被引用
解决办法:在语句前重新增加赋值语句,或者使用global告知内部作用域去全局作用域中定义这个变量
内部作用域中x = 1 之类的赋值语句会重新定义局部作用域使用的变量x,但是一旦声明全局global,声明x为全局的,那么x=1相当于在全局作用域的变量x赋值
例:
z = 1
def xx():
global z
z += 21
print(‘xx_z:‘,z)
xx()
print(‘global_z:‘,z)
xx_z: 22
global_z: 22
闭包
闭包在装饰器中使用比较广泛
闭包的概念
自由变量:
没有在本地作用域中定义变量,在内层函数外的定义函数的自由变量
并且在因曾函数引用到了外层函数的自由变量
def counter():
c = [0]
def inc():
c[0] += 1 #仅仅是对元素在赋值,与赋值变量是两码事
return c[0]
return inc # 是返回一个标识符(函数的引用),可调用的对象,inc本身就是可调用对象
foo = counter()
print([foo() for _ in range(10)])
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
查看函数的类型
def counter():
c = [0]
def inner():
c[0] += 1
return c[0]
return inner
foo = counter()
print(type(foo))
<class ‘function‘>
按照常理c会被赋值,但是现在为内部调用,所以在foo = counter()的时候,获取了引用变量,引用变量+1,inner不会被销毁,在return的时候直接return到inner()函数中进行了调用
c = 200
def counter():
c = [0]
def inner():
c[0] += 1
return c[0]
return inner
c = 200
foo = counter()
print(type(foo))
c = 200
print(foo())
print(foo())
<class ‘function‘>
1
2
内部函数使用了外部自由变量则产生了一个闭包
如果对外部的自由变量进行改变的话,在2版本中只能使用元素修改的方式
如下所示:
c = 200
def counter():
c = [0]
def inner():
c[0] += 1
print(c)
return c[0]
print(c[0])
return inner
foo = counter()
foo()
foo()
[0]
#以下为闭包所产生的值
1
2
3
总结:
闭包的概念:内部函数使用了“外部自由变量”的时候产生了一个所谓比高
nonlocal
python3使用了nonlocal方式进行闭包
将变量标记在上级的局部作用域中定义,但是不能在全局作用域中定义
python2 中和 python3种的闭包对比:
python3
def outer():
count = 0
def inner():
nonlocal count
count += 1
print(count)
return count
return inner
foo = outer()
foo()
foo()
1
2
在3中,使用nonlocal只能对其可以直接对上级变量进行操作,但是不能在全局中进行操作
python2中,仅能对自由变量进行操作
def outer():
c = [0]
def inner():
c[0] += 1
print(c)
return c
return inner
foo = outer()
foo()
foo()
默认值作用域 foo.__defaults__
涉及函数形参默认值
def foo(a=[]):
a.append(100)
print(a)
foo()
[100]
虽然是形式参数,但其也是局部变量
这个函数是一个对象,这个对象并非被销毁,说明对象存在,那么将其默认值a=[]存放在一个特殊属性上,也就是foo.__defaults__
In [2]: foo.__defaults__
Out[2]: ([],)
In [3]: foo()
[1]
In [4]: foo()
[1, 1]
在此查看默认值
In [10]: foo.__defaults__
Out[10]: ([1, 1],)
在函数外部调用并查看
In [11]: print(foo(),id(foo))
[1, 1, 1]
None 139733908903048
In [12]: print(foo.__defaults__)
([1, 1, 1],)
In [13]: print(foo(),id(foo))
[1, 1, 1, 1]
None 139733908903048
In [14]: print(foo.__defaults__)
([1, 1, 1, 1],)
In [15]: print(foo(),id(foo))
[1, 1, 1, 1, 1]
None 139733908903048
当传递进来lst之后,则是将引用对象传递,他们之间都是调用同一个id的,所以值是更改的
将更改的值return
def foo(a=None):
if a is None:
a = []
a.append(1)
return a
print(foo([1]))
print(foo())
[1, 1]
[1]
赋予函数None缺省值是一种惯例,一般形参上定义None说明接下来会传递某些参数进行一些处理性的操作
一般如果后期有定义或者传递的需求,建议将默认值写为None
总结:
1.使用浅拷贝创建一个新的对象,永远不能改变传入的参数
2.通过值判断,灵活的选择串讲或者修改传入的对象
方法很龙火,在很多场景下,函数定义都可以看到使用None,这个不可变的值作为默认值进行传参,
函数的销毁
1.del funcname
2.覆盖,查看地址是否一致
3.待到程序结束时
本文出自 “心情依旧” 博客,请务必保留此出处http://yijiu.blog.51cto.com/433846/1973113
以上是关于函数的定义解构及销毁过程的主要内容,如果未能解决你的问题,请参考以下文章