新聞中心
本篇內(nèi)容主要講解“在Python中如何實現(xiàn)單例模式”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“在Python中如何實現(xiàn)單例模式”吧!
方法一:使用裝飾器實現(xiàn)單例模式。
from functools
import wraps
def singleton(cls):
"""單例類裝飾器"""
instances = {}
@wraps(cls)
def wrapper(*args, **kwargs):
if cls
not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return wrapper
@singleton
class President:
pass
擴展:裝飾器是Python中非常有特色的語法,用一個函數(shù)去裝飾另一個函數(shù)或類,為其添加額外的能力。通常通過裝飾來實現(xiàn)的功能都屬橫切關注功能,也就是跟正常的業(yè)務邏輯沒有必然聯(lián)系,可以動態(tài)添加或移除的功能。裝飾器可以為代碼提供緩存、代理、上下文環(huán)境等服務,它是對設計模式中代理模式的踐行。在寫裝飾器的時候,帶裝飾功能的函數(shù)(上面代碼中的wrapper函數(shù))通常都會用functools模塊中的wraps再加以裝飾,這個裝飾器最重要的作用是給被裝飾的類或函數(shù)動態(tài)添加一個__wrapped__屬性,這個屬性會將被裝飾之前的類或函數(shù)保留下來,這樣在我們不需要裝飾功能的時候,可以通過它來取消裝飾器,例如可以使用President = President.__wrapped__來取消對President類做的單例處理。需要提醒大家的是:上面的單例并不是線程安全的,如果要做到線程安全,需要對創(chuàng)建對象的代碼進行加鎖的處理。在Python中可以使用threading模塊的RLock對象來提供鎖,可以使用鎖對象的acquire和release方法來實現(xiàn)加鎖和解鎖的操作。當然,更為簡便的做法是使用鎖對象的with上下文語法來進行隱式的加鎖和解鎖操作。
方法二:使用元類實現(xiàn)單例模式。
class SingletonMeta(type):
"""自定義單例元類"""
def __init__(cls, *args, **kwargs):
cls.__instance =
None
super().__init__(*args, **kwargs)
def __call__(cls, *args, **kwargs):
if cls.__instance
is None:
cls.__instance = super().__call__(*args, **kwargs)
return cls.__instance
class President(metaclass=SingletonMeta):
pass
擴展:Python是面向對象的編程語言,在面向對象的世界中,一切皆為對象。對象是通過類來創(chuàng)建的,而類本身也是對象,類這樣的對象是通過元類來創(chuàng)建的。我們在定義類時,如果沒有給一個類指定父類,那么默認的父類是object,如果沒有給一個類指定元類,那么默認的元類是type。通過自定義的元類,我們可以改變一個類默認的行為,就如同上面的代碼中,我們通過元類的__call__魔術方法,改變了President類的構造器那樣。
關于單例模式,在面試中還有可能被問到它的應用場景。通常一個對象的狀態(tài)是被其他對象共享的,就可以將其設計為單例,例如項目中使用的數(shù)據(jù)庫連接池對象和配置對象通常都是單例,這樣才能保證所有地方獲取到的數(shù)據(jù)庫連接和配置信息是完全一致的;而且由于對象只有唯一的實例,因此從根本上避免了重復創(chuàng)建對象造成的時間和空間上的開銷,也避免了對資源的多重占用。再舉個例子,項目中的日志操作通常也會使用單例模式,這是因為共享的日志文件一直處于打開狀態(tài),只能有一個實例去操作它,否則在寫入日志的時候會產(chǎn)生混亂。
題目002:不使用中間變量,交換兩個變量a和b的值。
點評:典型的送人頭的題目,在其他編程語言中不使用中間變量交換兩個變量的值可以使用異或運算,Python中還可以通過內(nèi)置的字節(jié)碼指令直接交換兩個變量的值。
方法一:
a = a ^ b
b = a ^ b
a = a ^ b
方法二:
a, b = b, a
擴展:需要注意,a, b = b, a這種做法其實并不是元組解包,雖然很多人都這樣認為。Python字節(jié)碼指令中有ROT_TWO指令來支持這個操作,類似的還有ROT_THREE,對于3個以上的元素,如a, b, c, d = b, c, d, a,才會用到創(chuàng)建元組和元組解包。想知道你的代碼對應的字節(jié)碼指令,可以使用Python標準庫中dis模塊的dis函數(shù)來反匯編你的Python代碼。
題目003:寫一個刪除列表中重復元素的函數(shù),要求去重后元素相對位置保持不變。
點評:這個題目在初中級Python崗位面試的時候經(jīng)常出現(xiàn),題目源于《Python Cookbook》這本書第一章的第10個問題,有很多面試題其實都是這本書上的原題,所以建議大家有時間的話好好研讀一下這本書。
def
dedup(items):
no_dup_items = []
seen =
set()
for item
in items:
if item not
in seen:
no_dup_items.append(item)
seen.add(item)
return no_dup_items
當然,也可以像《Python Cookbook》書上的代碼那樣,把上面的函數(shù)改造成一個生成器。
def
dedup(items):
seen =
set()
for item
in items:
if item not
in seen:
yield item
seen.add(item)
擴展:由于Python中的集合底層使用哈希存儲,所以集合的in和not in成員運算在性能上遠遠優(yōu)于列表,所以上面的代碼我們使用了集合來保存已經(jīng)出現(xiàn)過的元素。集合中的元素必須是hashable對象,因此上面的代碼在列表元素不是hashable對象時會失效,要解決這個問題可以給函數(shù)增加一個參數(shù),該參數(shù)可以設計為返回哈希碼或hashable對象的函數(shù)。
題目004:假設你使用的是官方的CPython,說出下面代碼的運行結果。
點評:下面的程序對實際開發(fā)并沒有什么意義,但卻是CPython中的一個大坑,這道題旨在考察面試者對官方的Python解釋器到底了解到什么程度。
a, b, c, d =
1,
1,
1000,
1000
print(a
is b, c
is d)
def foo():
e =
1000
f =
1000
print(e
is f, e
is d)
g =
1
print(g
is a)
foo()
結果:
True False
True False
True
上面代碼中a is b的結果是True但c is d的結果是False,這一點的確讓人費解。這個結果是因為CPython出于性能優(yōu)化的考慮,把頻繁使用的整數(shù)對象用一個叫small_ints的對象池緩存起來造成的。small_ints緩存的整數(shù)值被設定為[-5, 256]這個區(qū)間,也就是說,如果使用CPython解釋器,在任何引用這些整數(shù)的地方,都不需要重新創(chuàng)建int對象,而是直接引用緩存池中的對象。如果整數(shù)不在該范圍內(nèi),那么即便兩個整數(shù)的值相同,它們也是不同的對象。
CPython底層為了進一步提升性能還做了一個設定:對于同一個代碼塊中值不在small_ints緩存范圍之內(nèi)的整數(shù),如果同一個代碼塊中已經(jīng)存在一個值與其相同的整數(shù)對象,那么就直接引用該對象,否則創(chuàng)建新的int對象。需要大家注意的是,這條規(guī)則對數(shù)值型適用,但對字符串則需要考慮字符串的長度,這一點可以自行證明。
擴展:如果你用PyPy(另一種Python解釋器實現(xiàn),支持JIT,對CPython的缺點進行了改良,在性能上優(yōu)于CPython,但對三方庫的支持略差)來運行上面的代碼,你會發(fā)現(xiàn)所有的輸出都是True。
題目005:Lambda函數(shù)是什么,舉例說明的它的應用場景。
點評:這個題目主要想考察的是Lambda函數(shù)的應用場景,潛臺詞是問你在項目中有沒有使用過Lambda函數(shù),具體在什么場景下會用到Lambda函數(shù),借此來判斷你寫代碼的能力。因為Lambda函數(shù)通常用在高階函數(shù)中,主要的作用是通過傳入或返回函數(shù)實現(xiàn)代碼的解耦合。
Lambda函數(shù)也叫匿名函數(shù),它功能簡單用一行代碼就能實現(xiàn)的小型函數(shù)。Python中的Lambda函數(shù)只能寫一個表達式,這個表達式的執(zhí)行結果就是函數(shù)的返回值,不用寫return關鍵字。Lambda函數(shù)因為沒有名字,所以也不會跟其他函數(shù)發(fā)生命名沖突的問題。
面試的時候有可能還會考你用Lambda函數(shù)來實現(xiàn)一些功能,也就是用一行代碼來實現(xiàn)題目要求的功能,例如:用一行代碼實現(xiàn)求階乘的函數(shù),用一行代碼實現(xiàn)求大公約數(shù)的函數(shù)等。
fac = lambda x: __import__('functools').reduce(int.__mul__, range(1, x + 1), 1)
gcd = lambda x, y: y % x and gcd(y % x, x) or x
Lambda函數(shù)其實最為主要的用途是把一個函數(shù)傳入另一個高階函數(shù)(如Python內(nèi)置的filter、map等)中來為函數(shù)做解耦合,增強函數(shù)的靈活性和通用性。下面的例子通過使用filter和map函數(shù),實現(xiàn)了從列表中篩選出奇數(shù)并求平方構成新列表的操作,因為用到了高階函數(shù),過濾和映射數(shù)據(jù)的規(guī)則都是函數(shù)的調(diào)用者通過另外一個函數(shù)傳入的,因此這filter和map函數(shù)沒有跟特定的過濾和映射數(shù)據(jù)的規(guī)則耦合在一起。
items = [12, 5, 7, 10, 8, 19]
items = list(map(lambda x: x ** 2, filter(lambda x: x % 2, items)))
print(items) # [25, 49, 361]
當然,用列表的生成式來實現(xiàn)上面的代碼更加簡單明了,如下所示。
items = [12, 5, 7, 10, 8, 19]
items = [x ** 2 for x in items if x % 2]
print(items) # [25, 49, 361]
到此,相信大家對“在Python中如何實現(xiàn)單例模式”有了更深的了解,不妨來實際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關內(nèi)容可以進入相關頻道進行查詢,關注我們,繼續(xù)學習!
分享名稱:在Python中如何實現(xiàn)單例模式-創(chuàng)新互聯(lián)
網(wǎng)站路徑:http://www.dlmjj.cn/article/icjie.html