摘要:函數(shù)內省的內容到此結束。函數(shù)式編程并不是一個函數(shù)式編程語言,但通過和等包的支持,也可以寫出函數(shù)式風格的代碼。
《流暢的Python》筆記。1. 前言
本篇主要講述Python中函數(shù)的進階內容。包括函數(shù)和對象的關系,函數(shù)內省,Python中的函數(shù)式編程。
本片首先介紹函數(shù)和對象的關系;隨后介紹函數(shù)和可調用對象的關系,以及函數(shù)內省。函數(shù)內省這部分會涉及很多與IDE和框架相關的東西,如果平時并不寫框架,可以略過此部分。最后介紹函數(shù)式編程的相關概念,以及與之相關的兩個重要模塊:operator模塊和functools模塊。
首先補充“一等對象”的概念。“一等對象”一般定義如下:
在運行時創(chuàng)建;
能賦值給變量或數(shù)據(jù)結構中的元素;
能作為參數(shù)傳給函數(shù);
能作為函數(shù)的返回結果。
從上述定義可以看出,Python中的函數(shù)符合上述四點,所以在Python中函數(shù)也被視作一等對象。
“把函數(shù)視作一等對象”簡稱為“一等函數(shù)”,但這并不是指有一類函數(shù)是“一等函數(shù)”,在Python中所有函數(shù)都是一等函數(shù)!
2. 函數(shù) 2.1 函數(shù)是對象為了表明Python中函數(shù)就是對象,我們可以使用type()函數(shù)來判斷函數(shù)的類型,并且訪問函數(shù)的__doc__屬性,同時我們還將函數(shù)賦值給一個變量,并且將函數(shù)作為參數(shù)傳入另一個函數(shù):
def factorial(n): """return n!""" return 1 if n < 2 else n * factorial(n - 1) # 在Python控制臺中,help(factorial)也會訪問函數(shù)的__doc__屬性。 print(factorial.__doc__) print(type(factorial)) # 把函數(shù)賦值給一個變量 fact = factorial print(fact) fact(5) # 把函數(shù)傳遞給另一個函數(shù) print(list(map(fact, range(11)))) # 結果: return n![1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]
從上述結果可以看出,__doc__屬性保存了函數(shù)的文檔字符串,而type()的結果說明函數(shù)其實是function類的一個實例。將函數(shù)賦值給一個變量和將函數(shù)作為參數(shù)傳遞給另一個函數(shù)則體現(xiàn)了“一等對象”的特性。
2.2 高階函數(shù)接收函數(shù)作為參數(shù),或者把函數(shù)作為結果返回的函數(shù)叫做高階函數(shù)(higher-order function),上述的map函數(shù)就是高階函數(shù),還有我們常用的sorted函數(shù)也是。
大家或多或少見過map,filter和reduce三個函數(shù),這三個就是高階函數(shù),在過去很常用,但現(xiàn)在它們都有了替代品:
Python3中,map和filter依然是內置函數(shù),但由于有了列表推導和生成器表達式,這兩個函數(shù)已不常用;
Python3中,reduce已不是內置函數(shù),它被放到了functools模塊中。它常被用于求和,但現(xiàn)在求和最好用內置的sum函數(shù)。
sum和reduce這樣的函數(shù)叫做歸約函數(shù),它們的思想是將某個操作連續(xù)應用到一系列數(shù)據(jù)上,累計之前的結果,最后得到一個值,即將一系列元素歸約成一個值。
內置的歸約函數(shù)還有all和any:
all(iterable):如果iterable中每個值都為真,則返回True;all([])返回True;
any(iterable):如果iterable中有至少一個元素為真,則返回True;any([])返回False。
2.3 匿名函數(shù)lambda關鍵字在Python表達式內創(chuàng)建匿名函數(shù),但在Python中,匿名函數(shù)內不能賦值,也不能使用while,try等語句。但它和def語句一樣,實際創(chuàng)建了函數(shù)對象。
如果使用lambda表達式導致一段代碼難以理解,最好還是將其轉換成用def語句定義的函數(shù)。
3. 可調用對象函數(shù)其實一個可調用對象,它實現(xiàn)了__call__方法。Python數(shù)據(jù)模型文檔列出了7種可調用對象:
用于定義的函數(shù):使用def語句或lambda表達式創(chuàng)建;
內置函數(shù):使用C語言(CPython)實現(xiàn)的函數(shù),如len或time.strftime;
內置方法:使用C語言實現(xiàn)的方法,如dict.get;
方法:在類的定義體中定義的函數(shù);
類:調用類時(也就是實例化一個類時)會運行類的__new__方法創(chuàng)建一個實例,然后運行__init__方法初始化實例,最后把實例返回給調用方。因為Python沒有new運算符,所以調用類相當于調用函數(shù);
類的實例:如果類實現(xiàn)了__call__方法,那么它的實例可以作為函數(shù)調用;
生成器函數(shù):使用yield關鍵字的函數(shù)或方法。調用生成器函數(shù)返回的是生成器對象。
3.1 用戶定義的可調用類型任何Python對象都可以表現(xiàn)得像函數(shù),只要實現(xiàn)__call__方法。
class SayHello: def sayhello(self): print("Hello!") def __call__(self): self.sayhello() say = SayHello() say.sayhello() say() print(callable(say)) # 結果: Hello! Hello! True
實現(xiàn)__call__方法的類是創(chuàng)建函數(shù)類對象的簡便方式。有時這些類必須在內部維護一些狀態(tài),讓它在調用之間可用,比如裝飾器。裝飾器必須是函數(shù),而且有時還要在多次調用之間保存一些數(shù)據(jù)。
3.2 函數(shù)內省以下內容在編寫框架和IDE時用的比較多。
筆者之前偶有見到”內省“,但一直不明白”內省“這個詞究竟是什么意思。“自我反省”?其實在編程中,這個詞的意思就是:讓代碼自動確定某一段代碼能干什么。如果以函數(shù)舉例,就是函數(shù)A自動確定函數(shù)B是什么,包含哪些信息,能干什么。不過在講Python函數(shù)的內省之前,先來看看函數(shù)都有哪些屬性和方法。
3.2.1 函數(shù)的屬性和方法dir函數(shù)可以檢測一個參數(shù)所含有的屬性和方法。我們可以用該函數(shù)查看一個函數(shù)所包含的屬性和方法:
>>> dir(factorial) ["__annotations__", "__call__", "__class__", "__closure__", "__code__", "__defaults__", "__delattr__", "__dict__", "__dir__", "__doc__", "__eq__", "__format__", "__ge__", "__get__", "__getattribute__", "__globals__", "__gt__", "__hash__", "__init__", "__init_subclass__", "__kwdefaults__", "__le__", "__lt__", "__module__", "__name__", "__ne__", "__new__", "__qualname__", "__reduce__", "__reduce_ex__", "__repr__", "__setattr__", "__sizeof__", "__str__", "__subclasshook__"]
其中大多數(shù)屬性是Python對象共有的。函數(shù)獨有的屬性如下:
>>> class C:pass >>> obj = C() >>> def func():pass >>> sorted(set(dir(func)) - set(dir(obj))) ["__annotations__", "__call__", "__closure__", "__code__", "__defaults__", "__get__", "__globals__", "__kwdefaults__", "__name__", "__qualname__"]3.2.2 __dict__屬性
與用戶定義的常規(guī)類一樣,函數(shù)使用__dict__屬性存儲用戶賦予它的屬性。這相當于一種基本形式的注解。
這里可能有人覺得別扭:之前都是給變量或者對象賦予屬性,現(xiàn)在是給函數(shù)或者方法賦予屬性。不過正如前面說的,Python中函數(shù)就是對象。
一般來說,為函數(shù)賦予屬性不是個常見的做法,但Django框架就有這樣的行為:
def upper_case_name(obj): return ("%s %s" % (obj.first_name, obj.last_name)).upper() upper_case_name.short_description = "Customer name" # 給方法賦予了一個屬性3.2.3 獲取關于參數(shù)的信息
從這里開始就是函數(shù)內省的內容。在HTTP為框架Bobo中有個使用函數(shù)內省的例子,它以裝飾器的形式展示:
import bobo @bobo.query("/") def hello(person): return "Hello %s!" % person
通過裝飾器bobo.query,Bobo會內省hello函數(shù):Bobo會發(fā)現(xiàn)這個hello函數(shù)需要一個名為person的參數(shù),然后它就會從請求中獲取這個參數(shù),并將這個參數(shù)傳給hello函數(shù)。
有了這個裝飾器,我們就不用自己處理請求對象來獲取person參數(shù),Bobo框架幫我們自動完成了。
那這究竟是怎么實現(xiàn)的呢?Bobo怎么知道我們寫的函數(shù)需要哪些參數(shù)?它又是怎么知道參數(shù)有沒有默認值呢?
這里用到了函數(shù)對象特有的一些屬性(如果不了解參數(shù)類型,可以閱讀筆者的“Python學習之路7”中的相關內容):
__defaults__的值是一個元組,存儲著關鍵字參數(shù)的默認值和位置參數(shù);
__kwdefaults__存儲著命名關鍵字參數(shù)的默認值;
__code__屬性存儲參數(shù)的名稱,它的值是一個code對象引用,自身也有很多屬性。
下面通過一個例子說明這些屬性的用途:
def func(a, b=10):
"""This is just a test"""
c = 20
if a > 10:
d = 30
else:
e = 30
print(func.__defaults__)
print(func.__code__)
print(func.__code__.co_varnames)
print(func.__code__.co_argcount)
# 結果:
(10,)
("a", "b", "c", "d", "e")
2
可以看出,這種信息的組織方式并不方便:
參數(shù)名在__code__.co_varnames中,它同時還存儲了函數(shù)定義體中的局部變量,因此,只有前__code__.co_argcount個元素是參數(shù)名(不包含前綴為*何**的的變長參數(shù));
如果想將參數(shù)名和默認值對應上,只能從后向前掃描__default__屬性,比如上例中關鍵字參數(shù)b的默認值10。
不過,我們并不是第一個發(fā)現(xiàn)這種方式很不方便。已經有人為我們造好了輪子。
使用inspect模塊簡化上述操作>>> from mytest import func >>> from inspect import signature >>> sig = signature(func) # 返回一個inspect.Signature對象(簽名對象) >>> sig>>> str(sig) "(a, b=10)" >>> for name, param in sig.parameters.items(): ... print(param.kind, ":", name, "=",param.default) ... POSITIONAL_OR_KEYWORD : a = # 表示沒有默認值 POSITIONAL_OR_KEYWORD : b = 10
inspect.Signature對象有一個屬性parameters,該屬性是個有序映射,把參數(shù)名和inspect.Parameter對象對應起來。inspect.Parameter也有自己的屬性,如:
name:參數(shù)的名稱;
default:參數(shù)的默認值;
kind:參數(shù)的類型,有5種,POSITIONAL_OR_KEYWORD,VAR_POSITIONAL(任意數(shù)量參數(shù),以一個*號開頭的那種參數(shù)),VAR_KEYWORD(任意數(shù)量的關鍵字參數(shù),以**開頭的那種參數(shù)),KEYWORD_ONLY(命名關鍵字參數(shù))和POSITIONAL_ONLY(Python句法不支持該類型)
annotation和return_annotation:參數(shù)和返回值的注解,后面會講到。
inspect.Signature對象有個bind方法,它可把任意個參數(shù)綁定到Singature中的形參上,框架可使用這個方法在真正調用函數(shù)前驗證參數(shù)是否正確。比如你自己寫的框架中的某函數(shù)A自動獲取用戶輸入的參數(shù),并根據(jù)這些參數(shù)調用函數(shù)B,但在調用B之前,你想檢測下這些參數(shù)是否符合函數(shù)B對形參的要求,此時你就有可能用到這個bind方法,看能不能將這些參數(shù)綁定到函數(shù)B上,如果能,則可認為能夠根據(jù)這些參數(shù)調用函數(shù)B:
>>> from mytest import func >>> from inspect import signature >>> sig = signature(func) >>> my_tag = {"a":10, "b":20} >>> bound_args = sig.bind(**my_tag) >>> bound_args3.2.4 函數(shù)注解>>> for name, value in bound_args.arguments.items(): ... print(name, "=", value) a = 10 b = 20 >>> del my_tag["a"] >>> bound_args = sig.bind(**my_tag) Traceback (most recent call last): TypeError: missing a required argument: "a"
Python3提供了一種句法,用于為函數(shù)聲明中的參數(shù)和返回值附加元數(shù)據(jù)。如下:
# 未加注解 def func(a, b=10): return a + b # 添加注解 def func(a: int, b: "int > 0" = 10) -> int: return a + b
各個參數(shù)可以在冒號后面增加注解表達式,如果有默認值,注解放在冒號和等號之間。上述-> int是對返回值添加注解的形式。
這些注解都存放在函數(shù)的__annotations__屬性中,它是一個字典:
print(func.__annotations__) # 結果 # "return"表示返回值 {"a":, "b": "int > 0", "return": }
Python只是將注解存儲在函數(shù)的__annotations__屬性中,除此之外,再無任何操作。換句話說,這些注解對Python解釋器來說沒有意義。而這些注解的真正用途是提供給IDE、框架和裝飾器等工具使用,比如Mypy靜態(tài)類型檢測工具,它就會根據(jù)你寫的這些注解來檢測傳入的參數(shù)的類型是否符合要求。
inspect模塊可以獲取這些注解。inspect.Signature有個一個return_annotation屬性,它保存返回值的注解;inspect.Parameter對象中的annotation屬性保存了參數(shù)的注解。
函數(shù)內省的內容到此結束。后面將介紹標準庫中為函數(shù)式編程提供支持的常用包。
4. 函數(shù)式編程Python并不是一個函數(shù)式編程語言,但通過operator和functools等包的支持,也可以寫出函數(shù)式風格的代碼。
4.1 operator模塊在函數(shù)式編程中,經常需要把算術運算符當做函數(shù)使用,比如非遞歸求階乘,實現(xiàn)如下:
from functools import reduce def fact(n): return reduce(lambda a, b: a * b, range(1, n + 1))
operator模塊為多個算術運算符提供了對應的函數(shù)。使用算術運算符函數(shù)可將上述代碼改寫如下:
from functools import reduce from operator import mul def fact(n): return reduce(mul, range(1, n + 1))
operator模塊中還有一類函數(shù),能替代從序列中取出元素或讀取對象屬性的lambda表達式:itemgetter和attrgetter。這兩個函數(shù)其實會自行構建函數(shù)。
4.1.1 itemgetter()以下代碼展示了itemgetter的常見用途:
from operator import itemgetter test_data = [ ("A", 1, "Alpha"), ("B", 3, "Beta"), ("C", 2, "Coco"), ] # 相當于 lambda fields: fields[1] for temp in sorted(test_data, key=itemgetter(1)): print(temp) # 傳入多個參數(shù)時,它構建的函數(shù)返回下標對應的值構成的元組 part_tuple = itemgetter(1, 0) for temp in test_data: print(part_tuple(temp)) # 結果: ("A", 1, "Alpha") ("C", 2, "Coco") ("B", 3, "Beta") (1, "A") (3, "B") (2, "C")
itemgetter內部使用[]運算符,因此它不僅支持序列,還支持映射和任何實現(xiàn)了__getitem__方法的類。
4.1.2 attrgetter()attrgetter和itemgetter作用類似,它創(chuàng)建的函數(shù)根據(jù)名稱提取對象的屬性。如果傳入多個屬性名,它也會返回屬性名對應的值構成的元組。這里要展示的是,如果參數(shù)名中包含句點.,attrgetter會深入嵌套對象,獲取指定的屬性:
from collections import namedtuple from operator import attrgetter metro_data = [ ("Tokyo", "JP", 36.933, (35.689722, 139.691667)), ("Delhi NCR", "IN", 21.935, (28.613889, 77.208889)), ("Mexico City", "MX", 20.142, (19.433333, -99.133333)), ] LatLong = namedtuple("LatLong", "lat long") Metropolis = namedtuple("Metropolis", "name, cc, pop, coord") metro_areas = [Metropolis(name, cc, pop, LatLong(lat, long)) for name, cc, pop, (lat, long) in metro_data] # 返回新的元組,獲取name屬性和嵌套的coord.lat屬性 name_lat = attrgetter("name", "coord.lat") for city in sorted(metro_areas, key=attrgetter("coord.lat")): # 嵌套 print(name_lat(city)) # 結果: ("Mexico City", 19.433333) ("Delhi NCR", 28.613889) ("Tokyo", 35.689722)4.1.3 methodcaller()
從名字也可看出,它創(chuàng)建的函數(shù)會在對象上調用參數(shù)指定的方法(注意是方法,而不是函數(shù))。
>>> from operator import methodcaller >>> s = "The time has come" >>> upcase = methodcaller("upper") >>> upcase(s) # 相當于s.upper() "THE TIME HAS COME" >>> hiphenate = methodcaller("replace"," ","-") >>> hiphenate(s) # 相當于s.replace(" ", "-") "The-time-has-come"
從hiphenate這個例子可以看出,methodcaller還可以凍結某些參數(shù),即部分應用(partial application),這與functools.partial函數(shù)的作用類似。
4.2 使用functools.partial凍結參數(shù)functool模塊提供了一系列高階函數(shù),reduce函數(shù)相信大家已經很熟悉了,本節(jié)主要介紹其中兩個很有用的函數(shù)partial和它的變體partialmethod。
functools.partial用到了一個“閉包”的概念,這個概念的詳細內容下一篇再介紹。使用這個函數(shù)可以把接收一個或多個參數(shù)的函數(shù)改編成需要回調的API,這樣參數(shù)更少。
>>> from operator import mul >>> from functools import partial >>> triple = partial(mul, 3) >>> triple(7) 21 >>> list(map(triple, range(1,10))) # 這里無法直接使用mul函數(shù) [3, 6, 9, 12, 15, 18, 21, 24, 27] >>> triple.func # 訪問原函數(shù)>>> triple.args # 訪問固定參數(shù) (3,) >>> triple.keywords # 訪問關鍵字參數(shù) {}
functools.partialmethod函數(shù)的作用于partial一樣,只不過partialmethod用于方法,partial用于函數(shù)。
補充:回調函數(shù)(callback function)可以簡單理解為,當一個函數(shù)X被傳遞給函數(shù)A時,函數(shù)X就被稱為回調函數(shù),函數(shù)A調用函數(shù)X的過程叫做回調。
5. 總結本篇首先介紹了函數(shù),包括函數(shù)與對象的關系,高階函數(shù)和匿名函數(shù),重點是函數(shù)就是對象;隨后介紹了函數(shù)和可調用對象的關系,以及函數(shù)的內省;最后,我們介紹了關于函數(shù)式編程的概念以及與之相關的兩個重要模塊。
迎大家關注我的微信公眾號"代碼港" & 個人網站 www.vpointer.net ~
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/44715.html
摘要:本篇主要講述中使用函數(shù)來實現(xiàn)策略模式和命令模式,最后總結出這種做法背后的思想。 《流暢的Python》筆記。本篇主要講述Python中使用函數(shù)來實現(xiàn)策略模式和命令模式,最后總結出這種做法背后的思想。 1. 重構策略模式 策略模式如果用面向對象的思想來簡單解釋的話,其實就是多態(tài)。父類指向子類,根據(jù)子類對同一方法的不同重寫,得到不同結果。 1.1 經典的策略模式 下圖是經典的策略模式的U...
摘要:一等函數(shù)在中,函數(shù)是一等對象。匿名函數(shù)關鍵字在表達式內創(chuàng)建匿名函數(shù)然而,簡單的句法限制了函數(shù)的定義體只能使用純表達式,即函數(shù)的定義體中不能賦值,不能使用等語句。匿名函數(shù)適合用于作為函數(shù)的參數(shù) 一等函數(shù) 在python中,函數(shù)是一等對象。編程語言理論家把一等對象定義為滿足以下條件的程序實體: 在運行時創(chuàng)建 能賦值給變量或數(shù)據(jù)結構中的元素 能作為參數(shù)傳給函數(shù) 能作為函數(shù)的返回結果 在p...
摘要:本文重點了解函數(shù)在中是一等對象了解中的可調用對象掌握正確定義函數(shù)參數(shù)的方法了解和中支持函數(shù)式編程的方法。歸約函數(shù)定義能夠接受一個可迭代對象并返回單個結果的函數(shù)是歸約函數(shù)。 導語:本文章記錄了本人在學習Python基礎之函數(shù)篇的重點知識及個人心得,打算入門Python的朋友們可以來一起學習并交流。 本文重點: 1、了解函數(shù)在Python中是一等對象;2、了解Python中的可調用對象;3...
摘要:本文重點了解函數(shù)在中是一等對象了解中的可調用對象掌握正確定義函數(shù)參數(shù)的方法了解和中支持函數(shù)式編程的方法。歸約函數(shù)定義能夠接受一個可迭代對象并返回單個結果的函數(shù)是歸約函數(shù)。 本文章記錄了本人在學習Python基礎之函數(shù)篇的重點知識及個人心得,歡迎打算入門Python的朋友與我一起學習交流。。 本文重點: 1、了解函數(shù)在Python中是一等對象;2、了解Python中的可調用對象;3、掌握...
摘要:小結本篇主要講述了如何生成數(shù)據(jù)集以及如何對其進行可視化如何使用創(chuàng)建簡單的圖表如果使用散點圖來探索隨機漫步過程如何使用創(chuàng)建直方圖,以及如何使用直方圖來探索同時擲兩個面數(shù)不同的骰子的結果。 《Python編程:從入門到實踐》筆記。從本篇起將用三篇的篇幅介紹如何用Python進行數(shù)據(jù)可視化。 1. 前言 從本篇開始,我們將用三篇的篇幅來初步介紹如何使用Python來進行數(shù)據(jù)可視化操作。本篇的...
閱讀 2578·2021-09-30 09:48
閱讀 2568·2019-08-30 14:10
閱讀 2712·2019-08-29 11:22
閱讀 1840·2019-08-26 13:51
閱讀 2279·2019-08-26 12:02
閱讀 2420·2019-08-23 16:06
閱讀 3557·2019-08-23 14:06
閱讀 1096·2019-08-23 13:56