函数不可以直接调用其他函数内部定义的变量_python基础 | 函数

本文介绍了Python函数的基础知识,包括函数定义、形参与实参的概念、关键字参数和默认参数。重点讨论了全局变量与局部变量的区别,解释了在函数内部如何通过`global`和`nonlocal`关键字操作全局变量。此外,还讲解了内部函数的限制,特别是闭包、函数嵌套和递归等概念,以及lambda表达式、迭代器、生成器和装饰器的使用。
摘要由CSDN通过智能技术生成
254266044a7dc8325229679269b22536.png

f40ed4061e885365a11e66d5487282d2.png

ceb2ca15f158ef89c1572d0cf544ebff.png

图|一张顾巷

文|一张顾巷

函数定义

函数是组织好的,可 重复使用 的,用来实现单一或相关联功能的代码段,我们将这样的代码块提取出来,封装成一个函数。例如,获取当前日期前n天的日期:
def get_current_n_day(n):    """    获取当天日期的前n天的日期    :param n:int型    :return:日期字符串    """    return (datetime.date.today() + datetime.timedelta(days=-n)).strftime("%Y-%m-%d")    if __name__ == "__main__":    print(get_current_n_day(365))  # 输出结果 2019-12-10
从上述例子中可以发现函数定义的潜规则,如下:
  • 函数定义格式def 函数名(参数名)

  • 传入多个参数时,英文逗号隔开

  • return指定返回值,不指定默认返回None

  • 一个小tip:定义函数时,建议一定要写备注,备注中包含每个参数的含义、是否有默认值、参数类型、返回值含义以及返回值类型。

形参与实参

实参(argument): 全称为"实际参数"是在调用时传递给函数的参数. 实参可以是常量、变量、表达式、函数等, 无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值, 以便把这些值传送给形参。 因此应预先用赋值,输入等办法使实参获得确定值。

形参(parameter):全称为"形式参数" 由于它不是实际存在的变量,所以又称虚拟变量。是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数。在调用函数时,实参将赋值给形参。

以上的官方定义有点啰嗦,简单来说形参就是定义函数时,括号内传入的参数;而实参就是在实际调用函数时,传给形参的真实值。因而,必须注意实参的个数、类型应与形参一一对应,并且实参必须要有确定的值。

这里涉及到一个传值与传址的问题,传值就是传入一个参数的值,而传址则是传入一个参数的内存地址。两者的区别:如果函数是传值类型,函数里修改了参数的值,外部的变量(实参)是不会改变的,比如下述代码:
def func(a):    a = 1b = 0func(b)print(b)# 运行结果如下0
尽管我们修改了传入参数的值,但实参却没有改变,依旧是0,这就是传值。 如果传递的是内存地址,修改了的话,实参也会受影响。

Python开发中采用传对象引用的方式,如果传入的参数是一个不可变对象(数字、字符串和元组)的引用,就不能修改原始对象的值;如果传入的参数是一个可变对象(列表、字典)的引用,就能直接修改原始对象的值。比如下面的代码:

def f(a):        a[0] = 1    b = [0]f(b)print(b)# 运行结果如下:[1]

关键字参数与默认参数

关键字参数 :如果你有一些具有多个参数的函数,而你又希望只对其中的一些进行指定,那么你可以通过命名的方式给参数赋值,即传入时指定形参的参数名,比如:plus(a=1, b=2) 默认参数 :在定义形参时就赋初值,调用时就可以不带参数去调用函数。

可变参数

有时传入函数中的 参数数目可能是不固定 ,比如要计算一堆数字的和,具体有多少个数字不知道,此时就可以用可变参数了。只需 在函数定义时再参数前加上星号* ,指定这个参数为可变参数(实际上是把数据打包成了一个元组) 另外,如果除了可变参数外还有其他参数,写在可变参数后的参数要用关键字参数指定,否则后续参数会被加入可变参数的范畴! 还有一点要注意:如果传入参数是列表或元组,会被再次打包成元组,如果想解包,需在实参前加上星号*
def demo(*a):    for b in a:        print(b, end='\t')if __name__ == '__main__':    a = [1, 2, 3, 4, 5]    demo(a)    print()    demo(*a)# 运行结果如下:[1, 2, 3, 4, 5]  1  2  3  4  5

全局变量与局部变量

全局变量:定义在最外部,可在函数内部访问,但 不能直接修改 。 局部变量:定义在函数内部,函数 外部无法访问 的参数和变量。 局部变量无法 在外部访问的原因:

