使用 is (CustomClass) 可以安全地检测 Python 中的未初始化值
Is using `is (CustomClass)` safe to detect uninitialized values in Python
我正在创建一个装饰器来缓存可能抛出异常的昂贵函数的值,我想记录我们是否到达了初始化值的位置。截至目前,我只是将值初始化为 "odd" 字符串 new_val = "__THIS_IS_UNINITIALIZED__"
但是 感觉很脏 .
我想知道将 is
与自定义 class(什么都不做)一起使用是否安全。
这是我现在拥有的:
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 并没有真正的 "uninitialized variables"。变量不存在,直到你给它赋值。如果在为它赋值之前引用这样的变量,你将得到
- a
NameError
如果您的代码引用 a
但 a
未分配
- 一个
AttributeError
如果你的代码a.b
如果a
存在但b
未分配
- 一个
UnboundLocalError
如果你的代码知道 a
但它试图在它被分配之前从它获取一个值。
(可能还有一些我遗漏的案例。)
您似乎正在尝试重新设计这种安排,这可能不是一个好主意。
如果您想检查 class 实例 a
是否具有已定义的属性 b
,您可以 hasattr(a, "b")
。或者简单地写一个 try...except
来捕获 AttributeError
。
但在我看来,您正在尝试解决另一种语言中存在的问题。在存在此类事物的语言中引用未初始化的变量可能会导致段错误。但是在 Python 中做相应的事情只会导致运行时错误。你真的不需要为它设置陷阱,因为语言已经这样做了。
标准的习惯用法是基本上做你所做的,但是创建一个 object
的实例而不是为这样的哨兵定义一个新的 class。
Uninitialized = object()
由于您的 class 语句等同于
Uninitialized = type("Uninitialized", (object,), {})
唯一的区别是我实例化了 object
而不是 type
。
更新(通过 Sentinel object and its applications?): an instance of a custom class can provide a more useful representation, as demonstrated by an example from the dataclasses
module:
>>> from dataclasses import MISSING
>>> repr(MISSING)
'<dataclasses._MISSING_TYPE object at 0x10baeaf98>'
>>> repr(object())
'<object object at 0x10b961200>'
定义一个新的 class 允许您使用 class 名称作为简短的诊断消息或对哨兵用途的描述。
我认为您的模式有两种变体可以被视为改进。
哨兵
这是一个非常常见的模式,您创建了一些与您的独特字符串非常相似的东西,称为标记值。 cpython 库也使用 is,例如在 the dataclasses module 中。您基本上只是创建和共享一个被描述为没有任何意义的空对象,并使用它来实例化尚无有意义值的变量。
在这种情况下,您不需要不检查isinstance
,只需使用is
,它更快、更安全、更地道。由于您使用哨兵的参考 ID 作为标识符,因此无法意外匹配它。
constants.py
SENTINEL = object()
# some other constants maybe
code.py
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()
如果您想使用这种缓存机制,我个人建议您这样做。
函数属性黑客
由于函数首先是 class 对象,因此它们可以像任何其他对象一样具有属性。所以像这样的东西是完全有效的 python 代码:
def costly_compute():
try:
return costly_compute.cache
except AttributeError:
pass
costly_compute.cache = ... # some heavy lifting again
return costly_compute()
从文体的角度来看,这很糟糕。
我正在创建一个装饰器来缓存可能抛出异常的昂贵函数的值,我想记录我们是否到达了初始化值的位置。截至目前,我只是将值初始化为 "odd" 字符串 new_val = "__THIS_IS_UNINITIALIZED__"
但是 感觉很脏 .
我想知道将 is
与自定义 class(什么都不做)一起使用是否安全。
这是我现在拥有的:
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 并没有真正的 "uninitialized variables"。变量不存在,直到你给它赋值。如果在为它赋值之前引用这样的变量,你将得到
- a
NameError
如果您的代码引用a
但a
未分配 - 一个
AttributeError
如果你的代码a.b
如果a
存在但b
未分配 - 一个
UnboundLocalError
如果你的代码知道a
但它试图在它被分配之前从它获取一个值。
(可能还有一些我遗漏的案例。)
您似乎正在尝试重新设计这种安排,这可能不是一个好主意。
如果您想检查 class 实例 a
是否具有已定义的属性 b
,您可以 hasattr(a, "b")
。或者简单地写一个 try...except
来捕获 AttributeError
。
但在我看来,您正在尝试解决另一种语言中存在的问题。在存在此类事物的语言中引用未初始化的变量可能会导致段错误。但是在 Python 中做相应的事情只会导致运行时错误。你真的不需要为它设置陷阱,因为语言已经这样做了。
标准的习惯用法是基本上做你所做的,但是创建一个 object
的实例而不是为这样的哨兵定义一个新的 class。
Uninitialized = object()
由于您的 class 语句等同于
Uninitialized = type("Uninitialized", (object,), {})
唯一的区别是我实例化了 object
而不是 type
。
更新(通过 Sentinel object and its applications?): an instance of a custom class can provide a more useful representation, as demonstrated by an example from the dataclasses
module:
>>> from dataclasses import MISSING
>>> repr(MISSING)
'<dataclasses._MISSING_TYPE object at 0x10baeaf98>'
>>> repr(object())
'<object object at 0x10b961200>'
定义一个新的 class 允许您使用 class 名称作为简短的诊断消息或对哨兵用途的描述。
我认为您的模式有两种变体可以被视为改进。
哨兵
这是一个非常常见的模式,您创建了一些与您的独特字符串非常相似的东西,称为标记值。 cpython 库也使用 is,例如在 the dataclasses module 中。您基本上只是创建和共享一个被描述为没有任何意义的空对象,并使用它来实例化尚无有意义值的变量。
在这种情况下,您不需要不检查isinstance
,只需使用is
,它更快、更安全、更地道。由于您使用哨兵的参考 ID 作为标识符,因此无法意外匹配它。
constants.py
SENTINEL = object()
# some other constants maybe
code.py
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()
如果您想使用这种缓存机制,我个人建议您这样做。
函数属性黑客
由于函数首先是 class 对象,因此它们可以像任何其他对象一样具有属性。所以像这样的东西是完全有效的 python 代码:
def costly_compute():
try:
return costly_compute.cache
except AttributeError:
pass
costly_compute.cache = ... # some heavy lifting again
return costly_compute()
从文体的角度来看,这很糟糕。