Python基础学习

基础知识

数字和表达式

除法

普通情况下,整数被另一个整数除,结果只保留整数部分。

1
2
>>> 1/2
0

要执行普通的除法,有两种方法:

  1. 使用浮点数(Float)

    1
    2
    3
    4
    5
    6
    7
    8
    >>> 1.0 / 2.0
    0.5

    >>> 1 / 2.0
    0.5

    >>> 1 / 2.
    0.5
  2. 在程序前加上下面这句:

    1
    from __future__ import division

整除操作符

python提供了另一个用于实现整除的操作符——双斜线,即使是浮点数,也会执行整除:

1
2
3
4
5
>>> 1 // 2
0

>>> 1.0 // 2.0
0.0

幂运算符

1
2
3
4
5
6
>>> 2 ** 3
8
>>> -3 ** 2
-9
>>> (-3) ** 2
9

幂运算符的优先级高于取反。

变量

变量名可以包括字母,数字和下划线,但是变量不能以数字开头。

模块

普通模块导入按照“模块.函数”的格式使用这个模块的函数

1
2
3
>>> import math
>>> math.floor(32.9)
32.0

如果确认自己不会导入多个同名函数(从不同的模块导入),可以使用另一种导入模块的方式,这样每次调用函数的时候就不需要写上模块的名字了

1
2
3
>>> from math import sqrt
>>> sqrt(9)
3.0

为了不写模块名字,还可以通过变量来引用函数

1
2
3
4
>>> import math
>>> foo = math.sqrt
>>> foo(4)
2.0

__future__

通过__future__可以导入那些在未来会成为python组成部分的新特征。

序列

Python有6种内建序列,

  1. 列表
  2. 元组
  3. 字符串
  4. Unicode字符串
  5. buffer对象
  6. xrange对象

序列主要用来操作一组数值。
比如可以表示一个人的信息,下面的序列第一个元素是姓名,第二个元素是年龄。
可以看到,序列的元素不需要是同一类型,序列也可以包括其他序列。

1
2
3
4
5
>>> edawrd = ['Edward Gumby', 42]
>>> john = ['John Smith', 50]
>>> database = [edward, john]
>>> database
[['Edward Gumby', 42], ['John Smith', 50]]

通用序列操作

所有的序列类型都可以进行一些操作,包括:索引(indexing),分片(sliceing),加(adding),乘(multiplying)以及检查某个元素是否属于序列。
初次之外,Python还有计算序列长度,找到最大元素和最小元素的内建函数。

索引

序列中所有元素都是从0开始编号的。
可以直接通过编号访问:

1
2
3
>>> greeting = 'Hello'
>>> greeting[0]
'H'

可以看到,字符串就是一个由字符组成的序列。
可以通过索引获取元素,所有序列都可以通过这种方式进行索引。
使用负数索引时,Python就会从右边,也就是最后一个元素开始计数。
最后一个元素的位置编号是-1(不是-0,因为这样的话,会跟第一个元素重合)。

1
2
3
4
5
>>> squares = [1, 4, 9, 16, 25]
>>> squares[0]
1
>>> squares[-1]
25

分片

分片操作主要用来访问一定范围内的元素。
分片通过冒号相隔的两个索引来实现。
第一个索引是需要提取部分的第一个元素的编号,第二个索引则是分片后剩下部分的第一个元素的编号。

1
2
3
4
5
6
7
8
9
10
11
>>> tag = '<a href="http://www.python.org">Python web site</a>'
>>> tag[9:30]
'http://www.python.org'
>>> tag[32:-4]
'Python web site'

>>> numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> numbers[3:6]
[4, 5, 6]
>>> numbers[0:1]
[1]

捷径

如果要访问最后三个元素,可以显示操作:

1
2
3
>>> numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> numbers[7:10]
[8, 9, 10]

索引10指向了第11个元素,这个元素并不存在,但是却在最后一个元素的后面。
但如果想从队尾开始计数就麻烦了:

1
2
3
4
>>> numbers[-3:-1]
[8, 9]
>>> numbers[-3:0]
[]

正确的做法就是置空最后一个索引:

1
2
>>> numbers[-3:]
[8, 9, 10]

这种方式同样适用于序列开始:

1
2
>>> numbers[:3]
[1, 2, 3]

如果要复制整个序列,设置可以将两个索引都置空:

1
2
>>> numbers[:]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

步长

分片需要制定开始和计数的索引,其实,还有另一个隐藏的参数——步长(step length)。
普通分片中,步长默认为1,如果重设步长,就会跳步:

1
2
3
4
5
6
7
8
>>> numbers[0:10:1]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> numbers[0:10:2]
[1, 3, 5, 7, 9]
>>> numbers[3:6:3]
[4]
>>> numbers[::4]
[1, 5, 9]

步长不能为0,那样就不会向下执行了,会报异常。
但步长可以为负数,就会从右到左提取元素:

1
2
3
4
5
6
7
8
9
10
11
12
>>> numbers[8:3:-1]
[9, 8, 7, 6, 5]
>>> numbers[10:0:-2]
[10, 8, 6, 4, 2]
>>> numbers[0:10:-2]
[]
>>> numbers[::-2]
[10, 8, 6, 4, 2]
>>> numbers[5::-2]
[6, 4, 2]
>>> numbers[:5:-2]
[10, 8]

加号可以执行序列的连接操作:

1
2
3
4
>>> [1, 2, 3] + [4, 5, 6]
[1, 2, 3, 4, 5, 6]
>>> 'Hello ' + "World"
'Hello World'

但是要注意,同类型的序列才可以进行连接操作,比如字符串和列表相加会抛异常

数字n乘以序列会生成新的序列,新的序列中,旧的序列被重复了n次。

1
2
3
4
>>> 'python' * 5
'pythonpythonpythonpythonpython'
>>> [42] * 3
[42, 42, 42]

如果想初始化一个长度为10的空列表:

1
2
>>> sequence = [None] * 10
[None, None, None, None, None, None, None, None, None, None]

成员资格

in运算符用来检查一个值是否在序列中:

1
2
3
4
5
>>> permission = 'rw'
>>> 'w' in permission
True
>>> 'x' in permission
False

长度、最大值和最小值

长度、最大值和最小值对应的内建函数分别是lenmaxmin

1
2
3
4
5
6
7
8
9
10
11
>>> numbers = [100, 34, 678]
>>> len(numbers)
3
>>> max(numbers)
678
>>> min(numbers)
34
>>> max(2, 3)
3
>>> min(9, 3, 2, 5)
2

列表和元组

列表和元组主要区别在于,列表可以修改,元组则不能
几乎所有情况下列表都可以取代元组,除了一种例外:使用元组作为字典的键,这种情况下,因为键不可以修改,所以就不能使用列表

列表

列表是可变的,而且有很多专有方法。

list函数

字符串不能像列表一样被修改,有时候需要根据字符串创建列表,这个操作通过list函数进行。

1
2
>>> list('Hello')
['H', 'e', 'l', 'l', 'o']

list函数适用于所有的序列

基本列表操作

除了所有的序列基本操作,列表还有很多属于自己的方法:元素赋值,元素删除,分片赋值等等。

元素赋值
1
2
3
4
>>> x = [1, 1, 1]
>>> x[1] = 2
>>> x
[1, 2, 1]

不能给位置不存在的元素进行赋值,会抛异常。

元素删除

