警告:以下问题要求提供有关不良做法和脏代码的信息。建议开发商酌情决定。
注意:这与在 Python 中创建单例问题是因为我们想要解决酸洗和复制以及正常的对象创建问题。
目标:我想创造一个价值(称为NoParam
)模拟的行为None
。具体来说,我想要任何实例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()
我只是假设永远不会创建此类的第二个实例。每当我想使用我的值时import util_const
并使用util_const.NoParam
.
这对于大多数情况都很有效。然而我刚刚遇到一个案例
ANoParam
value 被设置为对象值。使用深度复制该对象copy.deepcopy
因此创建了第二个 NoParam 实例。
我找到了一个非常简单的解决方法,通过定义__copy__
and __deepcopy__
methods
class _NoParamType(object):
def __init__(self):
pass
def __copy__(self):
return NoParam
def __deepcopy__(self, memo):
return NoParam
NoParam = _NoParamType()
Now, if deepcopy
曾经被称为“不”NoParam
它只是返回现有的NoParam
实例。
现在问题是:
我可以做些什么来通过酸洗实现同样的行为吗?最初我以为我可以定义__getstate__
但此时第二个实例已经创建。本质上我想要pickle.loads(pickle.dumps(NoParam)) is NoParam
返回 True。有没有办法做到这一点(也许使用元类)?
更进一步:我能做些什么来确保只创建一个 NoParam 实例吗?
Solution
非常感谢@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)