使用`is(CustomClass)`安全地检测Python中未初始化的值

问题描述 投票:1回答:3

我正在创建一个装饰器来缓存可能抛出异常的代价高昂的函数的值,我想记录我们是否达到了初始化值的点。截至目前,我只是将值初始化为“奇数”字符串new_val = "__THIS_IS_UNINITIALIZED__",但感觉很脏。

我想知道是否使用is与自定义类(没有做任何事情)是安全的。

这就是我现在拥有的:

class Cache:
    _cached = {}
    @staticmethod
    def cache(prefix):
        def wrapper(wrapped):
            def inner(self, *args, **kwargs):
                new_val = "__THIS_IS_UNINITIALIZED__"
                key = Cache.make_key(*args, **kwargs)
                if key in Cache._cached:
                    print("cache hit")
                    return Cache._cached[key]
                print("cache miss")
                try:
                    # This can throw exceptions
                    new_val = wrapped(self, *args, **kwargs)
                    # Something below this can ALSO throw
                    # exceptions, but the value has been
                    # initialized at this point.
                except:
                    if new_val == "__THIS_IS_UNINITIALIZED__":
                        print("we never got to initialize :_( ")
                else:
                    Cache._cache[key] = new_val
            return inner
        return wrapper

我想知道我是否可以使用if is Class而不是if new_val == "__THIS_IS_UNINITIALIZED__"

像这样的东西:

class Uninitialized:
    pass

class Cache:
    _cached = {}
    @staticmethod
    def cache(prefix):
        def wrapper(wrapped):
            def inner(self, *args, **kwargs):
                new_val = Uninitialized
                key = Cache.make_key(*args, **kwargs)
                if key in Cache._cached:
                    print("cache hit")
                    return Cache._cached[key]
                print("cache miss")
                try:
                    # This can throw exceptions
                    new_val = wrapped(self, *args, **kwargs)
                    # Something below this can ALSO throw
                    # exceptions, but the value has been
                    # initialized at this point.
                except:
                    if new_val is Uninitialized:
                        print("we never got to initialize :_( ")
                else:
                    Cache._cache[key] = new_val
            return inner
        return wrapper

    @staticmethod
    def make_key(*args, **kwargs):
        positional = ':'.join([str(s) for s in args])
        kw = ':'.join('%s=%s' % kf for kv in kwargs.items())
        return ':'.join([positional, kw])

class Test:
    def __init__(self):
        self.foo = 'hi'

    @Cache.cache('api')
    def test_cache(self, a, b):
        raise Exception("foo")

if __name__ == "__main__":
    t = Test()
    t.test_cache(1, 2)

使用字符串"__THIS_IS_UNINITIALIZED__"到目前为止工作正常(并且在可预见的未来将正常工作)。说实话,这主要是为了学习目的。

先感谢您。

python caching types initialization
3个回答
2
投票

标准习惯用法基本上是你做的,但要创建object的实例,而不是为这样的标记定义一个新的类。

 Uninitialized = object()

因为你的类声明等同于

Uninitialized = type("Uninitialized", (object,), {})

唯一的区别是我实例化了object而不是type


更新(通过Sentinel object and its applications?):自定义类的实例可以提供更有用的表示,如the dataclasses module中的示例所示:

>>> from dataclasses import MISSING
>>> repr(MISSING)
'<dataclasses._MISSING_TYPE object at 0x10baeaf98>'
>>> repr(object())
'<object object at 0x10b961200>'

通过定义新类,您可以将类名用作短诊断消息或描述目标的描述。


2
投票

Python实际上没有“未初始化的变量”。在为其赋值之前,变量不存在。如果在为其分配值之前引用这样的变量,您将获得

  1. 一个NameError,如果你的代码引用aa未分配
  2. 一个AttributeError,如果您的代码执行a.b,如果a存在但b未分配
  3. 一个UnboundLocalError,如果你的代码知道a但它试图在分配之前从中获取一个值。

(可能还有其他一些我错过的案例。)

你似乎试图重新设计这种安排,这可能不是一个好主意。

如果你想检查你的类实例a是否有定义的属性b你可以做hasattr(a, "b")。或者只是写一个try...except陷阱AttributeError

但在我看来,你正试图解决另一种语言中的问题。在存在此类事物的语言中引用未初始化的变量会导致段错误。但是在Python中执行相应的操作只会导致运行时错误。你真的不需要陷阱,因为语言已经这样做了。


1
投票

我认为你的模式有两种变体可以被认为是一种改进。


哨兵

这是一种非常常见的模式,您创建的东西与您称为sentinel值的唯一字符串非常相似。 cpython库也使用,例如在the dataclasses module中。您基本上只是创建和共享一个被描述为没有任何意义的空对象,并使用它来实例化尚无有意义值的变量。

在这种情况下,你不需要检查isinstance,只需使用更快,更安全,更惯用的is。由于您使用sentinel的引用ID作为标识符,因此无法意外地匹配它。

constants.朋友

SENTINEL = object()
# some other constants maybe

code.朋友

from constants import SENTINEL

COSTLY_COMPUTE_RESULT = SENTINEL

def costly_compute():
    global COSTLY_COMPUTE_RESULT 
    if COSTLY_COMPUTE_RESULT is not SENTINEL:
        return COSTLY_COMPUTE_RESULT 
    COSTLY_COMPUTE_RESULT = ...  # assume this is some heavy lifting
    return costly_compute()

这是我个人建议你做的,以防你想要推出这种缓存机制。


功能属性黑客

由于函数是第一类对象,因此它们可以具有属性,就像任何其他对象一样。所以像这样的东西是完全有效的python代码:

def costly_compute():
    try:
        return costly_compute.cache
    except AttributeError:
        pass
    costly_compute.cache = ...  # some heavy lifting again
    return costly_compute()

从风格的角度来看,这是非常可怕的。

© www.soinside.com 2019 - 2024. All rights reserved.