图|一张顾巷
文|一张顾巷
函数定义
函数是组织好的,可 重复使用 的,用来实现单一或相关联功能的代码段,我们将这样的代码块提取出来,封装成一个函数。例如,获取当前日期前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__': 芝芝莓莓() 四季奶绿()
关于更多装饰器的便利用法,我还在摸索中
总结好后再分享给大家叭~~~晚安♡