Python在运行函数时,会利用栈(Stack)来存储数据,执行完函数后,所有数据会被自动删除。

函数中无法修改全局变量的原因:

当你视图在函数中修改全局变量的值时,Python会自动在函数内部新建一个名字一样的局部变量代替。如果硬是想在函数内修改全局变量,可在函数内部使用global关键字修饰全局变量,可以但不建议这样做,会使得程序维护成本的提高。

内部函数

所谓的内部函数其实就是 函数嵌套 ,在一个函数中嵌套定义另一个函数,要注意:

内部函数的作用域:只在内部函数的直接外部函数内,更外部是无法调用的!当你在更外部尝试调用内部函数,直接报:函数找不到错误!

内部函数无法直接修改外部函数中的变量,否则会报UnboundLocalError错误!如果想在内部函数中直接修改,可把直接外部函数中的变量通过容器类型来存放,也可以使用Python提供的nonlocal关键字修饰。

def fun_x():    x = [10]    y = 10    def fun_y():        x[0] += x[0]        nonlocal y        y *= y        return x[0] * y    return fun_y()if __name__ == '__main__':    print(fun_x())    # 运行结果如下:2000

闭包

在函数内嵌套另一个函数,如果内部函数引用了外部函数的变量,就可能产生闭包。Python中形成闭包的三个条件:
  • 函数嵌套

  • 内部函数引用外部函数变量

  • 外部函数返回内部函数

一个闭包的代码示例如下:
def outer(a):    b = 1      def inner():        print(a + b)    return innerif __name__ == '__main__':    demo = outer(2)    demo()# 运行结果如下:3
在上面的代码中,直接把内部函数当做返回值返回了,b是一个局部变量,按理来说,生命周期在调用完outer()函数后就完结了,但在上面的代码中,调用demo时,b变量的值却正常输出了, 函数闭包使得函数的局部变量信息得以保存。 Python中通过 __closure__属性记录着自由变量的地址 ,可以把上面test_1函数里的东西打印出来,代码如下:
print(test_1.__closure__)print(test_1.__closure__[0].cell_contents)print(test_1.__closure__[1].cell_contents)# 运行结果如下(<cell at 0x000001D09ACF85E8: int object at 0x00000000667D6C30>, <cell at 0x000001D09ACF8648: int object at 0x00000000667D6C10>)21

lambda表达式

在Python中可以使用 lambda关键字 来创建 匿名函数 ,直接返回一个函数对象。而不用去纠结为函数起什么名字,省去了定义函数的步骤,从而达到简化代码的目的。一个对比大小的简单lambda表达式代码示例如下:
bigger = lambda x, y: x > yprint("第一个参数比第二个参数大:%s" % bigger(1, 2))# 运行结果如下:第一个参数比第二个参数大:False

递归

递归简单点来说就是 函数自己调用自己 ,最简单的迭代求和代码示例如下:
def sum(n):    if n == 1:        return 1    else:        return n + sum(n - 1)print("1到100的求和结果是: %d" % sum(100))# 运行结果如下:1到100的求和结果是: 5050
Tips :递归要有结束条件,以避免递归的无休止调用!递归可以简化程序,但不一定能提高程序的执行效率!??‍♀️ 

迭代器

Python中的迭代器用于访问集合,是一种 可以记住遍历位置的对象 ,会从第一个元素开始访问,直到结束。 通过内置的 iter() 函数可获取对应的迭代器对象,然后循环遍历此迭代器对象;也可以通过另一个内置函数 next() ,返回容器的下一个元素,不过如果超过末尾是会报StopIteration异常,使用代码示例如下:
import sysa = [1, 2, 3, 4, 5]# 直接遍历迭代器对象it1 = iter(a)for x in it1:    print(x, end='\t')# 每调用一次next向后访问一个元素,超过元素会报StopIteration异常it2 = iter(a)print(next(it2))# 带异常捕获方法it3 = iter(a)while True:    try:        print(next(it3), end='\t')    except StopIteration:        sys.exit()# 运行结果如下:1   2   3   4   5 11   2   3   4   5
另外有一点要进行区分: iterable 和 iterator ,前者只实现了iter()函数,而后者实现了iter()和next()函数。 比如,list只是一个iterable而不是iterator,所以支持iter(),不支持next(),需要调用iter()函数获取一个iterator对象。有点拗口,我们可以从这两个单词的含义去理解:iterable是 可迭代的(adj.) ,iterator是指 迭代器(n.) ,是一个对象。 如果此时调用下type(it1),会发现数据类型位:,这是Python为list提供的默认迭代器对象。

