轻松初探Python(六)—函数

这是「AI 学习之路」的第 6 篇,「Python 学习」的第 6 篇

题外话

这周工作日 5 天,我并没有更新文章,但大家并不要以为小之懒惰了。正好相反,自从上篇的 AI 入门文章后,我自己便开始进行机器学习的系统学习了,这周一到周五,只要有空闲时间,我就开始看吴恩达 Coursera 的视频,可以说是非常痴迷了。

吴教授的课程非常通俗易懂,而且他本人的教学风格也是不紧不慢,循序渐进,甚至有关微积分和线代甚至 Octave 这些知识点都花了比较多的篇幅进行展开讲解,亲身体会后,再次推荐给大家。

目前,机器学习篇我已经学到一半了,实际上本来可以更快一些,但中间的一些微积分和线代的知识点,我又回炉复习了一下。非常庆幸我在大四的时候把高数重新复习了一遍,现在虽说不能完全回想起来,但回炉和记忆的串联算是比较快的,节省了很多的时间。

同时,我现在每天保持一到两题的 LeetCode 刷题量,实际上我不太追求说要刷的多快,刷题的目的一来是巩固基础,二来是每天刷一两道,活动下脑子。我每天早上上班前,先打开一道题,然后把题目阅读一下,过个脑子,上班途中就想想思路,如果思路比较清晰,到公司在别人吃早饭的时间,我就把代码提交了,如果思路不太顺,我就工作空闲或者中午的时候在桌上用纸笔画一画,然后晚上下班之后开始码代码。

如果这样的流程一天时间还是想不起来思路,那我就会直接看一下 discuss 或者 Solution,不追求必须靠自己解答出来,只要学到方法,过个脑子,然后把代码码出来上传到 GitHub 留个记录,对我来说就够了。

好了,回归正题,今天我们来较为细致的讲解下 Python 中的函数。

函数的定义

我们之前已经看了很多函数的使用例子了,比如我们要定义一个函数可以这样写

>>> def testFun(a):
...     print(a)
...
>>> testFun
<function testFun at 0x1060f28c8>
>>> testFun('这是一个方法输出')
这是一个方法输出

我们来看看这段代码。通过 def 语句,依次写出函数名、括号、括号中的参数,还有最后的冒号,千万不要忘记了。

冒号后面,就是具体的函数体了,函数体第一行和函数定义要有一个 Tab 的缩进距离。我们知道 Python 是没有分号和大括号来区分代码的结束和开始的,所以缩进的问题一定要注意,如果你的缩进不正确,可能会报 indented 错误。

在交互式环境下定义函数的时候,冒号输入完毕回车后,会有 ... 的提示,每行结束回车到下一行,连续两次回车定义该函数完毕,重新回到 >>> 提示符下。

Python 是一门面向对象语言,一切都是对象,甚至函数本身也是对象,我们称这种特性为「函数式编程」,上面的例子中,我们直接打印 testFun 是可以打印出它的函数类型的。

像我们熟悉的 Kotlin、Groovy 还有 Go 语言,都有这样的特性,函数式编程可以有非常强大的拓展性,同时可以很轻易的解决很多不支持函数式编程的语言下的一些写法问题。这个我会在后面专门写一篇文章来介绍 Python 的函数式编程。

使用函数很简单, 函数名、括号,加上参数即可调用函数。因为 Python 是动态类型语言,所以我们不需要像 Java 那样,对每一个变量和方法参数都提前在编译期设置好类型,我们定义 testFun(a) 的时候,并没有指示 a 到底是字符串类型还是别的类型。这样的自由肯定是有一定代价的,当函数体内部对参数的使用有较严格的要求的时候,如果传参类型错误,就会报错。

>>> def func_abs(x):
...   if x >= 0:
...      return x
...   else:
...      return -x
...
>>> func_abs(-1)
1
>>> func_abs('string')
TypeError: '>=' not supported between instances of 'str' and 'int'

函数的参数

函数的定义不是很复杂,但搭配参数,就会非常灵活,Python 的参数五花八门,除了我们常用的位置参数外,还有默认参数、可变参数和关键字参数。

位置参数

