首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Sentinel对象及其应用

Sentinel对象及其应用
EN

Stack Overflow用户
提问于 2016-09-04 13:52:40
回答 3查看 7.1K关注 0票数 6

我知道在python中,内置的object()会返回一个前哨对象。我很好奇它是什么,但主要是它的应用。

EN

回答 3

Stack Overflow用户

发布于 2016-09-04 14:36:01

object是Python3中所有其他类继承而来的基类,对于一个普通的老对象,你不能做很多事情。然而,对象的标识可能是有用的。例如,iter函数接受一个sentinel参数,该参数表示何时停止终止。我们可以为它提供一个object()。

代码语言:javascript
复制
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)

这将要求输入,直到用户键入stop、exit或done。我不能确切地确定什么时候带有前哨的iter比生成器更有用,但我猜它无论如何都很有趣。

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

票数 8
EN

Stack Overflow用户

发布于 2018-10-19 07:57:31

这是dataclasses的Python标准库中有关使用标记值的源代码示例

代码语言:javascript
复制
# A sentinel object to detect if a parameter is supplied or not.  Use
# a class to give it a better repr.
class _MISSING_TYPE:
    pass
MISSING = _MISSING_TYPE()
票数 5
EN

Stack Overflow用户

发布于 2021-01-18 22:27:11

Python中的对象标识和类

语句“我知道在python中内置对象()返回一个标记对象”。有点离谱,但并不是完全错误,所以让我先说明一下,以确保我们的想法是一致的:

Python中的object()仅仅是所有类的父类。在Python2中,这是显式的。在Python 2中,你必须写:

代码语言:javascript
复制
class Foo(object):
    ...

来获得一个所谓的“新风格对象”。你也可以在没有超类的情况下定义类,但这只是为了向后兼容,对这个问题并不重要。

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

代码语言:javascript
复制
class Foo:
    pass

class Foo(object):
    pass

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

...内置对象()返回一个标记对象。

然后变成:

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

所以,在写的时候:

代码语言:javascript
复制
my_sentinel = object()

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

代码语言:javascript
复制
>>> a = object()
>>> b = object()
>>> a is b
False

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

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

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

让我们假设您有一个类正在为一个开销很大的操作执行延迟加载,其中"None“是一个有效值。然后你可以这样写它:

代码语言:javascript
复制
#: 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可以返回任何值。即使没有或任何其他“假”值和“缓存”也不会出现意外的错误。它还使代码具有很好的可读性,因为它非常清楚地向代码块的读者传达了意图。您还可以为一个额外的"is_loaded“布尔值节省存储空间(尽管可能是否定的)。

使用布尔值的相同代码:

代码语言:javascript
复制
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

或者,使用"None“作为默认值:

代码语言:javascript
复制
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“示例只是一个可以方便使用标记值的示例。它们有助于使代码更具可读性和表现力。它们还可以避免某些类型的bug。

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

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

使用像这样的标记值有助于区分多个不同的概念,否则这是不可能的。如果您需要许多不同的“特殊”值,并且到处使用None,那么您可能最终会在一个概念的上下文中使用另一个概念中的None,并最终产生意想不到的副作用,这可能很难调试。想象一下这样一个人为设计的函数:

代码语言:javascript
复制
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如下所示:

代码语言:javascript
复制
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非常有用。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/39313943

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档