『壹』 lua和python哪個適合小白
python更適合小白,python方便好用的slice操作,lua就沒有。
lua的初衷本就是是作為一個嵌入式語言,為了這個目的,它的一切都是減無可減,拿來寫大點兒的東西,就會感覺老是要自己發明輪子。比如,python方便好用的slice操作,lua就沒有。python3的好處就是輪子基本上都發明好了,而且語言本身也有一堆語法糖讓你用起來方便快捷。但是如果項目需要一個嵌入式腳本引擎的話,我還是會選擇lua的。
『貳』 Python 有哪些好玩的語法糖
當然是函數式那一套黑魔法啦,且聽我細細道來。
lambda表達式
也就是匿名函數。
用法:lambda 參數列表 : 返回值
例:
+1函數
f=lambda x:x+1
max函數(條件語句的寫法如下)
f_max=lambda x,y:x if x>y else y
上述定義的函數與用def定義的函數沒有區別,而且左邊的f=在某些情況下並不是必要的。
filter,map,rece
filter函數接受兩個參數,第一個是過濾函數,第二個是可遍歷的對象,用於選擇出所有滿足過濾條件的元素,不同版本的filter的返回值稍有區別,我用的是python3.5,filter返回的是經過過濾的可遍歷對象。
例:
去除小寫字母
s=filter(lambda x:not str(x).islower(),"asdasfAsfBsdfC")
for ch in s:
print(ch)
map函數接受的參數類型與filter類似,它用於把函數作用於可遍歷對象的每一個元素。類似於數學中映射的概念。
例:
求y=2x+1(偷偷用了一下range函數生成定義域)
s=map(lambda x:2*x+1,range(6))
for x in s:
print(x)
rece函數對每個元素作累計操作,它接受的第一個參數必須是有兩個參數的函數。
例:
求和
from functools import rece
s=rece(lambda x,y:x+y,range(1,6))
print(s)
求乘積(第三個可選參數表示累計變數的初值)
from functools import rece
s=rece(lambda x,y:x*y,range(1,6),1)
print(s)
柯里化(curry)函數
如果一個函數需要2個參數,而你只傳入一個參數,那麼你就可以得到一個柯里化的函數,這是函數式編程語言的重要特性之一,遺憾的是,python並不能在語法層面支持柯里化調用,但它在庫中提供了介面。
例:
*3函數
f_mul=lambda x,y:x*y
from functools import partial
mul3=partial(f_mul,3)
print(mul3(1))
print(mul3(6))
打包與解包
有點類似於函數式中的模式匹配,略牽強。
t=(1,2,3)
x,y,z=t
列表生成式
這個也有點牽強,不知道嚴格意義上講屬不屬於函數式風格。
例:生成奇數序列
l=[2*x+1 for x in range(10)]
for i in l:
print(i)
最後來一個彩蛋(以前某答主提到的用調分函數來美顏的演算法,忘了出處了,侵刪)
from PIL import Image
from math import sqrt
im = Image.open("a.jpg")
ret= im.convert(mode="RGB")
ret = ret.point(lambda x:sqrt(x)*sqrt(255))
ret.save("b.jpg")
『叄』 Python 里為什麼函數可以返回一個函數內部定義的函數
「在Python中,函數本身也是對象」
這一本質。那不妨慢慢來,從最基本的概念開始,討論一下這個問題:
1. Python中一切皆對象
這恐怕是學習Python最有用的一句話。想必你已經知道Python中的list, tuple, dict等內置數據結構,當你執行:
alist = [1, 2, 3]
時,你就創建了一個列表對象,並且用alist這個變數引用它:
當然你也可以自己定義一個類:
class House(object):
def __init__(self, area, city):
self.area = area
self.city = city
def sell(self, price):
[...] #other code
return price
然後創建一個類的對象:
house = House(200, 'Shanghai')
OK,你立馬就在上海有了一套200平米的房子,它有一些屬性(area, city),和一些方法(__init__, self):
2. 函數是第一類對象
和list, tuple, dict以及用House創建的對象一樣,當你定義一個函數時,函數也是對象:
def func(a, b):
return a+b
在全局域,函數對象被函數名引用著,它接收兩個參數a和b,計算這兩個參數的和作為返回值。
所謂第一類對象,意思是可以用標識符給對象命名,並且對象可以被當作數據處理,例如賦值、作為參數傳遞給函數,或者作為返回值return 等
因此,你完全可以用其他變數名引用這個函數對象:
add = func
這樣,你就可以像調用func(1, 2)一樣,通過新的引用調用函數了:
print func(1, 2)
print add(1, 2) #the same as func(1, 2)
或者將函數對象作為參數,傳遞給另一個函數:
def caller_func(f):
return f(1, 2)
if __name__ == "__main__":
print caller_func(func)
可以看到,
函數對象func作為參數傳遞給caller_func函數,傳參過程類似於一個賦值操作f=func;
於是func函數對象,被caller_func函數作用域中的局部變數f引用,f實際指向了函數func;cc
當執行return f(1, 2)的時候,相當於執行了return func(1, 2);
因此輸出結果為3。
3. 函數對象 vs 函數調用
無論是把函數賦值給新的標識符,還是作為參數傳遞給新的函數,針對的都是函數對象本身,而不是函數的調用。
用一個更加簡單,但從外觀上看,更容易產生混淆的例子來說明這個問題。例如定義了下面這個函數:
def func():
return "hello,world"
然後分別執行兩次賦值:
ref1 = func #將函數對象賦值給ref1
ref2 = func() #調用函數,將函數的返回值("hello,world"字元串)賦值給ref2
很多初學者會混淆這兩種賦值,通過Python內建的type函數,可以查看一下這兩次賦值的結果:
In [4]: type(ref1)
Out[4]: function
In [5]: type(ref2)
Out[5]: str
可以看到,ref1引用了函數對象本身,而ref2則引用了函數的返回值。通過內建的callable函數,可以進一步驗證ref1是可調用的,而ref2是不可調用的:
In [9]: callable(ref1)
Out[9]: True
In [10]: callable(ref2)
Out[10]: False
傳參的效果與之類似。
4. 閉包&LEGB法則
所謂閉包,就是將組成函數的語句和這些語句的執行環境打包在一起時,得到的對象
聽上去的確有些復雜,還是用一個栗子來幫助理解一下。假設我們在foo.py模塊中做了如下定義:
#foo.py
filename = "foo.py"
def call_func(f):
return f() #如前面介紹的,f引用一個函數對象,然後調用它
在另一個func.py模塊中,寫下了這樣的代碼:
#func.py
import foo #導入foo.py
filename = "func.py"
def show_filename():
return "filename: %s" % filename
if __name__ == "__main__":
print foo.call_func(show_filename) #注意:實際發生調用的位置,是在foo.call_func函數中
當我們用python func.py命令執行func.py時輸出結果為:
chiyu@chiyu-PC:~$ python func.py
filename:func.py
很顯然show_filename()函數使用的filename變數的值,是在與它相同環境(func.py模塊)中定義的那個。盡管foo.py模塊中也定義了同名的filename變數,而且實際調用show_filename的位置也是在foo.py的call_func內部。
而對於嵌套函數,這一機制則會表現的更加明顯:閉包將會捕捉內層函數執行所需的整個環境:
#enclosed.py
import foo
def wrapper():
filename = "enclosed.py"
def show_filename():
return "filename: %s" % filename
print foo.call_func(show_filename) #輸出:filename: enclosed.py
實際上,每一個函數對象,都有一個指向了該函數定義時所在全局名稱空間的__globals__屬性:
#show_filename inside wrapper
#show_filename.__globals__
{
'__builtins__': <mole '__builtin__' (built-in)>, #內建作用域環境
'__file__': 'enclosed.py',
'wrapper': <function wrapper at 0x7f84768b6578>, #直接外圍環境
'__package__': None,
'__name__': '__main__',
'foo': <mole 'foo' from '/home/chiyu/foo.pyc'>, #全局環境
'__doc__': None
}
當代碼執行到show_filename中的return "filename: %s" % filename語句時,解析器按照下面的順序查找filename變數:
Local - 本地函數(show_filename)內部,通過任何方式賦值的,而且沒有被global關鍵字聲明為全局變數的filename變數;
Enclosing - 直接外圍空間(上層函數wrapper)的本地作用域,查找filename變數(如果有多層嵌套,則由內而外逐層查找,直至最外層的函數);
Global - 全局空間(模塊enclosed.py),在模塊頂層賦值的filename變數;
Builtin - 內置模塊(__builtin__)中預定義的變數名中查找filename變數;
在任何一層先找到了符合要求的filename變數,則不再向更外層查找。如果直到Builtin層仍然沒有找到符合要求的變數,則拋出NameError異常。這就是變數名解析的:LEGB法則。
總結:
閉包最重要的使用價值在於:封存函數執行的上下文環境;
閉包在其捕捉的執行環境(def語句塊所在上下文)中,也遵循LEGB規則逐層查找,直至找到符合要求的變數,或者拋出異常。
5. 裝飾器&語法糖(syntax sugar)
那麼閉包和裝飾器又有什麼關系呢?
上文提到閉包的重要特性:封存上下文,這一特性可以巧妙的被用於現有函數的包裝,從而為現有函數更加功能。而這就是裝飾器。
還是舉個例子,代碼如下:
#alist = [1, 2, 3, ..., 100] --> 1+2+3+...+100 = 5050
def lazy_sum():
return rece(lambda x, y: x+y, alist)
我們定義了一個函數lazy_sum,作用是對alist中的所有元素求和後返回。alist假設為1到100的整數列表:
alist = range(1, 101)
但是出於某種原因,我並不想馬上返回計算結果,而是在之後的某個地方,通過顯示的調用輸出結果。於是我用一個wrapper函數對其進行包裝:
def wrapper():
alist = range(1, 101)
def lazy_sum():
return rece(lambda x, y: x+y, alist)
return lazy_sum
lazy_sum = wrapper() #wrapper() 返回的是lazy_sum函數對象
if __name__ == "__main__":
lazy_sum() #5050
這是一個典型的Lazy Evaluation的例子。我們知道,一般情況下,局部變數在函數返回時,就會被垃圾回收器回收,而不能再被使用。但是這里的alist卻沒有,它隨著lazy_sum函數對象的返回被一並返回了(這個說法不準確,實際是包含在了lazy_sum的執行環境中,通過__globals__),從而延長了生命周期。
當在if語句塊中調用lazy_sum()的時候,解析器會從上下文中(這里是Enclosing層的wrapper函數的局部作用域中)找到alist列表,計算結果,返回5050。
當你需要動態的給已定義的函數增加功能時,比如:參數檢查,類似的原理就變得很有用:
def add(a, b):
return a+b
這是很簡單的一個函數:計算a+b的和返回,但我們知道Python是 動態類型+強類型 的語言,你並不能保證用戶傳入的參數a和b一定是兩個整型,他有可能傳入了一個整型和一個字元串類型的值:
In [2]: add(1, 2)
Out[2]: 3
In [3]: add(1.2, 3.45)
Out[3]: 4.65
In [4]: add(5, 'hello')
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
/home/chiyu/<ipython-input-4-f2f9e8aa5eae> in <mole>()
----> 1 add(5, 'hello')
/home/chiyu/<ipython-input-1-02b3d3d6caec> in add(a, b)
1 def add(a, b):
----> 2 return a+b
TypeError: unsupported operand type(s) for +: 'int' and 'str'
於是,解析器無情的拋出了一個TypeError異常。
動態類型:在運行期間確定變數的類型,python確定一個變數的類型是在你第一次給他賦值的時候;
強類型:有強制的類型定義,你有一個整數,除非顯示的類型轉換,否則絕不能將它當作一個字元串(例如直接嘗試將一個整型和一個字元串做+運算);
因此,為了更加優雅的使用add函數,我們需要在執行+運算前,對a和b進行參數檢查。這時候裝飾器就顯得非常有用:
import logging
logging.basicConfig(level = logging.INFO)
def add(a, b):
return a + b
def checkParams(fn):
def wrapper(a, b):
if isinstance(a, (int, float)) and isinstance(b, (int, float)): #檢查參數a和b是否都為整型或浮點型
return fn(a, b) #是則調用fn(a, b)返回計算結果
#否則通過logging記錄錯誤信息,並友好退出
logging.warning("variable 'a' and 'b' cannot be added")
return
return wrapper #fn引用add,被封存在閉包的執行環境中返回
if __name__ == "__main__":
#將add函數對象傳入,fn指向add
#等號左側的add,指向checkParams的返回值wrapper
add = checkParams(add)
add(3, 'hello') #經過類型檢查,不會計算結果,而是記錄日誌並退出
注意checkParams函數:
首先看參數fn,當我們調用checkParams(add)的時候,它將成為函數對象add的一個本地(Local)引用;
在checkParams內部,我們定義了一個wrapper函數,添加了參數類型檢查的功能,然後調用了fn(a, b),根據LEGB法則,解釋器將搜索幾個作用域,並最終在(Enclosing層)checkParams函數的本地作用域中找到fn;
注意最後的return wrapper,這將創建一個閉包,fn變數(add函數對象的一個引用)將會封存在閉包的執行環境中,不會隨著checkParams的返回而被回收;
當調用add = checkParams(add)時,add指向了新的wrapper對象,它添加了參數檢查和記錄日誌的功能,同時又能夠通過封存的fn,繼續調用原始的add進行+運算。
因此調用add(3, 'hello')將不會返回計算結果,而是列印出日誌:
chiyu@chiyu-PC:~$ python func.py
WARNING:root:variable 'a' and 'b' cannot be added
有人覺得add = checkParams(add)這樣的寫法未免太過麻煩,於是python提供了一種更優雅的寫法,被稱為語法糖:
@checkParams
def add(a, b):
return a + b
這只是一種寫法上的優化,解釋器仍然會將它轉化為add = checkParams(add)來執行。
6. 回歸問題
def addspam(fn):
def new(*args):
print "spam,spam,spam"
return fn(*args)
return new
@addspam
def useful(a,b):
print a**2+b**2
首先看第二段代碼:
@addspam裝飾器,相當於執行了useful = addspam(useful)。在這里題主有一個理解誤區:傳遞給addspam的參數,是useful這個函數對象本身,而不是它的一個調用結果;
再回到addspam函數體:
return new 返回一個閉包,fn被封存在閉包的執行環境中,不會隨著addspam函數的返回被回收;
而fn此時是useful的一個引用,當執行return fn(*args)時,實際相當於執行了return useful(*args);
最後附上一張代碼執行過程中的引用關系圖,希望能幫助你理解:
『肆』 Python協程之asyncio
asyncio 是 Python 中的非同步IO庫,用來編寫並發協程,適用於IO阻塞且需要大量並發的場景,例如爬蟲、文件讀寫。
asyncio 在 Python3.4 被引入,經過幾個版本的迭代,特性、語法糖均有了不同程度的改進,這也使得不同版本的 Python 在 asyncio 的用法上各不相同,顯得有些雜亂,以前使用的時候也是本著能用就行的原則,在寫法上走了一些彎路,現在對 Python3.7+ 和 Python3.6 中 asyncio 的用法做一個梳理,以便以後能更好的使用。
協程,又稱微線程,它不被操作系統內核所管理,而完全是由程序控制,協程切換花銷小,因而有更高的性能。
協程可以比作子程序,不同的是,執行過程中協程可以掛起當前狀態,轉而執行其他協程,在適當的時候返回來接著執行,協程間的切換不需要涉及任何系統調用或任何阻塞調用,完全由協程調度器進行調度。
Python 中以 asyncio 為依賴,使用 async/await 語法進行協程的創建和使用,如下 async 語法創建一個協程函數:
在協程中除了普通函數的功能外最主要的作用就是:使用 await 語法等待另一個協程結束,這將掛起當前協程,直到另一個協程產生結果再繼續執行:
asyncio.sleep() 是 asyncio 包內置的協程函數,這里模擬耗時的IO操作,上面這個協程執行到這一句會掛起當前協程而去執行其他協程,直到sleep結束,當有多個協程任務時,這種切換會讓它們的IO操作並行處理。
注意,執行一個協程函數並不會真正的運行它,而是會返回一個協程對象,要使協程真正的運行,需要將它們加入到事件循環中運行,官方建議 asyncio 程序應當有一個主入口協程,用來管理所有其他的協程任務:
在 Python3.7+ 中,運行這個 asyncio 程序只需要一句: asyncio.run(main()) ,而在 Python3.6 中,需要手動獲取事件循環並加入協程任務:
事件循環就是一個循環隊列,對其中的協程進行調度執行,當把一個協程加入循環,這個協程創建的其他協程都會自動加入到當前事件循環中。
其實協程對象也不是直接運行,而是被封裝成一個個待執行的 Task ,大多數情況下 asyncio 會幫我們進行封裝,我們也可以提前自行封裝 Task 來獲得對協程更多的控制權,注意,封裝 Task 需要 當前線程有正在運行的事件循環 ,否則將引 RuntimeError,這也就是官方建議使用主入口協程的原因,如果在主入口協程之外創建任務就需要先手動獲取事件循環然後使用底層方法 loop.create_task() ,而在主入口協程之內是一定有正在運行的循環的。任務創建後便有了狀態,可以查看運行情況,查看結果,取消任務等:
asyncio.create_task() 是 Python3.7 加入的高層級API,在 Python3.6,需要使用低層級API asyncio.ensure_future() 來創建 Future,Future 也是一個管理協程運行狀態的對象,與 Task 沒有本質上的區別。
通常,一個含有一系列並發協程的程序寫法如下(Python3.7+):
並發運行多個協程任務的關鍵就是 asyncio.gather(*tasks) ,它接受多個協程任務並將它們加入到事件循環,所有任務都運行完成後會返回結果列表,這里我們也沒有手動封裝 Task,因為 gather 函數會自動封裝。
並發運行還有另一個方法 asyncio.wait(tasks) ,它們的區別是: