瞭解幾個Python高級特性

  1. 前言算法

  Python 很是靈活強大,跟它具備一些特性有關,如匿名函數、列表推導式、迭代器、裝飾器等。本文主要簡單介紹:app

  切片ide

  迭代、可迭代對象、迭代器函數

  推導式(列表推導式、集合推導式、字典推導式)工具

  生成器和生成器表達式測試

  匿名函數spa

  裝飾器設計

  2. 切片日誌

  切片(slice)在 Python 中很是強大,能夠輕鬆對字符串、列表和元組進行切割,完成拷貝。注意切片是淺拷貝,關於淺拷貝和深拷貝留做之後討論。對象

  切片的語法是:obj[start: end: step]

  obj 是支持切片的對象,如:列表、字符串、元組等。

  start 是開始切的索引位置,索引是從 0 開始標記的。start 能夠省略,默認值是 0.

  end 是切片結束的位置,實際上切不到 obj[end]。end 也能夠省略,默認值是對象的長度。

  step 是切片的步長,也能夠省略,默認值是 1。

  對字符串、列表、元組進行切片。

  >>> word = "Python"

  >>> word[:]

  'Python'

  >>> word[1:3]

  'yt'

  >>> word[::2]

  'Pto'

  >>> ls = [1, 2, 3, 4, 5, 6]

  >>> ls[::2]

  [1, 3, 5]

  >>> t = (1, 2, 3, 4, 5, 6)

  >>> t[2:6]

  (3, 4, 5, 6)

  切片也支持負數,使用 obj[::-1] 能夠輕鬆實現翻轉,如把列表翻轉:

  >>> ls = [1, 2, 3, 4, 5, 6, 7]

  >>> ls[::-1]

  [7, 6, 5, 4, 3, 2, 1]

  obj[end] 是取不到的。

  >>> ls = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

  >>> ls[1:9]

  [2, 3, 4, 5, 6, 7, 8, 9]

  能夠看到 ls[1:9], ls[1],即 2 能夠取到,ls[9],即 10 是取不到的。

  3. 迭代、可迭代對象、迭代器

  迭代(iteration):迭代是一種操做,能夠理解爲遍歷,如用 for 循環遍歷列表或者元組。

  可迭代對象(iterable object):能夠用 for 循環迭代的對象。如列表、元組、字符串、字典、集合等。

  到目前爲止,能夠看到大多數容器對象均可以使用 for 語句:

  for element in [1, 2, 3]:

  print(element)

  for element in (1, 2, 3):

  print(element)

  for key in {'one':1, 'two':2}:

  print(key)

  for char in "123":

  print(char)

  for line in open("myfile.txt"):

  print(line, end='')

  咱們還能夠用 collections 模塊中的 Iterable 類型判斷一個對象是不是可迭代對象。

  >>> from collections import Iterable

  >>> isinstance([1, 2], Iterable)

  True

  >>> isinstance((1, 2), Iterable)

  True

  >>> isinstance('abc', Iterable)

  True

  迭代器(iterator):是遵循迭代器協議的可迭代對象就稱爲迭代器。迭代器協議機制是:for 語句會在容器對象上調用 iter()。 該函數返回一個定義了 __next__() 方法的迭代器對象,此方法將逐一訪問容器中的元素。 當元素用盡時,__next__() 將引起 StopIteration 異常來通知終止 for 循環。你可使用 next() 內置函數來調用 __next__() 方法。

  通俗理解就是:能被 next()函數調用並不斷返回下一個值的對象成爲迭代器。

  迭代器的使用很是廣泛並使得 Python 成爲一個統一的總體。下面這個例子顯示了迭代器的運做方式:

  >>> s = 'abc'

  >>> it = iter(s)

  >>> it

  >>> next(it)

  'a'

  >>> next(it)

  'b'

  >>> next(it)

  'c'

  >>> next(it)

  Traceback (most recent call last):

  File "", line 1, in

  next(it)

  StopIteration

  咱們可使用 isinstance()方法判斷一個對象是不是 Iterator 對象。

  >>> from collections import Iterator

  >>> ls = [1, 2, 3, 4]

  >>> isinstance(ls, Iterator)

  False

  >>> isinstance(iter(ls), Iterator)

  True

  >>> t = (1, 2, 3, 4, 5)

  >>> isinstance(t, Iterator)

  False

  >>> isinstance(iter(t), Iterator)

  True

  >>> s = 'abcde'

  >>> isinstance(s, Iterator)

  False

  >>> isinstance(iter(s), Iterator)

  True

  經過上面的例子咱們能夠看到,列表、元組、字符串等是可迭代對象,可是不是迭代器。可使用 iter()函數,輕鬆把列表、元組、字符串等轉爲迭代器。

  爲何列表、元組、字符串等不是迭代器呢?

  由於 Python 的 Iterator 對象表示的是一個數據流,迭代器能夠被 next()函數不斷調用並返回下一個數據,直到沒有數據時拋出 StopIteration 錯誤。咱們能夠把這個數據流當作一個有序序列,可是咱們卻不能提早知道序列的長度,只能不斷經過 next()函數實現按需計算下一個數據,所以 Iterator 的計算是惰性的,只有在須要返回下一個數據時它纔會計算。

  因此 Iterator 能夠表示一個無限大的數據流,如所有整數,可是列表等容器因爲內存空間限制,用於不可能存儲全體整數。

  小結:

  迭代(iteration):迭代是一種操做,用 for 循環遍歷。

  可迭代對象(iterable object):能夠用 for 循環迭代的對象。

  迭代器(iterator):能夠做用於 next()函數的可迭代對象,它們是一個惰性計算序列。

  能夠用 iter()函數把 list、str、tuple、dict 轉爲迭代器。

  4. 推導式

  推導式(comprehension)是 Python 很是重要的一個特性,提供了更加簡單的建立列表、集合、字典的方式。其中列表推導式(list comprehensions)是用的最多的。

  4.1 列表推導式

  列表推導式是 Python 很是重要的一個特性之一。列表推導式提供了一個更簡單的建立列表的方法。

  常見的用法是把某種操做應用於序列或可迭代對象的每一個元素上,而後使用其結果來建立列表,或者經過知足某些特定條件元素來建立子序列。

  好比:假設咱們想建立一個平方列表。

  一般咱們是這麼作:

  >>> squares = []

  >>> for x in range(10):

  ... squares.append(x**2)

  ...

  >>> squares

  [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

  這樣作儘管能夠達到目的,可是這裏建立(或被重寫)的名爲 x 的變量在 for 循環後仍然存在,可能存在反作用。咱們能夠經過如下方法計算平方列表的值而不會產生任何反作用。

  方法一:squares = list(map(lambda x: x**2, range(10)))

  方法一等價於:

  方法二:squares = [x**2 for x in range(10)]

  這裏的方法二就是列表推導式,咱們能夠看到列表推導式更加簡潔易讀。

  列表推導式的結構是:

  [ 表達式 for子句(必須有一個) 0 或多個 for 或者 if 子句]

  說明:由一對方括號([])所包含如下內容:一個表達式,後面跟一個 for 子句,而後是零個或多個 for 或 if 子句。根據後面的 for 等子句計算表達式的值,而後把全部計算的值存爲一個新列表。如:如下列表推導式會將兩個列表中不相等的元素組合起來變爲一個新的列表:

  [(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]

  它等價於

  >>> combs = []

  >>> for x in [1,2,3]:

  ... for y in [3,1,4]:

  ... if x != y:

  ... combs.append((x, y))

  ...

  >>> combs

  [(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

  可見,使用列表推導式很是簡潔。注意在上面兩個代碼片斷中, for 和 if 的順序是相同的。若是表達式是一個元組(例如上面的 (x, y)),那麼就必須加上括號。

  列表推導式可使用複雜的表達式和嵌套函數。如:

  >>> from math import pi

  >>> [str(round(pi, i)) for i in range(1, 6)]

  ['3.1', '3.14', '3.142', '3.1416', '3.14159']

  嵌套的列表推導式:列表推導式中的初始表達式能夠是任何表達式,包括另外一個列表推導式。

  考慮下面這個 3x4 的矩陣,它由 3 個長度爲 4 的列表組成

  >>> matrix = [

  ... [1, 2, 3, 4],

  ... [5, 6, 7, 8],

  ... [9, 10, 11, 12],

  ... ]

  下面的列表推導式將交換其行和列

  >>> [[row[i] for row in matrix] for i in range(4)]

  [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

  如上節所示,嵌套的列表推導式是基於跟隨其後的 for 進行求值的,因此這個例子等價於:

  >>> transposed = []

  >>> for i in range(4):

  ... transposed.append([row[i] for row in matrix])

  ...

  >>> transposed

  [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

  反過來講,也等價於

  >>> transposed = []

  >>> for i in range(4):

  ... # the following 3 lines implement the nested listcomp

  ... transposed_row = []

  ... for row in matrix:

  ... transposed_row.append(row[i])

  ... transposed.append(transposed_row)

  ...

  >>> transposed

  [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

  實際應用中,使用內置函數去組成複雜的流程語句是更好的選擇。 zip() 函數將會很好地處理這種狀況

  >>> list(zip(*matrix)) # *matrix 是列表解包,會去除最外層的[]

  [(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)]

  列表推導式小結:

  列表推導式格式: [表達式(可嵌套列表推導式) for子句 0或多個 for 或 if 子句]

  列表推導式建立新列表更加簡潔易讀,沒有反作用。

  注意,雖然元組和列表相似,可是列表是可變的,元組是不可變的,因此元組沒有推導式。

  元組是不可變的,其序列一般包含不一樣種類的元素,而且經過解包或者索引來訪問。一個元素的元組的建立必須在一個元素後面添加一個逗號,如 t = (1, )

  列表是可變的,通常列表的元素都是同種類型的,而且經過迭代訪問。

  4.2 集合推導式

  集合是無序、肯定、互異的。它一般用於成員測試和去重操做。此外還能夠進行並集、交集、差集、補集等操做。

  集合也和列表同樣支持推導式,集合推導式(set comprehensions)以下:

  >>> {x for x in 'abracadabra' if x not in 'abc'}

  {'r', 'd'}

  即:把[] 變爲 {} 便可,其餘和列表規則同樣。

  4.3 字典推導式

  字典也支持字典推導式(dict comprehensions),如:

  >>> {x: x**2 for x in (2, 4, 6)}

  {2: 4, 4: 16, 6: 36}

  >>> {x: y for x, y in zip("abcd", [1, 2, 3, 4])}

  {'a': 1, 'b': 2, 'c': 3, 'd': 4}

  即:把[] 變爲 {},同時表達式符合字典的鍵值對形式 key: value。其餘規則同列表推導式。

  5. 生成器和生成器表達式

  5.1 生成器

  生成器(Generator):是一個用於建立迭代器的簡單而強大的工具。 它們的寫法相似標準的函數,但當它們要返回數據時會使用 yield 語句。 每次對生成器調用 next() 時,它會從上次離開位置恢復執行(它會記住上次執行語句時的全部數據值)。就是把函數中 return 關鍵字換成了 yield 關鍵字,這樣定義出來的就不是函數了,而是一個生成器,一般咱們用 for 循環去迭代生成器,而不是用 next()函數一個一個調用,示例以下:

  def reverse(data):

  for index in range(len(data)-1, -1, -1):

  yield data[index]

  >>> for char in reverse('golf'):

  ... print(char)

  ...

  f

  l

  o

  g

  能夠用生成器來完成的操做一樣能夠用基於類的迭代器來完成。 但生成器的寫法更爲緊湊,由於它會自動建立 __iter__() 和 __next__() 方法。

  另外一個關鍵特性在於局部變量和執行狀態會在每次調用之間自動保存。

  除了會自動建立方法和保存程序狀態,當生成器終結時,它們還會自動引起 StopIteration。 這些特性結合在一塊兒,使得建立迭代器能與編寫常規函數同樣容易。

  生成器比較難理解的一點在於生成器的執行流程和函數流程不同,函數是順序執行,遇到 return 語句,或者最後一行函數語句就返回。而變成生成器的函數時,在每次調用 next()函數的時候執行,遇到 yield 語句返回,再次執行時從上次返回的 yield 語句處繼續執行。

  舉個栗子:

  >>> def language():

  ... print("Step 1")

  ... yield "Python"

  ... print("Step 2")

  ... yield "Java"

  ... print("Step 3")

  ... yield "C"

  ...

  >>> lang = language()

  >>> next(lang)

  Step 1

  'Python'

  >>> next(lang)

  Step 2

  'Java'

  >>> next(lang)

  Step 3

  'C'

  >>> next(lang)

  Traceback (most recent call last):

  File "", line 1, in

  StopIteration

  調用 language 生成器時,首先要生成一個 generator 對象,而後用 next()函數不斷得到下一個返回值。在執行的過程當中,遇到 yield 就中斷,下次又繼續執行,執行 3 次 yield 後,沒有 yield 能夠執行了,因此第 4 次調用 next(lang)就報錯了。

  5.2 生成器表達式

  生成器除了用相似於函數的定義方法外,還能夠用相似於列表推導式的方式生成,所用語法相似列表推導式,就是把外層的方括號換成圓括號便可。這種表達式被設計用於生成器將當即被外層函數所使用的狀況。

  生成器表達式相比完整的生成器更緊湊但較不靈活,相比等效的列表推導式則更爲節省內存。由於生成器表達式是生成迭代器,迭代器是惰性計算,它存儲的是算法,須要多少就計算多少,而列表推導式是直接所有計算出來,放到內存。

  例如:

  >>> sum(i*i for i in range(10)) # sum of squares

  285

  >>> xvec = [10, 20, 30]

  >>> yvec = [7, 5, 3]

  >>> sum(x*y for x,y in zip(xvec, yvec)) # dot product

  260

  >>> unique_words = set(word for line in page for word in line.split())

  >>> valedictorian = max((student.gpa, student.name) for student in graduates)

  >>> data = 'golf'

  >>> list(data[i] for i in range(len(data)-1, -1, -1))

  ['f', 'l', 'o', 'g']

  6. 匿名函數

  當咱們傳入函數時,有些時候不須要顯示的定義函數,直接傳入匿名函數( anonymous functions)更方便。

  匿名函數是經過 lambda 關鍵字來建立的。基本語法是:lambda 參數: 表達式

  其中參數能夠是多個,用逗號分隔。如:lambda a, b: a+b,這個匿名函數返回兩個參數的和。再舉個栗子看看匿名函數的經常使用用法:

  >>> pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]

  >>> pairs.sort(key=lambda pair: pair[1])

  >>> pairs

  [(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]

  Lambda 函數能夠在須要函數對象的任何地方使用。它們在語法上限於單個表達式。從語義上來講,它們只是正常函數定義的語法糖,不用寫 return,返回值就是表達式的結果。

  匿名函數的優點在於沒有名字,不用擔憂命名衝突。此外匿名函數也是一個函數對象,也能夠把匿名函數賦值給一個變量,再利用變量來調用該函數。如:

  >>> s = lambda x, y: x + y # 計算兩數之和

  >>> s

  at 0x000002B457B3AF78>

  >>> s(2, 3)

  5

  >>> s(3, 7)

  此外,與嵌套函數定義同樣,lambda 函數能夠引用所包含域的變量,把匿名函數做爲返回值返回,舉個官網栗子

  >>> def make_incrementor(n):

  ... return lambda x: x + n

  ...

  >>> f = make_incrementor(42)

  >>> f(0)

  42

  >>> f(1)

  43

  7. 裝飾器

  裝飾器(decorator)是一個很是有用的設計。它能夠在代碼運行期間動態增長功能。本質上,裝飾器是一個返回函數的高階函數。

  好比:咱們如今要定義一個能打印日誌的裝飾器,能夠定義以下:

  def log(func):鄭州作人流多少錢 http://wap.zyfuke.com/

  def wrapper(*args, **kwargs):

  print(f"call {func.__name__}()")

  return func(*args, **kwargs)

  return wrapper

  這個 log 其實就是一個裝飾器,它接收一個函數做爲參數,並返回一個函數。咱們藉助 Python 的 @ 語法,把裝飾器置於函數的定義處:

  def log(func):

  def wrapper(*args, **kwargs):

  print(f"call {func.__name__}()") # func.__name__獲取該函數的名字

  return func(*args, **kwargs)

  return wrapper

  @log

  def now():

  print("2020-05-05")

  if __name__ == '__main__':

  now()

  結果輸出:

  call now()

  2020-05-05

  把 @log 放到 now() 函數的定義處,至關於執行了語句:`now = log(now)。

  因爲 log()是一個裝飾器,返回一個函數,因此,原來的 now()函數仍然存在,只是如今同名的 now 變量指向了新的函數,因而調用 now()將執行新函數,即在 log()函數中返回的 wrapper()函數。

  wrapper()函數的參數定義是(*args, **kwargs),所以 wrapper()能夠接收任意參數的調用,在 wrapper()函數內,首先打印日誌,再緊接着調用原始函數。

  若是 decorator 自己須要傳入參數,那就須要編寫一個返回 decorator 的高階函數,寫出來會更復雜,好比,自定義 log 的文本:

  def log(text):

  def decorator(func):

  def wrapper(*args, **kwargs):

  print(f"{text} call {func.__name__}")

  return func(*args, **kwargs)

  return wrapper

  return decorator

  @log("開始")

  def now():

  print("2020-05-05")

  if __name__ == '__main__':

  now()

  結果輸出:

  開始 call now

  2020-05-05

  與 2 層嵌套相比,3 層嵌套效果如:now = log("開始")(now)

  說明:首先執行 log(「咱們」),而後返回 decorator 函數,在調用返回的函數,參數是 now 函數,返回值最終是 wrapper 函數。

  這兩種 decorator 定義方式都沒有問題,不過還差最後一步,由於函數也是對象,它有__name__等屬性,通過 decorator 裝飾以後的函數,它們的__name__已經從原來的 now 變成了,wrapper:

  def log(text):

  def decorator(func):

  def wrapper(*args, **kwargs):

  print(f"{text} call {func.__name__}")

  return func(*args, **kwargs)

  return wrapper

  return decorator

  @log("開始")

  def now():

  print("2020-05-05")

  if __name__ == '__main__':

  now()

  print(now.__name__)

  結果輸出:

  開始 call now

  2020-05-05

  wrapper

  這是由於返回的 wrapper()函數名字就是 wrapper,因此須要把原始函數的__name__等屬性複製到 wrapper()函數中,不然,有些依賴函數簽名的代碼執行會出錯。Python 內置的 functools.wraps 就是幹這個事的,因此一個完整的 decorator 的寫法以下:

  import functools

  def log(func):

  @functools.wraps(func)

  def wrapper(*args, **kwargs):

  print(f"call {func.__name__}()") # func.__name__獲取該函數的名字

  return func(*args, **kwargs)

  return wrapper

  @log

  def now():

  print("2020-05-05")

  if __name__ == '__main__':

  now()

  print(now.__name__)

  結果輸出:

  call now()

  2020-05-05

  now

  帶參數的裝飾器:

  import functools

  def log(text):

  def decorator(func):

  @functools.wraps(func)

  def wrapper(*args, **kwargs):

  print(f"{text} call {func.__name__}")

  return func(*args, **kwargs)

  return wrapper

  return decorator

  @log("如今")

  def now():

  print("2020-05-05")

  if __name__ == '__main__':

  now()

  print(now.__name__)

  結果輸出:

  如今 call now

  2020-05-05

  now

相關文章
相關標籤/搜索