我们之前定义的 testFun(a) 中的 a 就是一个位置参数,当然你可以设置很多位置参数,顾名思义,参数的意义是和位置一一对应的,比如我们现在改写下 testFun 让它具备输出两个参数的能力

>>> testFun(a,b):
...    print(a,b)
...
>>> testFun('测试','参数')
测试 参数

刚刚说的位置一致性的意思就是我们的输入参数的顺序是和函数定义时候的参数顺序是一致的,第一个参数是'测试',第二个参数是'参数',只能代表 a='测试',b='参数',顺序不能错乱。

默认参数

Java 中,如果需要使用一个函数的多种参数形式,是通过重载的形式的,这种方式是比较麻烦的,比如,上面的例子中,我们想让 testFun 既可以使用一个参数,也可以使用两个参数

public void testFun(String a){
    System.out.println(a);
}

public void testFun(String a,String b){
    System.out.println(a + " " + b);
}

在 Python 中,我们可以使用默认参数来一次性完成这样的函数定义

>>> def testFun(a,b='函数'):
...     print(a,b)
...
>>> testFun('测试')
测试 函数
>>> testFun('测试',"参数")
测试 参数

使用 testFun(a) 的时候,会自动把 b 赋值为 '函数',只有当你需要改变 b 的时候,才需要自己输入参数值。需要注意的是,默认参数定义要放在位置参数的后面。

不仅如此,默认参数的默认值一定要指向的是不可变对象,否则就会出现一些难以预料的问题,这里举一个例子

>>> def app_end(L=[]):
...     L.append('END')
...     return L
...
>>> app_end()
['END']
>>> app_end()
['END','END']

注意到了吗,虽然我们的默认参数 L 是一个空 list,但在调用的过程中,每次添加元素,都会被添加到 L 这个 list 中去,而不是我们预料的那样,每次都是一个新的单元素 list,所以我们的默认参数,尽量使用不可变对象,除非你的设计逻辑就是如此。

可变参数

默认参数虽然可以拓展函数的参数数量,但毕竟数量还是固定的,如果我们想让参数数量是任意的,可以使用可变参数,可变参数很简单,在参数前加 * 号即可

>>> def calcSum(*nums):
...     sum = 0
...     for n in nums:
...         sum = sum + n
...     return sum
...
>>> calcSum(1,2,3)
6
>>> nums = [1,2,3]
>>> calcSum(*nums)
6
>>> nums = (1,2,3)
>>> calcSum(*nums)
6

我们也可以把 list 和 tuple 加星号传入可变参数中

关键字参数

关键字参数和可变参数一样,可以允许你传入 0 到任意个参数,不过这些参数都是含有参数名的,在函数内部是以一个 dict 的形式组装

>>> def testKW(a,b,**kw):
...     print(a,b,kw)
...
>>> testKW('测试','关键字参数',name='小之')
测试 关键字参数 {'name':'小之'}
>>> kw = {'name':'小之','age':23}
>>> testKW('测试','关键字参数',**kw)
测试 关键字参数 {'name':'小之','age':23}

当然,你可以像可变参数那种形式一样,通过提前定义好 dict,再把变量以 ** 开头传入。

关键字参数比较随意,你可以传入不受限制的参数,如果你需要判断传了哪些参数,你还需要在函数体内部进行判断,这个时候,我们就可以用命名关键字参数来作一定的限制

>>> def testKW(a,b,*,name,age):
...     print(a,b,name,age)
...
>>> testKW('测试','命名关键字参数',name='小之',age=23)
测试 命名关键字参数 小之 23

命名关键字参数需要一个分隔符 * ,* 号后面就会被看作是命名关键字参数。如果定义中有一个可变参数,那么后面的命名关键字参数就不需要 * 了,就像这样

>>> def testKW(a,b,*args,name,age):
...     print(a,b,args,name,age)

命名关键字参数是可以有默认值的,有默认值的情况下,可以不传入该参数,这点和默认参数有点类似

>>> def testKW(a,b,*,name='小之',age):
...     print(a,b,name,age)
...
>>> testKW('测试','命名关键字参数',23)
测试 命名关键字参数 小之 23

参数的组合

