目录 | 上一节 (3.1 脚本) | 下一节 (3.3 错误检查)

3.2 深入函数

尽管函数在早先时候介绍了,但有关函数在更深层次上是如何工作的细节却很少提供。本节旨在填补这些空白,并讨论函数调用约定,作用域规则等问题。

调用函数

考虑以下函数:

def read_prices(filename, debug):
...

可以使用位置参数调用该函数:

prices = read_prices('prices.csv', True)

或者,可以使用关键字参数调用该函数:

prices = read_prices(filename='prices.csv', debug=True)

默认参数

有时候,你希望参数是可选的,如果是这样,请在函数定义中分配一个默认值。

def read_prices(filename, debug=False):
...

如果分配了默认值,则参数在函数调用中是可选的。

d = read_prices('prices.csv')
e = read_prices('prices.dat', True)

注意:带有默认值的参数(译注:即关键字参数)必须出现在参数列表的末尾(所有非可选参数都放在最前面)

首选关键字参数作为可选参数

比较以下两种不同的调用风格:

parse_data(data, False, True) # ?????

parse_data(data, ignore_errors=True)
parse_data(data, debug=True)
parse_data(data, debug=True, ignore_errors=True)

在大部分情况下,关键字参数提高了代码的简洁性——特别是对于用作标志的参数,或者与可选特性相关的参数。

设计最佳实践

始终为函数参数指定简短但有意义的名称。

使用函数的人可能想要使用关键字调用风格。

d = read_prices('prices.csv', debug=True)

Python 开发工具将会在帮助功能或者帮助文档中显示这些名称。

返回值

return 语句返回一个值:

def square(x):
return x * x

如果没有给出返回值或者 return 语句缺失,那么返回 None:

def bar(x):
statements
return a = bar(4) # a = None # OR
def foo(x):
statements # No `return` b = foo(4) # b = None

多个返回值

函数只能返回一个值。但是,通过将返回值放到元组中,函数可以返回多个值:

def divide(a,b):
q = a // b # Quotient
r = a % b # Remainder
return q, r # Return a tuple

用例:

x, y = divide(37,5) # x = 7, y = 2

x = divide(37, 5)   # x = (7, 2)

变量作用域

程序给变量赋值:

x = value # Global variable

def foo():
y = value # Local variable

变量赋值发生在函数的内部和外部。定义在函数外部的变量是“全局的”。定义在函数内部的变量是“局部的”。

局部变量

在函数内部赋值的变量是私有的。

def read_portfolio(filename):
portfolio = []
for line in open(filename):
fields = line.split(',')
s = (fields[0], int(fields[1]), float(fields[2]))
portfolio.append(s)
return portfolio

在此示例中,filename, portfolio, line, fields 和 s 是局部变量。在函数调用之后,这些变量将不会保留或者不可访问。

>>> stocks = read_portfolio('portfolio.csv')
>>> fields
Traceback (most recent call last):
File "<stdin>", line 1, in ?
NameError: name 'fields' is not defined
>>>

局部变量也不能与其它地方的变量冲突。

全局变量

函数可以自由地访问定义在同一文件中的全局变量值。

name = 'Dave'

def greeting():
print('Hello', name) # Using `name` global variable

但是,函数不能修改全局变量:

name = 'Dave'

def spam():
name = 'Guido' spam()
print(name) # prints 'Dave'

切记:函数中的所有赋值都是局部的

修改全局变量

如果必须修改全局变量,请像下面这样声明它:

name = 'Dave'

def spam():
global name
name = 'Guido' # Changes the global name above

全局声明必须在使用之前出现,并且相应的变量必须与该函数处在同一文件中。看上面这个函数,要知道这是一种糟糕的形式。事实上,如果可以的话,尽量避免使用 global 。如果需要一个函数来修改函数外部的某种状态,最好是使用类来代替(稍后详细介绍)。

参数传递

当调用一个函数的时候,参数变量的传递是引用传递。不拷贝值(参见2.7 节)。如果传递了可变数据类型(如列表,字典),它们可以被原地修改。

