Sentinel对象及其应用程序?

时间:2016-09-04 05:52:40

标签: python idioms sentinel

我知道在python中内置function dateToMDY(date) { var d = date.getDate(); var m = date.getMonth() + 1; var y = date.getFullYear(); return '' + (m <= 9 ? '0' + m : m) + '-' + (d <= 9 ? '0' + d : d) + '-' + y; } function graphTitleGenerator(data) { var formats = { sameDay: '[Today]', nextDay: '[Tomorrow]', nextWeek: 'dddd', lastDay: '[Yesterday]', lastWeek: 'MM/DD/YYYY', sameElse: 'MM/DD/YYYY' } var today = new Date(); var refDate = dateToMDY(today); var graphTitle = moment(data.date, 'MM-DD-YYYY').calendar(refDate, formats); return graphTitle; } 会返回一个sentinel对象。我很好奇它是什么,但主要是它的应用。

3 个答案:

答案 0 :(得分:5)

object是所有其他类在python 3中继承的基类。对于普通的旧对象,你可以做很多事情。但是,对象的身份可能很有用。例如,iter函数采用sentinel参数来表示何时停止终止。我们可以提供一个对象()。

sentinel = object()

def step():
    inp = input('enter something: ')
    if inp == 'stop' or inp == 'exit' or inp == 'done':
        return sentinel
    return inp

for inp in iter(step, sentinel):
    print('you entered', inp)

在用户输入停止,退出或完成之前,这将要求输入。我不确定iter带有哨兵的地方比发电机更有用,但我想它无论如何都很有趣。

我不确定这是否能回答你的问题。需要说明的是,这只是object的一种可能应用。从根本上说,它在python语言中的存在与它可用作哨兵价值无关(据我所知)。

答案 1 :(得分:1)

这是来自dataclasses的Python标准库中使用哨兵值的源代码示例

$collection->aggregate([
[
    '$match' => [
        'subject_projectId' => (int) $projectId
    ]
],
[
    '$sort' => [
        'classification_finished_at' => -1
    ]
],
[
    '$group' => [
        '_id' => '$user_name',
        'transcriptionCount' => [
            '$sum' => 1
        ],
        'expedition' => [
            '$addToSet' => '$subject_expeditionId'
        ],
        'last_date' => [
            '$first' => '$classification_finished_at'
        ]
    ]
],
[
    '$project' => [
        '_id' => 0,
        'user_name' => '$_id',
        'transcriptionCount' => 1,
        'expeditionCount' => [
            '$size' => '$expedition'
        ],
        'last_date' => 1
    ]
]]);

答案 2 :(得分:1)

Python 中的对象标识和类

你声明“我知道在 python 中内置 object() 返回一个哨兵对象。” 有点偏离,但并非完全错误,所以让我首先解决这个问题,以确保我们在同一页面上:

object() 在 Python 中只是所有类的父类。在 Python 2 中,这有一段时间是明确的。在 Python 2 中,您必须编写:

class Foo(object):
    ...

得到一个所谓的“新式对象”。您也可以定义没有该超类的类,但这只是为了向后兼容,对于这个问题并不重要。

今天在 Python 3 中,object 超类是隐式的。所以所有的类都继承自那个类。因此,以下两个类在 Python 3 中是相同的:

class Foo:
    pass

class Foo(object):
    pass

知道了这一点,我们可以稍微改写一下您的初始声明:

<块引用>

... 内置 object() 返回一个哨兵对象。

变成:

<块引用>

... 内置 object() 返回类“object”的对象实例

所以,在写作时:

my_sentinel = object()

只是在“内存中的某处”创建一个空对象实例。最后一部分很重要,因为默认情况下,内置的 id() 函数和使用 ... is ... 的检查依赖于内存地址。例如:

>>> a = object()
>>> b = object()
>>> a is b
False

这为您提供了一种创建对象实例的方法,您可以使用它来检查代码中的某种逻辑,否则这些逻辑非常困难甚至不可能。 这是“哨兵”对象的主要用途

示例用例:区分“无”和“无/未初始化/空/...”

有时值 None 是变量的有效值,您可能需要检测“空”或类似内容与 None 之间的差异。

假设您有一个类为“无”是有效值的昂贵操作执行延迟加载。然后你可以这样写:

#: sentinel value for uninitialised values
UNLOADED = object()

class MyLoader:
    def __init__(self, remote_addr):
        self.value = UNLOADED
        self.remote_addr = remote_addr
    def get(self):
        if self.value is UNLOADED:
            self.value = expensive_operation(self.remote_addr)
        return self.value

现在 expensive_operation 可以返回任何值。甚至 None 或任何其他“虚假”值和“缓存”都可以正常工作而不会出现意外错误。它还使代码非常易读,因为它将意图非常清楚地传达给代码块的读者。您还可以为额外的“is_loaded”布尔值节省存储空间(尽管可以忽略不计)。

使用布尔值的相同代码:

class MyLoader:
    def __init__(self, remote_addr):
        self.value = None
        self.remote_addr = remote_addr
        self.is_loaded = False  # <- need for an additional variable
    def get(self):
        if not self.is_loaded:
            self.value = expensive_operation(self.remote_addr)
            self.is_loaded = True  # <- source for a bug if this is forgotten
        return self.value

或者,使用“无”作为默认值:

class MyLoader:
    def __init__(self, remote_addr):
        self.value = None  #  <- We'll use this to detect load state
        self.remote_addr = remote_addr
    def get(self):
        if self.value is None:
            self.value = expensive_operation(self.remote_addr)
            # If the above returned "None" we will never "cache" the result
        return self.value

最后的想法

上面的“MyLoader”示例只是一个可以方便使用标记值的示例。它们有助于使代码更具可读性和表现力。他们还避免了某些类型的错误。

它们在人们倾向于使用 None 来表示特殊值的领域特别有用。每当您想到“当 X 是这种情况时,我会将变量设置为 None”时,可能就值得考虑使用标记值。因为您现在为值 None 赋予了特定上下文的特殊含义。

另一个这样的例子是无限整数的特殊值。无穷大的概念只存在于浮点数中。但是,如果您想确保类型安全,您可能需要创建自己的“特殊”值来表示无穷大。

使用这样的标记值有助于区分多个不同的概念,否则这些概念是不可能的。如果您需要许多不同的“特殊”值并在任何地方使用 None,您最终可能会在另一个概念的上下文中使用来自一个概念的 None,并最终导致意想不到的副作用,这可能很难调试。想象一个这样的人为函数:

SENTINEL_A = object()
SENTINEL_B = object()

def foobar(a = SENTINEL_A, b = SENTINEL_B):
    if a is SENTINEL_A:
        a = -12
    if b is SENTINEL_B:
        b = a * 2
    print(a+b)

通过使用这样的哨兵,不可能通过混淆变量来意外触发 if 分支。例如,假设您重构代码并在某处绊倒,像这样混合 a 和 b:

SENTINEL_A = object()
SENTINEL_B = object()

def foobar(a = SENTINEL_A, b = SENTINEL_B):
    if b is SENTINEL_A:  # <- bug: using *b* instead of *a*
        a = -12
    if b is SENTINEL_B:
        b = a * 2
    print(a+b)

在那种情况下,第一个 if 永远不会为真(当然,除非函数调用不当)。如果您将 None 用作默认值,则此错误将变得更难检测,因为您最终会在不期望的情况下使用 a = -12

从这个意义上说,哨兵使您的代码更加健壮。如果您的代码中出现逻辑错误,它们将更容易找到。

话虽如此,哨兵值非常罕见。我个人认为它们非常有用,可以避免过度使用 None 来标记特殊情况。