函数的定义、解构、及销毁过程

简介:

函数主要作用为了复用

函数中的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)

spacer.gif

可变的本身就是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进行解构

spacer.gif

spacer.gif

可变的类型:

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 

总结:

闭包的概念:内部函数使用了“外部自由变量”的时候产生了一个所谓比高

spacer.gif

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.待到程序结束时

spacer.gif



本文转自zuzhou 51CTO博客,原文链接:http://blog.51cto.com/yijiu/1973113

相关文章
|
12天前
|
编译器 C++
C++ 解引用与函数基础:内存地址、调用方法及声明
C++ 中的解引用允许通过指针访问变量值。使用 `*` 运算符可解引用指针并修改原始变量。注意确保指针有效且不为空,以防止程序崩溃。函数是封装代码的单元,用于执行特定任务。理解函数的声明、定义、参数和返回值是关键。函数重载允许同一名称但不同参数列表的函数存在。关注公众号 `Let us Coding` 获取更多内容。
135 1
|
7月前
|
Java
方法的传参过程
方法的传参过程
78 0
变量的生命周期
变量的生命周期
52 0
|
程序员 编译器 C++
函数的嵌套调用和链式访问
函数的嵌套调用和链式访问
|
编译器 C++
C++关于参数是对象时要调用拷贝构造函数进行传参的问题探讨
C++关于参数是对象时要调用拷贝构造函数进行传参的问题探讨
231 0
|
C语言
【C 语言】指针间接赋值 ( 直接赋值 和 间接赋值 | 在子函数中间接赋值 )
【C 语言】指针间接赋值 ( 直接赋值 和 间接赋值 | 在子函数中间接赋值 )
219 0
【C 语言】指针间接赋值 ( 直接赋值 和 间接赋值 | 在子函数中间接赋值 )
|
C++
C++ 用引用的方式向函数传递数组
C++ 用引用的方式向函数传递数组
123 0
C++ 用引用的方式向函数传递数组
|
C++
浅析C++的引用与const指针与各种传递方式
转自:https://www.jb51.net/article/120561.htm   首先我们知道 const int *p 与 int const *p 是一样的,即 *p 是常量; 而 int * const p 跟上面是不一样的,即 p 是常量; 我们知道引用只是一个别名,与变量共享存储空间,并且必须在定义的时候初始化,而且不能再成为别的变量的别名,这让我们想到什么呢,貌似跟  int * const p   的性质很像。
1114 0

热门文章

最新文章