def foo(items):
items.append(42) # Modifies the input object a = [1, 2, 3]
foo(a)
print(a) # [1, 2, 3, 42]

关键点:函数不接收输入参数的拷贝。

重新赋值与修改

确保了解修改值与给变量名重新赋值的细微差别。

def foo(items):
items.append(42) # Modifies the input object a = [1, 2, 3]
foo(a)
print(a) # [1, 2, 3, 42] # VS
def bar(items):
items = [4,5,6] # Changes local `items` variable to point to a different object b = [1, 2, 3]
bar(b)
print(b) # [1, 2, 3]

提醒:变量赋值永远不会重写内存。名称只是被绑定到了新的值上面

练习

本组练习实现的内容可能是本课程最强大的和最难的。有很多步骤,并且过去练习中的许多概念被一次性整合在一起。虽然最后的题解只有大约 25 行的代码,但要花点时间,确保你理解每一个部分。

report.py 的中心部分主要用于读取 CSV 文件。例如,read_portfolio() 函数读取包含投资组合数据的文件,read_prices() 函数读取包含价格数据的文件。在这两个函数中,有很多底层的“精细的”事以及相似的特性。例如,它们都打开一个文件并使用 csv 模块来处理,并且将各种字段转换为新的类型。

如果真的需要对大量的文件进行解析,可能需要清理其中的一些内容使其更通用。这是我们的目标。

通过打开 Work/fileparse.py 文件开始本练习,该文件是我们将要写代码的地方。

练习 3.3:读取 CSV 文件

首先,让我们仅关注将 CSV 文件读入字典列表的问题。在 fileparse.py 中,定义一个如下所示的函数:

# fileparse.py
import csv def parse_csv(filename):
'''
Parse a CSV file into a list of records
'''
with open(filename) as f:
rows = csv.reader(f) # Read the file headers
headers = next(rows)
records = []
for row in rows:
if not row: # Skip rows with no data
continue
record = dict(zip(headers, row))
records.append(record) return records

该函数将 CSV 文件读入字典列表中,但是隐藏了打开文件,使用 csv 模块处理,忽略空行等详细信息。

试试看:

提示: python3 -i fileparse.py.

>>> portfolio = parse_csv('Data/portfolio.csv')
>>> portfolio
[{'price': '32.20', 'name': 'AA', 'shares': '100'}, {'price': '91.10', 'name': 'IBM', 'shares': '50'}, {'price': '83.44', 'name': 'CAT', 'shares': '150'}, {'price': '51.23', 'name': 'MSFT', 'shares': '200'}, {'price': '40.37', 'name': 'GE', 'shares': '95'}, {'price': '65.10', 'name': 'MSFT', 'shares': '50'}, {'price': '70.44', 'name': 'IBM', 'shares': '100'}]
>>>

这很好,除了不能使用数据做任何有用的计算之外。因为所有的内容都是用字符串表示。我们将马上解决此问题,先让我们继续在此基础上进行构建。

练习 3.4:构建列选择器

在大部分情况下,你只对 CSV 文件中选定的列感兴趣,而不是所有数据。修改 parse_csv() 函数,以便让用户指定任意的列,如下所示:

>>> # Read all of the data
>>> portfolio = parse_csv('Data/portfolio.csv')
>>> portfolio
[{'price': '32.20', 'name': 'AA', 'shares': '100'}, {'price': '91.10', 'name': 'IBM', 'shares': '50'}, {'price': '83.44', 'name': 'CAT', 'shares': '150'}, {'price': '51.23', 'name': 'MSFT', 'shares': '200'}, {'price': '40.37', 'name': 'GE', 'shares': '95'}, {'price': '65.10', 'name': 'MSFT', 'shares': '50'}, {'price': '70.44', 'name': 'IBM', 'shares': '100'}] >>> # Read only some of the data
>>> shares_held = parse_csv('Data/portfolio.csv', select=['name','shares'])
>>> shares_held
[{'name': 'AA', 'shares': '100'}, {'name': 'IBM', 'shares': '50'}, {'name': 'CAT', 'shares': '150'}, {'name': 'MSFT', 'shares': '200'}, {'name': 'GE', 'shares': '95'}, {'name': 'MSFT', 'shares': '50'}, {'name': 'IBM', 'shares': '100'}]
>>>

