文档章节

python 中的函式定义

lemos
 lemos
发布于 2017/08/31 15:59
字数 1590
阅读 15
收藏 0

示例:定义一个斐波拉契数列

>>> def fib(n):    # 打印 Fibonacci 序列到 n
...     """打印到 n 的 Fibonacci 序列."""
...     a, b = 0, 1
...     while a < n:
...         print(a, end=' ')
...         a, b = b, a+b
...     print()
...
>>> # 现在调用我们刚定义的函式:
... fib(2000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597

 

几个概念:

文档字串(docstring),用于文档注释。

符号表(symbol table),函数中的符号表用于该函式的局部变量,因此称作局部符号表。在函式中被赋值的变量会存储在局部符号表中。变量引用会先在局部符号表中寻找,然后才是闭包函式的局部符号表,再然后是全局变量,最后是内建名字表。在函式中的全局变量尽管可以引用,但是不能赋值。函数内赋值只会创建一个新的同名的局部变量。

函式的实参,在它被调用时被引入到这个函式的局部变量表。并且参数是按值传递( 总是对象的一个 引用 , 而不是对象本身的值)。当一个函式调用另一个时, 对应这次调用,一个新的局部符号表就会被创建.

函式定义会在当前的符号表里引入该函式的名字. 函式名对应的值被解释器认定为自定义函式类型 函式名的值可以被赋予另一个名字, 使其也能作为函式使用. 

若函数没有返回值,默认返回 None(内建名字)。如果要唯一输出的值是 None, 那么解释器会正当的抑制这次返回. 如你实在想看看这个值,可以使用 print():

>>> fib(0)
>>> print(fib(0))
None

* 关于符号表

import dis

c = 3

def abc():
    a = 1
    b = a + c

dis.dis(abc)

输出

 19           0 LOAD_CONST               1 (1)
              3 STORE_FAST               0 (a)

 20           6 LOAD_FAST                0 (a)
              9 LOAD_GLOBAL              0 (c)
             12 BINARY_ADD
             13 STORE_FAST               1 (b)
             16 LOAD_CONST               0 (None)
             19 RETURN_VALUE

 

默认参数

使用默认参数,可以方便进行调用

def meet(you='lisi', him='wangwu'):
    print(you,'meet',him)

meet()
meet('wo')
meet('wo','zhangsan')

重要警告: 默认参数的值只会被求一次值. 但这在默认参数是可变参数的情况下就不一样了, 如列表, 字典, 或大多类的对象时. 例如, 下面的函式在随后的调用中会累积参数值:

通常情况

i = 5

def f(arg=i):
    print(arg)

i = 6
f()

# output:
5

特殊情况

def f(a, L=[]):
    L.append(a)
    return L

print(f(1))
print(f(2))
print(f(3))

# output:
[1]
[1, 2]
[1, 2, 3]

如果不希望参数值被后续调用共享:

def f(a, L=None):
    if L is None:
        L = []
    L.append(a)
    return L

* 为什么默认参数会变?

默认参数和函数对象本身是一一对应的,一个函数拥有一个默认参数的 tuple。这也很好理解,默认参数随函数一起定义,并且出现在函数签名里,理所应当是函数的一部分。

问题的点在于 Python 中对象传递全部为“引用”,list 对象又都是 mutable 的,所以函数调用时往 list 对象作为的默认参数中 append 会造成额外副作用。

事实上,Python 开发是倡导用 immutable 对象作为默认参数的。比如用 None 就是个不错的选择:

def foo(bar=None):
    bar = bar or []

又或者用了 mutable 参数一定记得创建副本:

def foo(bar=[]):
    bar = list(bar)

 

关键字参数

函数在定义或者调用时,可以指定参数名称

def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
    print("-- This parrot wouldn't", action, end=' ')
    print("if you put", voltage, "volts through it.")
    print("-- Lovely plumage, the", type)
    print("-- It's", state, "!")

可以使用下列方法调用(在函式调用时, 关键字参数必须跟在位置参数之后. )

parrot(1000)
parrot(action = 'VOOOOOM', voltage = 1000000)
parrot('a thousand', state = 'pushing up the daisies')
parrot('a million', 'bereft of life', 'jump')

当最后一个形参的形式为 **name 时, 则排除其他的形参的值, 它将以字典 (参阅 映射类型——字典) 的形式包含所有剩余关键字参数. 这种调用可以与具有 *name 形式的形式参数 (在下一小节中介绍) 联合使用, 这种形参接受所有超出函式接受范围的位置参数. ( *name 必须在 **name 之前使用) 例如, 如果我们像这样定义一个函式:

def cheeseshop(kind, *arguments, **keywords):
    print("-- Do you have any", kind, "?")
    print("-- I'm sorry, we're all out of", kind)
    for arg in arguments:
        print(arg)
    print("-" * 40)
    keys = sorted(keywords.keys())
    for kw in keys:
        print(kw, ":", keywords[kw])

它可以如下调用

cheeseshop("Limburger", "It's very runny, sir.",
           "It's really very, VERY runny, sir.",
           shopkeeper="Michael Palin",
           client="John Cleese",
           sketch="Cheese Shop Sketch")

打印结果(注意, 关键字参数名的列表是通过之前对字典 keys() 进行排序操作而创建的; 如果不这样做, 参数打印的顺序是不确定的.)

-- Do you have any Limburger ?
-- I'm sorry, we're all out of Limburger
It's very runny, sir.
It's really very, VERY runny, sir.
----------------------------------------
client : John Cleese
shopkeeper : Michael Palin
sketch : Cheese Shop Sketch

 

任意参数列表

指定函式能够在调用时接受任意数量的参数. 这些参数会被包装进一个元组 (参看 元组和序列). 在变长参数之前, 可以使用任意多个正常参数。

def write_multiple_items(file, separator, *args):
    file.write(separator.join(args))

一般地, 这种 variadic 参数必须在形参列表的末尾, 因为它们将接收传递给函式的所有剩余输入参数.除此之外,任何出现在 *arg 之后的形式参数只能是关键字参数

>>> def concat(*args, sep="/"):
...    return sep.join(args)
...
>>> concat("earth", "mars", "venus")
'earth/mars/venus'
>>> concat("earth", "mars", "venus", sep=".")
'earth.mars.venus'

 

参数列表解包

当参数存在于一个既存的列表或者元组之中, 但却需要解包以若干位置参数的形式被函数调用。

下面是一个利用 * 操作符解从列表或者元组中解包参数以供函数调用的例子

def cute(sex, age, height):
    print(sex,age,height,end=' ')
    print()

t = ['female', 18, 167]
cute(*t)

# output:
female 18 167 

同样的, 字典可以通过 ** 操作符来解包参数:

d = {'sex': 'male', 'age':18, 'height': 172}
cute(**d)

# output:
male 18 172 

* 或 **操作符有两个作用:

*arg 表示分散的参数,接受分散的参数。解析后的 arg 表示tuple或者map,可以传入函数参数为元组的函数。

© 著作权归作者所有

共有 人打赏支持
上一篇: docker 的基本命令
下一篇: Redux - example
lemos
粉丝 7
博文 180
码字总数 91399
作品 0
芜湖
后端工程师
私信 提问
Lubuntu 14.04 下 安装Ulipad

Ulipad – 超级好用的国产Python IDE,开发者是limodou。此IDE本身就是用Python + wxPython编写的。在Windows系统中一直使用Ulipad,最近想在Linux(Ubuntu 12.04)进行学习和实验,安装过程还...

yeyunxiaopan
2014/11/20
0
0
解析Kotlin 函数用法与函数式编程

导读 本篇文章主要介绍Kotlin函数的用法,以及自己对函数式编程的一些理解。并且会和Python,C++做一些比较。 自从Google爸爸宣布Kotlin为自己的干儿子之后,Kotlin被各大社区炒的火热。 如果...

问题终结者
2017/10/19
0
0
Python函数式编程指南(一):概述

这大概算是Python最难啃的一块骨头吧。在我 Python生涯的这一年里,我遇到了一些Pythoner,他们毫无例外地完全不会使用函数式编程(有些人喜欢称为Pythonic),比如,从来不会 传递函数,不知...

icheer
2012/05/31
0
0
為什麼 Node.js 不適合大型和商業專案?

JavaScript 和 Node.js 一直都是這幾年的話題,無論是前端還是後端,到處都可見 JavaScript,就好像爬滿了你全身上下,他們不斷地對你說道「嘿!老兄!快來用我吧!」。 為什麼 Node.js 會這...

临江仙卜算子
05/25
0
0
Install gevent in AIX with gcc

greenlet Download greenlet-0.4.1.zip from https://github.com/python-greenlet/greenlet libev Download libev-4.15.tar.gz from http://dist.schmorp.de/libev/ c-ares Download c-ares ......

querychinesesto
2013/12/31
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Confluence 6 教程:在 Confluence 中导航

当你对 Confluence 有所了解后,你会发现 Confluence 使用起来非常简单。这个教程主要是针对你使用的 Confluence 界面进行一些说明,同时向你展示在那里可以进行一些通用的任务和操作。 空间...

honeymose
今天
2
0
sed, awk 练习

1. sed打印某行到某行之间的内容 2. sed 转换大小写 将单词首字母转化大写 将所有小写转化大写 3. sed 在某一行最后面添加一个数字 4. 删除某行到最后一行 解析: {:a;N;$!ba;d} :a : 是...

Fc丶
今天
2
0
babel6升级到7,jest-babel报错:Requires Babel "^7.0.0-0", but was loaded with "6.26.3".

自从将前端环境更新到babel7,jest-babel之前是基于babel6的,执行时候就会报:Requires Babel "^7.0.0-0", but was loaded with "6.26.3". 很烦,因为连续帮好几台电脑修复这个问题,所以记...

曾建凯
今天
1
0
探索802.11ax

802.11ax承诺在真实条件下改善峰值性能和最差情况。 如何改善今天的Wi-Fi? 在决定如何改进当前版本以外的Wi-Fi时,802.11ac,IEEE和Wi-Fi联盟调查了Wi-Fi部署和行为,以确定更广泛使用的障碍...

linuxprobe16
今天
2
0
使用linux将64G的SDCARD格式化为FAT32

一、命令如下: sudo fdisk -lsudo mkfs.vfat /dev/sda -Isudo fdisk /dev/sda Welcome to fdisk (util-linux 2.29.2). Changes will remain in memory only, until you decide to wri......

mbzhong
今天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部