介紹Python中的一些進階編程技巧

來源:互聯網
上載者:User
本文:

本文展示一些進階的Python設計結構和它們的使用方法。在日常工作中,你可以根據需要選擇合適的資料結構,例如對快速尋找性的要求、對資料一致性的要求或是對索引的要求等,同時也可以將各種資料結構合適地結合在一起,從而產生具有邏輯性並易於理解的資料模型。Python的資料結構從句法上來看非常直觀,並且提供了大量的可選操作。這篇指南嘗試將大部分常用的資料結構知識放到一起,並且提供對其最佳用法的探討。
推導式(Comprehensions)

如果你已經使用了很長時間的Python,那麼你至少應該聽說過列表推導(list comprehensions)。這是一種將for迴圈、if運算式以及指派陳述式放到單一語句中的一種方法。換句話說,你能夠通過一個運算式對一個列表做映射或過濾操作。

一個列表推導式包含以下幾個部分:

  • 一個輸入序列
  • 一個表示輸入序列成員的變數
  • 一個可選的斷言運算式
  • 一個將輸入序列中滿足斷言運算式的成員變換成輸出資料行表成員的輸出運算式

舉個例子,我們需要從一個輸入列表中將所有大於0的整數平方產生一個新的序列,你也許會這麼寫:

num = [1, 4, -5, 10, -7, 2, 3, -1]filtered_and_squared = [] for number in num: if number > 0: filtered_and_squared.append(number ** 2)print filtered_and_squared # [1, 16, 100, 4, 9]

很簡單是吧?但是這就會有4行代碼,兩層嵌套外加一個完全不必要的append操作。而如果使用filter、lambda和map函數,則能夠將代碼大大簡化:

num = [1, 4, -5, 10, -7, 2, 3, -1]filtered_and_squared = map(lambda x: x ** 2, filter(lambda x: x > 0, num))print filtered_and_squared # [1, 16, 100, 4, 9]

嗯,這麼一來代碼就會在水平方向上展開。那麼是否能夠繼續簡化代碼呢?列表推導能夠給我們答案:

num = [1, 4, -5, 10, -7, 2, 3, -1]filtered_and_squared = [ x**2 for x in num if x > 0]print filtered_and_squared # [1, 16, 100, 4, 9]

  • 迭代器(iterator)遍曆輸入序列num的每個成員x
  • 斷言式判斷每個成員是否大於零
  • 如果成員大於零,則被交給輸出運算式,平方之後成為輸出資料行表的成員。

列表推導式被封裝在一個列表中,所以很明顯它能夠立即產生一個新列表。這裡只有一個type函數調用而沒有隱式調用lambda函數,列表推導式正是使用了一個常規的迭代器、一個運算式和一個if運算式來控制可選的參數。

另一方面,列表推導也可能會有一些負面效應,那就是整個列表必須一次性載入於記憶體之中,這對上面舉的例子而言不是問題,甚至擴大若干倍之後也都不是問題。但是總會達到極限,記憶體總會被用完。

針對上面的問題,產生器(Generator)能夠很好的解決。產生器運算式不會一次將整個列表載入到記憶體之中,而是產生一個產生器對象(Generator objector),所以一次只載入一個列表元素。

產生器運算式同列表推導式有著幾乎相同的文法結構,區別在於產生器運算式是被圓括弧包圍,而不是方括弧:

num = [1, 4, -5, 10, -7, 2, 3, -1]filtered_and_squared = ( x**2 for x in num if x > 0 )print filtered_and_squared #  at 0x00583E18> for item in filtered_and_squared: print item # 1, 16, 100 4,9

這比列表推導效率稍微提高一些,讓我們再一次改造一下代碼:

num = [1, 4, -5, 10, -7, 2, 3, -1] def square_generator(optional_parameter): return (x ** 2 for x in num if x > optional_parameter) print square_generator(0)#  at 0x004E6418> # Option Ifor k in square_generator(0): print k# 1, 16, 100, 4, 9 # Option IIg = list(square_generator(0))print g# [1, 16, 100, 4, 9]

除非特殊的原因,應該經常在代碼中使用產生器運算式。但除非是面對非常大的列表,否則是不會看出明顯區別的。

下例使用zip()函數一次處理兩個或多個列表中的元素:

alist = ['a1', 'a2', 'a3']blist = ['1', '2', '3'] for a, b in zip(alist, blist): print a, b # a1 1# a2 2# a3 3

再來看一個通過兩階列表推導式遍曆目錄的例子:

import osdef tree(top): for path, names, fnames in os.walk(top): for fname in fnames:  yield os.path.join(path, fname) for name in tree('C:\Users\XXX\Downloads\Test'): print name

裝飾器(Decorators)

裝飾器為我們提供了一個增加已有函數或類的功能的有效方法。聽起來是不是很像Java中的面向切面編程(Aspect-Oriented Programming)概念?兩者都很簡單,並且裝飾器有著更為強大的功能。舉個例子,假定你希望在一個函數的入口和退出點做一些特別的操作(比如一些安全、追蹤以及鎖定等操作)就可以使用裝飾器。

