如何在Python中创建第二个None?创建一个 id 始终相同的单例对象

Ero*_*mic 6 python

警告:以下问题要求提供有关不良做法和脏代码的信息。建议开发商酌情决定。

注意:这与在 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 
Run Code Online (Sandbox Code Playgroud)

但我们不必太担心原因。让我们重点关注如何做。


到目前为止我所拥有的:

我可以很容易地实现大部分这种行为。我创建了一个名为 的特殊模块util_const.py。它包含一个创建 NoParamType 的类,然后创建该类的单例实例。

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

NoParam = _NoParamType()
Run Code Online (Sandbox Code Playgroud)

我只是假设永远不会创建此类的第二个实例。每当我想使用值时,我import util_const就会使用util_const.NoParam.

这对于大多数情况都很有效。然而,我刚刚遇到了一个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()
Run Code Online (Sandbox Code Playgroud)

现在,如果deepcopy调用 no,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)
Run Code Online (Sandbox Code Playgroud)

use*_*ica 4

我可以做些什么来通过酸洗实现同样的行为吗?

是的。

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

NoParam = object.__new__(_NoParamType)
Run Code Online (Sandbox Code Playgroud)

更进一步:我能做些什么来确保只创建一个 NoParam 实例吗?

除非NoParam您用 C 语言编写并利用仅 C API 的功能,否则始终可以获取object.__new__(type(NoParam))另一个实例。

  • 覆盖`__new__`是矫枉过正的。您只需从 __reduce__` 返回 'NoParam'` ,然后 Pickler 将在模块的全局范围中查找该名称](https://docs.python.org/3/library/pickle.html#object. __减少__)。那时,您可以在使用该类构造“NoParam”后删除该类,然后您可以合理地确定没有人会使用它来构造新的类,而无需执行一些神奇的“type(NoParam)()”废话,如果你真的想的话,你也可以抓住并阻止。 (2认同)