python進階 Python高級特性
1 內建函數(__xxx__)
背景:為什麼要有這種帶下劃線的內建函數,個人認為這種內建函數開放了很多Python的特殊用法,只要詳盡掌握,就會理解平時用到的數據結構是複寫了什麼方法,自己也可以寫出類似set,dqueue,dict,list的數據類型方法。
1.1 類似字典的操作方式,__getitem__,__setitem__,__delitem__
我們熟悉的字典是可以像如下方式操作賦值取值:
d = dict()
d["a"] = 1
d["b"] = 2
print d
del d["a"]
print d
其實像這種賦值方式就是複寫了__setitem__,__getitem__,__delitem__請見如下類
class TestDict(object):
def __getitem__(self,key):
return self.__dict__.get(key)
def __setitem__(self,key,value):
self.__dict__[key] = value
def __delitem__(self,key):
self.__dict__.pop(key)
td = TestDict()
td["a"] = 1
td["b"] = 2
print td["a"]
print td.__dict__
del td["a"]
print td.__dict__
為了更加靈活,如上代碼也可以在對象執行賦值方法(__init__)的時候聲明個變數d = dict(),然後對這個變數進行賦值和取值操作也可以模擬上面的操作。
1.2 __new__和__init__
__new__: 在類實例化的時候調用,用來創建實例,如果不返回實例那麼__init__將不會執行,第一個參數是class對象,在創建實例的時候需要有返回值
__init__: 在初始化實例的時候調用,比如說實例屬性賦值,第一個參數是實例對象,一般都重寫__init__方法,在執行的時候不需要返回值
class TestNew(object):
def __new__(cls, *args, **kwargs):
print "__new__ called."
return super(TestNew,cls).__new__(cls,*args,**kwargs)
def __init__(self):
print "__init__ called."
self.a = 1
tn = TestNew()
print tn.a
可以用__new__來實現單例模式
class Singleton(object):
def __new__(cls):
# 關鍵在於這,每一次實例化的時候,我們都只會返回這同一個instance對象
if not hasattr(cls, "instance"):
cls.instance = super(Singleton, cls).__new__(cls)
return cls.instance
obj1 = Singleton()
obj2 = Singleton()
obj1.attr1 = "value1"
print obj1.attr1, obj2.attr1
print obj1 is obj2
Tips: 單例模式有很多種實現方式,也可以通過類變數+靜態方法的方式實現。
可以通過重載__new__來實現很多創建實例時的功能。
1.3 __iter__,next(python2),__next__(python3)
__iter__: 複寫這個函數的對象是可迭代對象
next/__next__: 複寫這個函數的對象都是一個迭代器
class TestIterNext(object):
def __init__(self,data=1):
self.data = data
def next(self):
if self.data > 5:
raise StopIteration
else:
self.data+=1
return self.data
def __iter__(self):
print "iter"
return self
當for循環去迭代tin = TestIterNext()對象的時候第一步會去看__iter__是否返回一個生成器(generator),如果返回的是對象本身才會去執行next函數。
1.4 __call__
把一個類實例的對象當做函數一樣調用就是複寫了__call__方法,如下:
class TestCall(object):
def __call__(self):
print "call it"
tc = TestCall()
tc()
複寫__call__方法大多數用在裝飾器類中(第5章)和繼承Type類(第3章)的時候。
1.5 __repr__,__str__
__repr__和__str__沒有太大區別,在Python交互模式下才能發現。複寫改方法後輸出對象就是按照該方法里的內容進行輸出。在類里可以這樣用__repr__ = __str__
1.6 __all__
可用於模塊導入時限制,,當我們from module import *的時候這個__all__就起作用了,__all__=["bar","sar"] ,[]里定義函數或者變數類等,有些模塊內部一些函數不對外開放,此時把一些對外開放的函數變數放入到__all__里就可以了,這樣避免了一些多餘的導入。如果在__init__.py里定義則在導入模塊的時候只導入__all__里定義的各個文件,此時無法定義到文件里具體哪個類或方法,如果需要細化則需要在具體的類里寫入__all__。如果是from module import Test這種使用不受__all__限制
1.7 __setattr__,__getattr__,__delattr__
__setattr__: 對變數賦值時調用。
__getattr__:默認查找對象屬性是在一個字典里(__dict__),這裡沒有要查找的對象則去__getattr__方法里查找,如果我們複寫__getattr__則可以根據實際需求來返回值。
__delattr__:刪除屬性時調用。
class TestAttr(object):
def __init__(self):
self.name = "abc"
def __getattr__(self, item):
print "item:" + str(item)
print "getattr"
return 10
def __setattr__(self, *args, **kwargs):
print "set attr"
object.__setattr__(self,*args,**kwargs)
def __delattr__(self, *args, **kwargs):
print "delete attr"
object.__delattr__(self,*args, **kwargs)
ta = TestAttr()
print ta.__dict__
print ta.names
del ta.name
print ta.__dict__
1.8 __le__,__lt__,__ge__,__gt__,__ne__,__eq__
一個對象和另一個對象比較大小,返回的並不一定是True和False,返回值有可能是我們定義的任何值,這裡就是複寫上面這些方法。如下:
class TestCompare(object):
def __lt__(self, other):
return "aaa"
t = TestCompare()
print t<1
這裡列印出的就不是True和False,列印的是我們再複寫的方法里定義的"aaa",其實有一些常用的orm,比如說sqlalchemy里查詢條件可以這樣寫
g.pg_db.query(Company.id).filter(Company.level_id == level)
這裡的Company.level_id == level返回的就是篩選條件,因為對Company的level_id對象複寫了__eq__
1.9 __slots__
優點:
1,更快的屬性訪問速度
2,減少內存消耗
每個類里都維護一個字典__dict__,這個字典維護了對象的所有屬性,但如果成千上萬個對象則就會創建很多個__dict__來存放對象屬性,為了性能我們可以不用Python幫我們維護這個字典。此時我們在類里定義__slots__ = ["name","age"]的時候就表示禁用了__dict__,並限定name和age為類的屬性(類里只能有name和age屬性),這樣做的好處能大大節省內存開支,對象越多節省的就越多,大概能節省40%以上。
class TestSlots(object):
__slots__ = ["name","age"]
def __init__(self, name, age):
self.name = name
self.age = age
ts = TestSlots("a",1)
ts.name = 1
print ts.name
2.0 __metaclass__
在理解元類之前,你需要先掌握Python中的類。Python中類的概念借鑒於Smalltalk,這顯得有些奇特。在大多數編程語言中,類就是一組用來描述如何生成一個對象的代碼段。在Python中這一點仍然成立
元類是什麼?
但就元類本身而言,它們其實是很簡單的:
1) 攔截類的創建
2) 修改類
3) 返回修改之後的類
但是,Python中的類還遠不止如此。類同樣也是一種對象。是的,沒錯,就是對象。只要你使用關鍵字class,Python解釋器在執行的時候就會創建一個對象。下面的代碼段:
class ObjectCreator(object):
pass
將在內存中創建一個對象,名字就是ObjectCreator。這個對象(類)自身擁有創建對象(類實例)的能力,而這就是為什麼它是一個類的原因。但是,它的本質仍然是一個對象,於是乎你可以對它做如下的操作:
1) 你可以將它賦值給一個變數
2) 你可以拷貝它
3) 你可以為它增加屬性
4) 你可以將它作為函數參數進行傳遞
下面是示例:
print ObjectCreator
# 你可以列印一個類,因為它其實也是一個對象
def echo(o):
print o
echo(ObjectCreator)
# 你可以將類做為參數傳給函數
print hasattr(ObjectCreator, "new_attribute")
ObjectCreator.new_attribute = "foo"
# 你可以為類增加屬性
print hasattr(ObjectCreator, "new_attribute")
print ObjectCreator.new_attribute
ObjectCreatorMirror = ObjectCreator
# 你可以將類賦值給一個變數
print ObjectCreatorMirror()
動態地創建類
因為類也是對象,你可以在運行時動態的創建它們,就像其他任何對象一樣。首先,你可以在函數中創建類,使用class關鍵字即可。
def choose_class(name):
if name == "foo":
class Foo(object):
pass
return Foo
# 返回的是類,不是類的實例
else:
class Bar(object):
pass
return Bar
MyClass = choose_class("foo")
print MyClass
# 函數返回的是類,不是類的實例>>> print MyClass()
# 你可以通過這個類創建類實例,也就是對象
# <__main__.foo object="" at="" 0x89c6d4c="">
但這還不夠動態,因為你仍然需要自己編寫整個類的代碼。由於類也是對象,所以它們必須是通過什麼東西來生成的才對。當你使用class關鍵字時,Python解釋器自動創建這個對象。但就和Python中的大多數事情一樣,Python仍然提供給你手動處理的方法。還記得內建函數type嗎?這個古老但強大的函數能夠讓你知道一個對象的類型是什麼,就像這樣:
print type(1)
print type("1")
print type(ObjectCreator)
print type(ObjectCreator())
這裡,type有一種完全不同的能力,它也能動態的創建類。type可以接受一個類的描述作為參數,然後返回一個類。(我知道,根據傳入參數的不同,同一個函數擁有兩種完全不同的用法是一件很傻的事情,但這在Python中是為了保持向後兼容性)
type可以像這樣工作:
type(類名, 父類的元組(針對繼承的情況,可以為空),包含屬性的字典(名稱和值))
比如下面的代碼:
class MyShinyClass(object):
pass
MyShinyClass = type("MyShinyClass", (), {})
# 返回一個類對象
print MyShinyClass
print MyShinyClass()
# 創建一個該類的實例
# <__main__.myshinyclass object="" at="" 0x8997cec="">
你會發現我們使用「MyShinyClass」作為類名,並且也可以把它當做一個變數來作為類的引用。類和變數是不同的,這裡沒有任何理由把事情弄的複雜。
type 接受一個字典來為類定義屬性,因此
class Foo(object):
bar = True
# 可以翻譯為:
Foo = type("Foo", (), {"bar":True})
為類增加方法。只需要定義一個有著恰當簽名的函數並將其作為屬性賦值就可以了。
def echo_bar(self):
print self.bar
FooChild = type("FooChild", (Foo,), {"echo_bar": echo_bar})
hasattr(Foo, "echo_bar")
False
hasattr(FooChild, "echo_bar")
True
my_foo = FooChild()
my_foo.echo_bar()
True
你可以看到,在Python中,類也是對象,你可以動態的創建類。這就是當你使用關鍵字class時Python在幕後做的事情,而這就是通過元類來實現的。
到底什麼是元類
元類就是用來創建類的「東西」。你創建類就是為了創建類的實例對象,不是嗎?但是我們已經學習到了Python中的類也是對象。好吧,元類就是用來創建這些類(對象)的,元類就是類的類,你可以這樣理解為:
MyClass = MetaClass()
MyObject = MyClass()
你已經看到了type可以讓你像這樣做:
MyClass = type("MyClass", (), {})
這是因為函數type實際上是一個元類。type就是Python在背後用來創建所有類的元類。現在你想知道那為什麼type會全部採用小寫形式而不是Type呢?好吧,我猜這是為了和str保持一致性,str是用來創建字元串對象的類,而int是用來創建整數對象的類。type就是創建類對象的類。你可以通過檢查__class__屬性來看到這一點。Python中所有的東西,注意,我是指所有的東西——都是對象。這包括整數、字元串、函數以及類。它們全部都是對象,而且它們都是從一個類創建而來。
>>> age = 35
>>> age.__class__
>>> name = "bob"
>>> name.__class__
>>> def foo(): pass
>>>foo.__class__
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
現在,對於任何一個__class__的__class__屬性又是什麼呢?
>>> a.__class__.__class__
>>> age.__class__.__class__
>>> foo.__class__.__class__
>>> b.__class__.__class__
因此,元類就是創建類這種對象的東西。如果你喜歡的話,可以把元類稱為「類工廠」(不要和工廠類搞混了:D) type就是Python的內建元類,當然了,你也可以創建自己的元類。
metaclass__屬性
你可以在寫一個類的時候為其添加__metaclass__屬性。
class Foo(object):
__metaclass__ = something…
[…]
如果你這麼做了,Python就會用元類來創建類Foo。小心點,這裡面有些技巧。你首先寫下class Foo(object),但是類對象Foo還沒有在內存中創建。Python會在類的定義中尋找__metaclass__屬性,如果找到了,Python就會用它來創建類Foo,如果沒有找到,就會用內建的type來創建這個類。把下面這段話反覆讀幾次。當你寫如下代碼時 :
class Foo(Bar):
pass
Python做了如下的操作:
Foo中有__metaclass__這個屬性嗎?如果是,Python會在內存中通過__metaclass__創建一個名字為Foo的類對象(我說的是類對象,請緊跟我的思路)。如果Python沒有找到__metaclass__,它會繼續在Bar(父類)中尋找__metaclass__屬性,並嘗試做和前面同樣的操作。如果Python在任何父類中都找不到__metaclass__,它就會在模塊層次中去尋找__metaclass__,並嘗試做同樣的操作。如果還是找不到__metaclass__,Python就會用內置的type來創建這個類對象。
現在的問題就是,你可以在__metaclass__中放置些什麼代碼呢?答案就是:可以創建一個類的東西。那麼什麼可以用來創建一個類呢?type,或者任何使用到type或者子類化type的東東都可以。
自定義元類
元類的主要目的就是為了當創建類時能夠自動地改變類。通常,你會為API做這樣的事情,你希望可以創建符合當前上下文的類。假想一個很傻的例子,你決定在你的模塊里所有的類的屬性都應該是大寫形式。有好幾種方法可以辦到,但其中一種就是通過在模塊級別設定__metaclass__。採用這種方法,這個模塊中的所有類都會通過這個元類來創建,我們只需要告訴元類把所有的屬性都改成大寫形式就萬事大吉了。
幸運的是,__metaclass__實際上可以被任意調用,它並不需要是一個正式的類(我知道,某些名字裡帶有『class』的東西並不需要是一個class,畫畫圖理解下,這很有幫助)。所以,我們這裡就先以一個簡單的函數作為例子開始。
# 元類會自動將你通常傳給『type』的參數作為自己的參數傳入
def upper_attr(future_class_name, future_class_parents, future_class_attr):
"""返回一個類對象,將屬性都轉為大寫形式"""
# 選擇所有不以"__"開頭的屬性
attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith("__"))
# 將它們轉為大寫形式
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
# 通過"type"來做類對象的創建
return type(future_class_name, future_class_parents, uppercase_attr)
__metaclass__ = upper_attr
# 這會作用到這個模塊中的所有類
class Foo(object):
# 我們也可以只在這裡定義__metaclass__,這樣就只會作用於這個類中
bar = "bip"
print hasattr(Foo, "bar")
# 輸出: False
print hasattr(Foo, "BAR")
# 輸出:True
f = Foo()
print f.BAR
# 輸出:"bip"
現在讓我們再做一次,這一次用一個真正的class來當做元類。
# 請記住,"type"實際上是一個類,就像"str"和"int"一樣
# 所以,你可以從type繼承
class UpperAttrMetaClass(type):
# __new__ 是在__init__之前被調用的特殊方法
# __new__是用來創建對象並返回之的方法
# 而__init__只是用來將傳入的參數初始化給對象
# 你很少用到__new__,除非你希望能夠控制對象的創建
# 這裡,創建的對象是類,我們希望能夠自定義它,所以我們這裡改寫__new__
# 如果你希望的話,你也可以在__init__中做些事情
# 還有一些高級的用法會涉及到改寫__call__特殊方法,但是我們這裡不用
def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr):
attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith("__"))
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
return type(future_class_name, future_class_parents, uppercase_attr)
但是,這種方式其實不是OOP。我們直接調用了type,而且我們沒有改寫父類的__new__方法。現在讓我們這樣去處理:
class UpperAttrMetaclass(type):
def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr):
attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith("__"))
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
# 復用type.__new__方法
# 這就是基本的OOP編程,沒什麼魔法
return type.__new__(upperattr_metaclass, future_class_name, future_class_parents, uppercase_attr)
你可能已經注意到了有個額外的參數upperattr_metaclass,這並沒有什麼特別的。類方法的第一個參數總是表示當前的實例,就像在普通的類方法中的self參數一樣。當然了,為了清晰起見,這裡的名字我起的比較長。但是就像self一樣,所有的參數都有它們的傳統名稱。因此,在真實的產品代碼中一個元類應該是像這樣的:class UpperAttrMetaclass(type):
def __new__(cls, name, bases, dct):
attrs = ((name, value) for name, value in dct.items() if not name.startswith("__")
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
return type.__new__(cls, name, bases, uppercase_attr)
如果使用super方法的話,我們還可以使它變得更清晰一些,這會緩解繼承(是的,你可以擁有元類,從元類繼承,從type繼承)
class UpperAttrMetaclass(type):
def __new__(cls, name, bases, dct):
attrs = ((name, value) for name, value in dct.items() if not name.startswith("__"))
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
return super(UpperAttrMetaclass, cls).__new__(cls, name, bases, uppercase_attr)
用元類實現單例模式
class Singleone(type):
def __init__(cls, name,bases,dct):
super(Singleone, cls).__init__(name,bases,dct)
cls._instance = None
def __call__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super(Singleone,cls).__call__(*args,**kwargs)
return cls._instance
class One(object):
__metaclass__ = Singleone
def __init__(self,*args, **kwargs):
self.name = args[0]
o = One("a")
print o.name
b = One("b")
print b.name
執行步驟從上到下當執行用Singleone創建One類,One = Singleone(...),當實例化Singleone的時候執行其內部的__init__()方法,代碼
繼續解析到o = One("a")的時候示例化了One類,相當於Singleone()(),此時執行Singleone裡面的__call__方法,在call方法里需要有
返回值。也可以在__call__里主動實例化(__new__)One並調用裡面的__init__方法。
就是這樣,除此之外,關於元類真的沒有別的可說的了。使用到元類的代碼比較複雜,這背後的原因倒並不是因為元類本身,而是因為你通常會使用元類去做一些晦澀的事情,依賴於自省,控制繼承等等。確實,用元類來搞些「黑暗魔法」是特別有用的,因而會搞出些複雜的東西來。
為什麼要用metaclass類而不是函數?
由於__metaclass__可以接受任何可調用的對象,那為何還要使用類呢,因為很顯然使用類會更加複雜啊?這裡有好幾個原因:
1) 意圖會更加清晰。當你讀到UpperAttrMetaclass(type)時,你知道接下來要發生什麼。
2) 你可以使用OOP編程。元類可以從元類中繼承而來,改寫父類的方法。元類甚至還可以使用元類。
3) 你可以把代碼組織的更好。當你使用元類的時候肯定不會是像我上面舉的這種簡單場景,通常都是針對比較複雜的問題。將多個方法歸總到一個類中會很有幫助,也會使得代碼更容易閱讀。
4) 你可以使用__new__, __init__以及__call__這樣的特殊方法。它們能幫你處理不同的任務。就算通常你可以把所有的東西都在__new__里處理掉,有些人還是覺得用__init__更舒服些。
5) 哇哦,這東西的名字是metaclass,肯定非善類,我要小心!
「元類就是深度的魔法,99%的用戶應該根本不必為此操心。如果你想搞清楚究竟是否需要用到元類,那麼你就不需要它。那些實際用到元類的人都非常清楚地知道他們需要做什麼,而且根本不需要解釋為什麼要用元類。」 —— Python界的領袖 Tim Peters
元類的主要用途是創建API。一個典型的例子是Django ORM。它允許你像這樣定義:
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()
這並不會返回一個IntegerField對象,而是會返回一個int,甚至可以直接從資料庫中取出數據。這是有可能的,因為models.Model定義了__metaclass__, 並且使用了一些魔法能夠將你剛剛定義的簡單的Person類轉變成對資料庫的一個複雜hook。Django框架將這些看起來很複雜的東西通過暴露出一個簡單的使用元類的API將其化簡,通過這個API重新創建代碼,在背後完成真正的工作。
結語
首先,你知道了類其實是能夠創建出類實例的對象。好吧,事實上,類本身也是實例,當然,它們是元類的實例。
Python中的一切都是對象,它們要麼是類的實例,要麼是元類的實例,除了type。type實際上是它自己的元類,在純Python環境中這可不是你能夠做到的,這是通過在實現層面耍一些小手段做到的。其次,元類是很複雜的。對於非常簡單的類,你可能不希望通過使用元類來對類做修改。你可以通過其他兩種技術來修改類:
1) Monkey patching
2) class decorators
當你需要動態修改類時,99%的時間裡你最好使用上面這兩種技術。當然了,其實在99%的時間裡你根本就不需要動態修改類
2 私有變數(__xx)
python類里的私有變數就是前面加兩個下劃線這樣用,但是這只是在使用上的私有變數,不像Java那種只能通過內部函數修改,python的私有變數可以通過 對象._類名__參數來從外部引用。
3 type
請查看 1.20
4 推導式
推導式又稱解析式,有三種
1,列表推導式
multiples = [ i for i in range(30) if i % 3 is 0 ]
2,字典推導式
mcase = {"a":10,"b":2,"c":3}
{k:v for k,v in mcase.items()}
3,集合推導式
其實大括弧里擴著的就是集合(set),例:
{"a","b",1}
squared = {x*2 for x in [1,2,3]}
5 裝飾器(@decorate)
裝飾器是python特色代表之一,非常好用,先介紹一下如何用裝飾器。
函數是可以返回函數的
def hi(name="yasoob"):
def greet():
return "in greet() function"
def welcome():
return "in welcome() function"
if name == "yasoob":
return greet
else:
return welcome
a = hi()
print a
<function greet at 0x1e08410>
在if/else裡面我們返回greet和welcome,而不是greet()和welcome(),為什麼? 是因為當把小括弧放到後面的時候這個函數就會執行,如果不放小括弧這個函數就可以到處傳遞,並且可以賦給變數而不去執行。
將函數作為參數傳遞給另一個函數
def hi():
return "hi yasoob"
def doSomethingBefore(func):
print "I am doing something before"
print (func())
doSomethingBefore(hi)
輸出:
I am doing something before
hi yasoob
裝飾器就是在一個函數前後執行代碼
上個例子里我們相當於創建了裝飾器,現在我們稍加修改並編寫一個更有用的程序。
def a_new_decorator(a_func):
def wrapTheFunction():
print "I am doing some before"
a_func()
print "I am doing some after"
return wrapTheFunction
def a_function_requiring_decoration():
print "I am in the function"
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
a_function_requiring_decoration()
明白了嗎? 這正是python裝飾器做的事情,它們封裝一個函數,並且用這樣或者那樣的方式修改它的行為,現在你可能疑惑,我們的代碼里並沒有使用@符號?那只是一個簡短的方式來生成一個被裝飾的函數。請見如下例子
@a_new_decorator
def a_function_requiring_decoration():
print "I am in the function"
a_function_requiring_decoration()
現在對裝飾器的理解差不多了吧!但如果我們運行如下代碼會存在一個問題:
print(a_function_requiring_decoration.__name__)
輸出:wrapTheFunction
這並不是我們想要看到的,我們想看到的是a_function_requiring_decoration,這裡的函數被wrapTheFunction替代了,它重寫了我們函數的名字和注釋文檔(docstring)。幸運的是python提供給我們一個簡單的函數來解決這個問題
from functools import wraps
def a_new_decorator(a_func):
@wraps(a_func)
def wrapTheFunction():
print "I am doing some before"
a_func()
print "I am doing some after"
return wrapTheFunction
下面我們看一下藍本規範:
from functools import wraps
def decorator_name(f):
@wraps(f)
def decorated(*args, **kwargs):
if not can_run:
return "Function will not run"
return f(*args, **kwargs)
return decorated
@decorator_name
def func():
return "Function is running"
can_run = True
print(func())
can_run = False
print(func())
注意:@wraps接受一個函數來進行裝飾,並加入了複製函數名稱,注釋文檔,參數列表等等的功能。這可以讓我們在裝飾器裡面訪問在裝飾器之前的函數的屬性。
裝飾器的使用場景:
授權(Authorization)
裝飾器能有助於檢查某個人是否被授權去使用一個web應用的端點(endpoint)。它們被大量使用於Flask和Django框架中。這裡是一個例子來使用基於裝飾器的授權:
from functools import wraps
def requires_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not check_auth(auth.username,auth.password):
authenticate()
return f(*args,**kwargs)
return decorated
日誌(Logging)
from functools import wraps
def logit(func):
@wraps(func)
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
@logit
def addition_func(x):
return x+x
addition_func(2)
在函數中嵌入裝飾器
我們回到日誌的例子,並創建一個包裹函數,能讓我們指定一個用於輸出的日誌文件。
from functools import wraps
def logit(logfile="out.log"):
def logging_decorator(func):
@wraps(func)
def wrapped_function(*args, **kwargs):
log_string = func.__name__ + " was called"
print(log_string)
with open(logfile, "wb") as f:
f.write(log_string + "
")
return func(*args, **kwargs)
return wrapped_function
return logging_decorator
@logit
def myfunc():
pass
裝飾器類
現在我們有了能用於正式環境的logit裝飾器,但當我們的應用的某些部分還比較脆弱時,異常也許是需要更緊急關注的事情。比方說有時候你只想打日誌到一個文件,而有時你想把引起你注意的問題發送到一個email,同事也保留日誌,留個記錄。這是一個使用繼承的場景,但目前為止我們只看到過用來構建裝飾器的函數。
The lucky is! 類也可以構建裝飾器,現在我們用類重新構建logit
class logit(object):
def __init__(self, logfile="out.log"):
self.logfile = logfile
def __call__(self, func):
@wraps(func)
def wrapped_function(*args, **kwargs):
log_string = func.__name__ + " was called"
print(log_string)
with open(self.logfile, "wb") as f:
f.write(log_string + "
")
self.notify()
return func(*args,**kwargs)
return wrapped_function
def notify(self):
# 可以做一些其它行為
pass
@logit()
def my_func():
pass
現在我們給logit創建子類,來添加email等功能
class email_logit(logit):
def __init__(self, email="admin@123.com", *args, **kwargs):
self.email = email
super(email_logit, self).__init__(*args, **kwargs)
def notify(self):
# 發送一封email
pass
從現在起,@email_logit會在logit基礎上多發送一封郵件。
注意:從以上方法中我們就可以發現__call__這種用法的好處,它在裝飾器類和新寫元類的時候起到了很大作用。
6 容器
python附帶一個模塊,它包含許多容器數據類型,名字叫做collections。我們將討論它的作用和用法。
defaultdict:
defaultdict不需要檢查key是否存在,我們一般這樣用
from collections import defaultdict
ddl = defaultdict(list)
ddl["x"].append(1)
print ddl
ddd = defaultdict(dict)
ddd["x"]["a"] = 1
print ddd
defaultdict(<type "list">, {"x": [1]})
defaultdict(<type "dict">, {"x": {"a": 1}})
Counter
counter是一個計數器,幫助我們對某項數據做統計。
from collections import Counter
c = Counter("aaaabbbc")
print c
d = {"a":1,"b":2,"c":3}
c = Counter( k for k,v in d.items())
print c
還可以用counter來統計一個文件
此處沒有弄明白,需要後期補上
deque
deque提供了一個雙向隊列,可以從頭尾兩端添加或刪除元素,類似於list
from collections import deque
dl = deque(range(5))
print dl
dl.popleft()
print dl
dl.pop()
print dl
dl.extendleft([-10])
print dl
dl.extend([10])
print dl
輸出:
deque([0, 1, 2, 3, 4])
deque([1, 2, 3, 4])
deque([1, 2, 3])
deque([-10, 1, 2, 3])
deque([-10, 1, 2, 3, 10])
deque也可以限制列表的大小,先進先出
dl = deque(maxlen=2)
dl.append(1)
dl.append(2)
print dl
dl.append(3)
print dl
輸出
deque([1, 2], maxlen=2)
deque([2, 3], maxlen=2)
namedtuple(命名元組)
正常訪問一個元組和訪問list一樣,都是通過下標來訪問,命名元組可以提供類似於字典的訪問方式,和tuple一樣不可變。
from collections import namedtuple
Animal = namedtuple("Animal","name age type")
perry = Animal(name="perry",age=10,type="cat")
print perry
print perry.name
一個命名元組需要兩個參數,他們是元組名稱和欄位名稱。在上面的例子中,我們的元組名稱是Animal,欄位名稱是"name,age,type"。
namedtuple讓你的元組變得自文檔了。不必使用證書索引來訪問一個命名元組,這讓代碼更易於維護。
而且,namedtuple的每個實例沒有對象字典(__dict__),所以它們更輕量,與普通的元組相比,並不需要更多的內存,這使他們比字典更快。
然而,要記住它仍然是一個元組,屬性在namedtuple中是不可變的,所以下面的代碼不行:
perry.age = 10
命名元組(namedtuple)向後兼容元組,所以用下標訪問也是可以的
print perry[0]
命名元組支持多態,可以轉換為字典
print (perry._asdict())
7 上下文
上下文管理器允許你在需要的時候,精確的分配和釋放資源。
使用上下文管理器最廣泛的案例就是with語句。想像一下你有個需要結對執行的操作,然後還要在中間放置一段代碼。
上下文管理器就是專門讓你做這種事情的,舉個例子:
with open("some_file", "wb") as f:
f.write("fuck u!")
上面這段代碼打開了一個文件,往裡面寫入了一些數據,然後關閉該文件。如果在往文件里寫數據的時候發生異常,它也會嘗試去關閉文件。上面的代碼與下面的是等價的。
file = open("some_file", "wb")
try:
file.write("funck u !")
finally:
file.close()
當與第一個例子比較的時候,有很多樣板代碼(boilerplate code)被消掉了。這就是with語句的主要優勢,它確保我們的文件會被關閉,而不用關注嵌套代碼如何退出。
上下文的又一用例就是資源的加鎖與解鎖,以及關閉已經打開的文件(就像上面的例子)
下面讓我們自己實現一下上下文管理器
一個上下文管理器的類,最起碼要定義__enter__,__exit__方法。
class File(object):
def __init__(self,file_name, method):
self.file_obj = open(file_name, method)
def __enter__(self):
return self.file_obj
def __exit__(self, exc_type, exc_val, exc_tb):
self.file_obj.close()
with File("demo.txt", "wb") as f:
f.write("Hello")
我們的__exit__函數接受三個參數。這些參數對於每個上下文管理器類中的__exit__方法都是必須得,我們來談談在底層都發生了什麼。
1,with語句先暫存了File類的__exit__方法
2,然後它調用File類的__enter__方法
3,__enter__方法返回打開文件對象
4,打開的文件對象被傳遞給 f
5,使用write來寫文件
6,調用之前暫存的__exit__
7,__exit__關閉了文件
處理異常
我們目前還沒有談到__exit__方法的這三個參數,exc_type,exc_val,exc_tb,在with以下部分如果發生異常,python會將異常的type,value和traceback傳遞給__exit__方法。
它讓__exit__方法來決定如何關閉文件以及是否需要其他步驟,如果沒有異常這三個參數的值為None
class File(object):
def __init__(self,file_name, method):
self.file_obj = open(file_name, method)
def __enter__(self):
return self.file_obj
def __exit__(self, exc_type, exc_val, exc_tb):
print exc_val
self.file_obj.close()
return True
with File("demo.txt", "wb") as f:
f.write_function("Hello")
當發生異常的時候with語句會採取如下步驟:
1,它把異常的type,value,traceback傳遞給__exit__方法
2,它讓__exit__方法來處理異常
3,如果__exit__返回的是True,那麼這個異常就優雅的處理了
4,如果__exit__返回的是除了True以外的其它值,那麼這個異常會被拋出
基於裝飾器和生成器來實現上下文管理
python有個contextlib專門用於這個,我們可以使用一個生成器函數來實現一個上下文管理器,而不是使用一個類。
from contextlib import contextmanager
@contextmanager
def open_file(name):
f = open(name,"w")
yield f
f.close()
with open_file("aaa.log") as of:
of.write("fuck u!")
這塊我個人用得比較少,因為內部也是通過__enter__和__exit__來實現的。
8 繼承
9 生成器,迭代器,可迭代對象
10 自省
11 閉包
12 反射
13 推導式
14 C擴展
15 函數緩存
16 Flask上下文
18 多線程,多進程,協程
20 socket和socketserver
21 yield
22 進程間通訊
23 進程內存共享
24 Python實現冒泡法排序
25 對象
26 繼承
io多路復用
27 動態載入
Python 模塊動態載入技術
C 語言中可以使用 dlopen,dlsym 和 dlclose 讓程序在運行過程中按需載入和卸載動態庫。Python 也支持這種方式,使用模塊動態載入技術,我們可以把程序的配置文件寫成可運行的 python 程序,在程序運行過程中可以動態去更新配置。當然也可以將 python 腳本作為業務邏輯載入到正在運行的主程序中,而不用重啟服務。
作者在個人項目 pyed 中使用了這種技術,本文對個人研究和使用這種技術的一個總結。如有問題,歡迎大家討論。
在 Python 中執行代碼
python 提供了 exec 用於在程序中執行一段 python 代碼,官方說明:
exec_stmt ::= "exec" or_expr ["in" expression ["," expression]]
該語句可以使用 exec() 函數進行替代。來看一個簡單的例子:
- >>> exec "print("Hello World")"
- Hello World
- >>>
這種使用方式,在程序中其實作用不大,我們使用動態載入,一般是希望將一個模塊中的某個變數或函數按需引入到正在執行的程序中,而不僅僅是去執行一下,列印一句 「Hello World」,exec 中的 in 解決了這個問題。
in 的作用是將執行代碼中的變數,函數或者類放入到一個字典中,這裡再來看一個例子:
- >>> exec "a=100" in tmp
- >>> print tmp
- {"__builtins__": ..., "a": 100}
- >>>
上面的語句等效於:
exec("a=100", tmp)
執行結果中,tmp 除了我們給定的一個 a 變數,賦值為 100 外,還有一個 __builtins__ 成員,內容很多,這裡使用 … 替代了實際的內容。如果要訪問 a 的值,只需要像操作字典一樣就行了:
- >>> print tmp["a"]
- 100
- >>>
簡單的模塊載入
簡單模塊載入庫
按照上面的思路,我們構造了一個模塊
- import traceback
- class loader(object):
- def __init__(self):
- pass
- def load(self, path):
- try:
- tmp = {}
- exec open(path).read() in tmp
- return tmp
- except:
- print("Load module [path %s] error: %s"
- % (path, traceback.format_exc()))
- return None
載入配置文件
有一個配置文件 test.conf:
- $ cat test.conf
- addr="127.0.0.1"
- port=2539
- $
使用以下代碼載入它:
- load = loader()
- m = load.load("test.conf")
- addr = m["addr"]
- port = m["port"]
- print addr + ":" + str(port)
執行結果:
- $ python loader.py
- 127.0.0.1:2539
- $
載入和執行函數
如果要執行載入模塊(test.py)中的函數:
- def greeting(name):
- print "Hello", name
使用以下代碼載入它:
- load = loader()
- m = load.load("test.py")
- func = m["greeting"]
- func("World")
執行結果:
- $ python loader.py
- Hello World
- $
載入和使用模塊中的類
按照上面的思路,如果載入的模塊是一個類,其實調用方式也是大同小異的。
修改 test.py
- class test(object):
- def __init__(self):
- pass
- def greeting(self, name):
- print "Hello", name
使用以下代碼載入它:
- load = loader()
- m = load.load("test.py")
- c = m["test"]
- print c
- print dir(c)
- t = c()
- t.greeting("World")
執行結果:
- $ python loader.py
- <class "test">
- ["__class__", "__delattr__", "__dict__", "__doc__", "__format__", "__getattribute__", "__hash__", "__init__", "__module__", "__new__", "__reduce__", "__reduce_ex__", "__repr__", "__setattr__", "__sizeof__", "__str__", "__subclasshook__", "__weakref__", "greeting"]
- Hello World
- $
從上面可以看到 m[「test」] 是一個 class 類型,我們可以使用它創建類的實例,並調用實例方法
載入的模塊引入了其它模塊
如果在載入的模塊中導入了其它模塊,調用方法也是不變的。我們引入一個 test1,繼承上例中的 test:
- from test import test
- class test1(test):
- def __init__(self):
- test.__init__(self)
使用以下代碼載入它:
- load = loader()
- m = load.load("subtest.py")
- c = m["test1"]
- print c
- print dir(c)
- t = c()
- t.greeting("World")
執行結果:
- $ python loader.py
- <class "test1">
- ["__class__", "__delattr__", "__dict__", "__doc__", "__format__", "__getattribute__", "__hash__", "__init__", "__module__", "__new__", "__reduce__", "__reduce_ex__", "__repr__", "__setattr__", "__sizeof__", "__str__", "__subclasshook__", "__weakref__", "greeting"]
- Hello World
- $
改進模塊載入
上一節介紹了使用 exec … in … 的方式動態去載入模塊。完成後可以直接使用返回的字典,訪問模塊中的變數,函數和類。但是從習慣上,我們更習慣使用模塊去調用模塊中的變數,函數和類,按此思路,我們對前面的模塊載入器進行修改。
新的模塊載入器
- import traceback, types
- class loader(object):
- def __init__(self):
- pass
- def load(self, name, path):
- try:
- m = types.ModuleType(name)
- exec open(path).read() in m.__dict__
- return m
- except:
- print("Load module [path %s] error: %s"
- % (path, traceback.format_exc()))
- return None
這裡使用 types.ModuleType 來構造一個模塊 m,將 exec 生成的字典放入到 m.__dict__。這樣就生成了一個簡單的模塊
使用新的模塊載入器
待載入的模塊:
- def test():
- s = 0
- for i in range(1000000):
- s += i
- print s
執行邏輯:
- load = loader()
- m = load.load("test", "test.py")
- print m
- print m.__dict__
- m.test()
執行結果:
- $ python loader.py
- <module "test" (built-in)>
- {"__builtins__": ..., "__name__": "test", "test": <function test at 0x1007f7398>, "__doc__": None}
- 499999500000
- $
從執行結果,我們可以看到使用新的模塊載入器,我們得到的是一個 module 類型的實例,其 __dict__ 中包含了 test 函數,我們可以直接使用 m.test() 調用該函數
TAG:程序員小新人學習 |