2016-12-08 28 views
1

警告:以下問題是要求提供有關不良實踐和髒代碼的信息。建議開發人員酌情決定。如何在Python中創建第二個None?創建一個id始終相同的單例對象

注意:這與Creating a singleton in Python問題不同,因爲我們想要解決酸洗和複製以及正常的對象創建問題。

目標:我想創建一個模擬None行爲的值(稱爲NoParam)。具體而言,我希望NoParamType的任何實例具有相同的值,即具有相同的值id ---以便is運算符始終在這些值中的兩個值之間返回True

爲什麼:我有保存參數值的配置類。其中一些參數可以採用None作爲有效類型。但是,如果沒有指定類型,我希望這些參數採用特定的默認值。因此我需要一些哨兵is not None來指定沒有指定參數。這個標記必須是 永遠不能用作有效參數值的東西。我寧願有一個特殊類型的哨兵,而不是使用一些不太可能被使用的字符串。

例如:

def add_param(name, value=NoParam): 
    if value is NoParam: 
     # do some something 
    else: 
     # do something else 

但是讓我們不要太在乎的原因。讓我們關注如何。


我有什麼至今:

我能實現大部分的這種行爲很容易地。我創建了一個名爲util_const.py的特殊模塊。它包含一個創建NoParamType然後創建該類的單例實例的類。

class _NoParamType(object): 
    def __init__(self): 
     pass 

NoParam = _NoParamType() 

我只是假設這個類的第二個實例永遠不會被創建。無論何時我想使用值I import util_const並使用util_const.NoParam

這適用於大多數情況。但是,我剛剛遇到一個案例,其中 a NoParam值被設置爲對象值。該對象使用copy.deepcopy進行了深度複製,因此創建了第二個NoParam實例。

我發現這是一個非常簡單的解決方法通過定義__copy____deepcopy__方法

class _NoParamType(object): 
    def __init__(self): 
     pass 
    def __copy__(self): 
     return NoParam 
    def __deepcopy__(self, memo): 
     return NoParam 

NoParam = _NoParamType() 

現在,如果deepcopy是不斷呼籲沒有NoParam它簡單地返回現有NoParam實例。


現在的問題:

有什麼我可以做,以實現與酸洗此相同的行爲?最初我以爲我可以定義__getstate__,但第二個實例已經在那個時候創建​​。基本上我想pickle.loads(pickle.dumps(NoParam)) is NoParam返回True。有沒有辦法做到這一點(也許與元類)?

爲了更進一步:有什麼我可以做的,以確保只有一個NoParam實例創建?


解決方案

非常感謝@ user2357112回答有關酸洗的問題。 我也想出瞭如何使這個類健壯的模塊重新加載以及。下面是我學到的東西都放在一起

# -*- coding: utf-8 -*- 
# util_const.py  

class _NoParamType(object): 
    """ 
    Class used to define `NoParam`, a setinal that acts like None when None 
    might be a valid value. The value of `NoParam` is robust to reloading, 
    pickling, and copying. 

    Example: 
     >>> import util_const 
     >>> from util_const import _NoParamType, NoParam 
     >>> from six.moves import cPickle as pickle 
     >>> import copy 
     >>> versions = { 
     ... 'util_const.NoParam': util_const.NoParam, 
     ... 'NoParam': NoParam, 
     ... '_NoParamType()': _NoParamType(), 
     ... 'copy': copy.copy(NoParam), 
     ... 'deepcopy': copy.deepcopy(NoParam), 
     ... 'pickle': pickle.loads(pickle.dumps(NoParam)) 
     ... } 
     >>> print(versions) 
     >>> assert all(id(v) == id_ for v in versions.values()) 
     >>> import imp 
     >>> imp.reload(util_const) 
     >>> assert id(NoParam) == id(util_const.NoParam) 
    """ 
    def __new__(cls): 
     return NoParam 
    def __reduce__(self): 
     return (_NoParamType,()) 
    def __copy__(self): 
     return NoParam 
    def __deepcopy__(self, memo): 
     return NoParam 
    def __call__(self, default): 
     pass 

# Create the only instance of _NoParamType that should ever exist 
# When the module is first loaded, globals() will not contain NoParam. A 
# NameError will be thrown, causing the first instance of NoParam to be 
# instanciated. 
# If the module is reloaded (via imp.reload), globals() will contain 
# NoParam. This skips the code that would instantiate a second object 
# Note: it is possible to hack around this via 
# >>> del util_const.NoParam 
# >>> imp.reload(util_const) 

try: 
    NoParam 
except NameError: 
    NoParam = object.__new__(_NoParamType) 
+0

[在Python中創建單例]的可能副本(http://stackoverflow.com/questions/6760685/creating-a-singleton-in-python) – lxop

+0

@lxop:這些單例不是singleton-y就足夠了。 (真的,他們不是。) – user2357112

+0

誠然,他們不處理酸洗方面的問題。 – lxop

回答

2

有什麼我可以做,以實現與酸洗此相同的行爲?

是的。

class _NoParamType(object): 
    def __new__(cls): 
     return NoParam 
    def __reduce__(self): 
     return (_NoParamType,()) 

NoParam = object.__new__(_NoParamType) 

把它更進一步:有什麼我可以做,以確保NoParam只有一個實例被創建過?

不無C.寫NoParam除非你把它寫在C和利用的僅C API的功能,它永遠是可能做到object.__new__(type(NoParam))得到另一個實例。

+0

這是完美的。必須有一定程度的信任才能按預期使用此對象。在保留「is」操作的同時添加pickle和copy值的功能足夠健壯,我很樂於使用它。我明白'__new__'方法,但是你介意'__reduce__'方法的目的是什麼? – Erotemic

+1

@Erotemic:這是[其中一種方法](https://docs.python.org/2/library/pickle.html#object.__reduce__)來自定義酸洗,因爲沒有它,pickle協議0實際上最終會試圖做'對象。__new __(_ NoParamType)'取消'NoParam'時。 – user2357112

+2

覆蓋'__new__'是矯枉過正。你可以從'__reduce__'返回''NoParam'',然後[Pickler會在模塊的全局範圍內查找這個名字](https://docs.python.org/3/library/pickle.html#object。 __減少__)。在那個時候,你可以在你使用它來構造'NoParam'後刪除這個類,然後你可以合理地確定沒有做一些神奇的類型(NoParam)()''胡說八道,如果你真的想要的話,你也可以捕捉並阻止它。 – Kevin

0

我要回答part X, not part Y

我有保存到的參數值的配置類。其中一些參數可以採用None作爲有效類型。但是,如果沒有指定類型,我希望這些參數採用特定的默認值。

...

例如:

def add_param(name, value=NoParam): 
    if value is NoParam: 
     # do some something 
    else: 
     # do something else 

,以測試是否value被調用者定義就是完全消除默認值的正確方法:

def add_param(name, **kwargs): 
    if 'value' not in kwargs: 
     # do some something 
    else: 
     # do something else 

確實,這打破了一些基於反思的功能,lik它會檢查你是否將正確的參數傳遞給函數,但這種頭痛應該很大,遠遠小於試圖向後彎曲身份系統。

相關問題