一个问题

使用scheme定义的银行账户模拟函数,可以十分完美的保持和更新函数内的balance状态

(define (make-account balance)
  (define (withdraw amount)
    (if (>= balance amount)
        (begin (set! balance (- balance amount))
               balance)
        "Insufficient funds"))
  (define (deposit amount)
    (set! balance (+ balance amount))
    balance)
  (define (dispath m)
    (cond ((eq? m 'withdraw) withdraw)
          ((eq? m 'deposit) deposit)
          (else (error "Unknown request -- MAKE-ACCOUNT"
                       m))))
  dispatch)

>> (define acc (make-account 100))
>> ((acc 'withdraw) 50)
   50
>> ((acc 'deposit) 60)
   110
>> ((acc 'withdraw) 120)
   "Insufficient funds"

使用Python定义的同样的函数

def make_account(balance):

    def withdraw(amount):
        if balance > amount:
            balance = balance - amount
            return balance
        else:
            print("Insufficient funds")

    def deposit(amount):
        balance = balance + amount
        return balance

    def dispath(msg):
        if msg == "withdraw":
            return withdraw
        elif msg == "deposit":
            return deposit
        else:
            print("Unknown request -- MAKE-ACCOUNT %s" % msg)

    return dispath
In [1]: from make_account1 import make_account

In [2]: acc = make_account(100)

In [3]: acc
Out[3]: <function make_account1.make_account.<locals>.dispath>

In [4]: acc("withdraw")
Out[4]: <function make_account1.make_account.<locals>.withdraw>

In [5]: acc("withdraw")(10)
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-5-0156b22a66b4> in <module>()
----> 1 acc("withdraw")(10)

E:\make_account1.py in withdraw(amount)
      3 def make_account(balance):
      4     def withdraw(amount):
----> 5         if balance > amount:
      6             balance = balance - amount
      7             return balance

UnboundLocalError: local variable 'balance' referenced before assignment

错误分析

根据错误提示,balance在赋值前被引用,但根据词法作用域,balance在函数withdraw内是可以访问到make_account的参数balance的。问题出在Python的变量声明,如果出现value = ...,Python就视为声明变量value,所以balance = balance - amount相当于JavaScript代码let balance; balance = balance - amountbalance在这里变成一个未被赋值的local变量,所以会出现错误”UnboundLocalError: local variable ‘balance’ referenced before assignment”。

使用Python不容易看出错误,可以用JavaScript写出相同的错误代码:

let make_account = function(balance){

    let withdraw = function(amount){
        let balance;
        if(balance > amount){
            balance = balance - amount;
            return balance;
        }else{
            console.log("Insufficient funds");
        }
    };

    let deposit = function(amount){
        let balance;
        balance = balance + amount;
        return balance;
    };

    let dispath = function(msg){
        if(msg === "withdraw"){
            return withdraw;
        }else if(msg === "deposit"){
            return deposit;
        }else{
            console.log("Unknown request -- MAKE-ACCOUNT", msg);
        }
    };

    return dispath;
};

而正确的JavaScript代码应该为:

let make_account = function(balance){

    let withdraw = function(amount){
        if(balance > amount){
            balance = balance - amount;
            return balance;
        }else{
            console.log("Insufficient funds");
        }
    };

    let deposit = function(amount){
        balance = balance + amount;
        return balance;
    };

    let dispath = function(msg){
        if(msg === "withdraw"){
            return withdraw;
        }else if(msg === "deposit"){
            return deposit;
        }else{
            console.log("Unknown request -- MAKE-ACCOUNT", msg);
        }
    };

    return dispath;
};

修改

既然Python将withdraw中的balance视为local变量,我们只需声明其为nonlocal变量即可。之后balance就可在enclose变量域内找到。

def make_account(balance):

    def withdraw(amount):
        nonlocal balance
        if balance > amount:
            balance = balance - amount
        else:
            print("Insufficient funds")

    def deposit(amount):
        nonlocal balance
        balance = balance + amount

    def dispath(msg):
        if msg == "withdraw":
            return withdraw
        elif msg == "deposit":
            return deposit
        else:
            print("Unknown request -- MAKE-ACCOUNT %s" % msg)

    return dispath
In [1]: from make_account2 import make_account

In [2]: acc = make_account(100)

In [3]: acc("withdraw")(10)
Out[3]: 90

In [4]: acc("withdraw")(100)
Insufficient funds

In [5]: acc("deposit")(50)
Out[5]: 140