翻譯:《實用的Python編程》07_03_Returning_functions

目錄 | 上一節 (7.2 匿名函數) | 下一節 (7.4 裝飾器)python

7.3 返回函數

本節介紹使用函數建立其它函數的思想。git

簡介

考慮如下函數:github

def add(x, y):
    def do_add():
        print('Adding', x, y)
        return x + y
    return do_add

這是返回其它函數的函數。segmentfault

>>> a = add(3,4)
>>> a
<function do_add at 0x6a670>
>>> a()
Adding 3 4
7

局部變量

請觀察內部函數是如何引用外部函數定義的變量的。閉包

def add(x, y):
    def do_add():
        # `x` and `y` are defined above `add(x, y)`
        print('Adding', x, y)
        return x + y
    return do_add

進一步觀察會發現,在 add() 函數結束後,這些變量仍然保持存活。函數

>>> a = add(3,4)
>>> a
<function do_add at 0x6a670>
>>> a()
Adding 3 4      # Where are these values coming from?
7

閉包

當內部函數做爲結果返回時,該內部函數稱爲閉包(closure)。ui

def add(x, y):
    # `do_add` is a closure
    def do_add():
        print('Adding', x, y)
        return x + y
    return do_add

基本特性:閉包保留該函數之後正常運行所需的全部變量的值。能夠將閉包視做一個函數,該函數擁有一個額外的環境來保存它所依賴的變量的值。翻譯

使用閉包

雖然閉包是 Python 的基本特性,可是它們的用法一般很微妙。常見應用:code

  • 在回調函數中使用。
  • 延遲計算。
  • 裝飾器函數(稍後介紹)。

延遲計算

考慮這樣的函數:get

def after(seconds, func):
    import time
    time.sleep(seconds)
    func()

使用示例:

def greeting():
    print('Hello Guido')

after(30, greeting)

after (延遲30 秒後)執行給定的函數......

閉包附帶了其它信息。

def add(x, y):
    def do_add():
        print(f'Adding {x} + {y} -> {x+y}')
    return do_add

def after(seconds, func):
    import time
    time.sleep(seconds)
    func()

after(30, add(2, 3))
# `do_add` has the references x -> 2 and y -> 3

代碼重複

閉包也能夠用做一種避免代碼大量重複的技術。

練習

練習 7.7:使用閉包避免重複

閉包的一個更強大的特性是用於生成重複的代碼。讓咱們回顧 練習 5.7 代碼,該代碼中定義了帶有類型檢查的屬性:

class Stock:
    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price
    ...
    @property
    def shares(self):
        return self._shares

    @shares.setter
    def shares(self, value):
        if not isinstance(value, int):
            raise TypeError('Expected int')
        self._shares = value
    ...

與其一遍又一遍地輸入代碼,不如使用閉包自動建立代碼。

請建立 typedproperty.py 文件,並把下述代碼放到文件中:

# typedproperty.py

def typedproperty(name, expected_type):
    private_name = '_' + name
    @property
    def prop(self):
        return getattr(self, private_name)

    @prop.setter
    def prop(self, value):
        if not isinstance(value, expected_type):
            raise TypeError(f'Expected {expected_type}')
        setattr(self, private_name, value)

    return prop

如今,經過定義下面這樣的類來嘗試一下:

from typedproperty import typedproperty

class Stock:
    name = typedproperty('name', str)
    shares = typedproperty('shares', int)
    price = typedproperty('price', float)

    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

請嘗試建立一個實例,並驗證類型檢查是否有效:

>>> s = Stock('IBM', 50, 91.1)
>>> s.name
'IBM'
>>> s.shares = '100'
... should get a TypeError ...
>>>

練習 7.8:簡化函數調用

在上面示例中,用戶可能會發現調用諸如 typedproperty('shares', int) 這樣的方法稍微有點冗長 ——尤爲是屢次重複調用的時候。請將如下定義添加到 typedproperty.py 文件中。

String = lambda name: typedproperty(name, str)
Integer = lambda name: typedproperty(name, int)
Float = lambda name: typedproperty(name, float)

如今,請從新編寫 Stock 類以使用如下函數:

class Stock:
    name = String('name')
    shares = Integer('shares')
    price = Float('price')

    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

啊,好一點了。這裏的要點是:閉包和 lambda 經常使用於簡化代碼,並消除使人討厭的代碼重複。這一般很不錯。

練習 7.9:付諸實踐

請從新編寫 stock.py 文件中的 Stock 類,以便使用上面展現的類型化特性(typed properties)。

目錄 | 上一節 (7.2 匿名函數) | 下一節 (7.4 裝飾器)

注:完整翻譯見 https://github.com/codists/practical-python-zh

相關文章
相關標籤/搜索