删除元素通过del语句实现

1
2
3
4
>>> names = ['Alice', 'Beth', 'Cecil']
>>> del names[1]
>>> names
['Alice', 'Cecil']

分片赋值
1
2
3
4
5
6
>>> name = list('Perl')
>>> name
['P', 'e', 'r', 'l']
>>> name[2:] = list('ar')
>>> name
['P', 'e', 'a' 'r']

分片赋值还可以使用与原序列不等长的序列将分片替换

1
2
3
4
>>> name = list('Perl')
>>> name[1:] = list('ython')
>>> name
['P', 'y', 't', 'h', 'o', n]

分片赋值也可以在不替换任何原有元素的情况下插入新的元素

1
2
3
4
>>> numbers = [1, 5]
>>> numbers[1:1] = [2, 3, 4]
>>> numbers
[1, 2, 3, 4, 5]

甚至可以通过分片赋值删除元素

1
2
3
4
>>> numbers = [1, 2, 3, 4, 5]
>>> numbers[1:4] = []
>>> numbers
[1, 5]

列表方法

append

1
2
3
4
>>> lst = [1, 2, 3]
>>> lst.append(4)
>>> lst
[1, 2, 3, 4]

count

1
2
3
4
5
6
7
>>> ['to', 'be', 'or', 'not', 'to', 'be'].count('to')
2
>>> x = [[1, 2], 1, 1, [2, 1, [1, 2]]]
>>> x.count(1)
2
>>> x.count([1, 2])
1

extend

1
2
3
4
5
>>> a = [1, 2, 3]
>>> b = [4, 5]
>>> a.extend(b)
>>> a
[1, 2, 3, 4, 5]

extend与连接操作的区别在于:
extend方法修改了被扩展的序列,而原始的连接操作则会返回一个全新的列表

1
2
3
4
5
6
>>> a = [1, 2, 3]
>>> b = [4, 5]
>>> a + b
[1, 2, 3, 4, 5]
>>> a
[1, 2, 3]

也可以通过以下方式实现,但是效率没有extend高

1
>>> a = a + b

或者

1
a[len(a):] = b

index
返回从列表中找出某个值第一个匹配项的索引位置

1
2
3
4
5
>>> knights = ['we', 'are', 'the', 'knights', 'who', 'say', 'ni']
>>> knights.index('who')
4
>>> knights.index('herring')
ValueError: list.index(x): x not in list

如果搜索的元素不在列表中,会抛异常。

insert
insert用于插入元素到列表中:

1
2
3
4
>>> numbers = [1, 2, 3, 5, 6]
>>> numbers.insert(3, 'four')
>>> numbers
[1, 2, 3, 'four', 5, 6]

extend方法一样,insert方法的操作也可以通过分片赋值来实现

1
2
3
4
>>> numbers = [1, 2, 3, 5, 6]
>>> numbers[3:3] = ['four']
>>> numbers
[1, 2, 3, 'four', 5, 6]

只是这样实现的可读性比较差。

pop
pop方法移除列表中的一个元素(默认最后一个),并返回该元素的值:

1
2
3
4
5
6
7
8
9
>>> x = [1, 2, 3]
>>> x.pop()
3
>>> x
[1, 2]
>>> x.pop(0)
1
>>> x
[2]

remove
remove方法移除列表中某个值的第一个匹配项

1
2
3
4
5
6
>>> x = ['to', 'be', 'or', 'not', 'to', 'be']
>>> x.remove("be")
>>> x
['to', 'or', 'not', 'to', 'be']
>>> x.remove('bee')
ValueError: list.remove(x): x not in list

remove方法没有返回值,跟pop不一样。

reverse

1
2
3
4
>>> x = [1, 2, 3]
>>> x.reverse()
>>> x
[3, 2, 1]

sort

1
2
3
4
>>> x = [4, 6, 2, 1, 7, 9]
>>> x.sort()
>>> x
[1, 2, 4, 6, 7, 9]

注意,sort方法对原列表进行排序,这意味着改变了原列表,并不是返回一个已排序的列表副本。
所以,如果需要一个排序的副本,下面的做法是错误的:

1
2
3
4
>>> x = [4, 6, 2, 1, 7, 9]
>>> y = x.sort()
>>> print y
None

因为sort方法修改了x却返回了空值。
实现返回序列排序副本的正确方法是先将x的副本赋值给y,然后对y进行排序:

1
2
3
4
5
6
7
>>> x = [4, 6, 2, 1, 7, 9]
>>> y = x[:]
>>> y.sort()
>>> x
[4, 6, 2, 1, 7, 9]
>>> y
[1, 2, 4, 6, 7, 9]

简单的将x赋值给y是没用的,这就让x和y指向了同一个列表:

1
2
3
4
5
6
>>> y = x
>>> y.sort()
>>> x
[1, 2, 4, 6, 7, 9]
>>> y
[1, 2, 4, 6, 7, 9]

另外一种获取已排序副本的方法就是使用sorted函数

1
2
3
4
5
6
>>> x = [4, 6, 2, 1, 7, 9]
>>> y = sorted(x)
>>> x
[4, 6, 2, 1, 7, 9]
>>> y
[1, 2, 4, 6, 7, 9]

sorted函数可以用于任何序列,但是总是返回一个列表:

1
2
>>> sorted('Python')
['P', 'h', 'n', 'o', 't', y]

元组

元组跟列表一样,是一种序列,但是元组不能够修改。
创建元组的方式很简单,用逗号分隔一些值,就自动创建了元组:

1
2
>>> 1, 2, 3
(1, 2, 3)

也可以通过圆括号括起来:

1
2
>>> (1, 2, 3)
(1, 2, 3)

要实现只包括一个值的元素,必须加括号:

1
2
3
4
5
6
7
8
>>> 42
42
>>> (42)
42
>>> 42,
(42,)
>>> (42,)
(42,)

一个逗号能改变很多东西,比如:

1
2
3
4
>>> 3 * (40 + 2)
126
>>> 3 * (40 + 2,)
(42, 42, 42)

tuple

tuple函数以一个序列为参数并将其转换为元组。

1
2
3
4
5
6
>>> tuple([1, 2, 3])
(1, 2, 3)
>>> tuple('abc')
('a', 'b', 'c')
>>> tuple((1, 2, 3))
(1, 2, 3)

元组操作

差不多就是序列操作

元组意义
  • 元组可以在映射(和集合的成员)中当做键使用,而列表不行
  • 元组作为很多内建函数的返回值。

字符串

字符串相加

字符串可以由 + 操作符连接(粘到一起),可以由 * 表示重复

1
2
3
>>> # 3 times 'un', followed by 'ium'
>>> 3 * 'un' + 'ium'
'unununium'

字符串索引和切片

字符串也可以被截取(检索),类似于 C ,字符串的第一个字符索引为 0 。
Python没有单独的字符类型;一个字符就是一个简单的长度为1的字符串。

1
2
3
4
5
>>> word = 'Python'
>>> word[0] # character in position 0
'P'
>>> word[5] # character in position 5
'n'

索引也可以是负数,这将导致从右边开始计算。例如:

1
2
3
4
5
6
>>> word[-1]  # last character
'n'
>>> word[-2] # second-last character
'o'
>>> word[-6]
'P'

注意 -0 实际上就是 0,所以它不会导致从右边开始计算。
除了索引,还支持切片。索引用于获得单个字符,切片让你获得一个子字符串:

1
2
3
4
5
>>> word = 'Python'
>>> word[0:2] # characters from position 0 (included) to 2 (excluded)
'Py'
>>> word[2:5] # characters from position 2 (included) to 5 (excluded)
'tho'

包含起始的字符,不包含末尾的字符。这使得 s[:i] + s[i:] 永远等于 s:

1
2
3
4
>>> word[:2] + word[2:]
'Python'
>>> word[:4] + word[4:]
'Python'

切片的索引有非常有用的默认值;省略的第一个索引默认为零,省略的第二个索引默认为切片的字符串的大小。

1
2
3
4
5
6
>>> word[:2]  # character from the beginning to position 2 (excluded)
'Py'
>>> word[4:] # characters from position 4 (included) to the end
'on'
>>> word[-2:] # characters from the second-last (included) to the end
'on'

试图使用太大的索引会导致错误。

1
2
3
4
>>> word[42]  # the word only has 6 characters
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: string index out of range

Python能够优雅地处理那些没有意义的切片索引:
一个过大的索引值(即下标值大于字符串实际长度)将被字符串实际长度所代替,当上边界比下边界大时(即切片左值大于右值)就返回空字符串:

1
2
3
4
>>> word[4:42]
'on'
>>> word[42:]
''

Python字符串不可以被更改——它们是不可变的。因此,赋值给字符串索引的位置会导致错误:

1
2
3
4
5
6
>>> word[0] = 'J'
...
TypeError: 'str' object does not support item assignment
>>> word[2:] = 'py'
...
TypeError: 'str' object does not support item assignment

字符串表示,str和repr

原始的print会显示值在python中的状态

1
2
3
4
5
6
7
8
>>> "Hello World"
'Hello World'
>>> 10000L
10000L
>>> print "Hello World"
Hello World
>>> print 10000L
10000

实际情况中,可能对一个值的类型感兴趣。

str函数

str函数会把值转换成合理形式的字符串

1
2
3
4
>>> print str("Hello World")
Hello World
>>> print str(10000L)
10000

repr函数

repr函数会创建一个字符串,以合法的python表达式的形式来表示值

1
2
3
4
>>> print repr("Hello World")
'Hello World'
>>> print repr(10000L)
10000L

反引号

