目录
变量作用域
变量作用域是指可以在程序的哪个部分引用或使用某个变量。
在函数中使用变量时,务必要考虑作用域。如果变量是在函数内创建的,则只能在该函数内使用该变量。你无法从该函数外面访问该变量。
# This will result in an error
def some_function():
word = "hello"
print(word)
这意味着你可以为在不同函数内使用的不同变量使用相同的名称。
# This works fine
def some_function():
word = "hello"
def another_function():
word = "goodbye"
像这样在函数之外定义的变量依然可以在函数内访问。
# This works fine
word = "hello"
def some_function():
print(word)
print(word)
注意,我们可以在此函数内以及函数外输出 word
。作用域对理解信息在用 Python 和任何编程语言编写的程序中的传递方式来说很关键。
关于变量作用域的更多信息
在编程时,你经常会发现相似的想法不断出现。你将使用变量进行计数、迭代和累积要返回的值。为了编写容易读懂的代码,你会发现你需要对相似的想法使用相似的名称。一旦你将多段代码放到一起(例如,一个脚本中有多个函数或函数调用),你可能需要为两个不同的概念使用相同的名称。
幸运的是,你不需要不断想出新的名称。可以为对象重复使用相同的名称,只要它们位于不同的作用域即可。
良好实践:建议将变量定义在所需的最小作用域内。虽然函数_可以_引用在更大的作用域内定义的变量,但是通常不建议这么做,因为如果程序有很多变量,你可能不知道你定义了什么变量。
原始代码段
egg_count = 0
def buy_eggs():
egg_count += 12 # purchase a dozen eggs
buy_eggs()
更好的编写方式为:
egg_count = 0
def buy_eggs(count):
return count + 12 # purchase a dozen eggs
egg_count = buy_eggs(egg_count)
原始代码段导致 UnboundLocalError
。
在函数内,我们可以成功地输出外部变量的值。因为我们只是访问该变量的值。当我们尝试将此变量的值更改或重新赋值为另一个值时,我们将遇到错误。Python 不允许函数修改不在函数作用域内的变量。
但是上面的原则仅适用于整数和字符串,列表、字典、集合、类中可以在子程序(子函数)中通过修改局部变量达到修改全局变量的目的。
Lambda 表达式
你可以使用 Lambda 表达式创建匿名函数,即没有名称的函数。lambda 表达式非常适合快速创建在代码中以后不会用到的函数。尤其对高阶函数或将其他函数作为参数的函数来说,非常实用。
我们可以使用 lambda 表达式将以下函数
def multiply(x, y):
return x * y
简写为:
double = lambda x, y: x * y
Lambda 函数的组成部分
- 关键字
lambda
表示这是一个 lambda 表达式。 lambda
之后是该匿名函数的一个或多个参数(用英文逗号分隔),然后是一个英文冒号:
。和函数相似,lambda 表达式中的参数名称是随意的。- 最后一部分是被评估并在该函数中返回的表达式,和你可能会在函数中看到的 return 语句很像。
鉴于这种结构,lambda 表达式不太适合复杂的函数,但是非常适合简短的函数
Lambda 与 Map
map()
是一个高阶内置函数,接受函数和可迭代对象作为输入,并返回一个将该函数应用到可迭代对象的每个元素的迭代器。下面的代码使用 map()
计算 numbers
中每个列表的均值,并创建列表 averages
。测试运行这段代码,看看结果如何。
通过将 mean
函数替换为在 map()
的调用中定义的 lambda 表达式,重写这段代码,使代码更简练。
numbers = [
[34, 63, 88, 71, 29],
[90, 78, 51, 27, 45],
[63, 37, 85, 46, 22],
[51, 22, 34, 11, 18]
]
def mean(num_list):
return sum(num_list) / len(num_list)
averages = list(map(mean, numbers))
print(averages)
averages = list(map(lambda x:sum(x)/len(x),numbers))
print(averages)
Lambda 与 Filter
filter()
是一个高阶内置函数,接受函数和可迭代对象作为输入,并返回一个由可迭代对象中的特定元素(该函数针对该元素会返回 True)组成的迭代器。下面的代码使用 filter()
从 cities
中获取长度少于 10 个字符的名称以创建列表 short_cities
。测试运行这段代码,看看结果如何。
通过将 is_short
函数替换为在 filter()
的调用中定义的 lambda 表达式,重写这段代码,使代码更简练。
cities = ["New York City", "Los Angeles", "Chicago", "Mountain View", "Denver", "Boston"]
def is_short(name):
return len(name) < 10
short_cities = list(filter(is_short, cities))
print(short_cities)
short_cities = list(filter(lambda x:len(x)<10,cities))
print(short_cities)
迭代器和生成器
迭代器是每次可以返回一个对象元素的对象,例如返回一个列表。我们到目前为止使用的很多内置函数(例如 enumerate)都会返回一个迭代器。迭代器是一种表示数据流的对象。这与列表不同,列表是可迭代对象,但不是迭代器,因为它不是数据流。
生成器是使用函数创建迭代器的简单方式。也可以使用类定义迭代器,更多详情请参阅此处。
下面是一个叫做 my_range
的生成器函数,它会生成一个从 0 到 (x - 1) 的数字流。
def my_range(x):
i = 0
while i < x:
yield i
i += 1
注意,该函数使用了 yield 而不是关键字 return。这样使函数能够一次返回一个值,并且每次被调用时都从停下的位置继续。关键字 yield 是将生成器与普通函数区分开来的依据。
注意,因为这段代码会返回一个迭代器,因此我们可以将其转换为列表或用 for 循环遍历它,以查看其内容。例如,下面的代码:
for x in my_range(5):
print(x)
输出:
0
1
2
3
4
练习:实现 my_enumerate
请自己写一个效果和内置函数 enumerate
一样的生成器函数。
如下所示地调用该函数:
lessons = ["Why Python Programming", "Data Types and Operators", "Control Flow", "Functions", "Scripting"]
for i, lesson in my_enumerate(lessons, 1):
print("Lesson {}: {}".format(i, lesson))
应该会输出:
Lesson 1: Why Python Programming
Lesson 2: Data Types and Operators
Lesson 3: Control Flow
Lesson 4: Functions
Lesson 5: Scripting
lessons = ["Why Python Programming", "Data Types and Operators", "Control Flow", "Functions", "Scripting"]
def my_enumerate(iterable, start=0):
# Implement your generator function here
count = start
for element in iterable:
yield count, element
count += 1
for i, lesson in my_enumerate(lessons, 1):
print("Lesson {}: {}".format(i, lesson))
为何要使用生成器?
你可能会疑问,为何要使用生成器,而不使用列表。下面这段摘自 stack overflow 页面 的内容回答了这个问题:
生成器是构建迭代器的 “懒惰” 方式。当内存不够存储完整实现的列表时,或者计算每个列表元素的代价很高,你希望尽量推迟计算时,就可以使用生成器。但是这些元素只能遍历一次。
另一种详细的解释如下(详细说明参见 该 stack overflow 页面。)
由于使用生成器是一次处理一个数据,在内存和存储的需求上会比使用list方式直接全部生成再存储节省很多资源。
由此区别,在处理大量数据时,经常使用生成器初步处理数据后,再进行长期存储,而不是使用 list。因为无论使用生成器还是 list,都是使用过就要丢弃的临时数据。既然功能和结果一样,那就不如用生成器。
但是生成器也有自己的局限,它产生的数据不能回溯,不像list可以任意选择。
练习:Chunker
如果可迭代对象太大,无法完整地存储在内存中(例如处理大型文件时),每次能够使用一部分很有用。
实现一个生成器函数 chunker
,接受一个可迭代对象并每次生成指定大小的部分数据。
如下所示地调用该函数:
for chunk in chunker(range(25), 4):
print(list(chunk))
应该会输出:
[0, 1, 2, 3]
[4, 5, 6, 7]
[8, 9, 10, 11]
[12, 13, 14, 15]
[16, 17, 18, 19]
[20, 21, 22, 23]
[24]
def chunker(iterable, size):
"""Yield successive chunks from iterable of length size."""
for i in range(0, len(iterable), size):
yield iterable[i:i + size]
for chunk in chunker(range(25), 4):
print(list(chunk))
函数
建议使用如下的方式进行错误检测和容错处理,确保用户的只能输入正确的数据,例如关于城市的输入可以使用如下的方式进行:
city = input('Chicago, new york city and washington, Which city would you like to explore? ').lower()
while city not in ['chicago', 'new york city', 'washington']:
city = input('Sorry, please choose one city from chicago,new york city and washington.').lower()
关于输入的程序,建议使用函数将输入的逻辑封装起来,可以简化你的程序。
建立一个用于获取输入的函数:
def input_mod(input_print, error_print, enterable_list, get_value):
while True:
ret = input(input_print)
ret = get_value(ret)#这里执行作为参数的函数
if ret in enterable_list:
return ret
else:
print(error_print)
print('\nWould you like to see data for Chicago,New York,or Washington?')
city = input_mod('Please input the city name:',
'Sorry,please input the correct city name.',
['chicago', 'new york', 'washington'],
lambda x: str.lower(x))
11111