函数

Python 中的函数是由若干语句组成的语句块、函数名称、参数列表构成,它是组织代码的最小单元,可以完成一定的功能

一.函数的作用

  • 结构化编程对代码的最基本的封装,一般按照功能组织一段代码
  • 封装的目的是为了复用,减少冗余代码
  • 代码更加简洁美观,可读易懂

二.函数的分类

  • 内建函数 : max(),reversed()
  • 库函数:math.ceil()

三.函数定义、调用

1.def关键字定义函数

def 函数名(形参列表):
    函数体(代码块)
    [return 返回值]
  • 函数名就是标识符,只能由英文,数字和下划线组成,不能以数字开头.
  • python函数使用return语句返回“返回值”
  • 所有函数都有返回值,如果没有return语句,隐式调用return None
  • return语句并不一定是函数的语句块的最后一条语句
  • 一个函数可以存在多个return 语句,但是只有一条可以被执行,如果没有return语句被执行,使用隐式调用return None
  • 如果有必要,可显示调用return None ,可以简写为return
  • 如果函数执行了return 语句,函数就会返回,当前被执行的return语句之后其他的语句就不会被执行
  • return语句的作用:结束函数调用,返回值
  • 函数不能同时返回多个值
  • 函数是可调用的对象 callable()

2.函数调用

  • 函数定义,只是声明了一个函数,它是不会被执行的,需要调用
  • 函数调用,需要在函数名后名加上()
  • 调用函数的时候在()中写入的参数是实际参数,简称实参。
  • 实参只有两种传参方式:按照形参位置顺序传参和关键字传参

关键字传参必须放在最后

3.函数形参

①位置参数
  • def fn(x,y) 可以使用 fn(1,2) 和fn(x=1,y=2)调用
  • 按照形参位置顺序传参,传参的顺序必须形参顺序和个数一致。
  • 关键字传入实参的时候,传参时的关键字必须和定义的形参名和个数一致,传参的顺序可以和形参顺序不一致。


    位置参数.gif
② 参数默认值(缺省值)

在定义形参的时候可以给形参赋予一个默认值.

def fn(x=3,y=2):
    print("x={},y={}".format(x,y))
  • 参数的默认值可以在未传入足够的实参的时候,对没有给定的参数赋值为默认值
  • 设置参数的缺省值,可以简化函数调用
③可变位置参数
  • 在位置参数前面使用“*” 表示该形参是可变参数,可以接受0到多个实参
  • 收集多个实参为一个元组tuple
  • 不接受关键字传参
  • 可变位置参数必须在位置参数后面
④keyword-only参数(python3 后加入)
  • 在可变位置参数后面,出现的形参
  • 在传参的时候一定要使用关键字传参
⑤ 可变关键字参数
  • 在形参签名使用“**”符号,表示可以接受0到多个关键字参数
  • 收集的实参的关键字和值组成一个字典dict,该字典在函数中是可以改变的
  • 如果形参中有keyword-only参数,一定要在该参数后面
⑥总结
  • 形参一般顺序是,位置参数,缺省位置参数,可变位置参数,keywork-only参数(可以带缺省值),可变关键字参数

函数参数解构

  • 给函数提供实参的时候,可以在可迭代对象中使用* 或者**,把集合类型的解构解开,提取出所有元素作为函数的实参
  • 非字典类型使用*解构成位置参数
  • 字典类型使用 ** 解构成关键字参数
  • 提出出来的元素树木要和参数的要求匹配,也要和参数的类型匹配
  • 参数解构只能用在函数中,print() 函数等
参数解构.gif

四. 函数嵌套

在一个函数中定义了另外一个函数,内部的函数不能在外部直接使用,会抛出NameError 异常。
函数有可见范围,这就是作用域的概念


函数嵌套.gif

1.作用域

  • 一个标识符的可见范围,这就是标识符的作用域。一般常说的是变量的作用域。
    作用域分为:全局作用域,和局部作用域
  • 全局作用域
    在整个程序运行环境中可见
  • 局部作用域
    在函数,类等内部可见,局部变量使用范围不能超过其所在的局部作用域
def outer2():
    o = 65
    def inner():
        o = 97
        print("inner {}".format(o))
    print("outer {}".format(o))
    inner()
outer2()

#结果
#outer 65
#inner 97

通过上述嵌套结构例子可以看出

  • 外层变量作用域在内层作用域可见
  • 内层作用域inner中,如果定义了o = 97,相当于当前作用域中重新定义了一个新的变量o,但是这个o并没有 覆盖外层作用域outer中的o。
①.全局变量globle
x = 5
def foo():
##如果不使用global,会出现先引用后赋值的异常报错
    global x 
    x += 1 
    print("x =",x)
foo()
print("x =",x)
###结果
#x = 6
#x = 6
  • 使用global 关键字的变量,将函数内的x声明为外部的全局作用域定义的x
  • 全局作用作用域中必须要有x的定义
  • 使用global可以告诉内部作用域,去全局作用域查找变量的定义,之后在函数内对x的所有操作,都相当于在为全局作用域的变量x操作赋值