repr(x)的功能也可以通过x实现(`是反引号,不是单引号)。
如果希望打印一个包含数字的句子,反引号就很有用

1
2
3
>>> temp = 12
>>> print "The num is " + `temp`
The num is 12

直接用print “The num is” + temp会报错,因为字符串不可以和数字相加

简而言之,str,repr和反引号是将Python值转换成字符串的3种方法。
函数str让字符串更容易阅读,而repr和反引号则把结果字符串转换成合法的Python表达式。

input和raw_input

在一个脚本文件中输入以下语句:

1
2
name = input("What is your name? ")
print "Hello, " + name + "!"

这个程序合法,但input会假设用户输入的是合法的Python表达式,所以,如果用户输入 XXX 就会报错,必须以字符串作为输入 ‘XXX’。
如果使用raw_input函数就没问题,它会把所有输入当做原始数据(raw data),将其放入字符串:

1
2
3
4
5
6
>>> input("Enter a number: ")
Enter a number: 3
3
>>> raw_input("Enter a number: ")
Enter a number: 3
'3'

长字符串

如果需要写一个非常非常长的字符串,需要跨多行,就可以使用三个引号代替普通引号。

1
2
3
4
5
print '''This is a very long string.
It continues here.
And it's not over yet.
"Hello World"
Still here.'''

也可以使用三个双引号,比如”””Like this”””。
注意,可以在字符串中同时使用双引号和单引号,而不需要使用反斜线进行转义。

普通跨行

普通字符串也可以跨行,如果一行之中最后一个字符是反斜线,那么,换行符本身就”转义“了,也就是被忽略了。

1
2
print "Hello, \
World!"

这句会打印Hello World!。
这个用法也适用于表达式和语句

1
2
3
4
5
6
>>> 1 + 2 + \
4 + 5
12
>>> print \
'Hello, World'
Hello World

原始字符串

原始字符串对反斜线比较友好

1
2
3
4
5
6
>>> path = 'C:\nowhere'
>>> path
'C:\nowhere'
>>> print path
C:
owhere

可以使用 \\,但是如果路径很长,就惨了。
这个时候,就可以使用原始字符串。

1
2
print r'C:\nowhere'
C:\nowhere

原始字符串以 r 开头,基本可以认为可以在原始字符串中放入任何字符。
但是,原始字符串最后一个字符不能是反斜线,因为如果最后一个字符是反斜线,Python就不知道是否应该结束该字符串。
如果最后一个字符串需要是反斜线,一个简单的方法是:

1
2
>>> print r'C:\Program Files\foo\bar' '\\'
C:\Program Files\foo\bar\

字符串常用函数

  • s.lower(), s.upper()
  • s.strip() —— 移除开头结尾的空格,中间的空格不移除
  • s.isalpha()/s.isdigit()/s.isspace()
  • s.startswith(‘other’), s.endswith(‘other’)
  • s.find(‘other’)
  • s.replace(‘old’, ‘new’)
  • s.split(‘delim’) —— ‘aaa,bbb,ccc’.split(‘,’) -> [‘aaa’, ‘bbb’, ‘ccc’]
  • s.join(list) —— ‘—‘.join([‘aaa’, ‘bbb’, ‘ccc’]) -> aaa—bbb—ccc

集合与字典

字典

字典就是键值对,字典的键可以是任何不可变类型,比如整型,浮点型,字符串或者元组。
字典可以通过以下形式创建:

1
phonebook = {'Alice' : '2341', "Beth" : "9102", "Cecil" : '3258'}

可以看到,字典键值之间用 : 分隔,项之间用 , 分隔,而整个字典则通过一对大括号括起来。
字典有个重要特性就是——自动添加:一个键在字典中不存在,也可以为其分配一个值,这样字典就会建立新的项。

1
2
3
4
5
6
7
>>> x = []
>>> x[42] = 'Foobar'
IndexError: list assignment index out of range
>>> x = {}
>>> x[42] = 'Foobar'
>>> x
{42: 'Foobar'}

dict函数

dict函数通过其它映射(比如其它字典)或者(键,值)这样的序列来建立字典。

1
2
3
4
5
6
>>> items = [('name', 'Gumby'), ('age', 42)]
>>> d = dict(items)
>>> d
{'age': 42, 'name': 'Gumby'}
>>> d['name']
'Gumby'

dict函数也可以通过关键字参数来创建字典,比如:

1
2
3
>>> d = dict(name='Gumby', age=42)
>>> d
{'age': 42, 'name': 'Gumby'}

字典基本操作

字典的基本操作类似序列,主要包括:

  • len(d) 返回键值对的数量
  • d[k] 返回关联到键k上的值
  • d[k] = v 将值v关联到键k上
  • del d[k] 删除键为k的项
  • k in d 检查d中是否有键为k的项

字典方法

clear
clear方法清除字典里所有的项。
这是一个原地操作,没有返回值,或者说返回None

1
2
3
4
5
6
7
8
9
10
>>> d = {}
>>> d['name'] = 'Gumby'
>>> d['age'] = 42
>>> d
{'age': 42, 'name': 'Gumby'}
>>> returned_value = d.clear()
>>> d
{}
>>> returned_value
None

copy
copy方法实现的是浅复制

1
2
3
4
5
6
7
8
>>> x = {'username': 'admin', 'machines': ['foo', 'bar', 'baz']}
>>> y = x.copy()
>>> y['username'] = 'mlh'
>>> y['machines'].remove('bar')
>>> y
{'username': 'mlh', 'machines': ['foo', 'baz']}
>>> x
{'username': 'admin', 'machines': ['foo', 'baz']}

可以看到,当在副本里面替换值的时候,原始字典不受影响,但是如果修改了某个值,注意,原地修改,不是替换,原始的字典也会改变。因为同样的值也存储在原字典中。
避免这个问题的一种方法就是使用深拷贝,复制其包含的所有值。使用copy模块的deepcopy函数完成。

1
2
3
4
5
6
7
8
9
10
>>> from copy import deepcopy
>>> d = {}
>>> d['names'] = ['Alfred', 'Beth']
>>> c = d.copy()
>>> dc = d.deepcopy()
>>> d['names'].append('Clive')
>>> c
['Alfred', 'Beth', 'Clive']
>>> dc
['Alfred', 'Beth']

fromkeys
fromkeys方法会使用给定的键建立新的字典,每个键默认对应的值为None。

1
2
>>> {}.fromkeys(['name', 'age'])
{'age': None, 'name': None}

也可以提供自己的默认值

1
2
>>> {}.fromkeys(['name', 'age'], 'unknown')
{'age': 'unknown', 'name': 'unknown'}

get
get方法用来访问字典,它的作用是,如果试图访问字典中不存在的项也不会报错

1
2
3
>>> d = {}
>>> print d['name']
KeyError: 'name'

而用get就不会

1
2
>>> print d.get('name')
>>> None

可以看到,用get访问一个不存在的键时,没有任何异常,得到的是None值。
还可以自定义默认值,替换None:

1
2
>>> d.get('name', 'N/A')
'N/A'

如果键存在,get用起来跟普通的字典查询一样。

has_key
has_key用来检查字典中是否含有给出的键。
表达式d.has_key(k)相当于表达式k in d
使用方式取决于个人喜好,但是注意,python3.0中不包含这个函数

1
2
3
4
5
6
>>> d = {}
>>> d.has_key('name')
False
>>> d['name'] = 'Eric'
>>> d.has_key('name')
True

itemsiteritems
items方法将字典以列表方式返回,但返回时没有特殊顺序。

1
2
3
>>> d = {'title': 'Python Web Site', 'url': 'http://www.python.org', 'spam': 0}
>>> d.items()
[('url', 'http://www.python.org'), ('spam', 0), ('title', 'Python Web Site')]

iteritems返回一个迭代器对象而不是列表:

1
2
3
4
5
>>> it = d.iteritems()
>>> it
<dictionary-itemiterator object at 0x10e4e2788>
>>> list(it) # Convert the iterator to a list
[('url', 'http://www.python.org'), ('spam', 0), ('title', 'Python Web Site')]

keysiterkeys
同上,只是返回的是键

valuesitervalues

同上,以列表形式返回值,但是与返回键不同,返回值的列表中可以包含重复的元素。

pop
pop用来获得给定键对应的值,同时将键值对从字典中移除

1
2
3
4
5
>>> d = {'x': 1, 'y': 2}
>>> d.pop('x')
1
>>> d
{'y': 2}

popitem
popitem方法类似于list.pop,后者会弹出列表的最后一个元素,但不同的是,popitem弹出随机的项,因为字典没有“最后的元素”或者相关顺序的概念。
若想一个接一个的移除并处理项,这个方法就很有用。

update
update方法会利用一个字典去更新另一个字典。

1
2
3
4
5
>>> d = {'name': 'Alice', 'age': 21}
>>> x = {'name': 'Beth', 'home': 'China'}
>>> d.update(x)
>>> d
{'home': 'China', 'age': 21, 'name': 'Beth'}

可以看到,提供的字典中的项会被添加到旧的字典中,若有相同的键则会覆盖。

集合

Python的Set跟其他语言类似,就是一个无序不重复数组。
作为一个无序的集合,Set不记录元素的插入位置或者插入点。
想要创建空集合,你必须使用 set() 而不是 {}。后者用于创建空字典。
一些例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>>> basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
>>> fruit = set(basket) # create a set without duplicates
>>> fruit
set(['orange', 'pear', 'apple', 'banana'])
>>> 'orange' in fruit # fast membership testing
True
>>> 'crabgrass' in fruit
False

>>> # Demonstrate set operations on unique letters from two words
...
>>> a = set('abracadabra')
>>> b = set('alacazam')
>>> a # unique letters in a
set(['a', 'r', 'b', 'c', 'd'])
>>> a - b # letters in a but not in b
set(['r', 'd', 'b'])
>>> a | b # letters in either a or b
set(['a', 'c', 'r', 'd', 'b', 'm', 'z', 'l'])
>>> a & b # letters in both a and b
set(['a', 'c'])
>>> a ^ b # letters in a or b but not both
set(['r', 'd', 'b', 'm', 'z', 'l'])

集合基本操作

  • len(s)
    set 的长度
  • x in s
    测试 x 是否是 s 的成员
  • x not in s
    测试 x 是否不是 s 的成员
  • s.issubset(t) 或者 s <= t
    测试是否 s 中的每一个元素都在 t 中
  • s.issuperset(t) 或者 s >= t
    测试是否 t 中的每一个元素都在 s 中
  • s.union(t) 或者 s | t
    返回一个新的 set 包含 s 和 t 中的每一个元素
  • s.intersection(t) 或者 s & t
    返回一个新的 set 包含 s 和 t 中的公共元素
  • s.difference(t) 或者 s - t
    返回一个新的 set 包含 s 中有但是 t 中没有的元素
  • s.symmetric_difference(t) 或者 s ^ t
    返回一个新的 set 包含 s 和 t 中不重复的元素
  • s.copy()
    返回 set “s”的一个浅复制
  • s.update(t) 或者 s |= t
    返回增加了 set “t”中元素后的 set “s”
  • s.intersection_update(t) 或者 s &= t
    返回只保留含有 set “t”中元素的 set “s”
  • s.difference_update(t) 或者 s -= t
    返回删除了 set “t”中含有的元素后的 set “s”
  • s.symmetric_difference_update(t) 或者 s ^= t
    返回含有 set “t”或者 set “s”中有而不是两者都有的元素的 set “s”
  • s.add(x)
    向 set “s”中增加元素 x
  • s.remove(x)
    从 set “s”中删除元素 x, 如果不存在则引发 KeyError
  • s.discard(x)
    如果在 set “s”中存在元素 x, 则删除
  • s.pop()
    删除并且返回 set “s”中的一个不确定的元素, 如果为空则引发 KeyError
  • s.clear()
    删除 set “s”中的所有元素

语句

import

从模块中导入函数,一般可以这样:

1
2
3
4
5
6
7
import somemodule

from somemodule import somefunction

from somemodule import somefunction, anotherfunction

from somemodule import *

如果两个模块都含有同一个函数,比如open(),就要使用第一种,然后:

1
2
module1.open()
module2.open()

还可以使用as语句,为模块提供别名:

1
2
3
>>> import math as foobar
>>> foobar.sqrt(4)
2.0

除了为模块提供别名,还可以为函数提供别名:

1
2
3
>>> from math import sqrt as foobar
>>> foobar(4)
2.0

所以回到上面的两个模块都含有open()函数的情况,就可以这样:

1
2
from module1 import open as open1
from module2 import open as open2

赋值

序列解包

赋值操作可以多个同时进行

1
2
3
>>> x, y, z = 1, 2, 3
>>> print x, y, z
1 2 3

也可以交换两个(或者多个)变量

1
2
3
>>> x, y = y, x
>>> print x, y, z
2 1 3

这种行为叫做序列解包,或可迭代解包——将多个值的序列解开,然后放到变量的序列中。
更形象一点就是:

1
2
3
4
5
6
>>> value = 1, 2, 3
>>> value
(1, 2, 3)
>>> x, y, z = values
>>> x
1

当函数返回元组(或者其他序列或可迭代对象)时,这个特性就非常有用了。

1
2
3
4
5
6
>>> scoundrel = {'name': 'Robin', 'girlfriend': 'Marion'}
>>> key, value = scoundrel.popitem()
>>> key
'girlfriend'
>>> value
'Marion'

可以看到,popitem()会获取字典中任意的键值对并返回,然后这个元组正好赋值给了我们的两个变量。
注意,所解包序列中的元素数量必须和放置在赋值符号=左边的变量数量完全一致,否则会抛异常。

链式赋值

链式赋值可以将同一个值赋给多个变量。

1
x = y = function()

增量赋值

1
2
3
4
5
6
7
8
9
10
11
>>> x = 2
>>> x += 1
>>> x *= 2
>>> x
6

>>> s = 'foo'
>>> s += 'bar'
>>> s *= 2
>>> s
'foobarfoobar'

条件语句

布尔变量

下面这些值作为布尔表达式的时候,会被解释器看做假(false):

1
False  None  0  ''  ()  []  {}

除此之外的一切都会被解释为真。
因为所有值都可以作为布尔类型,所以几乎不需要对它们进行显式转换。

循环语句

for语句

Python中的for语句和 C 或 Pascal 中的略有不同。通常的循环可能会依据一个等差数值步进过程(如Pascal),或由用户来定义迭代步骤和中止条件(如C),Python的for语句依据任意序列(链表或字符串)中的子项,按它们在序列中的顺序来进行迭代,例如:

1
2
3
4
5
6
7
8
>>> # Measure some strings:
... words = ['cat', 'window', 'defenestrate']
>>> for w in words:
... print w, len(w)
...
cat 3
window 6
defenestrate 12

range函数

1
2
3
4
5
6
7
8
>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> range(5, 10)
[5, 6, 7, 8, 9]
>>> range(0, 10, 3)
[0, 3, 6, 9]
>>> range(-10, -100, -30)
[-10, -40, -70]

需要迭代链表索引的话,如下所示结合使用range()len()

1
2
3
4
5
6
7
8
9
>>> a = ['Mary', 'had', 'a', 'little', 'lamb']
>>> for i in range(len(a)):
... print i, a[i]
...
0 Mary
1 had
2 a
3 little
4 lamb

一些迭代

并行迭代

同时迭代两个序列可以如下:

1
2
3
4
5
names = ['anne', 'beth', 'george', 'damon']
ages = [12, 45, 32, 102]

for i in range(len(names)):
print names[i], 'is', ages[i], 'years old'

这里的i是循环索引的标准变量名。
内建的zip函数可以用来进行并行迭代,将两个序列“压缩”起来,然后返回一个元组的列表:

1
2
>>> zip(names, ages)
[('anne', 12), ('beth', 45), ('george', 32), ('damon', 102)]

现在可以在循环中解包元组:

1
2
for name, age in zip(names, ages):
print name, 'is', age, 'years old'

zip函数可以作用于任意多的序列。
重要的是,zip函数可以用于不等长的序列,结束时机是最短序列结束

1
2
>>> zip(range(5), xrange(1000000000))
[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]

在上面的示例中,range会计算所有数字,但是xrange只会计算前五个。

编号迭代

有的时候想要迭代序列中的对象,同时还要获取当前对象的索引。
例如,在一个字符串列表中替换所有包含’xxx’的子字符串。
一般想到的方法如下:

1
2
3
4
for string in strings:
if 'xxx' in string:
index = strings.index(string) # search for the string in the list of strings
strings[index] = '[censored]'

上面这段代码,替换前要搜索给定的字符串似乎没有必要。
一个比较好的版本是:

1
2
3
4
5
index = 0
for string in strings:
if 'xxx' in string:
strings[index] = '[censored]'
index++;

这个方法有点笨,但是可以被接受。
更好的方法是使用内建的enumerate函数:

1
2
3
for index, string in enumerate(strings):
if 'xxx' in string:
strings[index] = '[censored]'

翻转和排序迭代

看看另外两个有用的函数:reversedsorted:它们同列表的reverse和sort方法类似,但作用于任何排序或可迭代对象上,不是原地修改对象,而是返回翻转或排序后的版本。

1
2
>>> sorted([4, 3, 6, 8, 3])
[3, 3, 4, 6, 8]

函数

定义函数

定义函数可以使用def语句:

1
2
3
4
5
def fibs(num):
result = [0, 1]
for i in range(num - 2):
result.append(result[-2] + result[-1])
return result

执行了这段语句后,编译器就知道如何计算斐波那契数列了.
现在不需要关注细节,只要用函数fibs就行了。

1
2
3
4
>>> fibs(10)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
>>> fibs(15)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]

函数文档

如果在函数的开头写下字符串,就会作为函数的一部分进行存储,这称为文档字符串

1
2
3
def square(x):
'Calculates the square of the number x.'
return x * x

文档字符串可以按如下方式访问:

1
2
>>> square.__doc__
'Calculates the square of the number x.'

__doc__是函数属性,属性名中的双下划线表示其为一个特殊属性。

函数参数

参数是否可变?

函数通过其参数获取一系列的值,那这些值能改变吗?
先看这种情况:

1
2
3
4
5
6
7
>>> def try_to_change(n):
n = 'Mr. Gumby'

>>> name = 'Mrs. Entity'
>>> try_to_change(name)
>>> name
'Mrs. Entity'

参数存储在局部作用域中。
在try_to_change内,参数n获取了新值,但是这并没有影响到name变量。
n实际上是一个完全不同的变量,具体工作方式类似于:

1
2
3
4
5
>>> name = 'Mrs. Entity'
>>> n = name # 这句话基本相当于传参
>>> n = 'Mr. Gumby' # 在函数内部完成
>>> name
'Mrs. Entity'

结果显然是改变n的时候,变量name不变。
字符串是不可变的,但如果参数是可变的数据结构如列表的时候呢?

1
2
3
4
5
6
7
>>> def change(n):
n[0] = 'Mr. Gumby'

>>> names = ['Mrs. Entity', 'Mrs. Thing']
>>> change(names)
>>> names
['Mr. Gumby', 'Mrs. Thing']

为什么呢?不用函数模拟一次:

1
2
3
4
5
>>> names = ['Mrs. Entity', 'Mrs. Thing']
>>> n = names # 模拟传参
>>> n[0] = 'Mr. Gumby'
>>> names
['Mr. Gumby', 'Mrs. Thing']

所以实际情况就是两个变量同时引用了一个列表。
为了避免这种情况,可以复制一个列表的副本

1
2
3
>>> change(names[:])
>>> names
['Mrs. Entity', 'Mrs. Thing']

关键字参数和默认值

前面提到的参数都是位置参数,其位置比其名字更加重要。
考虑下面两个函数:

1
2
3
4
5
def hello_1(greeting, name):
print '%s, %s!' % (greeting, name)

def hello_2(name, greeting):
print '%s, %s!' % (name, greeting)

这两个函数只是参数名字反过来了,实现的功能是完全一样的。

1
2
3
4
>>> hello_1('Hello', 'World')
Hello, World!
>>> hello_2('Hello', 'World')
Hello, World!

有些时候,参数的顺序很难记住,简单起见,可以提供参数的名字。

1
2
>>> hello_1(greeting='Hello', name='World')
Hello, World!

这样的话,参数的位置也就是顺序就没影响了:

1
2
>>> hello_1(name='World', greeting='Hello')
Hello, World!

不过要注意参数名和值一定要对应

1
2
>>> hello_2(greeting='Hello', name='World')
World, Hello!

这类使用参数名提供的参数叫做关键字参数
其主要作用是明确每个参数的作用,避免让人不知所云的函数调用。
而且即使弄乱了参数的顺序,也不会影响程序的功能。
当然,关键字参数最强大的功能还是为参数提供默认值

1
2
def hello_3(greeting='Hello', name='World'):
print '%s, %s!' % (greeting, name)

当参数有默认值的时候,调用的时候,就可以不提供、提供一些、提供全部参数。

1
2
3
4
5
6
>>> hello_3()
Hello, World!
>>> hello_3('Greetings')
Greetings, World!
>>> hello_3('Greeting', 'Universe')
Greeting, Universe!

如果只想给name提供参数,而greeting使用默认值该怎么办?

1
2
>>> hello_3(name='Gumby')
Hello, Gumby!

位置参数和关键字参数甚至可以联合使用,但是要注意,要将位置参数放置在前面,否则解释器会分不清。

收集参数

如果要给函数提供任意多的参数,可以这样定义函数:

1
2
def print_params(*params):
print params

这边用了一个星号,看看调用这个函数会出现什么情况。

1
2
>>> print_params('Testing')
('Testing',)

可以看到结果是一个元组,因为有一个逗号。
再看看传入多个参数:

1
2
>>> print_params(1, 2, 3)
(1, 2, 3)

可以看到参数前的星号将所有值放置到了一个元组中,也可以认为是将参数收集了起来,然后使用。
看看能不能联合普通参数:

1
2
3
def print_params_2(title, *parmas):
print title
print params

试着调用这个函数:

1
2
3
>>> print_params_2('Params', 1, 2, 3)
Params
(1, 2, 3)

所以星号就可以理解为“收集其余的位置参数”。
如果不提供任何可供收集的元素,params就是一个空元组。

1
2
3
>>> print_params_2('Nothing')
Nothing
()

但能不能联合使用关键字参数呢?

1
2
3
4
>>> print_params_2('Hmm...', something=42)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: print_params_2() got an unexpected keyword argument 'something'

看来不行,那么如果需要处理关键字参数的“收集”操作该怎么办?
会不会是“**”?

1
2
def print_params_3(**params):
print params

解释器没有报错,调用一下看看:

1
2
>>> print_params_3(x=1, y=2, z=3)
{'y': 2, 'x': 1, 'z': 3}

返回的是字典而不是元组。
那联合别的参数看看:

1
2
3
4
def print_params_4(x, y, z=3, *pospar, **keypar):
print x, y, z
print pospar
print keypar

调用看看:

1
2
3
4
>>> print_params_4(1, 2, 3, 4, 5, 6, 7, foo=1, bar=2)
1 2 3
(4, 5, 6, 7)
{'foo': 1, 'bar': 2}

可以看到,正是我们想要的结果。

分发参数

加入有下面这个函数:

1
2
def add(x, y):
return x + y

如果我们想将有两个要相加的数组组成的元组作为参数,该如何使用?

1
2
3
>>> params = (1, 2)
>>> add(*params)
3

也可以用同样的技术来处理字典——使用**运算符。
记得前面定义的hello_3函数

1
2
3
>>> params = {'name': 'Sir Robin', 'greeting': 'Well met'}
>>> hello_3(**params)
Well met, Sir Robin!

回想一下,可能与原始的参数调用混淆:

1
2
3
4
5
6
7
8
9
10
11
>>> def with_star(**kwds):
print kwds['name'], 'is', kwds['age'], 'years old'

>>> def without_star(kwds):
print kwds['name'], 'is', kwds['age'], 'years old'

>>> args = {'name': 'Mr. Aber', 'age': 42}
>>> with_star(**args)
Mr. Aber is 42 years old
>>> without_star(args)
Mr. Aber is 42 years old

可以看到,with_star()函数在定义和调用的时候都用到了星号,得到的结果同自始至终没用到星号的without_star()相同。
所以星号只在定义函数(允许使用不定数目的参数)或者调用(“分割”字典或者序列)时才有用。

作用域

除了全局作用域,每个函数的调用都会创建一个新的作用域。
函数内的变量称为局部变量
一般来说,函数内部,可以直接读取全局变量。
但是,如果局部变量或者参数与想访问的全部变量同名的话,就不行了。全局变量会被屏蔽。
如果需要用全局变量的话,就要使用globals函数获取全局变量值,它可以返回全局变量的字典。

1
2
3
4
5
6
>>> def combine(param):
print param + globals()['param']

>>> param = 'berry'
>>> combine('Shrub')
Shrubberry

如果在函数内部定义一个变量,它会自动成为局部变量——除非告知Python将其声明为全局变量。
怎样告知Python这是一个全局变量呢?

1
2
3
4
5
6
7
8
>>> x = 1
>>> def change_global():
global x
x = x + 1

>>> change_global()
>>> x
2

闭包

Python的函数可以嵌套,就是说一个函数可以放在另一个函数里面。比如:

1
2
3
4
def multiplier(factor):
def multiplyByFactor(number):
return number * factor
return multiplyByFactor

一个函数位于另一个里面,外层函数返回里层函数,也就是说函数本身被返回了(但是并没有被调用)。
重要的是返回的函数可以访问它定义所在的作用域,换句话说,它带着它的环境(和相关局部变量)。
每次调用外层函数,其内部函数都会重新绑定,factor的变量每次都有一个新值。

1
2
3
4
5
6
7
8
>>> double = multiplier(2)
>>> double(5)
10
>>> triple = multiplier(3)
>>> triple(3)
9
>>> multiplier(5)(4)
20

这种存储子封闭作用域的行为叫做闭包。
举个例子:

1
2
3
4
5
6
7
8
>>> def line_conf(a, b):
def line(x):
return ax + b
return line

>>> line1 = line_conf(1, 1)
>>> line2 = line_conf(4, 5)
>>> print(line1(5), line2(5))

这个例子中,函数line与环境变量a,b构成闭包。
在创建闭包的时候,我们通过line_conf的参数a,b说明了这两个环境变量的取值,这样,我们就确定了函数的最终形式(y = x + 1和y = 4x + 5)。
我们只需要变换参数a,b,就可以获得不同的直线表达函数。
由此,我们可以看到,闭包也具有提高代码可复用性的作用。
如果没有闭包,我们需要每次创建直线函数的时候同时说明a,b,x。
这样,我们就需要更多的参数传递,也减少了代码的可移植性。
利用闭包,我们实际上创建了泛函
line函数定义一种广泛意义的函数。
这个函数的一些方面已经确定(必须是直线),但另一些方面(比如a和b参数待定)。
随后,我们根据line_conf传递来的参数,通过闭包的形式,将最终函数确定下来。

类和对象

对象基本概念

Python是一门面向对象的语言。
对象就是数据以及一系列存取、操作这些数据的方法的集合。
面向对象的特性主要是三个:

  • 多态:可以对不同类的对象使用同样的操作
  • 封装:对外部世界隐藏对象的工作细节
  • 继承:以普通的类为基础建立专门的类对象

创建类

先来看一个简单的类:

1
2
3
4
5
6
7
8
9
10
11
__metaclass__ = type # 确定使用新式类

class Person:
def setName(self, name):
self.name = name

def getName(self):
return self.name

def greet(self):
print "Hello, I'm $s." % self.name

Person就是类名。
class语句会在函数定义的地方创建自己的命名空间。
self是对于对象自身的引用。

1
2
3
4
5
6
7
8
>>> foo = Person()
>>> bar = Person()
>>> foo.setName('Luck Skywalker')
>>> bar.setName('Anakin Skywalker')
>>> foo.greet()
Hello, I'm Luck Skywalker.
>>> bar.greet()
Hello, I'm Anakin Skywalker.

调用上面的函数时,对象自动将自己作为第一个参数传入函数中——因此形象的命名为self。
如果没有self,成员函数就没法访问需要进行操作的对象本身。
生成实例的时候,self会被当成实例本身传入,不论方法中有没有写self。
所以如果类中的实例方法没有参数的话,就会报错。
当然,属性也可以在外部访问:

1
2
3
4
5
>>> foo.name
'Luke Skywalker'
>>> bar.name = 'Yoda'
>>> bar.gree()
Hello, I'm Yoda.

属性和方法

可以将普通函数绑定给类的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> class Class:
def method(self):
print 'I have a self'

>>> def function():
print "I don't..."

>>> instance = Class()
>>> instance.method()
I have a self
>>> instance.method = function
>>> instance.method()
I don't...

也可以将类的方法绑定到普通函数上:

1
2
3
4
5
6
7
8
9
10
11
12
>>> class Bird:
song = 'BBB'

def sing(self):
print self.song

>>> bird = Bird()
>>> bird.sing()
BBB
>>> birdsong = bird.sing
>>> birdsong()
BBB

私有化

Python并不直接支持私有方式,主要靠程序员自己把握在外部进行特性修改的时机。
但还是有一些小技巧达到私有化的效果。
要让方法或属性变为私有,可以在其名字前加上双下划线。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Secretive:

def __inaccessible(self):
print 'Bet you can not see...'

def accessible(self):
print 'The secret msg is:'
self.__inaccessible()

>>> s = Secretive()
>>> s.__inaccessible()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: Secretive instance has no attribute '__inaccessible'
>>>
>>> s.accessible()
The secret msg is:
Bet you can not see...

实际上,所有双下划线开头的方法都被翻译为前面加上单下划线和类名的形式。
也就是说,实际还是可以在类外访问这些私有方法的。
但不要这么做!

1
2
3
4
>>> Secretive._Secretive__inaccessible
<unbound method Secretive.__inaccessible>
>>> s._Secretive__inaccessible()
Bet you can not see...

前面有下划线的名字都不会被带星号的imports语句(from module import*)导入

类的命名空间

所有位于class语句内的代码都在特殊的命名空间中执行——类命名空间
类的命名空间可以由类内所有成员访问。
类的定义其实就是执行代码块,这一点非常有用,比如,在类的定义区中并不只限于使用def语句:

1
2
3
4
5
>>> class C:
print 'Class C being defined...'

Class C being defined...
>>>

再看下一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>>	class MemberCount:

members = 0;

def init(self):
MemberCount.members += 1

>>> m1 = MemberCount()
>>> m1.init()
>>> MemberCount.members
1
>>> m2 = MemberCount()
>>> m2.init()
>>> MemberCount.members
2

上面的代码在类的作用域内定义了一个可供所有成员(实例)访问的变量,用来计算类的成员数量。
而类内作用域的变量也可以被所有实例访问:

1
2
3
4
>>> m1.members
2
>>> m2.members
2

但如果重新绑定的话:

1
2
3
4
5
>>> m1.members = 'Two'
>>> m1.members
'Two'
>>> m2.members
2

指定超类

将其他类名写在class语句后面的圆括号内就可以指定超类了。

1
2
3
4
5
6
7
8
9
10
class Filter:
def init(self):
self.blocked = []

def filter(self, sequence):
return [x for x in sequence if x not in self.blocked] # 第一次见这种代码

class SPAMFilter(Filter):
def init(self):
self.blocked = ['SPAM']

Filter是过滤基类,事实上,它不能过滤任何东西。
SPAMFilter是子类,可以将序列中的“SPAM”过滤掉。

1
2
3
4
>>> s = SPAMFilter()
>>> s.init()
>>> s.filter(['SPAM', 'egg', 'SPAM', 'SPAM', 'bacon'])
['egg', 'bacon']

查看继承关系

查看继承关系用内建的issubclass函数:

1
2
3
4
>>> issubclass(SPAMFilter, Filter)
True
>>> issubclass(Filter, issubclass)
False

也可以用内建的isinstance函数检查一个对象是否是一个类的实例:

1
2
3
4
5
6
7
>>> s = SPAMFilter()
>>> isinstance(s, SPAMFilter)
True
>>> isinstance(s, Filter)
True
>>> isinstance(s, str)
False

想知道一个类的基类,使用其特殊属性__bases__

1
2
>>> SPAMFilter.__bases__
(<class __main__.Filter at 0x10b8107a0>,)

想知道一个实例属于哪个类,可以用__class__属性

1
2
>>> s.__class__
<class __main__.SPAMFilter at 0x10b810808>

多重继承

上面提到了__bases__,是复数形式,意味着基类可以是多个。
事实上的确如此:

1
2
3
4
5
6
7
8
9
10
class Calculator:
def calculate(self, expression):
self.value = eval(expression)

class Talker:
def talk(self):
print 'Hi, my value is', self.value

class TalkingCalculator(Calculator, Talker):
pass

子类自己不做任何事情,从自己的超类中继承所有行为。

1
2
3
4
>>> tc = TalkingCalculator()
>>> tc.calculate('1 + 2 * 3')
>>> tc.talk()
Hi, my value is 7

多重继承有个问题需要注意,如果一个方法从多个超类继承,那么必须注意一下超类的顺序。
先继承的类中的方法会重写后继承的类中的方法
如果上面的Calculator也有一个talk方法,就会重写Talker的talk方法。

一些面向对象的思考

  • 将属于一类的对象放在一起。如果一个函数操纵一个全局变量,那么两者最好都在类内作为特性和方法出现。
  • 对象应该只关心自己实例的特性,让其他实例管理自己的状态。
  • 小心继承,特别是多重继承。

异常

什么是异常

Python用异常对象来表示异常情况。
遇到错误,就会引发异常。
如果异常没有被处理或捕获。程序就会用回溯(Traceback)终止执行:

1
2
3
4
>>> 1/0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero

事实上,每一个异常都是一些类的实例,比如上面的就是ZeroDivisionError。
这些实例可以被引发,并且可以用多种方法进行捕捉,使程序可以捉住作物并对其进行处理,而不是让整个程序失效。

自己的异常

raise语句

可以使用一个类(Exception的子类)或者实例参数调用raise语句,来引发异常。
使用类的时候,程序自动创建实例。

1
2
3
4
5
6
7
8
>>> raise Exception
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
Exception
>>> raise Exception('overload')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
Exception: overload

内建的异常类有很多,这些类都可以在exceptions模块中找到。
常见的异常类有:

class name description
Exception 所有异常的基类
AttributeError 特性引用或赋值失败
IOError 试图打开不存在的文件(也有其他情况)
IndexError 使用序列中不存在的索引
KeyError 使用映射中不存在的键
NameError 找不到名字(变量)
SyntaxError 代码为错误形式时引发
TypeError 内建操作或者函数应用于错误类型的对象
ValueError 内建操作或者函数应用于正确类型的对象,但是该对象使用不合适的值

自定义异常类

创建自己的异常类,只需要继承Exception即可。

1
class SomeCustomException(Exception): pass

捕捉异常

处理异常可以用try/catch来实现。

1
2
3
4
5
6
try:
x = input('Enter first num:')
y = input('Enter second num:')
print x/y
except ZeroDivisionError:
print "The second num can't be zero!"

如果捕获了异常,却又想重新引发它,就可以用不带参数的raise。

1
2
3
4
5
6
7
8
9
10
11
class MuffledCalculator:
muffled = false

def calc(self, expr):
try:
return eval(expr)
except ZeroDivisionError:
if self.muffled:
print 'Division by zero is illegal'
else:
raise

多个Except

1
2
3
4
5
6
7
8
try:
x = input('Enter the first num')
y = input('Enter the second num')
print x/y
except ZeroDivisionError:
print 'ZeroDivisionError'
except TypeError:
print 'TypeError'

这样的代码比用多个if/else可读性高的多

一个块捕获多个异常

1
2
3
4
5
6
try:
x = input('Enter the first num')
y = input('Enter the second num')
print x/y
except (ZeroDivisionError, TypeError, NameError):
print 'Your num were bugs...'

获取异常对象

1
2
3
4
5
6
try:
x = input('Enter the first num')
y = input('Enter the second num')
print x/y
except (ZeroDivisionError, TypeError), e:
print e

全捕获异常

1
2
3
4
5
6
try:
x = input('Enter the first num')
y = input('Enter the second num')
print x/y
except:
print 'Something wrong happened...'

异常中的else

可以像对条件和循环语句那样,给try/catch语句加上一个else子句。

1
2
3
4
5
6
7
8
9
while True:
try:
x = input('Enter the first num')
y = input('Enter the second num')
print x/y
except:
print 'Something wrong happened...'
else:
break

这个循环只有在没有异常发生的情况下才退出,换句话说,如果有错误,就会不断要求重新输入。

Finally

1
2
3
4
5
6
7
8
try:
1 / 0
except:
print "Error"
else:
print "That were well"
finally:
print "Cleaning up"

Finally语句可以用来异常后的清理,常用于关闭文件或者网络套接字。

异常和函数

如果函数中的异常没有得到处理,就会一层一层往上抛,直到主程序,如果那里也没有处理异常的逻辑,程序就会带着堆栈跟踪信息终止。

深入方法和类

构造方法

一个对象被创建后,会立即调用构造方法。
在Python中创建一个构造方法就是写一个__init__方法:

1
2
3
4
5
6
7
class FooBar:
def __init__(self):
self.somevar = 42

>>> f = FooBar()
>>> f.somevar
42

在构造方法中加入参数的情况:

1
2
3
4
5
6
7
8
9
10
class FooBar:
def __init__(self, value = 42):
self.somevar = value

>>> f = FooBar()
>>> f.somevar
42
>>> f = FooBar('Hello')
>>> f.somevar
'Hello'

Python里面还有析构函数__del__
它在对象要被垃圾回收之前调用,但是发生调用的具体时机不可知,所以要尽量避免使用

重写构造方法

重写是继承机制里面一个重要内容,对于构造方法尤其重要。
构造方法用来初始化新建对象的状态,大多数子类不仅要拥有自己的初始化代码,还要拥有超类的初始化代码。
考虑下面这个Bird类:

1
2
3
4
5
6
7
8
9
10
class Bird:
def __init__(self):
self.hungry = True

def eat(self):
if self.hungry:
print 'Ahaaa...'
self.hungry = False
else:
print 'No, thanks!'

考虑其一个子类SomeBird:

1
2
3
4
5
6
class SomeBird(Bird):
def __init__(self):
self.sound = 'Squawk'

def sing(self):
pting self.sound

调用这个子类的sing()方法会正常唱歌,但是,如果调用eat()方法,就会抛出异常。
异常显示SomeBird没有hungry属性,这是因为SomeBird重写了构造方法,新的构造方法里面没有任何关于初始化hungry的代码。
要实现预期效果,SomeBird的构造方法必须调用其超类的构造方法来确保进行基本的初始化。
实现这个有两种方法:

  • 调用超类构造方法的未绑定版本
  • 使用super函数
未绑定方法

这个方法是历史方法,现在一般不推荐使用:

1
2
3
4
5
6
7
class SomeBird(Bird):
def __init__(self):
Bird.__init__(self)
self.sound = 'Squawk'

def sing(self):
pting self.sound

可以发现,添加了一行代码:Bird.__init__(self)
在调用一个实例的方法时,这个方法的self参数会自动绑定到实例上。但是如果直接调用类的方法,就没有实例会被绑定,这样就可以自由的提供需要的self参数。这种称为未绑定方法。
通过将当前实例作为self参数提供给未绑定方法,SomeBird就能使用其超类构造方法的所有实现。

super函数
1
2
3
4
5
6
7
8
9
10
11
12
__metaclass__ = type # super函数只能在新式类中起作用

class Bird:
......

class SomeBird(Bird):
def __init__(self):
super(SomeBird, self).__init__()
self.sound = 'Squawk'

def sing(self):
pting self.sound

属性

静态方法和类成员方法

静态方法和类成员方法分别在创建时被装入Staticmethod类型和Classmethod类型的对象中。
静态方法的定义没有self参数,且能够被类本身直接调用。
类方法在定义时需要名为cls的类似self的参数,类成员方法可以直接用类的具体对象调用。
但cls参数是自动被绑定到类的。

1
2
3
4
5
6
7
8
9
10
11
__metaclass__ = type

class MyClass:

def smeth():
print 'This is a static method'
smeth = staticmethod(smeth)

def cmeth(cls):
print 'This is a class method of', cls
cmeth = classmethod(cmeth)

Python 2.4以后,可以用装饰器去取代上面的手动包装和替换方法。

1
2
3
4
5
6
7
8
9
10
11
__metaclass__ = type

class MyClass:

@staticmethod
def smeth():
print 'This is a static method'

@classmethod
def cmeth(cls):
print 'This is a class method of ', cls

定义了这些方法后,就可以如下使用(例子中没有实例化类):

1
2
3
4
>>> MyClass.smeth()
This is a static method
>>> MyClass.cmeth()
This is a class method of <class '__main__.MyClass'>

静态方法和类成员方法在Python中并不是很重要,主要原因是大部分情况下可以使用函数或者绑定方法代替。

迭代器

关于迭代器,讨论一个特殊方法:__iter__