Python文件
Python程序保存为文件以.py结尾,一个简单的例子:
#!/usr/bin/python
#Filename: helloworld.py
print('Hello World')
.py文件能不能像.exe文件那样直接运行呢?在Windows上是不行的,但是,在Mac和Linux上是可以的,方法是在.py文件的第一行加上一个特殊的注释:
#!/usr/bin/env python3
ps:此处指定为python3的可执行程序,python 3版本没有向前兼容。
然后,通过命令给hello.py以执行权限:
$ chmod a+x hello.py
就可以直接运行hello.py了。
帮助
在 Python 中,如果你想得到任何关于函数或语句的快速信息帮助,就可以使用内置的 help 函数:
>>> help(print)
注意是在Python交互模式下输入,可以通过在命令行模式下敲命令python3就进入到Python交互模式,它的提示符是>>>
。
不想看时,按 q 来退出帮助。
如果你需要得到关于类似 return 操作符的帮助,需要在内部加上引号, >>> help('return')
, 这样 Python 就能理解你到底想干什么。
输入输出
- 输出
使用print方法,例如:
>>> print('hello, world')
help获取到的信息:
print(...)
print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
Prints the values to a stream, or to sys.stdout by default.
Optional keyword arguments:
file: a file-like object (stream); defaults to the current sys.stdout.
sep: string inserted between values, default a space.
end: string appended after the last value, default a newline.
flush: whether to forcibly flush the stream.
- 输入
使用input方法,例如:
>>> input('请输入信息:')
help获取到的信息:
input(prompt=None, /)
Read a string from standard input. The trailing newline is stripped.
The prompt string, if given, is printed to standard output without a
trailing newline before reading input.
If the user hits EOF (*nix: Ctrl-D, Windows: Ctrl-Z+Return), raise EOFError.
On *nix systems, readline is used if available.
基础语法
以#
开头的语句是注释,注释是给人看的,可以是任意内容,解释器会忽略掉注释。其他每一行都是一个语句,当语句以冒号:
结尾时,缩进的语句视为代码块。
Python程序是大小写敏感的,如果写错了大小写,程序会报错。
变量
变量是标识符的例子。标识符是用来标识某样东西的名字。在命名标识符的时候,你要遵循这些规则:
标识符的第一个字符必须是字母表中的字母(大写或小写)或者一个下划线
_
。标识符名称的其他部分可以由字母(大写或小写)、下划线
_
或数字0-9
组成。标识符名称是对大小写敏感的。例如,
myname
和myName
不是一个标识符。注意前者中的小写n
和后者中的大写N
。有效标识符名称的例子有
i
、__my_name
、name_23
和a1b2_c3
。无效标识符名称的例子:
2things
、this is spaced out
和my-name
。
逻辑行和物理行
物理行是你在编写程序时所看见的。逻辑行是 Python 看见的单个语句。 Python 假 定每个物理行对应一个逻辑行。
逻辑行的例子如 print ’Hello World’
这样的语句 —— 如果它本身就是一行(就像 你在编辑器中看到的那样),那么它也是一个物理行。
默认地, Python 希望每行都只使用一个语句,这样使得代码更加易读。
如果你想要在一个物理行中使用多于一个逻辑行,那么你需要使用分号;
来 特别地标明这种用法。分号表示一个逻辑行/语句的结束。(但是并不建议这样写)
仅仅当逻辑行太长的时候, 在多于一个物理行写一个逻辑行,可以在一行末尾添加\
表示逻辑行并没有结束:
print \
i
缩进
在 Python 中空白非常重要。实际上,在每行开头的空白很重要。称之为缩进。 在行首的主要的空白(空格键和制表符)用来决定逻辑行缩进的层次,从而来决定语 句分组。
这意味着同一层次的语句必须有相同的缩进。每一组这样的语句称为一个块。
你需要记住的一样东西是错误的缩进会引发错误。例如:
i=5
print('Value is ', i) # Error! Notice a single space at the start of the line
print('I repeat, the value is ', i)
当你运行的时候,会得到下面的出错信息:
File "whitespace.py", line 4
print('Value is ', i) # Error! Notice a single space at the start
of the line ^
IndentationError: unexpected indent
注意在第二行的开头有一个空格。Python 给出的错误信息告诉我们程序的语法不正确。
不要混合使用制表符和空格来缩进,因为这在跨越不同的平台的时候,无法正常工作,推荐使用4个空格的缩进。
给静态语言程序员的注释:
Python 总是使用缩进来代表代码块,不再使用括号。
数据类型
在 Python 中数的类型有三种 —— 整数、浮点数和复数。
- 2是一个整数的例子。
- 3.23 和 52.3E-4 是浮点数的例子。 E 标记表示 10 的幂。在这里,2.3E-4 表示 52.3 * 10−4。
- (-5+4j)和(2.3-4.6j)是复数的例子。
给有经验的程序员的注释:
在 Python 中不用区分'long int'类型。默认的整数类型可以任意长。
字符串
Python中默认所有的字符串的编码是 Unicode。没有仅仅使用 ASCII 的字符串,原因是 Unicode 是 ASCII 的超集。如果要严格使用 ASCII 编码的 字节流,可用 str.encode("ascii") 。
如果你写的文本基本上全部是英文的话,用Unicode编码比ASCII编码需要多一倍的存储空间,在存储和传输上就十分不划算。因此通常会使用UTF-8编码,可以通过如下方式声明.py文件的编码:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
对于不清楚什么UTF-8的同学可以看下:ASCII、Unicode和UTF-8的关系
- 单引号
你可以用单引号指定字符串,如 ’Quote me on this’ 。所有的空白,即空格和制表符都照原样保留。
- 双引号
在双引号中的字符串与单引号中的字符串的使用完全相同,例如 "What’s yourname?" 。
- 三引号
利用三引号("""or”’),你可以指示一个多行的字符串。你可以在三引号中自由 的使用单引号和双引号。例如:
'''This is a multi-line string. This is the first line.
This is the second line.
"What's your name?," I asked.
He said "Bond, James Bond."
'''
- 转义序列
假如你有一个字符串包含单引号’
,如何表示这个字符串呢?例如,字符串是What’s your name?
。你不能用 ’What’s your name?’
来表示,因为 Python 不知道字符串的起始和结束位置。所以应该将字符串中间的这个单引号指定为不表示字符串的结束。这可在称之为转义序列的协助下实现。可以讲单引号指定为 \'
—— 注意反斜杠。现在,就能将字符串表示为 ’What\’s your name?’
。
还有一种方式就是用双引号"What’s your name?"
。类似地,在用双引号的字符串 中用双引号必须用转义符。还有,必须用转义符\\
来表示反斜杠。
如果你想指定两行字符串,该如何做呢?一种方式就是用前面提到的用三引号的 字符串,或者可以用转义符\n
表示新的一行的开始。例如 This is the first line\nThis is the second line 。
另外一个有用的转义字符是Tab键 ——\t
。有许多转义序列,这儿仅仅提到了最有用的几个。
需要说明的是,在一个字符串中,在一行末尾的反斜杠仅仅表示下一行的字符串是上一行的继续,但并不增加新的行。例如:
"This is the first sentence.\
This is the second sentence."
与"This is the first sentence. This is the second sentence."
等价。
- 自然字符串
如果你想指定一些不被特殊处理,例如像转义序列,那么,就需要通过在字符 串前面附加 r 或 R 来指定自然字符串。
给有经验的程序员的注释:
在 Python 中没有单独的 char 数据类型。
记住单引号和双引号是一样的 —— 没有丝毫差异。
用正则表达式的时候请使用自然字符串。否则,可能会用到许多反斜杠。例如,后向引用符可以写成’\\1’
或r’\1’
。
格式化
- 使用
%
>>> 'Hello, %s' % 'world'
'Hello, world'
>>> 'Hi, %s, you have $%d.' % ('Michael', 1000000)
'Hi, Michael, you have $1000000.'
%
运算符就是用来格式化字符串的。在字符串内部,%s
表示用字符串替换,%d
表示用整数替换,有几个%?
占位符,后面就跟几个变量或者值,顺序要对应好。如果只有一个%?
,括号可以省略。
常见的占位符有:
占位符 | 替换内容 |
---|---|
%d | 整数 |
%f | 浮点数 |
%s | 字符串 |
%x | 十六进制整数 |
其中,格式化整数和浮点数还可以指定是否补0和整数与小数的位数:
>>> print('%2d-%02d' % (3, 1))
3-01
>>> print('%.2f' % 3.1415926)
3.14
>>>
如果你不太确定应该用什么,%s
永远起作用,它会把任何数据类型转换为字符串。
有些时候,字符串里面的%
是一个普通字符怎么办?这个时候就需要转义,用%%
来表示一个%
:
>>> 'growth rate: %d %%' % 7
'growth rate: 7 %'
- 使用format()
>>> print('{0} is {1} years old'.format('makey', 12))
makey is 12 years old
>>> '{0:.3}'.format(1/3) # decimal (.) precision of 3 for float
'0.333'
>>> '{0:_^11}'.format('hello') # fill with underscores (_) with the text centered (^) to 11 width
'___hello___'
>>> '{name} wrote {book}'.format(name='Swaroop', book='A Byte of Python') # keyword-based
'Swaroop wrote A Byte of Python'
操作符
操作符 | 解释 | 示例 |
---|---|---|
+ | 两数相加或者字符串拼接 | 3 + 5 gives 8. ’a’ + ’b’ gives ’ab’. |
- | 表示负数或者两数相减 | -5.2 gives a negative number. 50 - 24 gives 26. |
* | 乘法或者复制字符串 | 2 * 3 gives 6. ’la’ * 3 gives ’lalala’. |
** | 次方 | 3 ** 4 gives 81 (i.e. 3 * 3 * 3 * 3 ) |
/ | 除法 | 4 / 3 gives 1.333333333333. |
// | 除法之后的整数部分 | 4 // 3 gives 1. |
% | 整除后的剩余部分 | 8 % 3 gives 2. -25.5 % 2.25 gives 1.5. |
<< | 位运算 | 2 << 2 gives 8. 2 is represented by 10 in bits. left shifting by 2 bits gives 1000 which represents the decimal 8. |
>> | 位运算 | 11 >> 1 gives 5. 11 is represented in bits by 1011 which when right shifted by 1 bit gives 101 which is the decimal 5. |
& | 按位与 | 5 & 3 gives 1. |
| | 按位或 | 5 | 3 gives 7 |
^ | 异或 | 5 ˆ 3 gives 6 |
~ | The bit-wise inversion of x is -(x+1) | ~5 gives -6 |
<, <= , == , != ,>, >= | 比较 | |
not | 非 | 相当于java中的 !true. x = True; not x returns False. |
and | 逻辑与 | 相当于java中的&& |
or | 逻辑或 | 相当于java中|| |
控制流
if 语句
if 语句用来检验一个条件,如果条件为真,我们运行一块语句(称为 if-块),否 则我们处理另外一块语句(称为 else-块)。当然,还有elif 用于判断另一个条件, elif 和 else 部分是可选的。
if <条件判断1>:
<执行1>
elif <条件判断2>:
<执行2>
elif <条件判断3>:
<执行3>
else:
<执行4>
while 语句
只要在一个条件为真的情况下, while 语句允许你重复执行一块语句。 while 语 句是所谓循环语句的一个例子。 while 语句有一个可选的 else 从句。
# !/usr/bin/python3
# Filename while.py
number = 23
running = True
while running:
guess=int(input("Enter an integer:"))
if guess == number:
print("Congratulation, you guessed it.")
running=False #this causes the while loop to stop
elif guess < number:
print("No, it is a little higher.")
else:
print("No, it is a little lower.")
else:
print("the while loop is over.")
# Do anything else you want to do here
print("done")
输出:
$ python3 while.py
Enter an integer : 50
No, it is a little lower than that.
Enter an integer : 22
No, it is a little higher than that.
Enter an integer : 23
Congratulations, you guessed it.
The while loop is over.
Done
当 while 循环的条件为假时 —— 这或许在第一次检查条件的时候。如果 while 循 环有 else 从句,当不满足条件时else块会触发,除非你的 while 循环将永远循环下去不会结束!
for 循环
for..in 是另外一个循环语句,它在一序列的对象上迭代,即逐一使用序列中的每 个项目。
for i in range(1, 5):
print (i)
else :
print ( 'The for loop is over' )
range 返回一个序列的数。这个序列从第一个数开 始到第二个数为止。例如, range(1,5) 给出序列 [1, 2, 3, 4]。默认地, range 的步长 为 1。如果我们为 range 提供第三个数,那么它将成为步长。例如,range(1,5,2) 给出 [1,3]。记住,range 向上延伸到第二个数,即它不包含第二个数。
for 循环在这个范围内递归 ——for i in range(1,5) 等价于 for i in [1, 2, 3, 4],这就 如同把序列中的每个数(或对象)赋值给 i ,一次一个,然后以每个 i 的值执行这个 程序块。在这个例子中,我们只是打印 i 的值。
给有经验的程序员的注释:
记得 Python中的循环可以有 else 语句,但是如果循环中触发break则else不会执行。
break
break 语句是用来终止循环语句的,即哪怕循环条件没有变为 False 或序列还没有 被完全迭代结束,也停止执行循环语句。
一个重要的注释是,如果你从 for 或 while 循环中终止,任何对应的循环 else 块 将不执行。
continue
continue 语句被用来告诉 Python 跳过当前循环块中的剩余语句,然后继续进行下 一轮循环。
数据结构
list
Python内置的一种数据类型是列表:list。list是一种有序的集合,可以随时添加和删除其中的元素。
比如,列出班里所有同学的名字,就可以用一个list表示:
>>> classmates = ['Michael', 'Bob', 'Tracy']
>>> classmates
['Michael', 'Bob', 'Tracy']
变量classmates就是一个list。用len()函数可以获得list元素的个数:
>>> len(classmates)
3
用索引来访问list中每一个位置的元素,记得索引是从0开始的:
>>> classmates[0]
'Michael'
>>> classmates[1]
'Bob'
>>> classmates[2]
'Tracy'
>>> classmates[3]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
当索引超出了范围时,Python会报一个IndexError错误,所以,要确保索引不要越界,记得最后一个元素的索引是len(classmates) - 1。
如果要取最后一个元素,除了计算索引位置外,还可以用-1做索引,直接获取最后一个元素:
>>> classmates[-1]
'Tracy'
以此类推,可以获取倒数第2个、倒数第3个:
>>> classmates[-2]
'Bob'
>>> classmates[-3]
'Michael'
>>> classmates[-4]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
当然,倒数第4个就越界了。
list是一个可变的有序表,所以,可以往list中追加元素到末尾:
>>> classmates.append('Adam')
>>> classmates
['Michael', 'Bob', 'Tracy', 'Adam']
也可以把元素插入到指定的位置,比如索引号为1的位置:
>>> classmates.insert(1, 'Jack')
>>> classmates
['Michael', 'Jack', 'Bob', 'Tracy', 'Adam']
要删除list末尾的元素,用pop()方法:
>>> classmates.pop()
'Adam'
>>> classmates
['Michael', 'Jack', 'Bob', 'Tracy']
要删除指定位置的元素,用pop(i)方法,其中i是索引位置:
>>> classmates.pop(1)
'Jack'
>>> classmates
['Michael', 'Bob', 'Tracy']
要把某个元素替换成别的元素,可以直接赋值给对应的索引位置:
>>> classmates[1] = 'Sarah'
>>> classmates
['Michael', 'Sarah', 'Tracy']
list里面的元素的数据类型也可以不同,比如:
>>> L = ['Apple', 123, True]
list元素也可以是另一个list,比如:
>>> s = ['python', 'java', ['asp', 'php'], 'scheme']
>>> len(s)
4
要注意s只有4个元素,其中s[2]又是一个list,如果拆开写就更容易理解了:
>>> p = ['asp', 'php']
>>> s = ['python', 'java', p, 'scheme']
要拿到'php'可以写p[1]或者s[2][1],因此s可以看成是一个二维数组,类似的还有三维、四维……数组,不过很少用到。
如果一个list中一个元素也没有,就是一个空的list,它的长度为0:
>>> L = []
>>> len(L)
0
tuple(元组)
另一种有序列表叫元组:tuple。tuple和list非常类似,但是tuple一旦初始化就不能修改,比如同样是列出同学的名字:
>>> classmates = ('Michael', 'Bob', 'Tracy')
现在,classmates这个tuple不能变了,它也没有append()
,insert()
这样的方法。其他获取元素的方法和list是一样的,你可以正常地使用classmates[0]
,classmates[-1]
,但不能赋值成另外的元素。
不可变的tuple有什么意义?因为tuple不可变,所以代码更安全。如果可能,能用tuple代替list就尽量用tuple。
tuple的陷阱:当你定义一个tuple时,在定义的时候,tuple的元素就必须被确定下来,比如:
>>> t = (1, 2)
>>> t
(1, 2)
如果要定义一个空的tuple,可以写成():
>>> t = ()
>>> t
()
但是,要定义一个只有1个元素的tuple,如果你这么定义:
>>> t = (1)
>>> t
1
定义的不是tuple,是1这个数!这是因为括号()既可以表示tuple,又可以表示数学公式中的小括号,这就产生了歧义,因此,Python规定,这种情况下,按小括号进行计算,计算结果自然是1。
所以,只有1个元素的tuple定义时必须加一个逗号,,来消除歧义:
>>> t = (1,)
>>> t
(1,)
Python在显示只有1个元素的tuple时,也会加一个逗号,
,以免你误解成数学计算意义上的括号。
最后来看一个“可变的”tuple:
>>> t = ('a', 'b', ['A', 'B'])
>>> t[2][0] = 'X'
>>> t[2][1] = 'Y'
>>> t
('a', 'b', ['X', 'Y'])
这个tuple定义的时候有3个元素,分别是a
,b
和一个list。不是说tuple一旦定义后就不可变了吗?怎么后来又变了?
表面上看,tuple的元素确实变了,但其实变的不是tuple的元素,而是list的元素。tuple一开始指向的list并没有改成别的list,所以,tuple所谓的“不变”是说,tuple的每个元素,指向永远不变。即指向a
,就不能改成指向b
,指向一个list,就不能改成指向其他对象,但指向的这个list本身是可变的!
理解了“指向不变”后,要创建一个内容也不变的tuple怎么做?那就必须保证tuple的每一个元素本身也不能变。
dict
Python内置了字典:dict的支持,dict全称dictionary,在其他语言中也称为map,使用键-值(key-value)存储,具有极快的查找速度。
举个例子,假设要根据同学的名字查找对应的成绩,用Python写一个dict如下:
>>> d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
>>> d['Michael']
95
把数据放入dict的方法,除了初始化时指定外,还可以通过key放入:
>>> d['Adam'] = 67
>>> d['Adam']
67
由于一个key只能对应一个value,所以,多次对一个key放入value,后面的值会把前面的值冲掉:
>>> d['Jack'] = 90
>>> d['Jack']
90
>>> d['Jack'] = 88
>>> d['Jack']
88
如果key不存在,dict就会报错:
>>> d['Thomas']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'Thomas'
要避免key不存在的错误,有两种办法,一是通过in判断key是否存在:
>>> 'Thomas' in d
False
二是通过dict提供的get()
方法,如果key不存在,可以返回None
,或者自己指定的value:
>>> d.get('Thomas')
>>> d.get('Thomas', -1)
-1
注意:返回None
的时候Python的交互环境不显示结果。
要删除一个key,用pop(key)方法,对应的value也会从dict中删除:
>>> d.pop('Bob')
75
>>> d
{'Michael': 95, 'Tracy': 85}
请务必注意,dict内部存放的顺序和key放入的顺序是没有关系的。
和list比较,dict有以下几个特点:
查找和插入的速度极快,不会随着key的增加而变慢;
需要占用大量的内存,内存浪费多。
而list相反:
查找和插入的时间随着元素的增加而增加;
占用空间小,浪费内存很少。
所以,dict是用空间来换取时间的一种方法。
dict可以用在需要高速查找的很多地方,在Python代码中几乎无处不在,正确使用dict非常重要,需要牢记的第一条就是dict的key必须是不可变对象。
这是因为dict根据key来计算value的存储位置,如果每次计算相同的key得出的结果不同,那dict内部就完全混乱了。这个通过key计算位置的算法称为哈希算法(Hash)。
要保证hash的正确性,作为key的对象就不能变。在Python中,字符串、整数等都是不可变的,因此,可以放心地作为key。而list是可变的,就不能作为key:
>>> key = [1, 2, 3]
>>> d[key] = 'a list'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
set
set和dict类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在set中,没有重复的key。
要创建一个set,需要提供一个list作为输入集合:
>>> s = set([1, 2, 3])
>>> s
{1, 2, 3}
注意,传入的参数[1, 2, 3]
是一个list,而显示的{1, 2, 3}
只是告诉你这个set内部有1,2,3这3个元素,显示的顺序也不表示set是有序的。。
重复元素在set中自动被过滤:
>>> s = set([1, 1, 2, 2, 3, 3])
>>> s
{1, 2, 3}
通过add(key)方法可以添加元素到set中,可以重复添加,但不会有效果:
>>> s.add(4)
>>> s
{1, 2, 3, 4}
>>> s.add(4)
>>> s
{1, 2, 3, 4}
通过remove(key)方法可以删除元素:
>>> s.remove(4)
>>> s
{1, 2, 3}
set可以看成数学意义上的无序和无重复元素的集合,因此,两个set可以做数学意义上的交集、并集等操作:
>>> s1 = set([1, 2, 3])
>>> s2 = set([2, 3, 4])
>>> s1 & s2
{2, 3}
>>> s1 | s2
{1, 2, 3, 4}
set和dict的唯一区别仅在于没有存储对应的value,但是,set的原理和dict一样,所以,同样不可以放入可变对象,因为无法判断两个可变对象是否相等,也就无法保证set内部“不会有重复元素”。
切片 Slice
取一个list或tuple的部分元素是非常常见的操作,Python提供了切片(Slice)操作符,能大大简化这种操作。
>>> L[0:3]
['Michael', 'Sarah', 'Tracy']
L[0:3]
表示,从索引0开始取,直到索引3为止,但不包括索引3。即索引0,1,2,正好是3个元素。
如果第一个索引是0,还可以省略:
>>> L[:3]
['Michael', 'Sarah', 'Tracy']
也可以从索引1开始,取出2个元素出来:
>>> L[1:3]
['Sarah', 'Tracy']
类似的,既然Python支持L[-1]
取倒数第一个元素,那么它同样支持倒数切片,试试:
>>> L[-2:]
['Bob', 'Jack']
>>> L[-2:-1]
['Bob']
记住倒数第一个元素的索引是-1。
中括号中什么都不写,只写[:]
就可以原样复制一个list。
你也可以给切片规定第三个参数,就是切片的步长(默认步长是 1)。
>>> shoplist = ['apple', 'mango', 'carrot', 'banana']
>>> shoplist[::1]
['apple', 'mango', 'carrot', 'banana']
>>> shoplist[::2]
['apple', 'carrot']
>>> shoplist[::3]
['apple', 'banana']
>>> shoplist[::-1]
['banana', 'carrot', 'mango', 'apple']
注意当步长是 2 时,我们得到在位置 0,2,... 的项,当步长是 3 时,得到位置 0,3, 等等的项。
切片操作同样可以作用于tuple(元组)和str(字符串)
列表生成式
列表生成式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式。
>>> [x * x for x in range(1, 11) if x % 2 == 0]
[4, 16, 36, 64, 100]
还可以使用两层循环,可以生成全排列:
>>> [m + n for m in 'ABC' for n in 'XYZ']
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']
列表生成式也可以使用两个变量来生成list:
>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> [k + '=' + v for k, v in d.items()]
['y=B', 'x=A', 'z=C']
最后把一个list中所有的字符串变成小写:
>>> L = ['Hello', 'World', 'IBM', 'Apple']
>>> [s.lower() for s in L]
['hello', 'world', 'ibm', 'apple']
生成器
函数
函数是重用的程序段。它们允许你给一个语句块一个名称,然后你用这个名字可 以在你的程序的任何地方,任意多次地运行这个语句块。这被称为调用函数。
实际上之前我们一直在调用函数,例如:
>>> print('abc')
函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”:
>>> a = abs # 变量a指向abs函数
>>> a(-1) # 所以也可以通过a调用abs函数
1
定义
函数用关键字 def 来定义。def
关键字后跟一个函数的标识符名称,然后跟一对圆括号()
。圆括号之中可以包括一些变量名,该行以冒号:
结尾。接下来是一块语句,它们是函数体。下面这个例子将说明这事实上是十分简单的:
#!/usr/bin/python3
# Filename: function1.py
def sayHello():
print('Hello World!') # block belonging to the function # End of function
sayHello() # call the function
sayHello() # call the function again
参数
函数取得的参数是你提供给函数的值,这样函数就可以利用这些值做一些事情。 这些参数就像变量一样,只不过它们的值是在我们调用函数的时候定义的,而非在函 数本身内赋值。
参数在函数定义的圆括号对内指定,用逗号分割。当我们调用函数的时候,我们 以同样的方式提供值。注意我们使用过的术语 —— 函数中的参数名称为形参而你提 供给函数调用的值称为实参。
局部变量
当你在函数定义内声明变量的时候,它们与函数外具有相同名称的其他变量没有任何关系,即变量名称对于函数来说是局部的。这称为变量的作用域。所有变量的作用域是它们被定义的块,从它们的名称被定义的那点开始。
#!/usr/bin/python3
# Filename: func_local.py
x = 50
def func(x):
print('x is', x)
x=2
print('Changed local x to', x)
func(x)
print('x is still', x)
输出:
$ python3 func_local.py
x is 50
Changed local x to 2
x is still 50
在函数中,我们第一次使用x
的值的时候, Python 使用函数声明的形参的值。 接下来,我们把值 2
赋给x
。 x
是函数的局部变量。所以,当我们在函数内改
变 x
的值的时候,在主块中定义的 x
不受影响。在最后一个 print
语句中,我们证明 了主块中的x
的值确实没有受到影响。
使用全局语句
如果你想要为一个定义在函数外的变量赋值,那么你就得告诉 Python 这个变量 名不是局部的,而是全局的。我们使用 global
语句完成这一功能。没有 global
语句, 是不可能为定义在函数外的变量赋值的。
你可以使用定义在函数外的变量的值(假设在函数内没有同名的变量)。然而, 我并不鼓励你这样做,并且你应该尽量避免这样做,因为这使得程序的读者会不清楚 这个变量是在哪里定义的。使用global
语句可以清楚地表明变量是在外面的块定义的。
#!/usr/bin/python
#Filename: func_global.py
x = 50
def func():
global x
print('x is',x)
x=2
print('Changed global x to',x)
func()
print('Value of x is',x)
输出:
$ python func_global.py
x is 50
Changed global x to 2
Value of x is 2
global
语句被用来声明x
是全局的 —— 因此,当我们在函数内把值赋给 x
的时 候,这个变化也反映在我们在主块中使用x
的值的时候。
你可以使用同一个 global
语句指定多个全局变量。例如global x, y, z
。
使用非局部语句
上面给出了如何在局部和全局作用域内使用变量。还有一种作用域叫做“非局 部”域,处于这两种作用域之间。
非局部作用域在你定义函数内的函数时会看到。
由于在 Python 中,任何事物是可执行的代码,你可以在任何地方定义函数。
#!/usr/bin/python3
# Filename: func_nonlocal.py
def func_outer():
x=2
print('x is',x)
def func_inner():
nonlocal x
x=5
func_inner()
print('Changed local x to',x)
func_outer()
输出:
$ python3 func_nonlocal.py
x is 2
Changed local x to 5
当在函数 func_inner
的内部时,在函数 func_outer
的第一行定义的 x
相对来讲 既不是在局部范围也不是在全局的作用域中,使用这样的 x
称之为非局部 x
,因此可以使用这个变量。
默认参数值
对于一些函数,你可能希望它的一些参数是可选的,如果用户不想要为这些参数 提供值的话,这些参数就使用默认值。这个功能借助于默认参数值完成。你可以在函 数定义的形参名后加上赋值运算符=
和默认值,从而给形参指定默认参数值。
#!/usr/bin/python3
# Filename: func_default.py
def say(message, times = 1):
print(message * times)
say('Hello')
say('World', 5)
输出:
$ python3 func_default.py
Hello
WorldWorldWorldWorldWorld
名为say
的函数用来打印一个字符串任意所需的次数。如果我们不提供一个值, 那么默认地,字符串将只被打印一遍。我们通过给形参 times
指定默认参数值 1 来实 现这一功能。
只有在形参表末尾的那些参数可以有默认参数值,即你不能在声明函数形参的时候,先声明有默 认值的形参而后声明没有默认值的形参。这是因为赋给形参的值是根据位置而赋值的。例如,def func(a, b=5) 是有效的,但是 def func (a=5, b) 是无效的。
默认参数很有用,但使用不当,也会掉坑里。默认参数有个最大的坑,演示如下:
先定义一个函数,传入一个list,添加一个END再返回:
def add_end(L=[]):
L.append('END')
return L
当你正常调用时,结果似乎不错:
>>> add_end([1, 2, 3])
[1, 2, 3, 'END']
>>> add_end(['x', 'y', 'z'])
['x', 'y', 'z', 'END']
当你使用默认参数调用时,一开始结果也是对的:
>>> add_end()
['END']
但是,再次调用add_end()时,结果就不对了:
>>> add_end()
['END', 'END']
>>> add_end()
['END', 'END', 'END']
默认参数是[],但是函数似乎每次都“记住了”上次添加了'END'后的list。
原因解释如下:
Python函数在定义的时候,默认参数L
的值就被计算出来了,即[]
,因为默认参数L
也是一个变量,它指向对象[]
,每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]
了。
定义默认参数要牢记一点:默认参数必须指向不变对象!
要修改上面的例子,我们可以用None这个不变对象来实现:
def add_end(L=None):
if L is None:
L = []
L.append('END')
return L
现在,无论调用多少次,都不会有问题:
>>> add_end()
['END']
>>> add_end()
['END']
为什么要设计str
、None
这样的不变对象呢?因为不变对象一旦创建,对象内部的数据就不能修改,这样就减少了由于修改数据导致的错误。此外,由于对象不变,多任务环境下同时读取对象不需要加锁,同时读一点问题都没有。我们在编写程序时,如果可以设计一个不变对象,那就尽量设计成不变对象。
关键参数
如果你的某个函数有许多参数,而你只想指定其中的一部分,那么你可以通过命 名来为这些参数赋值 —— 这被称作关键参数 —— 我们使用名字(关键字)而不是位 置(我们前面所一直使用的方法)来给函数指定实参。
这样做有两个优势 ——
一、由于我们不必担心参数的顺序,使用函数变得更加 简单了。
二、假设其他参数都有默认值,我们可以只给我们想要的那些参数赋值。
#!/usr/bin/python3
# Filename: func_key.py
def func(a, b=5, c=10):
print('a is', a, 'and b is', b, 'and c is', c)
func(3, 7)
func(25, c=24)
func(c=50, a=100)
输出:
$ python3 func_key.py
a is 3 and b is 7 and c is 10
a is 25 and b is 5 and c is 24
a is 100 and b is 5 and c is 50
VarArgs 参数
有时,你或许想定义一个能获取任意个数参数的函数,这可通过使用*
号来实现。
#!/usr/bin/python3
# Filename: total.py
def total(initial=5, *numbers, **keywords):
count = initial
for number in numbers:
count += number
for key in keywords:
count += keywords[key]
return count
print(total(10, 1, 2, 3, vegetables=50, fruits=100))
输出:
$ python3 total.py
166
当我们定义一个带星的参数,像*param
时,从那一点后所有的参数被收集为一 个叫做 ’param’ 的元组(tuple)。在该例中,首先会给initial
的值由 5 变成 10 , 然后numbers
将 1,2,3,收集作为一个元组numbers=(1,2,3)
。
类似地,当我们定义一个带两个星的参数,像**param
时,从那一点开始的 所有的关键字参数会被收集为一个叫做 ’param’ 的字典(dict)。在该例子中, 从 vegetables=50
后的所有参数收集为一个字典 keywords=’vegetables’: 50, ’fruits’: 100
。
如果已经有一个list或者tuple,要调用一个可变参数怎么办?可以这样做:
>>> nums = [1, 2, 3]
>>> calc(nums[0], nums[1], nums[2])
14
这种写法当然是可行的,问题是太繁琐,所以Python允许你在list或tuple前面加一个*
号,把list或tuple的元素变成可变参数传进去:
>>> nums = [1, 2, 3]
>>> calc(*nums)
14
*nums
表示把nums
这个list的所有元素作为可变参数传进去。这种写法相当有用,而且很常见。
Keyword-only 参数
如果想指定特定的关键参数为 keyword-only 而不是位置参数(只能通过关键字进行赋值),可以在带星的参数后申明:
#!/usr/bin/python3
# Filename: keyword_only.py
def total(initial=5, *numbers, vegetables):
count = initial
for number in numbers:
count += number
count += vegetables
return count
print(total(10, 1, 2, 3, vegetables=50))
print(total(10, 1, 2, 3,))# Raises error because we have not supplied a default argument value for 'vegetables'
输出:
$ python3 keyword_only.py
66
Traceback (most recent call last):
File "test.py", line 12, in <module>
print(total(10, 1, 2, 3))
TypeError: total() needs keyword-only argument vegetables
在带星号的参数后面申明参数会导致 keyword-only 参数。如果这些参数没有默认值,且像上面那样不给关键参数赋值,调用函数的时候会引发错误。
如果你想使用 keyword-only 参数,但又不需要带星的参数,可以简单地使用不适 用名字的空星,如 def total(initial=5, *, vegetables):
。
参数组合
在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
return 语句
return
语句用来从一个函数返回即跳出函数。我们也可选是否从函数返回一个值。
没有返回值的 return
语句等 价于 return None
。 None
是 Python 中表示没有任何东西的特殊类型。例如,如果一 个变量的值为 None
,可以表示它没有值。除非你提供你自己的 return
语句,每个函 数都在结尾暗含有 return None
语句。通过运行 print(someFunction())
,你可以明白这 一点,函数 someFunction
没有使用 return
语句,如同:
def someFunction():
pass
pass
语句在 Python 中表示一个空的语句块。
pass
语句什么都不做,那有什么用?实际上pass
可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass
,让代码能运行起来。
返回多个值
函数可以返回多个值吗?答案是肯定的。
比如在游戏中经常需要从一个点移动到另一个点,给出坐标、位移和角度,就可以计算出新的新的坐标:
import math
def move(x, y, step, angle=0):
nx = x + step * math.cos(angle)
ny = y - step * math.sin(angle)
return nx, ny
import math
语句表示导入math
包,并允许后续代码引用math
包里的sin
、cos
等函数。
然后,我们就可以同时获得返回值:
>>> x, y = move(100, 100, 60, math.pi / 6)
>>> print(x, y)
151.96152422706632 70.0
实际上返回值是一个tuple!但是,在语法上,返回一个tuple可以省略括号,而多个变量可以同时接收一个tuple,按位置赋给对应的值,所以,Python的函数返回多值其实就是返回一个tuple,但写起来更方便。
DocStrings
Python 有一个很奇妙的特性,称为文档字符串,它通常被简称为 docstrings 。 DocStrings 是一个重要的工具,由于它帮助你的程序文档更加简单易懂,你应该尽量 使用它。你甚至可以在程序运行的时候,从函数恢复文档字符串!
#!/usr/bin/python3
# Filename: func_doc.py
def printMax(x, y):
'''Prints the maximum of two numbers.
The two values must be integers.'''
x = int(x) # convert to integers, if possible
y = int(y)
if x > y:
print(x, 'is maximum')
else:
print(y, 'is maximum')
printMax(3, 5)
print(printMax.__doc__)
输出:
$ python3 func_doc.py
5 is maximum
Prints the maximum of two numbers.
The two values must be integers.
在函数的第一个逻辑行的字符串是这个函数的文档字符串。注意, DocStrings 也 适用于模块和类。
文档字符串的惯例是一个多行字符串,它的首行以大写字母开始,句号结尾。第二行是空行,从第三行开始是详细的描述。强烈建议你在你的函数中使用文档字符串时遵循这个惯例。
你可以使用__doc__
(注意双下划线)调用printMax
函数的文档字符串属性(属于函数的名称)。请记住 Python 把每一样东西都作为对象,包括这个函数。
如果你已经在 Python 中使用过 help()
,那么你已经看到过 DocStings 的使用了! 它所做的只是抓取函数的__doc__
属性,然后整洁地展示给你。你可以对上面这个函 数尝试一下 —— 只是在你的程序中包括help(printMax)
。记住按 q
退出 help
。
自动化工具也可以以同样的方式从你的程序中提取文档。因此,我强烈建议你 对你所写的任何正式函数编写文档字符串。随你的 Python 发行版附带的 pydoc 命令, 与 help()
类似地使用 DocStrings 。