练习 2.23 中给出了列选择器的示例。

然而,这里有一个方法可以做到这一点:

# fileparse.py
import csv def parse_csv(filename, select=None):
'''
Parse a CSV file into a list of records
'''
with open(filename) as f:
rows = csv.reader(f) # Read the file headers
headers = next(rows) # If a column selector was given, find indices of the specified columns.
# Also narrow the set of headers used for resulting dictionaries
if select:
indices = [headers.index(colname) for colname in select]
headers = select
else:
indices = [] records = []
for row in rows:
if not row: # Skip rows with no data
continue
# Filter the row if specific columns were selected
if indices:
row = [ row[index] for index in indices ] # Make a dictionary
record = dict(zip(headers, row))
records.append(record) return records

这部分有一些棘手的问题,最重要的一个可能是列选择到行索引的映射。例如,假设输入文件具有以下标题:

>>> headers = ['name', 'date', 'time', 'shares', 'price']
>>>

现在,假设选定的列如下:

>>> select = ['name', 'shares']
>>>

为了执行正确的选择,必须将选择的列名映射到文件中的列索引。这就是该步骤正在执行的操作:

>>> indices = [headers.index(colname) for colname in select ]
>>> indices
[0, 3]
>>>

换句话说,名称("name" )是第 0 列,股份数目("shares" )是第 3 列。

当从文件读取数据行的时候,使用索引对其进行过滤:

>>> row = ['AA', '6/11/2007', '9:50am', '100', '32.20' ]
>>> row = [ row[index] for index in indices ]
>>> row
['AA', '100']
>>>

练习 3.5:执行类型转换

修改 parse_csv() 函数,以便可以选择将类型转换应用到返回数据上。例如:

>>> portfolio = parse_csv('Data/portfolio.csv', types=[str, int, float])
>>> portfolio
[{'price': 32.2, 'name': 'AA', 'shares': 100}, {'price': 91.1, 'name': 'IBM', 'shares': 50}, {'price': 83.44, 'name': 'CAT', 'shares': 150}, {'price': 51.23, 'name': 'MSFT', 'shares': 200}, {'price': 40.37, 'name': 'GE', 'shares': 95}, {'price': 65.1, 'name': 'MSFT', 'shares': 50}, {'price': 70.44, 'name': 'IBM', 'shares': 100}] >>> shares_held = parse_csv('Data/portfolio.csv', select=['name', 'shares'], types=[str, int])
>>> shares_held
[{'name': 'AA', 'shares': 100}, {'name': 'IBM', 'shares': 50}, {'name': 'CAT', 'shares': 150}, {'name': 'MSFT', 'shares': 200}, {'name': 'GE', 'shares': 95}, {'name': 'MSFT', 'shares': 50}, {'name': 'IBM', 'shares': 100}]
>>>

在 练习 2.24 中已经对此进行了探索。需要将下列代码片段插入到题解中:

...
if types:
row = [func(val) for func, val in zip(types, row) ]
...

练习 3.6:处理无标题的数据

某些 CSV 文件不包含任何的标题信息。例如,prices.csv 文件看起来像下面这样:

"AA",9.22
"AXP",24.85
"BA",44.85
"BAC",11.27
...

修改 parse_csv() 文件以便通过创建元组列表来处理此类文件。例如:

