rob*_*bru 21 python singleton caching unique memoization
好吧,这是现实世界的场景:我正在编写一个应用程序,我有一个代表某种类型文件的类(在我的例子中,这是照片,但细节与问题无关).Photos类的每个实例对于照片的文件名应该是唯一的.
问题是,当用户告诉我的应用程序加载文件时,我需要能够识别文件何时已加载,并使用现有实例作为该文件名,而不是在同一文件名上创建重复实例.
对我而言,使用memoization似乎是一个很好的情况,并且有很多例子,但在这种情况下,我不只是记住一个普通的函数,我需要记忆__init__().这造成了一个问题,因为在__init__()被调用的时候已经太晚了,因为已经创建了一个新实例.
在我的研究中,我发现了Python的__new__()方法,我实际上能够编写一个简单的工作示例,但是当我尝试在我的真实世界对象上使用它时它就崩溃了,我不知道为什么(我唯一可以做到的)我想到的是我的真实世界对象是我无法控制的其他对象的子类,因此这种方法存在一些不兼容性.这就是我所拥有的:
class Flub(object):
instances = {}
def __new__(cls, flubid):
try:
self = Flub.instances[flubid]
except KeyError:
self = Flub.instances[flubid] = super(Flub, cls).__new__(cls)
print 'making a new one!'
self.flubid = flubid
print id(self)
return self
@staticmethod
def destroy_all():
for flub in Flub.instances.values():
print 'killing', flub
a = Flub('foo')
b = Flub('foo')
c = Flub('bar')
print a
print b
print c
print a is b, b is c
Flub.destroy_all()
Run Code Online (Sandbox Code Playgroud)
哪个输出:
making a new one!
139958663753808
139958663753808
making a new one!
139958663753872
<__main__.Flub object at 0x7f4aaa6fb050>
<__main__.Flub object at 0x7f4aaa6fb050>
<__main__.Flub object at 0x7f4aaa6fb090>
True False
killing <__main__.Flub object at 0x7f4aaa6fb050>
killing <__main__.Flub object at 0x7f4aaa6fb090>
Run Code Online (Sandbox Code Playgroud)
这是完美的!给出了两个唯一ID,只有两个实例,而Flub.instances显然只有两个列出.
但是当我尝试用我正在使用的对象采用这种方法时,我得到了各种无意义的错误,关于如何__init__()只使用0个参数,而不是2.所以我会改变一些事情,然后它会告诉我__init__()需要一个参数.完全离奇.
经过一段时间的战斗,我基本上只是放弃并将所有__new__()黑魔法移动到一个名为的静态方法中get,这样我就可以调用Photograph.get(filename)它,只有Photograph(filename)在文件名不存在的情况下它才会调用Photograph.instances.
有人知道我在哪里出错吗?有没有更好的方法来做到这一点?
考虑它的另一种方式是它类似于单例,除了它不是全局单例,只是单个文件名.
这是我使用staticmethod得到的真实世界代码,如果你想一起看到它.
bra*_*zzi 17
让我们看看关于你的问题的两点.
你可以使用memoization,但你应该装饰类,而不是__init__方法.假设我们有这个备忘录:
def get_id_tuple(f, args, kwargs, mark=object()):
"""
Some quick'n'dirty way to generate a unique key for an specific call.
"""
l = [id(f)]
for arg in args:
l.append(id(arg))
l.append(id(mark))
for k, v in kwargs:
l.append(k)
l.append(id(v))
return tuple(l)
_memoized = {}
def memoize(f):
"""
Some basic memoizer
"""
def memoized(*args, **kwargs):
key = get_id_tuple(f, args, kwargs)
if key not in _memoized:
_memoized[key] = f(*args, **kwargs)
return _memoized[key]
return memoized
Run Code Online (Sandbox Code Playgroud)
现在你只需要装饰这个类:
@memoize
class Test(object):
def __init__(self, somevalue):
self.somevalue = somevalue
Run Code Online (Sandbox Code Playgroud)
让我们看一下测试?
tests = [Test(1), Test(2), Test(3), Test(2), Test(4)]
for test in tests:
print test.somevalue, id(test)
Run Code Online (Sandbox Code Playgroud)
输出如下.请注意,相同的参数会返回返回对象的相同ID:
1 3072319660
2 3072319692
3 3072319724
2 3072319692
4 3072319756
Run Code Online (Sandbox Code Playgroud)
无论如何,我更愿意创建一个函数来生成对象并记住它.对我来说似乎更干净,但它可能是一些无关紧要的宠儿:
class Test(object):
def __init__(self, somevalue):
self.somevalue = somevalue
@memoize
def get_test_from_value(somevalue):
return Test(somevalue)
Run Code Online (Sandbox Code Playgroud)
__new__:或者,当然,你可以覆盖__new__.几天前,我发布了一个关于覆盖的输入,输出和最佳实践的答案,__new__这可能会有所帮助.基本上,它总是传递*args, **kwargs给你的__new__方法.
举个例子,我更喜欢记住一个创建对象的函数,甚至编写一个特定的函数,它将负责永远不会将对象重新创建到同一个参数.然而,当然,这主要是我的意见,而不是一个规则.
我最终使用的解决方案是这样的:
class memoize(object):
def __init__(self, cls):
self.cls = cls
self.__dict__.update(cls.__dict__)
# This bit allows staticmethods to work as you would expect.
for attr, val in cls.__dict__.items():
if type(val) is staticmethod:
self.__dict__[attr] = val.__func__
def __call__(self, *args):
key = '//'.join(map(str, args))
if key not in self.cls.instances:
self.cls.instances[key] = self.cls(*args)
return self.cls.instances[key]
Run Code Online (Sandbox Code Playgroud)
然后,您可以使用此装饰类,而不是__init__。尽管brandizzi为我提供了这些关键信息,但他的示例装饰器并未按预期运行。
我发现这个概念相当微妙,但是基本上,当您在Python中使用装饰器时,您需要了解被装饰的东西(无论是方法还是类)实际上是由装饰器本身代替的。因此,例如,当我尝试访问Photograph.instancesor Camera.generate_id()(一种静态方法)时,由于Photograph实际上未引用原始的Photograph类,因此实际上无法访问它们,而是引用了该memoized 函数(来自brandizzi的示例)。
为了解决这个问题,我必须创建一个装饰器类,该装饰器类实际上从装饰性类中获取所有属性和静态方法,并将它们作为自己的属性公开。几乎就像一个子类,不同的是装饰器类不提前知道它将装饰什么类,因此它必须在事后复制属性。
最终结果是,memoize该类的任何实例都变成了围绕它装饰的实际类的几乎透明的包装器,只是尝试实例化它(但实际上调用它)会在可用时为您提供缓存的副本。
| 归档时间: |
|
| 查看次数: |
8052 次 |
| 最近记录: |