生成器

生成器叫生成器函数可能会更加贴切,它是一种特别的函数,用 yield关键字 来返回一个生成器对象,本质上还是迭代器,只是更加简洁。 yield对应的值在函数调用时不会立即返回,只有去 调用next() 函数时 才会返回 ,而使用for x in xx 时调用的其实还是next()函数,简单的生成器代码示例如下:
def demo(n):    yield n * nif __name__ == '__main__':    print(demo(10))    print(next(demo(10)))    for i in demo(10):        print(i)        # 运行结果如下:func at 0x0000019AF47330A0>100100
可以看到返回的对象类型是 generator ,相比起迭代器,生成器显得更加优雅简洁。

装饰器

本质上也是函数。 作用:帮助其他函数在不改动代码的情况下增加额外的功能。 装饰器函数的返回值也是一个函数。 //一步步了解装饰器// 对于装饰器我也是最近才慢慢理解, 举个简单的奶茶例子慢慢引入吧(另外函数名不要用中文,这里用中文只是想让宝宝们更容易理解,还有命名习惯:类名驼峰命名,函数名下划线分割命名!)
import timedef 芝芝莓莓():    time.sleep(1)    print("制作一杯芝芝莓莓")if __name__ == '__main__':    芝芝莓莓()# 运行结果如下:制作一杯芝芝莓莓
如果现在我还想知道制作一杯奶茶的时间,可以改动下芝芝莓莓这个函数:
def 波霸奶茶():    start = time.clock()    time.sleep(2)    print("制作一杯芝芝莓莓")    end = time.clock()    print("耗时", end - start)if __name__ == '__main__':    芝芝莓莓()# 运行结果如下:制作一杯芝芝莓莓耗时 1.004335880279541
这个时候问题来了,不止芝芝莓莓,还有四季奶绿:
def 四季奶绿():    time.sleep(2)    print("制作一杯四季奶绿")
如 果想知道四季奶绿的 制作时间,需要把统计时间部分的代码复制粘贴一下,有点繁琐,如果还有一款多肉葡萄,也是要复制粘贴,每增加一个都要这样操作,有没有办法简化这个过程呢? 答案肯定是有的:Python支持把函数作为参数,我们可以把计时部分的代码剥离为一个单独的函数,然后在需要查看制作时间的奶茶传入,所以有了下面这样的代码:
def 芝芝莓莓():    time.sleep(1)    print("制作一杯芝芝莓莓")    def 四季奶绿():    time.sleep(2)print("制作一杯四季奶绿")def 计时(func):    start = time.time()    func()    end = time.time()print("耗时", end - start)if __name__ == '__main__':    计时(芝芝莓莓)    计时(四季奶绿)# 运行结果如下:制作一杯芝芝莓莓耗时 1.0002198219299316制作一杯四季奶绿耗时 2.0016701221466064
功能可行,不过问题又来了,使用上面的代码,意味着所有制作奶茶方法的地方都要改成计时(奶茶),如果奶茶有N多种,每个调用制作奶茶的函数都要改成这样的形式,有没有更简单的方法? 有, Python支持返回一个函数 ,我们通过一个 内嵌的包装函数,为传入的函数附加功能,然后返回这个函数 ,修改后的代码:
def 计时(func):    def decorator():        start = time.time()        func()        end = time.time()        print("耗时", end - start)return decoratorif __name__ == '__main__':    芝芝莓莓 = 计时(芝芝莓莓)    芝芝莓莓()    四季奶绿 = 计时(四季奶绿)    四季奶绿()# 运行结果如下:制作一杯芝芝莓莓耗时 1.0033059120178223制作一杯四季奶绿耗时 2.0044848918914795
现在只有在我们需要查询某种奶茶制作时间时才需要这样写,平常的函数还是正常使用。在Python中还可以写得更精简,它为我们提供了一枚语法糖 @ ,简化后的代码:
def 计时(func):    def decorator():        start = time.time()        func()        end = time.time()        print("耗时", end - start)return decorator@计时def 芝芝莓莓():    time.sleep(1)print("制作一杯芝芝莓莓")@计时def 四季奶绿():    time.sleep(2)print("制作一杯四季奶绿")if __name__ == '__main__':    芝芝莓莓()    四季奶绿()
关于更多装饰器的便利用法,我还在摸索中 3c77713a7836dc81ef16f7da1fdeb573.png总结好后再分享给大家叭~~~晚安♡
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值