裝飾器是一個封裝了另一個函數的特殊函數:主函數被調用,並且其傳回值將會被傳給裝飾器,接下來裝飾器將返回一個封裝了主函數的替代函數,程式的其他部分看到的將是這個封裝函數。

def timethis(func): ''' Decorator that reports the execution time. ''' pass @timethisdef countdown(n): while n > 0: n -= 1

文法糖@標識了裝飾器。

好了,讓我們回到剛才的例子。我們將用裝飾器做一些更典型的操作:

import timefrom functools import wraps def timethis(func): ''' Decorator that reports the execution time. ''' @wraps(func) def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(func.__name__, end-start) return result return wrapper @timethisdef countdown(n): while n > 0: n -= 1 countdown(100000) # ('countdown', 0.006999969482421875)

當你寫下如下代碼時:

@timethisdef countdown(n):

意味著你分開執行了以下步驟:

def countdown(n):...countdown = timethis(countdown)

裝飾器函數中的代碼建立了一個新的函數(正如此例中的wrapper函數),它用 *args 和 **kwargs 接收任意的輸入參數,並且在此函數內調用原函數並且返回其結果。你可以根據自己的需要放置任何額外的代碼(例如本例中的計時操作),新建立的封裝函數將作為結果返回並取代原函數。

@decoratordef function(): print("inside function")

當編譯器查看以上代碼時,function()函數將會被編譯,並且函數返回對象將會被傳給裝飾器代碼,裝飾器將會在做完相關操作之後用一個新的函數對象代替原函數。

裝飾器代碼是什麼樣的?大部分的例子都是將裝飾器定義為函數,而我發覺將裝飾器定義成類更容易理解其功能,並且這樣更能發揮裝飾器機制的威力。

對裝飾器的類實現唯一要求是它必須能如函數一般使用,也就是說它必須是可調用的。所以,如果想這麼做這個類必須實現__call__方法。

這樣的裝飾器應該用來做些什嗎?它可以做任何事,但通常它用在當你想在一些特殊的地方使用原函數時,但這不是必須的,例如:

class decorator(object):  def __init__(self, f): print("inside decorator.__init__()") f() # Prove that function definition has completed  def __call__(self): print("inside decorator.__call__()") @decoratordef function(): print("inside function()") print("Finished decorating function()") function() # inside decorator.__init__()# inside function()# Finished decorating function()# inside decorator.__call__()

譯者註:
1. 文法糖@decorator相當於function=decorator(function),在此調用decorator的__init__列印“inside decorator.__init__()”
2. 隨後執行f()列印“inside function()”
3. 隨後執行“print(“Finished decorating function()”)”
4. 最後在調用function函數時,由於使用裝飾器封裝,因此執行decorator的__call__列印 “inside decorator.__call__()”。

一個更實際的例子:

def decorator(func): def modify(*args, **kwargs): variable = kwargs.pop('variable', None) print variable x,y=func(*args, **kwargs) return x,y return modify @decoratordef func(a,b): print a**2,b**2 return a**2,b**2 func(a=4, b=5, variable="hi")func(a=4, b=5) # hi# 16 25# None# 16 25

上下文管理庫(ContextLib)

contextlib模組包含了與上下文管理器和with聲明相關的工具。通常如果你想寫一個上下文管理器,則你需要定義一個類包含__enter__方法以及__exit__方法,例如:

import timeclass demo: def __init__(self, label): self.label = label  def __enter__(self): self.start = time.time()  def __exit__(self, exc_ty, exc_val, exc_tb): end = time.time() print('{}: {}'.format(self.label, end - self.start))

完整的例子在此:

import time class demo: def __init__(self, label): self.label = label  def __enter__(self): self.start = time.time()  def __exit__(self, exc_ty, exc_val, exc_tb): end = time.time() print('{}: {}'.format(self.label, end - self.start)) with demo('counting'): n = 10000000 while n > 0: n -= 1 # counting: 1.36000013351

上下文管理器被with聲明所啟用,這個API涉及到兩個方法。
1. __enter__方法,當執行流進入with代碼塊時,__enter__方法將執行。並且它將返回一個可供上下文使用的對象。
2. 當執行流離開with代碼塊時,__exit__方法被調用,它將清理被使用的資源。

利用@contextmanager裝飾器改寫上面那個例子:

from contextlib import contextmanagerimport time @contextmanagerdef demo(label): start = time.time() try: yield finally: end = time.time() print('{}: {}'.format(label, end - start)) with demo('counting'): n = 10000000 while n > 0: n -= 1 # counting: 1.32399988174

看上面這個例子,函數中yield之前的所有代碼都類似於上下文管理器中__enter__方法的內容。而yield之後的所有代碼都如__exit__方法的內容。如果執行過程中發生了異常,則會在yield語句觸發。
描述器(Descriptors)

描述器決定了對象屬性是如何被訪問的。描述器的作用是定製當你想引用一個屬性時所發生的操作。

構建描述器的方法是至少定義以下三個方法中的一個。需要注意,下文中的instance是包含被訪問屬性的對象執行個體,而owner則是被描述器修辭的類。

