python - Python中的“哨兵对象”模式有什么意义
问题描述
我最近了解了 python 中的“哨兵对象”模式。我被它所吸引,并开始尽可能地使用它。但是,在不需要它的地方使用它之后,一位同事问我这个问题。现在,鉴于“x in dict”存在,我看不到它的用途。这是一个(截断的)规范示例,来自 functools LRU 缓存库:
def _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo):
# Constants shared by all lru cache instances:
sentinel = object() # unique object used to signal cache misses
make_key = _make_key # build a key from the function arguments
PREV, NEXT, KEY, RESULT = 0, 1, 2, 3 # names for the link fields
cache = {}
hits = misses = 0
full = False
cache_get = cache.get # bound method to lookup a key or return None
cache_len = cache.__len__ # get cache size without calling len()
lock = RLock() # because linkedlist updates aren't threadsafe
root = [] # root of the circular doubly linked list
root[:] = [root, root, None, None] # initialize by pointing to self
if maxsize == 0:
def wrapper(*args, **kwds):
# No caching -- just a statistics update after a successful call
nonlocal misses
result = user_function(*args, **kwds)
misses += 1
return result
elif maxsize is None:
def wrapper(*args, **kwds):
# Simple caching without ordering or size limit
nonlocal hits, misses
key = make_key(args, kwds, typed)
result = cache_get(key, sentinel)
if result is not sentinel:
hits += 1
return result
result = user_function(*args, **kwds)
cache[key] = result
misses += 1
return result
现在,只关注使用模式的部分:
result = cache_get(key, sentinel)
if result is not sentinel:
hits += 1
return result
result = user_function(*args, **kwds)
cache[key] = result
misses += 1
return result
据我所知,这可以通过以下方式重写:
if key not in cache:
result = user_function(*args, **kwds)
cache[key] = result
misses += 1
else:
result = cache_get(key)
hits += 1
return result
我想知道:这种哨兵方法有什么好处?我认为这可能是效率。python wiki说“x in s”是O(n)平均情况,而get item是O(1)平均情况。但这真的会产生实际的时差吗?
我在笔记本电脑上进行了一些快速测试,运行时间很接近,无论是在大多数键被命中和大多数键未命中的情况下。
作为对@martineau 的回复,我不认为我们可以从这个模式中获得任何额外的功能,正如这个交互式会话所展示的:
>>> d={1:None}
>>> if 1 in d:
... print('one is there')
...
one is there
>>> if 2 in d:
... print('two is not')
...
>>> d={1:None,None:3}
>>> if None in d:
... print('we can find a none key as well')
...
we can find a none key as well
所以,问题仍然存在:这种模式的意义何在?
解决方案
在您显示的代码中,使用标记值是针对字典中确实存在dict.get
键的情况的一个小优化。在这种情况下,您只需要在调用中执行一次散列和密钥查找过程,而不是在等效过程中需要的两次。get
if key in dict: value = dict[key]
这不会改变计算复杂性,因为字典索引和成员资格测试都是O(1)
,但是如果它们在经常运行的“热”代码中,即使是很小的性能改进也很重要。这正是您展示的代码所提供的记忆最有用的地方!
您可能会在标准库的一些 Python 代码中看到其他一些非常常见的微优化。您的示例包含另一个示例,将绑定方法 ( cache.get
) 保存到局部变量 ( cache_get
)。这让代码可以避免每次需要时重新绑定方法,这涉及到实例和类字典的索引以及创建绑定的方法对象。
推荐阅读
- c# - 如何在 C# 中构建结构列表数组(具有预定义的数组大小)
- sql - 在 SQLite 中实现查询缓存
- amazon-web-services - aws ECS,ECS 实例未注册到 ALB 目标组
- android - 应用程序正常运行,但单击注销按钮时崩溃
- java - 为什么 counter 是 double 而不是 long?
- javascript - 提交数据 Express 时附加多个 JSON 条目
- python - 如何使用 BeautifulSoup 抓取超链接标题?
- sonos - 有声读物的自测支持
- svg - 如何在 apache Royale 中使用图形 svg 元素?
- javascript - : 和 :? 的区别