阅读开源深度学习源码的时候,使用到了很多封装函数以及Python的高级语法,看起来很混乱很痛苦很困难。对python函数语法做个总结!!!
Table of Contents
- 熟练Python语法,尤其是函数参数、迭代器与生成器、函数式编程、面向对象编程
- 学会查看封装函数的具体实现
先看两个pythton关键字:
-
in关键字
:in关键字用于检查一个元素是否存在于一个集合中,如列表、元组、字典等。它通常与条件语句(如if语句)一起使用,也可以用在循环中。它是用来检查某个值是否在一个容器中的成员,比如:numbers = [1, 2, 3, 4, 5] if 3 in numbers:print("3 存在于列表中") else:print("3 不存在于列表中")
-
range()函数
:range()函数用于生成一个指定范围内的整数序列。它常与for循环结合使用,用来遍历一定范围内的整数。range()函数的语法是range(start,
stop, step),其中start是序列的起始值(默认为0),stop是序列的结束值(不包含),step是序列的步长(默认为1)。for i in range(5):print(i) ```
下面,进入正题!
1.Python语法
1.1 函数参数
1.1.1位置参数
位置参数就是平时用到最多的一种情况:实参与形参数量相等、位置对应。
举个例子:计算一个实数 x 的 n 次幂
def power(x, n):s = 1while n > 0:n = n - 1s = s * xreturn s
那么如果少一个参数或者位置不对应就达不到预期效果,甚至会报错。但我们平时用到的最多的就是 2 次幂,所以我们想即使少一个参数 n 也能达到预期效果,该怎么办呢?
答案是 使用默认参数
。
1.1.2 默认参数
def power(x, n=2):s = 1while n > 0:n = n - 1s = s * xreturn s
使用默认参数要注意一下几点:
- 必选参数在前,默认参数在后,否则Python的解释器会报错(因为Python解释器会按照位从左至右的顺序去寻找);
- 当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面。变化小的参数就可以作为默认参数。
你可能会说:这虽然解决了参数数量问题,还是得考虑位置对应问题呀!是的,看个例子:
def enroll(name, gender, age=6, city='Beijing'):print('name:', name)print('gender:', gender)print('age:', age)print('city:', city)
所以,默认参数是按顺序提供的,即实参会与形参从左至右匹配,最后剩余没有匹配完的参数采用默认值。
当然,也可以不按顺序提供部分默认参数,当不按顺序提供部分默认参数时,需要把参数名写上。
注: 定义默认参数要牢记一点:默认参数必须指向不变对象!
因为可变参数会导致默认参数改变
1.1.3 可变参数
你是不是在想:在参数数量不相等的情况下靠默认参数对应,能不能实现不依赖参数数量呢?
即参数数量随时可变!
使用方式:加一个*
def calc(*numbers):sum = 0for n in numbers:sum = sum + n * nreturn sum
看!加一个 * 就实现可变参数的方式
,其实在函数内部,参数numbers接收到的是一个tuple(元组)。
注:Python允许你在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去。(1.1.4节关键字参数也一样)
1.1.4 关键字参数
可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。
而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。
使用方式:加两个**
注:当没有关键字参数时默认是一个空字典{}。
关键字参数有什么用?它可以扩展函数的功能。就像上面所说的,他真正实现了“不依赖于参数数量与参数位置”,可以任意添加参数。
1.2 生成器 (暂时没遇到过,不太清楚)
掌握生成器你必须理解两个问题:
- 什么是生成器?
- 为什么需要生成器?
1.2.1 什么是生成器?
在Python中,一边循环一边计算的机制就是生成器(generator)。那么如何创建 generator 呢?
方法一:把一个列表生成式的 [ ] 改成 () 。
注:generator 也是可迭代对象,因此可以通过 for 循环来遍历迭代器中每一个元素。
方法二:计算规则过于复杂,用列表生成式写不出来时,可以用函数实现。
注:如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator。
这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。
回顾一下,列表元素可以按照某种算法推算出来,那我们就可以在循环的过程中不断推算出后续的元素,这有什么用呢?这样就不必创建完整的list,从而节省大量的空间。
因此,生成器可以创建很大容量的数据对象,但是不需要很大的存储空间。
上面例子可以充分体现生成器一个应用:用于生成训练数据,每调用一次生成一个batch的数据。
1.3 函数式编程
函数是编程是Python中极为重要的,主要包括高阶函数、匿名函数、返回函数、装饰器与偏函数。
这里详细介绍一下装饰器(decorator
),也是项目中的一个常用方法。
装饰器是什么? 在不希望修改函数的定义,在代码运行期间动态增加函数功能的方式,称之为“装饰器”(Decorator)。本质上,decorator就是一个返回函数的高阶函数。
怎么去使用装饰器呢? decorator接受一个函数作为参数,并返回一个函数。 借助Python的@语法,把decorator置于函数的定义处,实现功能扩展的目的。
在我另一篇博客有关于装饰器的理解与介绍:python装饰器
可以看到,now() 函数功能是打印今天的日期,log() 是定义的一个装饰器,借助@将装饰器置于函数now() 的定义处,是的now() 具有打印日志的功能。
但是这个原理是怎么样的呢? 千万不要以为是按顺序依次地调用了两个函数!
把 @log 放到 now() 函数的定义处,相当于执行了语句:now = log(now)
由于 log() 是一个decorator,返回一个函数,所以,原来的 now() 函数仍然存在,只是现在同名的 now 变量指向了新的函数,于是调用 now() 将执行新函数,即在log()函数中返回的wrapper()函数。
wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。在wrapper()函数内,首先打印日志,再紧接着调用原始函数。
所以原始函数 now() 的功能是在装饰器中实现的,通过传入的参数(是一个函数)来实现的。
你应该知道下面代码是如何运行的了。
1.4 面向对象编程
面向对象的三个基本特征是:封装、继承、多态。面向对象的优势只有在实战中才能感受,就不多说空洞的话了!
注意:@property,多重继承、定制类、枚举类、元类等都是面向对象编程的重要知识点。
2. 封装函数
在深度学习框架中,我们可以看见基本都是封装的函数,在查看官方文档弄清楚函数功能之后,我们要读封装函数的具体实现。
比如 keras 中 fit_generator() 函数
如何进入函数内部呢?Ctrl+鼠标左键!(pycharm中)
进入到函数定义处如下: