Python3 学习笔记_基础篇

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组成。

  • 标识符名称是对大小写敏感的。例如, mynamemyName 不是一个标识符。注意前者中的小写 n 和后者中的大写 N

  • 有效标识符名称的例子有i__my_namename_23a1b2_c3

  • 无效标识符名称的例子: 2thingsthis is spaced outmy-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个元素,分别是ab和一个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']

生成器

廖雪峰Python教程-生成器

函数

函数是重用的程序段。它们允许你给一个语句块一个名称,然后你用这个名字可 以在你的程序的任何地方,任意多次地运行这个语句块。这被称为调用函数。
实际上之前我们一直在调用函数,例如:

>>> 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 赋给xx是函数的局部变量。所以,当我们在函数内改
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']

为什么要设计strNone这样的不变对象呢?因为不变对象一旦创建,对象内部的数据就不能修改,这样就减少了由于修改数据导致的错误。此外,由于对象不变,多任务环境下同时读取对象不需要加锁,同时读一点问题都没有。我们在编写程序时,如果可以设计一个不变对象,那就尽量设计成不变对象。

关键参数

如果你的某个函数有许多参数,而你只想指定其中的一部分,那么你可以通过命 名来为这些参数赋值 —— 这被称作关键参数 —— 我们使用名字(关键字)而不是位 置(我们前面所一直使用的方法)来给函数指定实参。
这样做有两个优势 ——
一、由于我们不必担心参数的顺序,使用函数变得更加 简单了。
二、假设其他参数都有默认值,我们可以只给我们想要的那些参数赋值。

#!/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 NoneNone 是 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包里的sincos等函数。

然后,我们就可以同时获得返回值:

>>> 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 。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容

  • 最近在慕课网学习廖雪峰老师的Python进阶课程,做笔记总结一下重点。 基本变量及其类型 变量 在Python中,...
    victorsungo阅读 1,656评论 0 5
  • 一、python 变量和数据类型 1.整数 Python可以处理任意大小的整数,当然包括负整数,在Python程序...
    绩重KF阅读 1,648评论 0 1
  • http://python.jobbole.com/85231/ 关于专业技能写完项目接着写写一名3年工作经验的J...
    燕京博士阅读 7,548评论 1 118
  • 本教程基于Python 3,参考 A Byte of Python v1.92(for Python 3.0) 以...
    yuhuan121阅读 3,038评论 1 6
  • 巴爷小程序商店私信发小程序压缩包2589593980@qq.com
    Sivan007阅读 229评论 0 0