写在之前
今天我们又开始了新的篇章 -- 函数篇, 在现代的任何科技门类, 乃至于政治学, 经济学等都已经普遍使用函数, 可以说函数的出现直接的加快了现代科技和社会的发展, 下面就开始我们与 Python 中的函数的初次相见吧.
函数是什么?
在我们学生时代的数学中, 定义函数的方式一般是这样的: y = ax + b, 这是一个一次函数, 当然我们也可以写成 f(x) = ax + b, 其中 x 是变量, 可以代表任何数, 但是这个并不是函数的全部, 在函数中, 其实变量并没有规定只能是数, 它可以是猪狗牛羊, 也可以是花鸟木鱼, 说到这不知道你有没有理解我的意思, 其实, 函数就是一种映射.
如果你尝试着将变量 x 理解为小猪佩奇, 那么 ax + b 就是 a 个佩奇再加上 b, 这个结果对应着的是另一个东西, 比如熊大, 即我们可以理解为 a 个佩奇加上 b 就对应的是熊大, 这就是我们所说的映射关系.
如果你理解了这些, 我们下面用纯粹的中学的数学方式, 在 Python 中建立函数:
- >>> x = 6
- >>> y = 2 * x + 1
- >>> y
- 13
在我们的学生时代我们就是这么用的, 那么在 Python 中这种方式还有用吗? 上面的例子我们建立了一个所谓的函数, 那么我们来尝试改变一下 x 的值:
- >>> x = 7
- >>> y
- 13
结果是 y 的值并没有改变, 所以说用纯粹的数学方式定义函数在 Python 中其实并没有什么用, 所以我们要用一种新的定义函数的方式, 请接着向下看.
如何定义函数?
在 Python 中定义了函数的格式, 下面我举一个例子来说一下 Python 中函数的格式和调用的方法:
- >>> def add(x,y):
- ... return x + y
上面的例子虽然短小, 但内有乾坤, 下面我以此函数为例, 详述函数的组成.
def :def 是函数的开始, 也就是在声明要创建一个函数的时候, 一定要先使用 def, 这就是告诉 Python 解析器, 这里要声明的是一个函数.
add:add 是函数的名称, 在 Python 中起名字的讲究就是要起的有意义, 能从函数的名字上看出这个函数是干什么的. 同时函数的命名规范和变量名是一样的, 必须使用字母和下划线开头, 且仅能含有字母, 数字和下划线.
( x,y ): 这个是参数列表, 要写在括号里, 其中的参数指向函数的输入, 本例中函数有两项输入, 但是通常情况下, 输入的个数可以是任意的, 也包括 0 个.
: : 冒号非常重要, 如果少了, 会报错, 所以希望你们不要像我一样.
return a + b: 这一行, 就是函数体了, 函数体是缩进了 4 个空格的代码块, 完成你需要完成的工作. return 是函数的关键字, 意思是要返回一个值, 函数中的 return 也不是必须要写的, 如果不写的话, Python 会默认返回一个值, 那就是 None.
调用函数
在这之前, 我们想一下我们为什么要写函数? 理论上来说, 不用函数也可以写代码, 之所以用函数, 大佬们给我们总结了以下几点, 我在这借花献佛:
写函数可以降低编程的难度. 通常将一个复杂的大问题分解成一系列更小的问题, 然后小问题再分解成更小的问题, 当问题细化到足够简单时, 就可以分而治之.
代码重用. 其实我们在编程的时候比较忌讳同一段代码不断重复, 因此有必要将某个常用的功能抽象为一段公用的代码, 也就是函数.
从上面来看, 使用函数还是很有必要的, 下面就来看看函数是怎么调用的:
- >>> def add(x,y):
- ... print('x = {}'.format(x))
- ... print('y = {}'.format(y))
- ... return x + y
我们把之前的例子稍作改变, 然后接下来看:
- >>> add(10,3)
- x = 10
- y = 3
- 13
- >>> add(3,10)
- x = 3
- y = 10
- 13
函数调用, 最关键的是要弄懂如何给函数的参数赋值, 上面就是按照参数次序赋值, 根据参数的位置, 值与之相对应.
- >>> add(x = 3,y = 10)
- x = 3
- y = 10
- 13
- >>> add(y = 10,x = 3)
- x = 3
- y = 10
- 13
还可以像上面一样直接把赋值语句写到里面, 这就明确了参数和对象的关系, 这个时候顺序就不重要了.
当然还可以在定义函数的时候就赋给一个默认值, 如果不给那个有默认值的参数赋值, 那么它就用默认值, 如果给它传一个, 则采用新传给它的值:
- >>> def add(x,y = 1):
- ... print('x = {}'.format(x))
- ... print('y =',y)
- ... return x + y
- ...
- >>> add(1)
- x = 1
- y = 1
- 2
- >>> add(1,1000000)
- x = 1
- y = 1000000
- 1000001
在这里想在强调一次, 参数和对象的关系与变量和对象的关系一样, 即在函数中的参数所传的都是对象的引用, 而不是对象本身.
函数是很有深度的, 需要我们深入探究, 实践过程中, 有很多对函数的不同理解, 需要我们在学习的过程中不断的思考, 下面我们学习一些函数的相关应用.
返回值
所谓的返回值, 就是在调用函数的地方由函数返回的数据. 下面我们用我们最熟悉的斐波那契数列为例, 我们编写一个函数来实现斐波那契数列:
- >>> def fibs(n):
- ... res = [0,1]
- ... for i in range(n-2):
- ... res.append(res[-2] + res[-1])
- ... return res
- ...
- >>> if __name__ == "__main__":
- ... now = fibs(10)
- ... print(now)
- ...
在上面的代码中我们首先定义了一个函数, 名字叫做 fibs, 参数是输入一个整数 (其实你输入非整数也是可以的, 只是结果不同), 然后通过 now = fibs(10) 调用这个函数. 这里的参数给的是 10, 这就以为着要得到的是 n = 10 的斐波那契数列. 运行以后的结果如下 :
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
当然如果你想换 n 的值, 只需要在调用的时候修改一下参数就好了. 然后我们来观察上面的函数, 最后有一个语句 return res, 意思是将 res 的值返回, 但是返回给谁呢? 这要看是在什么位置调用的函数. 在上面的代码中, 用 now = fibs(10) 调用了函数, 那么函数就将值返回到当前状态, 并记录在内存中, 然后把它赋值给变量 now.
需要注意的, 上面虽然返回的是列表, 但其实只是返回了一个返回值, 有时候我们需要返回多个的时候, 要用元组的方式.
- >>> def my_digit():
- ... return 1,2,3
- ...
- >>> now = my_digit()
- >>> now
- (1, 2, 3)
对于上面的这个函数, 其实我们还可以像下面一样:
- >>> x,y,z = my_digit()
- >>> x
- 1
- >>> y
- 2
- >>> z
- 3
并不是所以的函数都有 return, 比如某些函数就只是执行一条语句或者干脆什么也不做, 它们不需要返回值, 其实看过昨天文章的朋友可能会有印象, 其实它们也有, 只不过是 None. 比如下面的函数:
- >>> def cau():
- ... pass
- ...
- >>> now = cau()
- >>> print(now)
- None
这个函数的作用就是什么也不做, 当然也就不需要 return.
我们可以特别注意一下那个 return, 它其实还有一个作用, 请看下面的例子:
- >>> def my_info():
- ... print('my name is rocky')
- ... return
- ... print('i like python')
- ...
- >>> my_info()
- my name is rocky
看出什么了吗? 明明有两个 print, 在中间插入一个 return 以后, 只执行了第一个 print, 第二个并没有执行. 这是因为在第一个之后遇到 return, 它告诉函数要终端函数体内的流程, 所以 return 在这里的作用就是: 结束正在执行的流程, 并离开函数体返回到调用的位置.
函数的文档
函数的文档, 一般是写在函数的名字下面, 说明这个函数的用途, 因为这个我感觉很重要, 之前虽然也说过注释的重要性, 但还是感觉有必要再次说明.
- def fibs(n):
- """
- 这是一个求斐波那契数列的函数
- """
在函数的下面, 用三对引号的方式包裹着这个函数文档, 也叫函数的说明.
比如我们用 dir 来查看函数对象, 比如 dir(type), 我们会看到 doc, 这个就是文档:
- >>> dir(type)
- ['__abstractmethods__', '__base__', '__bases__', '__basicsize__', '__call__', '__class__', '__delattr__', '__dict__', '__dictoffset__', '__dir__', '__doc__', '__eq__', '__flags__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__instancecheck__', '__itemsize__', '__le__', '__lt__', '__module__', '__mro__', '__name__', '__ne__', '__new__', '__prepare__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasscheck__', '__subclasses__', '__subclasshook__', '__text_signature__', '__weakrefoffset__', 'mro']
- >>> type.__doc__
- "type(object_or_name, bases, dict)\ntype(object) -> the object's type\ntype(name, bases, dict) -> a new type"
如果上面的例子在交互模式下的话, 用 help(fibs), 得到的也是三对引号所包裹的文档信息, 感兴趣的可以尝试一下.
函数的属性
任何对象都具有属性, 我们前面的文章说过函数是对象, 那么函数也有属性.
- >>> def cau():
- ... """this is a cau function"""
- ... pass
- ...
对于上面的函数, 最熟悉的属性应该就是上面提到的函数文档 doc, 它可以用英文句号的方式表示为 cau.__doc__:
- >>> cau.__doc__
- 'this is a cau function'
这就能体现出这种方式表示函数属性的优势, 只要对象不同, 不管你属性的名字是否相同, 用英文句号都可以说明属性所对应的对象.
我们还可以给对象增加属性, 比如我们给 cau 增加一个 pig 属性, 并设置为 100, 顺便我们再调用一下它:
- >>> cau.pig = 100
- >>> cau.pig
- 100
还记得上面我说的那个查看对象属性和方法的 dir 吗? 现在有请它闪亮登场:
- >>> dir(cau)
- ['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'pig']
在这里列出了所有 cau 这个函数对象的属性和方法, 仔细观察我们会发现, 我们刚用过的 doc 和我们新增加的 pig 都在其中, 至于你在纠结那些名字前后都是用双下划线的, 你暂且可以把它们称之为特殊属性, 所有的这些属性都是可以用英文句点的方式调用, 感兴趣的可以试一试.
写在之后
更多内容, 欢迎关注公众号「Python 空间」, 期待和你的交流.
来源: https://juejin.im/post/5c318c6a5188252378558145