x = 5
def foo():
    global x
    x += 1
    print("x =",x)
    x = 9
foo()
print("x =",x)

##结果
#x = 6
#x = 9

globle 使用原则

  • 外部作用域变量会内部作用域可见,但也不要在这个内部的局部作用域中直接使用,因为函数的目的就是为了封装,尽量与外界隔离
  • 如果函数需要使用外部全局变量,请使用函数的形参传参解决

不要用global,学习它,只是为了深入理解变量作用域!!!

对“作用域”可以进行如下理解:
1.在最顶层,比如shell层,有一个符号表会跟踪记录这一层所有的名称定义和绑定
2.调用函数的时候,会建立一个新的符号表(常称为栈帧)。这个表跟踪记录函数中所有的名称定义(包括形参)和它们当前的绑定。如果函数体内又调用了一个函数,就再建立一个栈帧。
3.函数结束时候,它的栈帧也随之消息。

2.闭包

  • 自由变量:未在本地作用域定义的变量。
    例如:定义在内层函数外的外层函数的作用域中的变量
  • 闭包:就是一个概念,出现在嵌套函数中,指的是内层函数引用到了外层函数的自由变量,这就形成了闭包.
def counter():
    c = [0]
    def inc():
        c[0] += 1  #应用的是自由变量正式counter的变量c
        return c[0]
    print(c[0])
    return inc
  
#counter()()
foo = counter()
print(foo(),foo())   #调用的是inner()
print(foo())

这是python2 实现闭包的方式,python3 还可以使用nonlocal 关键字

①nonlocal 关键字

使用nonlocal 关键字,将变量标记为不再本地作用域定义,而在上级的某一级局部作用域中定义,但不能是全局作用域中的定义

def counter():
    count = 0
    def inc():
        nonlocal count
        count += 1
        return count
    return inc
foo = counter()
print(foo())
print(foo())
  • count 是外层函数的局部变量,被内部函数引用
  • 内部函数使用nonlocal 关键字声明count变量在上级作用域而非 本地作用域中定义
  • 形成了闭包
②默认值的作用域
def foo(xyz=[]):
    xyz.append(1)
    print(xyz)
foo()
foo()

###result
#[1]
#[1, 1]
  • 函数也是对象,python 把函数的默认值放在了属性中,这个属性就伴随着这个函数对象的整个生命周期
  • 查看foo.__defaults__属性 ([1, 1],)
def foo(xyz=[],u='abc',z=123):
   xyz.append(1)
   return xyz
print(foo(),id(foo))
print(foo.__defaults__)
print(foo(),id(foo))
print(foo.__defaults__)
  • 函数地址并没有变,就是说函数这个对象没有变,调用它,它的属性__defaults__ 中使用元组保存默认值
  • xyz 默认值是引用类型,引用类型的元素变动,并不是元组的变化

非引用类型例子

def foo(w,u='abc',z=123):
    u = 'xyz'
    z = 789
    print(w,u,z)
print(foo.__defaults__)
foo('wing')
print(foo.__defaults__)

属性__defaults__中使用元组保存所有位置参数默认值,它不会因为在函数体内使用了它而发生改变

def foo(w,u='abc',*,z=123,zz=[456]):
    u = 'xyz'
    z = 789
    zz.append(1)
    print(w,u,z,zz)
print(foo.__defaults__)
foo('wing')
print(foo.__kwdefaults__)
  • 属性__defaults__ 中使用元组保存所有位置参数默认值
  • 属性__kwdefaults__ 中使用字典保存所有keyword-only 参数的默认值
  • 使用可变类型作为形参默认值的时候,就可能修改这个默认值

1.函数体内,不改变默认值

def foo(xyz=[],u='abc',z=123):
    xyz = xyz[:]     #影子拷贝
    xyz.append(1)
    print(xyz)
print(foo.__defaults__)
foo()
print(foo.__defaults__)
foo([10])    
foo()
print(foo.__defaults__)
foo([10,5])
print(foo.__defaults__)
  • 函数中的xyz 都是传入参数或者默认参数的副本,如果就想修改原参数,不可以

2.使用不可变类型的默认值

def foo(xyz=None,u='abc',z=123):
    if xyz is None:
        xyz = []
    xyz.append(1)
    print(xyz)
print(foo.__defaults__)
foo()
print(foo.__defaults__)
foo([10])    
print(foo.__defaults__)
foo([10,5])
print(foo.__defaults__)
  • 如果使用缺省值None就创建一个列表
  • 如果传入一个列表,就修改这个列表

3.总结

  • 第一种方法
    使用影子拷贝,创建了一个新的对象,永远不能改变传入的参数。
  • 第二种方法
    通过值的判断就可以灵活的选择创建或者修改传入的参数,这种方式灵活,应用广泛。
    很多函数的定义,都可以看到使用None这个不可变的值作为默认参数,这可以说是一种惯用法

四.变量名解析原则LEGB

  • Local,本地作用域,局部作用域的local命名空间。函数调用时创建,调用结束消亡
  • Enclosing,Python2.2时引入的嵌套函数,实现了闭包,这个就是嵌套函数的外部函数的命名空间
  • Global 全局作用域,即一个模块的命名空间。模块被import时创建,解释器退出时消亡
  • Build-in,内置模块的命名空间,生命周期从python解析器启动的时创建到解释器退出时消亡。例如print(open),print 和open 都是内置的变量
  • 所以一个名词的查找顺序就是LEGB

五.函数的销毁

全局函数销毁

  • 重新定义同名函数
  • del语句删除函数对象
  • 程序结束时
  • 局部函数
局部函数销毁
  • 重新在上级作用域定义同名函数
  • del语句删除函数名称,函数对象的引用计数减1
  • 上级作用域销毁时

五.文档字符串:

""" text """
三引号之间的文本在Python中称为文档字符串。按照惯例,使用文档字符串提供函数的规范。可以使用内置的函数help访问这些字符串。

文档字符串.gif

六.递归

2.定义

** 函数直接或者间接调用自身就是递归**

  • 递归需要边界条件,递归前进段,递归返回段
  • 递归一定要有边界条件
  • 当边界条件不满足的时候,递归前进
  • 当边界条件满足的时候,递归返回

2.递归要求

  • 递归一定要有退出条件,递归调用一定要执行到这个退出条件。没有退出条件的递归调用,就是无限调用
  • 递归深度不宜过深
    可以通过sys.getrecursionlimit() 查看解释器的深度限制

3.递归的性能

  • 循环稍微复杂一些,但是只要不是死循环,可以多次迭代直至算出结果。
  • 递归有深度限制,如果递归复杂,函数反复亚栈,栈内存很快就溢出了。
  • 递归中可以通过形参记录每次递归的计算,减少递归次数

4.间接递归:

def foo1():
    foo2()
def foo2():
    foo1()
foo1()

间接递归,是通过别的函数调用了函数自身。
但是,如果构成了循环递归抵用是非常危险的,但是往往这种情况在代码复杂的情况下,还是可能发生这种调用。要用代码的规范来避免这种递归调用的发生。

5.递归总结

  • 递归是一种很自然的表达,符合逻辑思维
  • 递归相对运行效率低,每一次调用函数都要开辟栈帧
  • 递归有深度限制,如果递归层次太深,函数反复压栈,栈内存很快就溢出了
  • 如果是有限次数的递归,可以使用递归调用,或者使用循环代替,循环代码稍微复杂一点,但是只要不是死循环,可以多次迭代直至算出结果
  • 绝大多数递归,都可以使用循环实现
  • 即使递归代码间接,不建议使用递归

七.匿名函数

  • 没有函数名
  • 借助lambda 表达式构建匿名函数
  • 格式:lambda 参数列表:表达式
  • 使用lambda 关键字来定义匿名函数
  • 参数列表不需要小括号
  • 冒号是用来分割参数列表和表达式的
  • 不需要使用return ,表达式的值,就是匿名函数返回值
  • lambda表达式(匿名函数) 只能写在一行上,被称为单行函数
  • 用途:在高阶函数传参时,使用lamdba表达式,往往能简化代码。
print((lambda :0)())
print((lambda x,y=3:x+y)(5))
print((lambda x, y=3: x + y)(5, 6))
print((lambda x, *, y=30: x + y)(5))
print((lambda x, *, y=30: x + y)(5, y=10))
print((lambda *args: (x for x in args))(*range(5)))
print((lambda *args: [x+1 for x in args])(*range(5)))
print((lambda *args: {x+2 for x in args})(*range(5)))
[x for x in (lambda *args: map(lambda x: x+1, args))(*range(5))] 
[x for x in (lambda *args: map(lambda x: (x+1,args), args))(*range(5))]
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,710评论 4 376
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,839评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,295评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,776评论 0 223
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,198评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,074评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,200评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,986评论 0 214
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,733评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,877评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,348评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,675评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,393评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,209评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,212评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,003评论 2 280

推荐阅读更多精彩内容

  • 函数和对象 1、函数 1.1 函数概述 函数对于任何一门语言来说都是核心的概念。通过函数可以封装任意多条语句,而且...
    道无虚阅读 4,354评论 0 5
  • 第5章 函数和函数式编程 5.1 引言函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。函数...
    VIVAFT阅读 900评论 0 5
  • 函数只定义一次,但可能被执行或调用任意次。JS函数是参数化的,函数的定义会包括一个称为形参的标识符列表,这些参数在...
    PySong阅读 285评论 0 0
  • 周五大盘再现普跌格局。下跌个股2448只,上涨个股仅有751只,前期还领涨的板块,如乡村振兴、通信网络、券商、保险...
    d719f6b05f08阅读 221评论 0 0
  • 妈妈是温暖的港湾,她敞开的怀抱和接纳的双臂是孩子安全感最重要的来源! 爸爸是力量的源泉,他与孩子的连接和支持是建构...
    刘泸阅读 1,820评论 0 1