Python learning
编码
# -*- coding: utf-8 -*-
计算
Python 支持的数字类型有:int、float、Decimal(十进制)、Fraction(分数)、复数
>>> 8 / 5
1.6
>>> 17 // 5
3
>>> 5 * 2
10
>>> 5 ** 2
25
>>> 3.5 // 1.7
2.0
字符串
可以单双引号,特殊字符使用反斜杠转义。
特殊的一些:
- 如果不想让反斜杠当做转义输出,可以用原始字符串,方法是在第一个引号前面加上一个
r
,r"C:\some\name"
- 字符串文本能够分成多行。一种方法是使用三引号:
"""..."""
或者'''...'''
。行尾换行符会被自动包含到字符串中,但是可以在行尾加上\
来避免这个行为(行尾加上则不换行) - 字符串可以由
+
操作符连接(粘到一起),可以由*
表示重复,3 * 'un' + 'ium' == 'unununium'
- 字符串也可以被截取,索引从0记起,如
word[2]
;索引也可以是负数,这将导致从右边开始计算,word[-1]
表示最后一个字符 - 字符串还支持切片,如
word[2:5]
表示获取位置[2,5)的三个字符,如果word是Python,则获取的字符串是tho,word[:5]
表示从最开始到第4个位置的字符串,切片也支持负数 - 索引过大会引发IndexError,但是切片过大不会报错
- Python字符串不可以被更改 — 它们是 不可变的 (整体赋值是可以的)。因此,赋值给字符串索引的位置会导致错误。
word[1] = 'j'
是错误的!而word = 'aaa'
可以
相关链接:
列表
同样支持索引和切片、所有的切片操作都会返回一个包含请求的元素的新列表。这意味着下面的切片操作返回列表一个新的(浅)拷贝副本
>>> sq1 = [1, 2, 3]
>>> sq2 = sq1
>>> sq3 = sq1[:]
>>> sq1
[1, 2, 3]
>>> sq2
[1, 2, 3]
>>> sq3
[1, 2, 3]
>>> sq2[1] = 10
>>> sq1
[1, 10, 3]
>>> sq2
[1, 10, 3]
>>> sq3[1] = 100
>>> sq1
[1, 10, 3]
>>> sq3
[1, 100, 3]
所以,我们知道,要想复制一个列表,需要使用[:]来获取浅拷贝副本,而不能直接赋值。从上面的例子,也可以知道,列表支持对每个元素单独修改
所以,还可以这样:
>>> sq1
[1, 10, 3]
>>> sq1 + [1, 2]
[1, 10, 3, 1, 2]
>>> sq1
[1, 10, 3]
>>> sq1.append(10)
>>> sq1
[1, 10, 3, 10]
>>> sq1.extend([7, 6, 5])
>>> sq1
[1, 10, 3, 10, 7, 6, 5]
>>> # insert value
... sq1[1:1] = [40, 50]
>>> sq1
[1, 40, 50, 10, 3, 10, 7, 6, 5]
>>> # replace value
... sq1[1:3] = [60, 70]
>>> sq1
[1, 60, 70, 10, 3, 10, 7, 6, 5]
>>> # remove value
... sq1[1:3] = []
>>> sq1
[1, 10, 3, 10, 7, 6, 5]
一个例子
>>> # fibonacci
... a, b = 0, 1
>>> while b < 10:
... print(b)
... a, b = b, a+b
...
1
1
2
3
5
8
这里要注意的是:
注意a, b = 0, 1
和 a, b = b, a+b
,这是 多重赋值 ,变量在赋值前,右边的表达式从左到右计算,然后再分别赋值给左边的每个变量。所以,将第二个多重赋值改成b, a = a+b, b
也没有问题
条件判断:0、空序列(长度为0就算)为false,标准比较操作符与 C 相同: <
, >
, ==
, <=
, >=
和 !=
print()
函数可以用,
隔开变量输出,并自动会在两个变量之间加一个空格;print('aaa', end=',')
,end的默认值是\n
,改掉这个之后,print不换行,而是以,结尾
流程控制
if语句
if x < 0:
print(x)
elif x == 0:
print("hello")
else:
print("+")
for语句
words = ['cat', 'window', 'defenestrate']
for w in words:
print(w, len(w))
这里需要注意的是,Python 的 for 语句依据任意序列(链表或字符串)中的子项,按它们在序列中的顺序来进行迭代。所以,在迭代过程中修改迭代序列不安全。如果你想要修改你迭代的序列(例如,复制选择项),你可以迭代它的复本
for w in words[:]:
if len(w) > 6:
words.insert(0, w)
print(words)
# ['defenestrate', 'cat', 'window', 'defenestrate']
在循环(for, while)中同样可以使用break
和 continue
,作用和 C 中相同
循环中,支持else
子句,作用是在循环正常结束(break不算)后执行
range()函数
用于生成一个等差级数链表(注意:这不是列表List,是迭代器),以下是例子:
>>> for i in range(5):
... print(i)
...
0
1
2
3
4
range(5, 10)
5 through 9
range(0, 10, 3)
0, 3, 6, 9
range(-10, -100, -30)
-10, -40, -70
>>> 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
>>> print(range(10))
range(0, 10)
>>> list(range(5))
[0, 1, 2, 3, 4]
定义函数
关键字def引入一个函数定义,在其后必须跟有函数名和包括形式参数的圆括号
函数体的第一行语句可以是可选的字符串文本,这个字符串是函数的文档字符串,或者称为 docstring。有些工具通过 docstrings 自动生成在线的或可打印的文档,或者让用户通过代码交互浏览;在你的代码中包含 docstrings 是一个好的实践,让它成为习惯吧
def func():
""" 这是一个docstring """
...
没有return
语句的函数会返回None
在函数中引用全局的变量,可以用global语句声明,如:global a
函数参数
默认参数值
def func(num, str="hello"):
if num in (1, 2, 3):
print(num)
重要警告:默认值只被赋值一次。这使得当默认值是可变对象时会有所不同,比如列表、字典或者大多数类的实例。例如,下面的函数在后续调用过程中会累积(前面)传给它的参数!!
def f(a, L=[]):
L.append(a)
return L
print(f(1))
print(f(2))
print(f(3))
会输出:
[1]
[1, 2]
[1, 2, 3]
取而代之的方法:
def f(a, L=None):
if L is None:
L = []
L.append(a)
return L
关键字参数
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, "!")
# 接受一个必选参数 (voltage) 以及三个可选参数 (state, action, 和 type)。可以用以下的任一方法调用
parrot(1000) # 1 positional argument
parrot(voltage=1000) # 1 keyword argument
parrot(voltage=1000000, action='VOOOOOM') # 2 keyword arguments
parrot(action='VOOOOOM', voltage=1000000) # 2 keyword arguments
parrot('a million', 'bereft of life', 'jump') # 3 positional arguments
parrot('a thousand', state='pushing up the daisies') # 1 positional, 1 keyword
可变参数列表
一个最不常用的选择是可以让函数调用可变个数的参数。这些参数被包装进一个元组。在这些可变个数的参数之前,可以有零到多个普通的参数
def write_multiple_items(file, separator, *args):
file.write(separator.join(args))
通常,这些可变参数是参数列表中的最后一个(只针对于非关键字参数),因为它们将把所有的剩余输入参数传递给函数。任何出现在*args
后的参数是关键字参数,这意味着,他们只能被用作关键字,而不是位置参数
>>> def concat(*args, sep="/"):
... return sep.join(args)
...
>>> concat("earth", "mars", "venus")
'earth/mars/venus'
>>> concat("earth", "mars", "venus", sep=".")
'earth.mars.venus'
可变关键字参数列表
相当于接收一个字典,必须在可变参数列表(如果有)的后面
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")
""" result:
-- 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
"""
在传递可变参数的时候可能遇到一种情况是,当要传递的参数已经是一个列表,而函数需要的是可变长参数,可以使用*
来拆开。同理,字典拆成关键字参数可以用**
>>> args = [3, 6]
>>> list(range(*args))
[3, 4, 5]
>>> list(range(3, 6))
[3, 4, 5]
Lambda形式
通过lambda
关键字,可以创建短小的匿名函数。这里有一个函数返回它的两个参数的和: lambda a, b: a+b
lambda 形式可以从外部作用域引用变量:
>>> def make_incrementor(n):
... return lambda x: x + n
...
>>> f = make_incrementor(42)
>>> f(0)
42
>>> f(1)
43
pass语句
pass 语句什么也不做。它用于那些语法上必须要有什么语句,但程序什么也不做的场合
class MyCls:
pass
def func():
pass
while True:
pass
各种数据结构
列表方法
>>> a = [66.25, 333, 333, 1, 1234.5]
>>> print(a.count(333), a.count(66.25), a.count('x'))
2 1 0
>>> a.insert(2, -1)
>>> a.append(333)
>>> a
[66.25, 333, -1, 333, 1, 1234.5, 333]
>>> a.index(333)
1
>>> a.remove(333)
>>> a
[66.25, -1, 333, 1, 1234.5, 333]
>>> a.reverse()
>>> a
[333, 1234.5, 1, 333, -1, 66.25]
>>> a.sort()
>>> a
[-1, 1, 66.25, 333, 333, 1234.5]
>>> a.pop()
1234.5
>>> a
[-1, 1, 66.25, 333, 333]
可以看到,列表自带pop和append方法,所以,可以直接把列表当做堆栈使用
>>> stack = [3, 4, 5]
>>> stack.append(6)
>>> stack.append(7)
>>> stack
[3, 4, 5, 6, 7]
>>> stack.pop()
7
>>> stack
[3, 4, 5, 6]
>>> stack.pop()
6
>>> stack.pop()
5
>>> stack
[3, 4]
使用队列:如果把列表当做队列来使用,效率不高(头部插入和弹出很慢)。可以使用collections.deque
>>> from collections import deque
>>> queue = deque(["Eric", "John", "Michael"])
>>> queue.append("Terry") # Terry arrives
>>> queue.append("Graham") # Graham arrives
>>> queue.popleft() # The first to arrive now leaves
'Eric'
>>> queue.popleft() # The second to arrive now leaves
'John'
>>> queue # Remaining queue in order of arrival
deque(['Michael', 'Terry', 'Graham'])
列表推导式为从序列中创建列表提供了一个简单的方法
常规的方法如下,副作用是x
变量在循环完毕后依然存在,而且长...
>>> squares = []
>>> for x in range(10):
... squares.append(x**2)
...
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
使用列表推导式:
>>> squares = [x ** 2 for x in range(10)]
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
列表推导式由包含一个表达式的括号组成,表达式后面跟随一个for
子句,之后可以有零或多个for
或if
子句。结果是一个列表,由表达式依据其后面的for
和if
子句上下文计算而来的结果构成
另一个例子:
>>> [(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
del语句
del语句可以删除列表的对应的索引项 或 变量,与pop()方法的区别在于不返回值,并且可以删除切片和整个变量
>>> a = [-1, 1, 66.25, 333, 333, 1234.5]
>>> del a[0]
>>> a
[1, 66.25, 333, 333, 1234.5]
>>> del a[2:4]
>>> a
[1, 66.25, 1234.5]
>>> del a[:]
>>> a
[]
>>> del a
>>> a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
元组和序列
我们知道列表和字符串有很多通用的属性,例如索引和切割操作。它们是序列类型中的两种
元组也是一种标准序列类型,一个元组由数个逗号分隔的值组成:
>>> t = 12345, 54321, 'hello!'
>>> t[0]
12345
>>> t
(12345, 54321, 'hello!')
>>> # Tuples may be nested:
... u = t, (1, 2, 3, 4, 5)
>>> u
((12345, 54321, 'hello!'), (1, 2, 3, 4, 5))
>>> # Tuples are immutable:
... t[0] = 88888
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> # but they can contain mutable objects:
... v = ([1, 2, 3], [3, 2, 1])
>>> v
([1, 2, 3], [3, 2, 1])
上面的例子说明,元组在输出的时候总是有括号的,但是输入的时候可以不用。元组元素就像字符串元素,不可变。但是,如果元组中包含可变元素,里面的元素是可变的(如:列表)
特殊的情况,创建零个或一个元素的元组。零个元素直接用()
,而一个元素的时候,需要额外加一个逗号,丑但有效1,
或 (1,)
>>> empty = ()
>>> singleton = 'hello', # <-- note trailing comma
>>> len(empty)
0
>>> len(singleton)
1
>>> singleton
('hello',)
集合
集合(set)是一个无序不重复元素的集,基本功能包括关系测试和消除重复元素。集合对象还支持 union(联合),intersection(交),difference(差)和 sysmmetric difference(对称差集)等数学运算
大括号{...}
或set()
函数可以用来创建集合。注意:想要创建空集合,你必须使用set()
而不是{}
。后者用于创建空字典
>>> basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'}
>>> print(basket) # show that duplicates have been removed
{'orange', 'banana', 'pear', 'apple'}
>>> 'orange' in basket # fast membership testing
True
>>> 'crabgrass' in basket
False
>>> # Demonstrate set operations on unique letters from two words
...
>>> a = set('abracadabra')
>>> b = set('alacazam')
>>> a # unique letters in a
{'a', 'r', 'b', 'c', 'd'}
>>> a - b # letters in a but not in b
{'r', 'd', 'b'}
>>> a | b # letters in either a or b
{'a', 'c', 'r', 'd', 'b', 'm', 'z', 'l'}
>>> a & b # letters in both a and b
{'a', 'c'}
>>> a ^ b # letters in a or b but not both
{'r', 'd', 'b', 'm', 'z', 'l'}
以上例子证明,集合支持自动的去重(但乱序)和数学运算
集合也支持推导式(并去重)
>>> a = {x for x in 'abracadabra' if x not in 'abc'}
>>> a
{'r', 'd'}
字典
字典在其他语言中,可能被称为“关联数组”(associative arrays)。字典以关键字
为索引,关键字可以是任意不可变类型,通常用字符串或数值。如果元组中只包含字符串和数字,它可以做为关键字,如果它直接或间接的包含了可变对象,就不能当做关键字。不能用列表做关键字,因为列表可以用索引、切割或者append()
和extend()
等方法改变
字典的主要操作是依据键来存储和析取值。也可以用del
来删除键:值对(key:value
)。如果你用一个已经存在的关键字存储值,以前为该关键字分配的值就会被遗忘。试图从一个不存在的键中取值会导致错误
>>> tel = {'jack': 4098, 'sape': 4139}
>>> tel['guido'] = 4127
>>> tel
{'sape': 4139, 'guido': 4127, 'jack': 4098}
>>> tel['jack']
4098
>>> del tel['sape']
>>> tel['irv'] = 4127
>>> tel
{'guido': 4127, 'irv': 4127, 'jack': 4098}
>>> list(tel.keys())
['irv', 'guido', 'jack']
>>> sorted(tel.keys())
['guido', 'irv', 'jack']
>>> 'guido' in tel
True
>>> 'jack' not in tel
False
dict()
构造函数可以直接从key-value
对中创建字典。此外,字典推导式可以从任意的键值表达式中创建字典
>>> dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
{'sape': 4139, 'jack': 4098, 'guido': 4127}
>>> dict(sape=4139, guido=4127, jack=4098)
{'sape': 4139, 'jack': 4098, 'guido': 4127}
>>> {x: x**2 for x in (2, 4, 6)}
{2: 4, 4: 16, 6: 36}
循环技巧
循环字典,使用item()
方法:
>>> knights = {'gallahad': 'the pure', 'robin': 'the brave'}
>>> for k, v in knights.items():
... print(k, v)
...
gallahad the pure
robin the brave
循环序列,使用enumerate()
:
>>> for i, v in enumerate(['tic', 'tac', 'toe']):
... print(i, v)
...
0 tic
1 tac
2 toe
同时循环两个或更多序列,使用zip()
整体打包:
>>> questions = ['name', 'quest', 'favorite color']
>>> answers = ['lancelot', 'the holy grail', 'blue']
>>> for q, a in zip(questions, answers):
... print('What is your {0}? It is {1}.'.format(q, a))
...
""" result:
What is your name? It is lancelot.
What is your quest? It is the holy grail.
What is your favorite color? It is blue.
"""
逆向循环,使用reversed()
:
>>> for i in reversed(range(1, 10, 2)):
... print(i)
...
9
7
5
3
1
排序去重后输出,使用sorted()
:
>>> basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
>>> for f in sorted(set(basket)):
... print(f)
...
apple
banana
orange
pear
若要在循环内部修改正在遍历的序列(例如复制某些元素),建议您首先制作副本(!)
>>> words = ['cat', 'window', 'defenestrate']
>>> for w in words[:]: # Loop over a slice copy of the entire list.
... if len(w) > 6:
... words.insert(0, w)
...
>>> words
['defenestrate', 'cat', 'window', 'defenestrate']
条件判断
- 比较操作符
in
和not in
审核值是否在一个区间之内,1 not in [2, 3]
- 比较操作符
is
和is not
比较两个对象是否相同,[] is ()
- 比较操作可以传递,
1 <= 3 > 2
,比较操作符具有相同的优先级 - 短路运算符
and
和or
,参数从左向右解析,一旦结果可以确定即停止,短路操作符的返回值通常是最后一个变量0 and 1
-
not
与and
和or
同属于逻辑操作符,优先级not
>and
>or
- 总优先级:数值操作 > 比较操作 > 逻辑操作符
- 与
C
不同,Python
在表达式内不能赋值
序列对象可以与相同类型的其他对象作比较,比较操作按字典序进行:首先比较前两个元素,如果不同,就决定了比较的结果;如果相同,就比较后两个元素,依此类推,直到所有序列都完成比较。如果两个元素本身就是同样类 型的序列,就递归字典序比较。如果两个序列的所有子项都相等,就认为序列相等。如果一个序列是另一个序列的初始子序列,较短的一个序列就小于另一个。字符串的字典序按照单字符的ASCII
顺序
模块
模块是包括Python
定义和声明的文件。文件名就是模块名加上.py
后缀。模块的模块名(做为一个字符串)可以由全局变量__name__
得到
# 先在桌面新建一个test.py,定义一个hello()方法
>>> os.getcwd()
'/home/my/Desktop'
>>> import test
>>> test.hello()
hello world
>>> test.__name__
'test'
除了包含函数定义外,模块也可以包含可执行语句。这些语句一般用来初始化模块。他们仅在 第一次 被导入的地方执行一次
# 在test.py中加入一个输出语句
>>> import test
import mod test successfully!
每个模块都有自己私有的符号表,被模块内所有的函数定义作为全局符号表使用。因此,模块的作者可以在模块内部使用全局变量,而无需担心它与某个用户的全局变量意外冲突
模块可以导入其他的模块。一个(好的)习惯是将所有的import
语句放在模块的开始(或者是脚本),这并非强制。被导入的模块名会放入当前模块的全局符号表中
import
语句的一个变体直接从被导入的模块中导入命名到本模块的语义表中
>>> from fibo import fib, fib2
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
作为脚本来执行模块
只需要添加一个if判定:
if __name__ == "__main__":
import sys
fib(int(sys.argv[1]))
然后用python fibo.py 50
即可传入参数并执行
模块的搜索路径
导入一个叫spam
的模块时,解释器先在当前目录中搜索名为spam.py
的文件。如果没有找到的话,接着会到sys.path
变量中给出的目录列表中查找,变量的初始值如下:
- 输入脚本的目录(当前目录)
- 环境变量PYTHONPATH表示的目录列表中搜索
- Python默认安装路径中的搜索
>>> sys.path
['', '/usr/lib/python35.zip', '/usr/lib/python3.5', '/usr/lib/python3.5/plat-x86_64-linux-gnu', '/usr/lib/python3.5/lib-dynload', '/usr/local/lib/python3.5/dist-packages', '/usr/lib/python3/dist-packages']
需要注意的是由于这些目录中包含有搜索路径中运行的脚本,所以这些脚本不应该和标准模块重名,否则在导入模块时Python
会尝试把这些脚本当作模块来加载。这通常会引发错误
dir()函数
内置函数dir()
用于按模块名搜索模块定义,它返回一个字符串类型的存储列表
>>> import fibo, sys
>>> dir(fibo)
['__name__', 'fib', 'fib2']
包
包通常是使用用“圆点模块名”的结构化模块命名空间。例如,名为 A.B 的模块表示了名为A
的包中名为B
的子模块。可以避免全局变量之间的相互冲突
包的结构可能是这样的
sound/ Top-level package
__init__.py Initialize the sound package
formats/ Subpackage for file format conversions
__init__.py
wavread.py
wavwrite.py
aiffread.py
aiffwrite.py
auread.py
auwrite.py
...
effects/ Subpackage for sound effects
__init__.py
echo.py
surround.py
reverse.py
...
filters/ Subpackage for filters
__init__.py
equalizer.py
vocoder.py
karaoke.py
...
当导入这个包时,Python
通过sys.path
搜索路径查找包含这个包的子目录
为了让Python
将目录当做内容包,目录中必须包含__init__.py
文件。这是为了避免一个含有烂俗名字的目录无意中隐藏了稍后在模块搜索路径中出现的有效模块,比如string。最简单的情况下,只需要一个空的__init__.py
文件即可。当然它也可以执行包的初始化代码,或者定义稍后介绍的__all__
变量
导入模块的方法:
# 1
import sound.effects.echo
sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)
# 2
from sound.effects import echo
echo.echofilter(input, output, delay=0.7, atten=4)
# 3
from sound.effects.echo import echofilter
echofilter(input, output, delay=0.7, atten=4)
from sound.effects import *
可能会很慢,或者出现一些意外(强烈不推荐的写法)。但是这种情况难以避免,对于包的作者来说唯一的解决方案就是提供一个明确的包索引:执行from package import *
时,如果包中的__init__.py
代码定义了一个名为__all__
的列表,就会按照列表中给出的模块名进行导入(可以避免导入所有模块)
输入输出
print()
函数对于每个参数,自动用空格分开输出,以下是比较优雅的输出方式(不是拼接的):
>>> print('We are the {} who say "{}!"'.format('knights', 'Ni'))
We are the knights who say "Ni!"
>>> print('{0} and {1}'.format('spam', 'eggs'))
spam and eggs
>>> print('{1} and {0}'.format('spam', 'eggs'))
eggs and spam
>>> print('This {food} is {adjective}.'.format(
... food='spam', adjective='absolutely horrible'))
This spam is absolutely horrible.
>>> print('The story of {0}, {1}, and {other}.'.format('Bill', 'Manfred',
other='Georg'))
The story of Bill, Manfred, and Georg.
>>> import math
>>> print('The value of PI is approximately {0:.3f}.'.format(math.pi))
The value of PI is approximately 3.142.
可以看到,{}
用于指代格式化的内容,里面可以规定顺序、关键字等
字段名后允许可选的':'
和格式指令。这允许对值的格式化加以更深入的控制
旧式的字符串格式化,使用%
>>> print("Name:%10s Age:%8d Height:%8.2f" % ("Aviad", 25, 1.83))
Name: Aviad Age: 25 Height: 1.83
文件读写
函数open()
返回文件对象,这个函数通常需要两个字符串参数:文件名、打开模式
这里要特别注意的是,对于非文本文件,要在模式后面加上'b'
,否则会错误当做文本文件,修改一些平台有关的行结束符字符(Python
打开文本,会默认会转换成平台相关的行结束符)
>>> f = open('openfile', 'w');
文件的对象方法:
f.read(size)
方法用于读取文件内容,size是可选的数值,如果没有指定size或者指定为负数,就会读取并返回整个文件。当文件大小为当前机器内存两倍时,就会产生问题。反之,会尽可能按比较大的size
读取和返回数据。如果到了文件末尾,f.read()
会返回一个空字符串''
>>> f.read()
'This is the entire file.\n'
>>> f.read()
''
>>> f.read()
''
f.readline()
从文件中读取单独一行,字符串结尾会自动加上一个换行符\n
,只有当文件最后一行没有以换行符结尾时,这一操作才会被忽略。这样返回值就不会有混淆,如果f.readline()
返回一个空字符串,那就表示到达了文件末尾,如果是一个空行,就会描述为'\n'
,一个只包含换行符的字符串
>>> f.readline()
'This is the first line of the file.\n'
>>> f.readline()
'Second line of the file\n'
>>> f.readline()
''
高效的读取方法
>>> for line in f:
... print(line, end='')
...
''' result:
This is the first line of the file.
Second line of the file
'''
将所有行读到一个列表中
>>> f.seek(0)
>>> list(f)
['dfsafdsa\n', 'dfsafds\n', 'fdsagdfhfdh\n', 'hfgng\n', 'nh\n', 'trh\n', 'trh\n', 'tr\n', '\n', '\n', 'h\n', 'tr\n', 'htrhtr\n', 'h\n', 'tr\n', 'htr\n', 'h\n', 'tr\n', '\n', '\n', '\n', 'htrhtr\n']
>>> list(f)
[]
>>> f.seek(0)
>>> f.readlines()
['dfsafdsa\n', 'dfsafds\n', 'fdsagdfhfdh\n', 'hfgng\n', 'nh\n', 'trh\n', 'trh\n', 'tr\n', '\n', '\n', 'h\n', 'tr\n', 'htrhtr\n', 'h\n', 'tr\n', 'htr\n', 'h\n', 'tr\n', '\n', '\n', '\n', 'htrhtr\n']
f.write(string)
方法将string
的内容写入文件(是覆盖还是追加,视打开文件的模式而定),Python3.5.2
经测试没有返回值。如果想写入非字符串的内容,首先要转换为字符串
>>> f.write('This is a test\n')
>>> value = ('the answer', 42)
>>> s = str(value)
>>> f.write(s)
文件对象的指针
f.tell()
返回一个整数,代表文件对象在文件中的指针位置,该数值计量了自文件开头到指针处的比特数。需要改变文件对象指针话话,使用f.seek(offset,from_what)
。指针在该操作中从指定的引用位置移动offset
比特,引用位置由from_what
参数指定。from_what
值为0
表示自文件起始处开始,1
表示自当前文件指针位置开始,2
表示自文件末尾开始(配合的offset
是负数)。from_what
可以忽略,其默认值为0
>>> f = open('workfile', 'rb+')
>>> f.write(b'0123456789abcdef')
16
>>> f.seek(5) # Go to the 6th byte in the file
5
>>> f.read(1)
b'5'
>>> f.seek(-3, 2) # Go to the 3rd byte before the end
13
>>> f.read(1)
b'd'
当你使用完一个文件时,调用f.close()
方法就可以关闭它并释放其占用的所有系统资源。在调用f.close()
方法后,试图再次使用文件对象将会自动失败
>>> f.close()
>>> f.read()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: I/O operation on closed file
>>> f
<closed file 'test.txt', mode 'a+' at 0x7f8cd23ca5d0>
使用with处理文件对象的好习惯
使用关键字with
的先进之处在于文件用完后会自动关闭,就算发生异常也没关系。它是try-finally
块的简写
>>> with open('test.txt', 'r') as f:
... read_data = f.read()
...
>>> read_data
'dfsafdsa\n'
>>> f.closed
True
以上的语句相当于,先调用了open()
函数,并将返回值赋给f
,然后里面的语句是try
块里面的内容,finally
的内容隐藏在f
对象的__exit__
方法中,即f.close()
。这里需要注意的是,这里出现异常的话,仍然需要自己定义try-except
来处理。参考链接
使用json存储格式化数据
需要使用
json
标准模块,import json
>>> json.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}])
'["foo", {"bar": ["baz", null, 1.0, 2]}]'
>>> json.loads('["foo", {"bar":["baz", null, 1.0, 2]}]')
['foo', {'bar': ['baz', None, 1.0, 2]}]
>>> x
'["foo", {"bar": ["baz", null, 1.0, 2]}]'
>>> f = open("test.txt", "w+")
>>> f.read()
''
>>> f.tell()
0
>>> json.dump(x, f)
>>> f.seek(0)
>>> f.read()
'"[\\"foo\\", {\\"bar\\": [\\"baz\\", null, 1.0, 2]}]"'
>>> f.seek(0)
>>> y = json.load(f)
>>> y
u'["foo", {"bar": ["baz", null, 1.0, 2]}]'
>>> z = json.loads(y)
>>> z[1]["bar"][2]
1.0
错误和异常
语法错误
语法错误,也被称作解析错误(最常见)
>>> while True print('Hello world')
File "<stdin>", line 1
while True print('Hello world')
^
SyntaxError: invalid syntax
异常
即使一条语句或表达式在语法上是正确的,当试图执行它时也可能会引发错误。运行期检测到的错误称为异常,并且程序不会无条件的崩溃:很快,你将学到如何在Python
程序中处理它们。然而,大多数异常都不会被程序处理,像这里展示的一样最终会产生一个错误信息
>>> 10 * (1/0)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
ZeroDivisionError: int division or modulo by zero
>>> 4 + spam*3
Traceback (most recent call last):
File "<stdin>", line 1, in ?
NameError: name 'spam' is not defined
>>> '2' + 2
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: Can't convert 'int' object to str implicitly
错误信息的最后一行指出发生了什么错误。异常也有不同的类型,异常类型做为错误信息的一部分显示出来
异常处理
try
语句按如下方式工作:
- 首先,执行
try
子句 (在try
和except
关键字之间的部分) - 如果没有异常发生,
except
子句在try
语句执行完毕后就被忽略了 - 如果在
try
子句执行过程中发生了异常,那么该子句其余的部分就会被忽略。如果异常匹配于except
关键字后面指定的异常类型,就执行对应的except
子句。然后继续执行try
语句之后的代码 - 如果发生了一个异常,在
except
子句中没有与之匹配的分支,它就会传递到上一级try
语句中。如果最终仍找不到对应的处理语句,它就成为一个未处理异常,终止程序运行,显示提示信息
一个except
子句可以在括号中列出多个异常的名字去匹配相应的异常;如果省略括号,则表示全部匹配
import sys
try:
f = open('myfile.txt')
s = f.readline()
i = int(s.strip())
except OSError as err:
print("OS error: {0}".format(err))
except ValueError:
print("Could not convert data to an integer.")
except:
print("Unexpected error:", sys.exc_info()[0])
raise
try-except
语句可以带有一个else
子句,该子句只能出现在所有except
子句之后。当try
语句没有抛出异常时,需要执行一些代码,可以使用这个子句
for arg in sys.argv[1:]:
try:
f = open(arg, 'r')
except IOError:
print('cannot open', arg)
else:
print(arg, 'has', len(f.readlines()), 'lines')
f.close()
使用else
子句比在try
子句中附加代码要好,因为这样可以避免try-except
意外的截获本来不属于它们保护的那些代码抛出的异常
所以,except
里面的代码是错误的时候执行的;else
里面的代码是正确的时候执行的;finally
里面的代码是无论如何都会执行的(后面讲到)
发生异常时,可能会有一个附属值,作为异常的参数存在。这个参数是否存在、是什么类型,依赖于异常的类型。通常可以为except子句指定(as)一个变量,可以直接print出来看
异常处理器不仅仅处理那些在 try 子句中立刻发生的异常,也会处理那些 try 子句中调用的函数内部发生的异常
>>> def this_fails():
... x = 1/0
...
>>> try:
... this_fails()
... except ZeroDivisionError as err:
... print('Handling run-time error:', err)
...
Handling run-time error: int division or modulo by zero
raise
语句允许程序员强制抛出一个指定的异常(必须是一个异常实例或异常类)
如果你需要明确一个异常是否抛出,但不想处理它,raise
语句可以让你很简单的重新抛出该异常(一些监控工具的实现)
>>> try:
... raise NameError('HiThere')
... except NameError:
... print('An exception flew by!')
... raise
...
''' result:
An exception flew by!
Traceback (most recent call last):
File "<stdin>", line 2, in ?
NameError: HiThere
'''
用户可以自定义异常,异常类通常应该直接或间接从Exception
类派生,下面是一个例子,更多例子看这里
>>> class MyError(Exception):
... def __init__(self, value):
... self.value = value
... def __str__(self):
... return repr(self.value)
...
>>> try:
... raise MyError(2*2)
... except MyError as e:
... print('My exception occurred, value:', e.value)
...
''' result:
My exception occurred, value: 4
>>> raise MyError('oops!')
Traceback (most recent call last):
File "<stdin>", line 1, in ?
__main__.MyError: 'oops!'
'''
前面说到的finally
语句,是无论什么情况都会执行的功能,通常放的是清理行为,如果遇到异常,执行完这段清理语句之后就结束了,下面是一个混合try-except-else-finally
的代码块
>>> def divide(x, y):
... try:
... result = x / y
... except ZeroDivisionError:
... print("division by zero!")
... else:
... print("result is", result)
... finally:
... print("executing finally clause")
...
>>> divide(2, 1)
result is 2
executing finally clause
>>> divide(2, 0)
division by zero!
executing finally clause
>>> divide("2", "1")
executing finally clause
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 3, in divide
TypeError: unsupported operand type(s) for /: 'str' and 'str'
在真实场景的应用程序中,finally
子句用于释放外部资源(文件 或网络连接之类的),无论它们的使用过程中是否出错
有些对象定义了标准的清理行为,无论对象操作是否成功,不再需要该对象的时候就会起作用。下面有两段代码:
# para 1
for line in open("myfile.txt"):
print(line)
# para 2
with open("myfile.txt") as f:
for line in f:
print(line)
区别在于,第一段代码没有关闭打开的文件,而with
语句可以使文件之类的对象确保总能及时准确地进行清理。在第二段语句执行后,文件对象总会被关闭,即使是在处理文件中的数据时出错也一样可以
类
关于作用域的示例(注意变量的查找顺序),以下例子:
def scope_test():
def do_local():
spam = "local spam"
def do_nonlocal():
nonlocal spam
spam = "nonlocal spam"
def do_global():
global spam
spam = "global spam"
spam = "test spam"
do_local()
print("After local assignment:", spam)
do_nonlocal()
print("After nonlocal assignment:", spam)
do_global()
print("After global assignment:", spam)
scope_test()
print("In global scope:", spam)
类定义语法
类定义就像函数定义,要先执行才能生效
class ClassName:
<statement-1>
.
.
.
<statement-N>
Python
的类对象支持属性引用和实例化,如:
class MyClass:
"""A simple example class"""
i = 12345
def f(self):
return 'hello world'
属性引用: MyClass.i
, MyClass.f
, MyClass.__doc__
实例化: x = MyClass()
构造函数的定义:
def __init__(self[, other params]):
pass
类方法的第一个参数一定是self
,在调用的时候省略传入,代指实例本身
Python
的实例对象唯一可用的操作是属性引用。和局部变量一样,数据属性不需要声明,第一次使用时它们就会生成
在调用方法的时候,如:x.f()
事实上是调用了MyClass.f(x)
,这就是要在定义类方法的时候加上参数self
的原因
类和实例变量
一般来说,实例变量用于对每一个实例都是唯一的数据,类变量用于类的所有实例共享的属性和方法
class Dog:
kind = 'canine' # class variable shared by all instances
def __init__(self, name):
self.name = name # instance variable unique to each instance
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.kind # shared by all dogs
'canine'
>>> e.kind # shared by all dogs
'canine'
>>> d.name # unique to d
'Fido'
>>> e.name # unique to e
'Buddy'
这样会出现,可变对象作共享数据的问题
class Dog:
tricks = [] # mistaken use of a class variable
def __init__(self, name):
self.name = name
def add_trick(self, trick):
self.tricks.append(trick)
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks # unexpectedly shared by all dogs
['roll over', 'play dead']
正确的做法是,将这个列表作为一个实例变量,即放入构造方法中赋值
class Dog:
def __init__(self, name):
self.name = name
self.tricks = [] # creates a new empty list for each dog
def add_trick(self, trick):
self.tricks.append(trick)
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks
['roll over']
>>> e.tricks
['play dead']
类的继承
派生类的定义例子:
class DerivedClassName(BaseClassName):
<statement-1>
.
.
.
<statement-N>
派生类定义的执行过程和基类是一样的。构造派生类对象时,就记住了基类。这在解析属性引用的时候尤其有用:如果在类中找不到请求调用的属性,就搜索基类
如果基类是由别的类派生而来,这个规则会递归的应用上去。方法引用按如下规则解析:搜索对应的类属性,必要时沿基类链逐级搜索,如果找到了函数对象这个方法引用就是合法的
派生类可能会覆盖其基类的方法。因为方法调用同一个对象中的其它方法时没有特权,基类的方法调用同一个基类的方法时,可能实际上最终调用了派生类中的覆盖方法
派生类中调用基类方法,这时需要传入self
参数了,BaseClassName.methodname(self, arguments)
判断类或者实例之间的关系
- 函数
isinstance()
用于检查实例类型:isinstance(obj, int)
只有在obj.__class__
是int
或其它从int
继承的类型 - 函数
issubclass()
用于检查类继承:issubclass(bool, int)
为True
,因为bool
是int
的子类 - 然而,
issubclass(float, int)
为False
,因为float
不是int
的子类
Python
同样有限的支持多继承形式,例子如下:
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
.
.
.
<statement-N>
搜索顺序:如果在DerivedClassName
(示例中的派生类)中没有找到某个属性,就会搜索Base1
,然后(递归的)搜索其基类,如果最终没有找到,就搜索Base2
,以此类推
上面的搜索顺序有一个问题,如果多个Base
拥有相同的基类,就会发生重复访问基类的情况,这时,可以用super()
来动态改变解析顺序
为了防止重复访问基类,通过动态的线性化算法,每个类都按从左到右的顺序特别指定了顺序,每个祖先类只调用一次
私有变量
Python
中不存在只能从对象内部访问的实例变量,一般规定私有变量以一个下划线开头命名,例如_spam
。此外,形如__spam
,以两个下划线开头,后面至多只有一个下划线的变量,会被替换为_classname__spam
>>> class Ms1:
... def _update(self):
... print(345)
... def __hello(self):
... print("hello world")
...
>>> m = Ms1()
>>> m._Ms1__hello()
hello world
>>> m.__hello()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Ms1' object has no attribute '__hello'
>>> m._Ms1__hello()
hello world
>>> m._update()
345
迭代器
大多数容器对象都可以用for
遍历,如for elem in [1, 2, 3]
。在后台,for
语句在容器对象中调用iter()
,该函数返回一个定义了__next__()
方法的迭代器对象,它在容器中逐一访问元素,当没有后续的元素时,__next__()
抛出一个StopIteration
异常来通知for
语句结束循环
可以使用内建的next()
函数来调用__next__()
方法
>>> s = 'abc'
>>> it = iter(s)
>>> it
<iterator object at 0x00A1DB50>
>>> next(it)
'a'
>>> next(it)
'b'
>>> next(it)
'c'
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
next(it)
StopIteration
根据之前所说的原理,可以很容易的为自己的类添加迭代器行为,以下例子:
class Reverse:
"""Iterator for looping over a sequence backwards."""
def __init__(self, data):
self.data = data
self.index = len(data)
def __iter__(self):
return self
def __next__(self):
if self.index == 0:
raise StopIteration
self.index = self.index - 1
return self.data[self.index]
>>> rev = Reverse('spam')
>>> iter(rev)
<__main__.Reverse object at 0x00A1DB50>
>>> for char in rev:
... print(char)
...
m
a
p
s
生成器
生成器是创建迭代器的简单而强大的工具,它们写起来就像是正规的函数,需要返回数据的时候使用yield
语句
def reverse(data):
for index in range(len(data)-1, -1, -1):
yield data[index]
>>> for char in reverse('golf'):
... print(char)
...
f
l
o
g
前面中描述了基于类的迭代器,它能做的每一件事生成器也能做到。因为自动创建了__iter__()
和__next__()
方法,生成器显得如此简洁。除了创建和保存程序状态的自动方法,当发生器终结时,还会自动抛出StopIteration
异常。综上所述,这些功能使得编写一个正规函数成为创建迭代器的最简单方法
网友评论