>>> prices = parse_csv('Data/prices.csv', types=[str,float], has_headers=False)
>>> prices
[('AA', 9.22), ('AXP', 24.85), ('BA', 44.85), ('BAC', 11.27), ('C', 3.72), ('CAT', 35.46), ('CVX', 66.67), ('DD', 28.47), ('DIS', 24.22), ('GE', 13.48), ('GM', 0.75), ('HD', 23.16), ('HPQ', 34.35), ('IBM', 106.28), ('INTC', 15.72), ('JNJ', 55.16), ('JPM', 36.9), ('KFT', 26.11), ('KO', 49.16), ('MCD', 58.99), ('MMM', 57.1), ('MRK', 27.58), ('MSFT', 20.89), ('PFE', 15.19), ('PG', 51.94), ('T', 24.79), ('UTX', 52.61), ('VZ', 29.26), ('WMT', 49.74), ('XOM', 69.35)]
>>>

要执行此更改,需要修改代码以便数据的第一行不被解释为标题行。另外,需要确保不创建字典,因为不再有可用于列名的键。

练习 3.7:选择其它的列分隔符

尽管 CSV 文件非常普遍,但还可能会遇到使用其它列分隔符(如 制表符(tab) 或空格符(space))的文件。例如,如下所示的 Data/portfolio.dat 文件:

name shares price
"AA" 100 32.20
"IBM" 50 91.10
"CAT" 150 83.44
"MSFT" 200 51.23
"GE" 95 40.37
"MSFT" 50 65.10
"IBM" 100 70.44

csv.reader() 函数允许像下面这样指定不同的分隔符:

rows = csv.reader(f, delimiter=' ')

修改 parse_csv() 函数以便也允许修改分隔符。

例如:

>>> portfolio = parse_csv('Data/portfolio.dat', types=[str, int, float], delimiter=' ')
>>> portfolio
[{'price': '32.20', 'name': 'AA', 'shares': '100'}, {'price': '91.10', 'name': 'IBM', 'shares': '50'}, {'price': '83.44', 'name': 'CAT', 'shares': '150'}, {'price': '51.23', 'name': 'MSFT', 'shares': '200'}, {'price': '40.37', 'name': 'GE', 'shares': '95'}, {'price': '65.10', 'name': 'MSFT', 'shares': '50'}, {'price': '70.44', 'name': 'IBM', 'shares': '100'}]
>>>

说明

到目前为止,如果你已经完成,那么你创建了一个非常有用的库函数。你可以使用它去解析任意的 CSV 文件,选择感兴趣的列,执行类型转换,而不用对文件或者 csv 模块的内部工作有太多的担心。

目录 | 上一节 (3.1 脚本) | 下一节 (3.3 错误检查)

注:完整翻译见 https://github.com/codists/practical-python-zh