__get__(self, instance, owner) – 這個方法是當屬性被通過(value = obj.attr)的方式擷取時調用,這個方法的傳回值將被賦給請求此屬性值的代碼部分。
__set__(self, instance, value) – 這個方法是當希望設定屬性的值(obj.attr = ‘value')時被調用,該方法不會返回任何值。
__delete__(self, instance) – 當從一個對象中刪除一個屬性時(del obj.attr),調用此方法。

譯者註:對於instance和owner的理解,考慮以下代碼:

class Celsius(object): def __init__(self, value=0.0): self.value = float(value) def __get__(self, instance, owner): return self.value def __set__(self, instance, value): self.value = float(value) class Temperature(object): celsius = Celsius() temp=Temperature()temp.celsius #calls Celsius.__get__

上例中,instance指的是temp,而owner則是Temperature。

LazyLoading Properties例子:

import weakref class lazyattribute(object): def __init__(self, f): self.data = weakref.WeakKeyDictionary() self.f = f def __get__(self, obj, cls): if obj not in self.data:  self.data[obj] = self.f(obj) return self.data[obj] class Foo(object): @lazyattribute def bar(self): print "Being lazy" return 42 f = Foo() print f.bar# Being lazy# 42 print f.bar# 42

描述器很好的總結了Python中的Binder 方法(bound method)這個概念,Binder 方法是經典類(classic classes)的實現核心。在經典類中,當在一個對象執行個體的字典中沒有找到某個屬性時,會繼續到類的字典中尋找,然後再到基類的字典中,就這麼一直遞迴的尋找下去。如果在類字典中找到這個屬性,解譯器會檢尋找到的對象是不是一個Python函數對象。如果是,則返回的並不是這個對象本身,而是返回一個柯裡化(currying function)的封裝器對象。當調用這個封裝器時,它會首先在參數列表之前插入執行個體,然後再調用原函數。

譯者註:
1. 柯裡化 – http://zh.wikipedia.org/wiki/%E6%9F%AF%E9%87%8C%E5%8C%96
2. function,method,bound method及unbound method的區別。首先,函數(function)是由def或lambda建立的。當一個函數在class語句塊中定義或是由type來建立時,它會轉成一個非Binder 方法(unbound method),而當通過類執行個體(instance)來訪問此方法的時候,它將轉成Binder 方法(bound method),Binder 方法會自動將執行個體作為第一個參數傳入方法。綜上所述,方法是出現在類中的函數,Binder 方法是一個綁定了具體執行個體的方法,反之則是非Binder 方法。

綜上,描述器被賦值給類,而這些特殊的方法就在屬性被訪問的時候根據具體的訪問類型自動地調用。
元類(MetaClasses)

元類提供了一個改變Python類行為的有效方式。

元類的定義是“一個類的類”。任何執行個體是它自己的類都是元類。

class demo(object): pass obj = demo() print "Class of obj is {0}".format(obj.__class__)print "Class of obj is {0}".format(demo.__class__) # Class of obj is # Class of obj is 

在上例中,我們定義了一個類demo,並且產生了一個該類的對象obj。首先,可以看到obj的__class__是demo。有意思的來了,那麼demo的class又是什麼呢?可以看到demo的__class__是type。

所以說type是python類的類,換句話說,上例中的obj是一個demo的對象,而demo本身又是type的一個對象。

所以說type就是一個元類,而且是python中最常見的元類,因為它使python中所有類的預設元類。

因為元類是類的類,所以它被用來建立類(正如類是被用來建立對象的一樣)。但是,難道我們不是通過一個標準的類定義來建立類的嗎?的確是這樣,但是python內部的運作機制如下:

  • 當看見一個類定義,python會收集所有屬性到一個字典中。
  • 當類定義結束,python將決定類的元類,我們就稱它為Meta吧。
  • 最後,python執行Meta(name, bases, dct),其中:

a. Meta是元類,所以這個調用是執行個體化它。
b. name是建立類的類名。
c. bases是建立類的基類元組
d. dct將屬性名稱映射到對象,列出所有的類屬性。

那麼如何確定一個類(A)的元類呢?簡單來說,如果一個類(A)自身或其基類(Base_A)之一有__metaclass__屬性存在,則這個類(A/Base_A)就是類(A)的元類。否則type就將是類(A)的元類。
模式(Patterns)

“請求寬恕比請求許可更容易(EFAP)”

這個Python設計原則是這麼說的“請求寬恕比請求許可更容易(EFAP)”。不提倡深思熟慮的設計思路,這個原則是說應該盡量去嘗試,如果遇到錯誤,則給予妥善的處理。Python有著強大的異常處理機制可以支援這種嘗試,這些機制協助程式員開發出更為穩定,容錯性更高的程式。

單例

單例是指只能同時存在一個的執行個體對象。Python提供了很多方法來實現單例。

Null對象

Null對象能夠用來代替None類型以避免對None的測試。

觀察者

觀察者模式允許多個對象訪問同一份資料。

建構函式

建構函式的參數經常被賦值給執行個體的變數。這種模式能夠用一行代碼替代多個手動指派陳述式。
總結

謝謝閱讀,如有疑問,請留言討論。

  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.