歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

Python描述符(descriptor)解密

Python中包含了許多內建的語言特性,它們使得代碼簡潔且易於理解。這些特性包括列表/集合/字典推導式,屬性(property)、以及裝飾器(decorator)。對於大部分特性來說,這些“中級”的語言特性有著完善的文檔,並且易於學習。

但是這裡有個例外,那就是描述符。至少對於我來說,描述符是Python語言核心中困擾我時間最長的一個特性。這裡有幾點原因如下:

  1. 有關描述符的官方文檔相當難懂,而且沒有包含優秀的示例告訴你為什麼需要編寫描述符(我得為Raymond Hettinger辯護一下,他寫的其他主題的Python文章和視頻對我的幫助還是非常大的)
  2. 編寫描述符的語法顯得有些怪異
  3. 自定義描述符可能是Python中用的最少的特性,因此你很難在開源項目中找到優秀的示例

但是一旦你理解了之後,描述符的確還是有它的應用價值的。這篇文章告訴你描述符可以用來做什麼,以及為什麼應該引起你的注意。

一句話概括:描述符就是可重用的屬性

在這裡我要告訴你:從根本上講,描述符就是可以重復使用的屬性。也就是說,描述符可以讓你編寫這樣的代碼:

f = Foo()
b = f.bar
f.bar = c
del f.bar

而在解釋器執行上述代碼時,當發現你試圖訪問屬性(b = f.bar)、對屬性賦值(f.bar = c)或者刪除一個實例變量的屬性(del f.bar)時,就會去調用自定義的方法。

讓我們先來解釋一下為什麼把對函數的調用偽裝成對屬性的訪問是大有好處的。

property——把函數調用偽裝成對屬性的訪問

想象一下你正在編寫管理電影信息的代碼。你最後寫好的Movie類可能看上去是這樣的:

class Movie(object):
    def __init__(self, title, rating, runtime, budget, gross):
        self.title = title
        self.rating = rating
        self.runtime = runtime
        self.budget = budget
        self.gross = gross

    def profit(self):
        return self.gross - self.budget

你開始在項目的其他地方使用這個類,但是之後你意識到:如果不小心給電影打了負分怎麼辦?你覺得這是錯誤的行為,希望Movie類可以阻止這個錯誤。 你首先想到的辦法是將Movie類修改為這樣:

class Movie(object):
    def __init__(self, title, rating, runtime, budget, gross):
        self.title = title
        self.rating = rating
        self.runtime = runtime
        self.gross = gross
        if budget < 0:
            raise ValueError("Negative value not allowed: %s" % budget)
        self.budget = budget

    def profit(self):
        return self.gross - self.budget

但這行不通。因為其他部分的代碼都是直接通過Movie.budget來賦值的——這個新修改的類只會在__init__方法中捕獲錯誤的數據,但對於已經存在的類實例就無能為力了。如果有人試著運行m.budget = -100,那麼誰也沒法阻止。作為一個Python程序員同時也是電影迷,你該怎麼辦?

幸運的是,Python的property解決了這個問題。如果你從未見過property的用法,下面是一個示例:

class Movie(object):
    def __init__(self, title, rating, runtime, budget, gross):
        self._budget = None

        self.title = title
        self.rating = rating
        self.runtime = runtime
        self.gross = gross
        self.budget = budget

    @property
    def budget(self):
        return self._budget

    @budget.setter
    def budget(self, value):
        if value < 0:
            raise ValueError("Negative value not allowed: %s" % value)
        self._budget = value

    def profit(self):
        return self.gross - self.budget

m = Movie('Casablanca', 97, 102, 964000, 1300000)
print m.budget      # calls m.budget(), returns result
try:
    m.budget = -100  # calls budget.setter(-100), and raises ValueError
except ValueError:
    print "Woops. Not allowed"

964000
Woops. Not allowed

我們用@property裝飾器指定了一個getter方法,用@budget.setter裝飾器指定了一個setter方法。當我們這麼做時,每當有人試著訪問budget屬性,Python就會自動調用相應的getter/setter方法。比方說,當遇到m.budget = value這樣的代碼時就會自動調用budget.setter。

花點時間來欣賞一下Python這麼做是多麼的優雅:如果沒有property,我們將不得不把所有的實例屬性隱藏起來,提供大量顯式的類似get_budget和set_budget方法。像這樣編寫類的話,使用起來就會不斷的去調用這些getter/setter方法,這看起來就像臃腫的Java代碼一樣。更糟的是,如果我們不采用這種編碼風格,直接對實例屬性進行訪問。那麼稍後就沒法以清晰的方式增加對非負數的條件檢查——我們不得不重新創建set_budget方法,然後搜索整個工程中的源代碼,將m.budget = value這樣的代碼替換為m.set_budget(value)。太蛋疼了!!

因此,property讓我們將自定義的代碼同變量的訪問/設定聯系在了一起,同時為你的類保持一個簡單的訪問屬性的接口。干得漂亮!

property的不足

對property來說,最大的缺點就是它們不能重復使用。舉個例子,假設你想為rating,runtime和gross這些字段也添加非負檢查。下面是修改過的新類:

class Movie(object):
    def __init__(self, title, rating, runtime, budget, gross):
        self._rating = None
        self._runtime = None
        self._budget = None
        self._gross = None

        self.title = title
        self.rating = rating
        self.runtime = runtime
        self.gross = gross
        self.budget = budget

    #nice
    @property
    def budget(self):
        return self._budget

    @budget.setter
    def budget(self, value):
        if value < 0:
            raise ValueError("Negative value not allowed: %s" % value)
        self._budget = value

    #ok   
    @property
    def rating(self):
        return self._rating

    @rating.setter
    def rating(self, value):
        if value < 0:
            raise ValueError("Negative value not allowed: %s" % value)
        self._rating = value

    #uhh...
    @property
    def runtime(self):
        return self._runtime

    @runtime.setter
    def runtime(self, value):
        if value < 0:
            raise ValueError("Negative value not allowed: %s" % value)
        self._runtime = value       

    #is this forever?
    @property
    def gross(self):
        return self._gross

    @gross.setter
    def gross(self, value):
        if value < 0:
            raise ValueError("Negative value not allowed: %s" % value)
        self._gross = value       

    def profit(self):
        return self.gross - self.budget

可以看到代碼增加了不少,但重復的邏輯也出現了不少。雖然property可以讓類從外部看起來接口整潔漂亮,但是卻做不到內部同樣整潔漂亮。

Python 的詳細介紹:請點這裡
Python 的下載地址:請點這裡

Copyright © Linux教程網 All Rights Reserved