名字存在于哪里
名字的作用域是程序中可以使用它的范围。在函数内部创建的名字是局部的:只在该函数运行期间存在,外部看不到。在文件最顶层创建的名字是全局的:函数可以读取它。Python 遇到一个名字时,先在函数内部找,再找外层函数,然后是文件,最后是内置名字。
rate = 0.001 # global: defined outside any function
def fee(notional):
minimum = 1.0 # local: exists only during the call
return max(minimum, notional * rate)
print(fee(36850.0)) # 36.85
# print(minimum) # would raise NameError: minimum is not defined赋值会创建局部名字
在函数里读取全局名字没有问题。但在函数里对一个名字赋值,会创建一个新的局部名字,即使存在同名的全局名字,全局的那个也不会被改动。这保护了你的数据,也是为什么在函数里修改全局状态需要额外的关键字(global),而你应该很少需要它。请把值作为参数传入,用 return 把结果交出来。
# Assigning inside a function makes a new local name
balance = 1000
def deposit(amount):
balance = amount # a different, local balance
return balance
print(deposit(50)) # 50
print(balance) # 1000 (the global one is unchanged)生成函数的函数
函数可以在内部定义另一个函数并用 return 返回它。内层函数即使在外层函数结束之后,仍能访问外层的变量。这种“函数加上它记住的值”的组合叫做闭包。这是根据设置造出现成工具的好办法:一个用于股票的计算器,另一个用于 ETF。
# A closure remembers the variables of the function that made it
def make_fee_calculator(rate, minimum):
def fee(notional):
return max(minimum, notional * rate)
return fee
stock_fee = make_fee_calculator(0.001, 1.0)
etf_fee = make_fee_calculator(0.0005, 0.5)
print(stock_fee(36850.0)) # 36.85
print(etf_fee(36850.0)) # 18.425在多次调用之间保持的状态:nonlocal
闭包还可以修改它记住的值。因为赋值会创建新的局部名字,内层函数要先声明 nonlocal count,意思是“这个名字属于外层函数”。外层函数每被调用一次,就会创建一份各自独立的记忆,所以两个计数器不会互相干扰。订单号生成器或滚动平均就是这样在没有全局变量的情况下保存状态的。
# nonlocal lets the inner function rebind the outer variable
def make_counter():
count = 0
def next_id():
nonlocal count
count += 1
return count
return next_id
order_ids = make_counter()
print(order_ids(), order_ids(), order_ids()) # 1 2 3
other = make_counter()
print(other()) # 1 (its own count)# State between calls: an exponential moving average
def make_ema(alpha):
value = None
def update(price):
nonlocal value
value = price if value is None else alpha * price + (1 - alpha) * value
return value
return update
ema = make_ema(0.5)
print(ema(100.0), ema(110.0), ema(110.0)) # 100.0 105.0 107.5闭包保留的是变量本身,而不是副本内层函数在运行时才去查找这个变量,所以看到的总是最新值。计数器和平均值能工作正是因为这一点;同样的原因也使得在循环里创建的闭包看到的是循环变量的最终值,除非你把它作为参数或默认值传进去。
- 函数内赋值的名字是局部的;数据通过参数传入,用
return传出。 - 构建并返回函数的函数,把设置打包成一个工具。
- 需要在多次调用间保留的状态用
nonlocal,每个闭包各有一份记忆。
下一节:列表推导式,用一行清晰的代码从一个列表构建出新列表。
实验:评分内容
- 用
make_counter构建会记住计数的计数器25 分 - 用
make_fee_calculator把费用设置打包成函数25 分 - 用
make_ema在多次调用之间保持移动平均25 分 - 用
make_limit_checker根据限价生成价格检查函数25 分