所以我最近問了一個關於memoization的問題,並得到了一些很好的答案,現在我想把它提升到一個新的水平。經過相當多的搜索後,我找不到一個能夠緩存帶關鍵字參數的函數的memoize裝飾器的參考實現。事實上,大多數人只是用*args
作爲緩存查找的關鍵,這意味着它也將打破,如果你想memoize的接受了列表或類型的字典作爲參數的函數。Python是否有支持memoize裝飾器的關鍵字參數的pythonic方法?
在我的情況下,函數的第一個參數本身就是一個唯一標識符,適合用作緩存查找的詞典鍵,但是我希望能夠使用關鍵字參數並仍然訪問相同的緩存。我的意思是,my_func('unique_id', 10)
和my_func(foo=10, func_id='unique_id')
都應該返回相同的緩存結果。
爲了做到這一點,我們需要的是說「檢查是否有哪個關鍵字kwargs它是對應於第一個參數)」的清潔,pythonic的方法。這是我想出了:
class memoize(object):
def __init__(self, cls):
if type(cls) is FunctionType:
# Let's just pretend that the function you gave us is a class.
cls.instances = {}
cls.__init__ = cls
self.cls = cls
self.__dict__.update(cls.__dict__)
def __call__(self, *args, **kwargs):
"""Return a cached instance of the appropriate class if it exists."""
# This is some dark magic we're using here, but it's how we discover
# that the first argument to Photograph.__init__ is 'filename', but the
# first argument to Camera.__init__ is 'camera_id' in a general way.
delta = 2 if type(self.cls) is FunctionType else 1
first_keyword_arg = [k
for k, v in inspect.getcallargs(
self.cls.__init__,
'self',
'first argument',
*['subsequent args'] * (len(args) + len(kwargs) - delta)).items()
if v == 'first argument'][0]
key = kwargs.get(first_keyword_arg) or args[0]
print key
if key not in self.cls.instances:
self.cls.instances[key] = self.cls(*args, **kwargs)
return self.cls.instances[key]
瘋狂的事情是,這實際工作。例如,如果你裝點這樣的:
@memoize
class FooBar:
instances = {}
def __init__(self, unique_id, irrelevant=None):
print id(self)
然後從你的代碼,你可以調用FooBar('12345', 20)
或FooBar(irrelevant=20, unique_id='12345')
和真正得到FooBar的的同一個實例。然後,您可以爲第一個參數定義一個不同名稱的不同類,因爲它以一般方式工作(即,裝飾器不需要知道關於正在裝飾的類的任何特定信息)。
的問題是,這是一個不敬虔的混亂;-)
它的工作原理,因爲inspect.getcallargs
返回一個字典映射定義的關鍵字爲您提供它的參數,所以我爲它供給了一些假的論點,然後檢查的字典第一個參數通過了。
如果這樣的事情甚至存在,什麼會更好,是inspect.getcallargs
的類似物,它將兩種參數統一爲參數列表,而不是作爲關鍵字參數的詞典。這將允許這樣的事情:
def __call__(self, *args, **kwargs):
key = inspect.getcallargsaslist(self.cls.__init__, None, *args, **kwargs)[1]
if key not in self.cls.instances:
self.cls.instances[key] = self.cls(*args, **kwargs)
return self.cls.instances[key]
的另一種方式,我可以看到解決這個將使用由inspect.getcallargs
提供直接查找緩存鍵的字典內,但這需要一個可重複的方式,利用相同的字符串相同的散列,這是我聽到的東西不能依賴(我想我必須在排序鍵後自己構造字符串)。
有沒有人對此有任何想法?想用關鍵字參數調用一個函數並緩存結果是錯誤的嗎?或者只是非常困難?
我喜歡這樣的外觀,但我關注'key'返回'元組(a.items())'的部分。是否保證按照相同的順序對不同但相同的字典進行排序?我聽說dict是無序的,並且依靠像'str({1:2,3:4})'這樣的東西來產生可重複的字符串,只要給出相同的輸入即爲非常錯誤。 – robru
似乎'inspect.getargspec(func).args [0]'是我問的具體問題(如何找到第一個參數的名稱)的確切答案,但我想將其擴展爲更一般的解決方案稍後我會花一些時間來細化我的結果。 – robru
@Robru:關於字典排序的好處。改成'tuple(sorted(a.items()))'(另一個選項是'frozenset(a.items())')。 – georg