翻译:《实用的Python编程》03_02_More_functions的更多相关文章

  1. 翻译:《实用的Python编程》InstructorNotes

    实用的 Python 编程--讲师说明 作者:戴维·比兹利(David Beazley) 概述 对于如何使用我的课程"实用的 Python 编程"进行教学的问题,本文档提供一些通用 ...

  2. 翻译:《实用的Python编程》README

    欢迎光临 大约 25 年前,当我第一次学习 Python 时,发现 Python 竟然可以被高效地应用到各种混乱的工作项目上,我立即被震惊了.15 年前,我自己也将这种乐趣教授给别人.教学的结果就是本 ...

  3. 翻译:《实用的Python编程》05_02_Classes_encapsulation

    目录 | 上一节 (5.1 再谈字典) | 下一节 (6 生成器) 5.2 类和封装 创建类时,通常会尝试将类的内部细节进行封装.本节介绍 Python 编程中有关封装的习惯用法(包括私有变量和私有属 ...

  4. 翻译:《实用的Python编程》04_02_Inheritance

    目录 | 上一节 (4.1 类) | 下一节 (4.3 特殊方法) 4.2 继承 继承(inheritance)是编写可扩展程序程序的常用手段.本节对继承的思想(idea)进行探讨. 简介 继承用于特 ...

  5. 翻译:《实用的Python编程》01_02_Hello_world

    目录 | 上一节 (1.1 Python) | 下一节 (1.3 数字) 1.2 第一个程序 本节讨论有关如何创建一个程序.运行解释器和调试的基础知识. 运行 Python Python 程序始终在解 ...

  6. 翻译:《实用的Python编程》03_03_Error_checking

    目录 | 上一节 (3.2 深入函数) | 下一节 (3.4 模块) 3.3 错误检查 虽然前面已经介绍了异常,但本节补充一些有关错误检查和异常处理的其它细节. 程序是如何运行失败的 Python 不 ...

  7. 翻译:《实用的Python编程》03_04_Modules

    目录 | 上一节 (3.3 错误检查) | 下一节 (3.5 主模块) 3.4 模块 本节介绍模块的概念以及如何使用跨多个文件的函数. 模块和导入 任何一个 Python 源文件都是一个模块. # f ...

  8. 翻译:《实用的Python编程》03_05_Main_module

    目录 | 上一节 (3.4 模块) | 下一节 (3.6 设计讨论) 3.5 主模块 本节介绍主程序(主模块)的概念 主函数 在许多编程语言中,存在一个主函数或者主方法的概念. // c / c++ ...

  9. 翻译:《实用的Python编程》04_01_Class

    目录 | 上一节 (3.6 设计讨论) | 下一节 (4.2 继承) 4.1 类 本节介绍 class 语句以及创建新对象的方式. 面向对象编程(OOP) 面向对象编程是一种将代码组织成对象集合的编程 ...

随机推荐

  1. E 快速排序

    :以下代码可以从数组a[]中找出第k小的元素. 它使用了类似快速排序中的分治算法,期望时间复杂度是O(N)的. 请仔细阅读分析源码,填写划线部分缺失的内容. #include <stdio.h& ...

  2. B 等差素数列

    B 等差素数列:2,3,5,7,11,13,....是素数序列.类似:7,37,67,97,127,157 这样完全由素数组成的等差数列,叫等差素数数列.上边的数列公差为30,长度为6.2004年,格 ...

  3. HDU4787 GRE Words Revenge【AC自动机 分块】

    HDU4787 GRE Words Revenge 题意: \(N\)次操作,每次记录一个\(01\)串或者查询一个\(01\)串能匹配多少个记录的串,强制在线 题解: 在线的AC自动机,利用分块来降 ...

  4. hdu 6827 Road To The 3rd Building

    题意: t组输入,每一组一个n,然后后面是n个树的值(我们放到数组v里面),你需要从[1,n]这个区间内挑选出来两个数i,j,你需要保证i<=j,之后你要求一下v[i]+v[i+1]+...+v ...

  5. JavaScript——六

    magin和padding的区别:https://www.cnblogs.com/zxnn/p/8186225.html magin:兄弟之间的 padding:父子关系 body和网页边框左右距离上 ...

  6. 远程连接 出现身份验证错误,要求的函数不受支持(这可能是由于CredSSP加密Oracle修正)

    修改本地组策略: 计算机配置>管理模板>系统>凭据分配>加密Oracle修正 选择启用并选择"易受攻击". 原文:https://blog.csdn.net ...

  7. DSC注册Agent失败- InternalServerError

    问题 有大概5台Agent Server,注册的时候,发现2台可以成功,其他的不成功. 注册失败的错误日志如下: 初步尝试 首先,Pull Server已经平稳的运行了几年了,此次注册还有部分Agen ...

  8. 计组CPU设计实验关键材料和关键设计

    我记得这是2016春季学期搞得,参考和学习了很多别人的东西,这里小小的总结一下,逻辑性还不是太强,还需要好好整理 首先是指令集 CPU架构 外部接线架构 指令格式 机器状态自动机 这部分忘了,汗 这部 ...

  9. 使用SQL-Server分区表功能提高数据库的读写性能

    首先祝大家新年快乐,身体健康,万事如意. 一般来说一个系统最先出现瓶颈的点很可能是数据库.比如我们的生产系统并发量很高在跑一段时间后,数据库中某些表的数据量会越来越大.海量的数据会严重影响数据库的读写 ...

  10. uni-app 实战-打包 &#128230; App

    uni-app 实战-打包 App Android & iOS 证书 广告 refs xgqfrms 2012-2020 www.cnblogs.com 发布文章使用:只允许注册用户才可以访问 ...