Python 中,上述参数可以组合使用,但需要注意一定的顺序:必选位置参数、默认参数、可变参数、命名关键字参数、关键字参数,这部分我将用廖大的例子来简单介绍下,我们先定义两个有若干参数的函数:

def f1(a, b, c=0, *args, **kw):
    print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)

def f2(a, b, c=0, *, d, **kw):
    print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)

调用的时候,Python 解释器会自动按照参数位置和参数名把对应参数穿进去

>>> f1(1, 2)
a = 1 b = 2 c = 0 args = () kw = {}
>>> f1(1, 2, c=3)
a = 1 b = 2 c = 3 args = () kw = {}
>>> f1(1, 2, 3, 'a', 'b')
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
>>> f1(1, 2, 3, 'a', 'b', x=99)
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}
>>> f2(1, 2, d=99, ext=None)
a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}

最神奇的是可以通过一个 tuple 和 dict 完成上述函数的调用

>>> args = (1, 2, 3, 4)
>>> kw = {'d': 99, 'x': '#'}
>>> f1(*args, **kw)
a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'}
>>> args = (1, 2, 3)
>>> kw = {'d': 88, 'x': '#'}
>>> f2(*args, **kw)
a = 1 b = 2 c = 3 d = 88 kw = {'x': '#'}

所以我们可以通过类似 func(*args, **kw) 的形式来调用任意函数,无论它的参数是如何定义的。你去翻看源码,可以看到很多内置的函数都是用这种形式定义函数的。

原文发布于微信公众号 - WeaponZhi(WeaponZhi)

原文发表时间:2017-12-09

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏程序员互动联盟

【编程入门】C语言堆栈入门——堆和栈的区别

在计算机领域,堆栈是一个不容忽视的概念,我们编写的C语言程序基本上都要用到。但对于很多的初学着来说,堆栈是一个很模糊的概念。堆栈:一种数据结构、一个在程序运行时...

2946
来自专栏Python小屋

Pythonic:递归、回溯等5种方法生成不重复数字整数

问题描述:从0到9这10个数字任选3个不重复的数字,能构成哪些三位数? So easy!看到这样的问题,很多人会写出类似(注意,只是类似,我为了使得本文几个函...

3517
来自专栏用户2442861的专栏

sizeof小览

http://blog.csdn.net/scythe666/article/details/47012347

381
来自专栏华章科技

从Zero到Hero,一文掌握Python关键代码

首先,什么是 Python?根据 Python 创建者 Guido van Rossum 所言,Python 是一种高级编程语言,其设计的核心理念是代码的易读性...

723
来自专栏C语言及其他语言

[每日一题]IP判断

今天介绍的这题难度不大,和前面的弟弟的作业有异曲同工之妙 题目描述 在基于Internet的程序中,我们常常需要判断一个IP字符串的合法性。 合法的IP...

3035
来自专栏猿人谷

怎样写解释器

解释器是比较深入的内容。虽然我试图从最基本的原理讲起,尽量让这篇文章不依赖于其它的知识,但是这篇教程并不是针对函数式编程的入门,所以我假设你已经学会了最基本的 ...

1867
来自专栏FreeBuf

PHP代码安全杂谈

虽然PHP是世界上最好的语言,但是也有一些因为弱类型语言的安全性问题出现。WordPress历史上就出现过由于PHP本身的缺陷而造成的一些安全性问题,如CVE-...

2166
来自专栏小詹同学

Leetcode打卡 | No.017 电话号码的字母组合

欢迎和小詹一起定期刷leetcode,每周一和周五更新一题,每一题都吃透,欢迎一题多解,寻找最优解!这个记录帖哪怕只有一个读者,小詹也会坚持刷下去的!

1013
来自专栏Android机动车

数据结构学习笔记——树(上)

之前一直介绍的是一对一的线性结构,可现实中还有多一对多的情况需要处理,这就是今天要介绍的一对多的数据结构——树。

652
来自专栏noteless

[一] java8 函数式编程入门 什么是函数式编程 函数接口概念 流和收集器基本概念

说起来好像很啰嗦,但是如果有人告诉你 通过sin(x) 计算后, x的值被改变了,你不会觉得异常奇怪么

752

扫码关注云+社区