master
/ 01.02 Python 进阶.ipynb

01.02 Python 进阶.ipynb @masterview markup · raw · history · blame

Notebook

1.2 Python 进阶

1.2.1 列表推导式

循环可以用来生成列表:

In [ ]:
a = [x for x in range(4)]
a

在循环的过程中使用 if:

In [ ]:
a = [x for x in range(3,10) if x % 2 == 0]
a

2 个for 循环:

In [ ]:
a = [(x,y) for x in range(1,3) for y in range(1,3)]
a

3 个 for 循环:

In [ ]:
a = [(x,y,z) for x in range(1,3) for y in range(1,3) for z in range(1,3)]
a

也可以使用推导式生成集合和字典:
字典推导式

In [ ]:
values = [10, 21, 4, 7, 12]
square_dict = {x: x**2 for x in values if x <= 10}
print(square_dict)

集合推导式

In [ ]:
values = [10, 21, 4, 7, 12]
square_set = {x**2 for x in values if x <= 10}
print(square_set)

1.2.2 函数

定义函数

函数function,通常接收输入参数,并有返回值。

它负责完成某项特定任务,而且相较于其他代码,具备相对的独立性。

In [ ]:
def add(x, y):
    """Add two numbers"""
    a = x + y
    return a

函数通常有一下几个特征:

  • 使用 def 关键词来定义一个函数。
  • def 后面是函数的名称,括号中是函数的参数,不同的参数用 , 隔开, def foo(): 的形式是必须要有的,参数可以为空;
  • 使用缩进来划分函数的内容;
  • docstring""" 包含的字符串,用来解释函数的用途,可省略;
  • return 返回特定的值,如果省略,返回 None

使用函数

使用函数时,只需要将参数换成特定的值传给函数。

Python 并没有限定参数的类型,因此可以使用不同的参数类型:

In [ ]:
print(add(2, 3))
print(add('foo', 'bar'))

在这个例子中,如果传入的两个参数不可以相加,那么 Python 会将报错:

In [ ]:
print(add(2, "foo"))

如果传入的参数数目与实际不符合,也会报错:

In [ ]:
print(add(1, 2, 3))

传入参数时,Python 提供了两种选项,第一种是上面使用的按照位置传入参数,另一种则是使用关键词模式,显式地指定参数的值:

In [ ]:
print(add(x=2, y=3))
print(add(y="foo", x="bar"))

可以混合这两种模式:

In [ ]:
print(add(2, y=3))

设定参数默认值

可以在函数定义的时候给参数设定默认值,例如:

In [ ]:
def quad(x, a=1, b=0, c=0):
    return a*x**2 + b*x + c

可以省略有默认值的参数:

In [ ]:
print(quad(2.0))

可以修改参数的默认值:

In [ ]:
print(quad(2.0, b=3))
In [ ]:
print(quad(2.0, 2, c=4))

这里混合了位置和指定两种参数传入方式,第二个 2 是传给 a 的。

注意,在使用混合语法时,要注意不能给同一个值赋值多次,否则会报错,例如:

In [ ]:
print(quad(2.0, 2, a=2))

接收不定长参数

使用如下方法,可以使函数接受不定数目的参数:

In [ ]:
def add(x, *args):
    total = x
    for arg in args:
        total += arg
    return total

这里,*args 表示参数数目不定,可以看成一个元组,把第一个参数后面的参数当作元组中的元素。

In [ ]:
print(add(1, 2, 3, 4))
print(add(1, 2))

这样定义的函数不能使用关键词传入参数,要使用关键词,可以这样:

In [ ]:
def add(x, **kwargs):
    total = x
    for arg, value in kwargs.items():
        print("adding %s=%s"%(arg,value))
        total += value
    return total

这里, **kwargs 表示参数数目不定,相当于一个字典,键和值对应于键值对。

In [ ]:
print(add(10, y=11, z=12, w=13))

再看这个例子,可以接收任意数目的位置参数和键值对参数:

In [ ]:
def foo(*args, **kwargs):
    print(args, kwargs)

foo(2, 3, x='bar', z=10)

不过要按顺序传入参数,先传入位置参数 args ,再传入关键词参数 kwargs

返回多个值

函数可以返回多个值:

In [ ]:
def divid(a, b):
    """
    除法
    :param a: number 被除数
    :param b: number 除数
    :return: 商和余数
    """
    quotient = a // b
    remainder = a % b
    return quotient, remainder

quotient, remainder = divid(7,4)
print(quotient, remainder)

事实上,Python 将返回的两个值变成了元组:

In [ ]:
print(divid(7,4))

因为这个元组中有两个值,所以可以使用

quotient, remainder = divid(7,4)

给两个值赋值。

列表也有相似的功能:

In [ ]:
a, b, c = [1, 2, 3]
print(a, b, c)

事实上,不仅仅返回值可以用元组表示,也可以将参数用元组以这种方式传入:

In [ ]:
def divid(a, b):
    """
    除法
    :param a: number 被除数
    :param b: number 除数
    :return: 商和余数
    """
    quotient = a // b
    remainder = a % b
    return quotient, remainder

z = (7,4)
print(divid(*z))

这里的*必不可少。

事实上,还可以通过字典传入参数来执行函数:

In [ ]:
def divid(a, b):
    """
    除法
    :param a: number 被除数
    :param b: number 除数
    :return: 商和余数
    """
    quotient = a // b
    remainder = a % b
    return quotient, remainder

z = {'a':7,'b':4}
print(divid(**z))

map 方法生成序列

其用法为:

map(aFun, aSeq)

将函数 aFun 应用到序列 aSeq 上的每一个元素上,返回一个列表,不管这个序列原来是什么类型。

事实上,根据函数参数的多少,map 可以接受多组序列,将其对应的元素作为参数传入函数:

In [ ]:
def divid(a, b):
    """
    除法
    :param a: number 被除数
    :param b: number 除数
    :return: 商和余数
    """
    quotient = a // b
    remainder = a % b
    return quotient, remainder

a = (10, 6, 7)
b = [2, 5, 3]
print(list(map(divid,a,b)))

1.2.3 模块和包

模块

Python 会将所有 .py 结尾的文件认定为 Python 代码文件,考虑下面的脚本 ex1.py

In [ ]:
%%writefile ex1.py

PI = 3.1416

def sum(lst):
    """
    计算 lst 序列所有元素的和
    :param lst: 序列 e.g. [1,2,3]
    :return: lst 序列所有元素的总和
    """
    
    # 获取 lst序列第一个元素
    tot = lst[0]
    
    # 循环遍历 lst 序列剩余元素
    for value in lst[1:]:
        tot = tot + value
    return tot

w = [0, 1, 2, 3]
print(sum(w), PI)

可以执行它:

In [ ]:
%run ex1.py

这个脚本可以当作一个模块,可以使用import关键词加载并执行它(这里要求ex1.py在当前工作目录):

In [ ]:
import ex1
In [ ]:
ex1

在导入时,Python 会执行一遍模块中的所有内容。

ex1.py 中所有的变量都被载入了当前环境中,不过要使用

ex1.变量名

的方法来查看或者修改这些变量:

In [ ]:
print(ex1.PI)
In [ ]:
ex1.PI = 3.141592653
print(ex1.PI)

还可以用

ex1.函数名

调用模块里面的函数:

In [ ]:
print(ex1.sum([2, 3, 4]))

为了提高效率,Python 只会载入模块一次,已经载入的模块再次载入时,Python 并不会真正执行载入操作,哪怕模块的内容已经改变。

例如,这里重新导入 ex1 时,并不会执行 ex1.py 中的 print 语句:

In [ ]:
import ex1

需要重新导入模块时,可以使用 reload 强制重新载入它,例如:

In [ ]:
from imp import reload
reload(ex1)

删除之前生成的文件:

In [ ]:
import os
os.remove('ex1.py')

__name__ 属性

有时候我们想将一个 .py 文件既当作脚本,又能当作模块用,这个时候可以使用 __name__ 这个属性。

只有当文件被当作脚本执行的时候, __name__的值才会是 '__main__',所以我们可以:

In [ ]:
%%writefile ex2.py

PI = 3.1416

def sum(lst):
    """ Sum the values in a list
    """
    tot = 0
    for value in lst:
        tot = tot + value
    return tot

def add(x, y):
    " Add two values."
    a = x + y
    return a

def test():
    w = [0,1,2,3]
    assert(sum(w) == 6)
    print('test passed.')

if __name__ == '__main__':
    test()

运行文件:

In [ ]:
%run ex2.py

当作模块导入, test() 不会执行:

In [ ]:
import ex2

但是可以使用其中的变量:

In [ ]:
ex2.PI

引入模块时可以为它设置一个别名让使用更方便:

In [ ]:
import ex2 as e2
e2.PI

其它导入方法

可以从模块中导入变量:

In [ ]:
from ex2 import add, PI

使用 from 后,可以直接使用 addPI

In [ ]:
add(2, 3)

或者使用 * 导入所有变量:

In [ ]:
from ex2 import *
add(3, 4.5)

这种导入方法不是很提倡,因为如果你不确定导入的都有哪些,可能覆盖一些已有的函数。

删除文件:

In [ ]:
import os
os.remove('ex2.py')

假设我们有这样的一个文件夹:

foo/

  • __init__.py
  • bar.py (defines func)
  • baz.py (defines zap)

这意味着 foo 是一个包,我们可以这样导入其中的内容:

from foo.bar import func
from foo.baz import zap

barbaz 都是 foo 文件夹下的 .py 文件。

导入包要求:

  • 文件夹 fooPython 的搜索路径中
  • __init__.py 表示 foo 是一个包,它可以是个空文件。

1.2.4 异常

写代码的时候,出现错误不可避免,即使代码语法没有问题,也可能遇到其它问题。

看下面这段代码:

import math

while True:
    text = input('> ')
    if text[0] == 'q':
        break
    x = float(text)
    y = math.log10(x)
    print("log10({0}) = {1}".format(x, y))

这段代码接收命令行的输入,当输入为数字时,计算它的对数并输出,直到输入值为 q 为止。

乍看没什么问题,然而当我们输入 0 或者负数时:

In [ ]:
import math

while True:
    text = input('> ')
    if text[0] == 'q':
        break
    x = float(text)
    y = math.log10(x)
    print("log10({0}) = {1}".format(x, y))

log10 函数会报错,因为不能接受非正值。

一旦报错,程序就会停止执行。如果不希望程序停止执行,并且想要捕捉异常,那么我们可以按照 try/except 语句。

import math

while True:
    try:
        text = input('> ')
        if text[0] == 'q':
            break
        x = float(text)
        y = math.log10(x)
        print("log10({0}) = {1}".format(x, y))
    except ValueError:
        print("the value must be greater than 0")

一旦 try 块中的内容出现了异常,那么 try 块后面的内容会被忽略,Python 会寻找 except 里面有没有对应的内容,如果找到,就执行对应的块,没有则抛出这个异常。

在上面的例子中,try 抛出的是 ValueErrorexcept 中有对应的内容,所以这个异常被 except 捕捉到,程序可以继续执行:

In [ ]:
import math

while True:
    try:
        text = input('> ')
        if text[0] == 'q':
            break
        x = float(text)
        y = math.log10(x)
        print("log10({0}) = {1}".format(x, y))
    except ValueError:
        print("the value must be greater than 0")

捕捉不同的错误类型

import math

while True:
    try:
        text = input('> ')
        if text[0] == 'q':
            break
        x = float(text)
        y = 1 / math.log10(x)
        print("log10({0}) = {1}".format(x, y))
    except ValueError:
        print("the value must be greater than 0")

假设我们将这里的 y 更改为 1 / math.log10(x),此时输入 1

In [ ]:
import math

while True:
    try:
        text = input('> ')
        if text[0] == 'q':
            break
        x = float(text)
        y = 1 / math.log10(x)
        print("log10({0}) = {1}".format(x, y))
    except ValueError:
        print("the value must be greater than 0")

因为我们的 except 里面并没有 ZeroDivisionError,所以会抛出这个异常,我们可以通过两种方式解决这个问题。

捕捉所有异常

except 的值改成 Exception 类,来捕获所有的异常。

In [ ]:
import math

while True:
    try:
        text = input('> ')
        if text[0] == 'q':
            break
        x = float(text)
        y = 1 / math.log10(x)
        print("1 / log10({0}) = {1}".format(x, y))
    except Exception:
        print("invalid value")

指定特定异常

这里,我们把 ZeroDivisionError 加入 except

In [ ]:
import math

while True:
    try:
        text = input('> ')
        if text[0] == 'q':
            break
        x = float(text)
        y = 1 / math.log10(x)
        print("1 / log10({0}) = {1}".format(x, y))
    except (ValueError, ZeroDivisionError):
        print("invalid value")

或者另加处理:

In [ ]:
import math

while True:
    try:
        text = input('> ')
        if text[0] == 'q':
            break
        x = float(text)
        y = 1 / math.log10(x)
        print("1 / log10({0}) = {1}".format(x, y))
    except ValueError:
        print("the value must be greater than 0")
    except ZeroDivisionError:
        print("the value must not be 1")

事实上,我们还可以将这两种方式结合起来,用 Exception 来捕捉其他的错误:

In [ ]:
import math

while True:
    try:
        text = input('> ')
        if text[0] == 'q':
            break
        x = float(text)
        y = 1 / math.log10(x)
        print("1 / log10({0}) = {1}".format(x, y))
    except ValueError:
        print("the value must be greater than 0")
    except ZeroDivisionError:
        print("the value must not be 1")
    except Exception:
        print("unexpected error")

得到异常的具体信息

在上面的例子中,当我们输入不能转换为浮点数的字符串时,它输出的是 the value must be greater than 0,这并没有反映出实际情况。

为了得到异常的具体信息,我们将这个 ValueError 具体化:

In [ ]:
import math

while True:
    try:
        text = input('> ')
        if text[0] == 'q':
            break
        x = float(text)
        y = 1 / math.log10(x)
        print("1 / log10({0}) = {1}".format(x, y))
    except ValueError as exc:
        if exc.message == "math domain error":
            print("the value must be greater than 0")
        else:
            print("could not convert '%s' to float" % text)
    except ZeroDivisionError:
        print("the value must not be 1")
    except Exception as exc:
        print("unexpected error:", exc.message)

同时,我们也将捕获的其他异常的信息显示出来。

这里,exc.message 显示的内容是异常对应的说明,例如

ValueError: could not convert string to float: a

对应的 message

could not convert string to float: a

当我们使用 except Exception 时,会捕获所有的 Exception 和它派生出来的子类,但不是所有的异常都是从 Exception 类派生出来的,可能会出现一些不能捕获的情况,因此,更加一般的做法是使用这样的形式:

try:
    pass
except:
    pass

这样不指定异常的类型会捕获所有的异常,但是这样的形式并不推荐。

else

try/except 块有一个可选的关键词 else

如果使用这个子句,那么必须放在所有的 except 子句之后。else 子句将在 try 子句没有发生任何异常的时候执行。

In [ ]:
try:
    print(1)
except:
    pass
else:
    print('else was called.')

出现异常,else 不会执行。

In [ ]:
try:
    print(1/0)
except ZeroDivisionError:
    print('divide by 0.')
else:
    print('else was called.')

finally

try/except 块还有一个可选的关键词 finally

不管 try 块有没有异常, finally 块的内容总是会被执行,而且会在抛出异常前执行,因此可以用来作为安全保证,比如确保打开的文件被关闭。

In [ ]:
try:
    print(1)
finally:
    print('finally was called.')

在抛出异常前执行:

In [ ]:
try:
    print(1 / 0)
finally:
    print('finally was called.')

如果异常被捕获了,在最后执行:

In [ ]:
try:
    print(1 / 0)
except ZeroDivisionError:
    print('divide by 0.')
finally:
    print('finally was called.')

异常的处理流程可参考下图:

1.2.5 警告

出现了一些需要让用户知道的问题,但又不想停止程序,这时候我们可以使用警告:

首先导入警告模块:

In [ ]:
import warnings

在需要的地方,我们使用 warnings 中的 warn 函数:

warn(msg, WarningType = UserWarning)
In [ ]:
def month_warning(m):
    if not 1<= m <= 12:
        msg = "month (%d) is not between 1 and 12" % m
        warnings.warn(msg, RuntimeWarning)

month_warning(13)

有时候我们想要忽略特定类型的警告,可以使用 warningsfilterwarnings 函数:

filterwarnings(action, category)

action 设置为 'ignore' 便可以忽略特定类型的警告:

In [ ]:
warnings.filterwarnings(action = 'ignore', category = RuntimeWarning)

month_warning(13)

1.2.6 文件读写

写入测试文件:

In [ ]:
%%writefile test.txt
this is a test file.
hello world!
python is good!
today is a good day.

读文件

使用 open 函数来读文件,使用文件名的字符串作为输入参数:

In [ ]:
f = open('test.txt')

默认以读的方式打开文件,如果文件不存在会报错:

In [ ]:
f = open('test1.txt')

可以使用 read 方法来读入文件中的所有内容:

In [ ]:
text = f.read()
print(text)

也可以按照行读入内容,readlines 方法返回一个列表,每个元素代表文件中每一行的内容:

In [ ]:
f = open('test.txt')
lines = f.readlines()
print(lines)

使用完文件之后,需要将文件关闭。

In [ ]:
f.close()

事实上,我们可以将 f 放在一个循环中,得到它每一行的内容:

In [ ]:
f = open('test.txt')
for line in f:
    print(line)
f.close()

删除刚才创建的文件:

In [ ]:
import os
os.remove('test.txt')

写文件

我们使用 open 函数的写入模式来写文件:

In [ ]:
f = open('myfile.txt', 'w')
f.write('hello world!')
f.close()

使用 w 模式时,如果文件不存在会被创建,我们可以查看是否真的写入成功:

In [ ]:
print(open('myfile.txt').read())

如果文件已经存在, w 模式会覆盖之前写的所有内容:

In [ ]:
f = open('myfile.txt', 'w')
f.write('another hello world!')
f.close()
print(open('myfile.txt').read())

除了写入模式,还有追加模式 a ,追加模式不会覆盖之前已经写入的内容,而是在之后继续写入:

In [ ]:
f = open('myfile.txt', 'a')
f.write('... and more')
f.close()
print(open('myfile.txt').read())

写入结束之后一定要将文件关闭,否则可能出现内容没有完全写入文件中的情况。

还可以使用读写模式 w+

In [ ]:
f = open('myfile.txt', 'w+')
f.write('hello world!')
f.seek(6)
print(f.read())
f.close()

这里 f.seek(6) 移动到文件的第6个字符处,然后 f.read() 读出剩下的内容。

删除刚才创建的文件:

In [ ]:
import os
os.remove('myfile.txt')

关闭文件

Python 中,如果一个打开的文件不再被其他变量引用时,它会自动关闭这个文件。

所以正常情况下,如果一个文件正常被关闭了,忘记调用文件的 close 方法不会有什么问题。

关闭文件可以保证内容已经被写入文件,而不关闭可能会出现意想不到的结果:

In [ ]:
f = open('newfile.txt','w')
f.write('hello world')
g = open('newfile.txt', 'r')
print(repr(g.read()))

虽然这里写了内容,但是在关闭之前,这个内容并没有被写入磁盘。

使用循环写入的内容也并不完整:

In [ ]:
f = open('newfile.txt','w')
for i in range(30):
    f.write('hello world: ' + str(i) + '\n')

g = open('newfile.txt', 'r')
print(g.read())
f.close()
g.close()
In [ ]:
import os
os.remove('newfile.txt')

出现异常时候的读写:

In [ ]:
f = open('newfile.txt','w')
for i in range(30):
    x = 1.0 / (i - 10)
    f.write('hello world: ' + str(i) + '\n')

查看已有内容:

In [ ]:
g = open('newfile.txt', 'r')
print(g.read())
f.close()
g.close()

可以看到,出现异常的时候,磁盘的写入并没有完成,为此我们可以使用 try/except/finally 块来关闭文件,这里 finally 确保关闭文件,所有的写入已经完成。

In [ ]:
f = open('newfile.txt','w')
try:
    for i in range(30):
        x = 1.0 / (i - 10)
        f.write('hello world: ' + str(i) + '\n')
except Exception:
    print("something bad happened")
finally:
    f.close()
In [ ]:
g = open('newfile.txt', 'r')
print(g.read())
g.close()

with 方法

事实上,Python 提供了更安全的方法,当 with 块的内容结束后,Python 会自动调用它的close 方法,确保读写的安全:

In [ ]:
with open('newfile.txt','w') as f:
    for i in range(30):
        x = 1.0 / (i - 10)
        f.write('hello world: ' + str(i) + '\n')

try/exception/finally 效果相同,但更简单。

In [ ]:
g = open('newfile.txt', 'r')
print(g.read())
g.close()

所以,写文件时候要确保文件被正确关闭。

删除刚才创建的文件:

In [ ]:
import os
os.remove('newfile.txt')

1.2.7 CSV 文件和 csv 模块

标准库中有自带的 csv 模块处理 csv 格式的文件:

In [ ]:
import csv

读 csv 文件

假设我们有这样的一个文件:

In [ ]:
%%file data.csv
"alpha 1",  100, -1.443
"beat  3",   12, -0.0934
"gamma 3a", 192, -0.6621
"delta 2a",  15, -4.515

打开这个文件,并产生一个文件 reader:

In [ ]:
# 打开 data.csv 文件
fp = open("data.csv")

# 读取文件“”
r = csv.reader(fp)

# 可以按行迭代数据
for row in r:
    print(row)

# 关闭文件
fp.close()

默认数据内容都被当作字符串处理,不过可以自己进行处理:

In [ ]:
data = []

with open('data.csv') as fp:
    r = csv.reader(fp)
    for row in r:
        data.append([row[0], int(row[1]), float(row[2])])

data

清除刚刚创建的文件:

In [ ]:
import os
os.remove('data.csv')

写 csv 文件

可以使用 csv.writer 写入文件,不过相应地,传入的应该是以写方式打开的文件,不过一般要用 'wb' 即二进制写入方式,防止出现换行不正确的问题:

In [ ]:
data = [('one', 1, 1.5), ('two', 2, 8.0)]
with open('out.csv', 'w') as fp:
    w = csv.writer(fp)
    w.writerows(data)

显示结果:

In [ ]:
! cat 'out.csv'