如何在Python中记忆类实例化?
好的,这里是真实世界的场景:我正在编写一个应用程序,我有一个表示某种类型文件的类(在我的例子中,这是照片,但细节与问题无关)。photo类的每个实例对于照片的文件名都应该是唯一的 问题是,当用户告诉我的应用程序加载文件时,我需要能够识别文件已加载的时间,并使用该文件名的现有实例,而不是在同一文件名上创建重复实例 对我来说,这似乎是一个很好的使用记忆的情况,并且有很多这样的例子,但在这种情况下,我不仅仅是在记忆一个普通函数,我需要记忆如何在Python中记忆类实例化?,python,caching,singleton,unique,memoization,Python,Caching,Singleton,Unique,Memoization,好的,这里是真实世界的场景:我正在编写一个应用程序,我有一个表示某种类型文件的类(在我的例子中,这是照片,但细节与问题无关)。photo类的每个实例对于照片的文件名都应该是唯一的 问题是,当用户告诉我的应用程序加载文件时,我需要能够识别文件已加载的时间,并使用该文件名的现有实例,而不是在同一文件名上创建重复实例 对我来说,这似乎是一个很好的使用记忆的情况,并且有很多这样的例子,但在这种情况下,我不仅仅是在记忆一个普通函数,我需要记忆\uuuu init\uu()。这带来了一个问题,因为当调用\u
\uuuu init\uu()
。这带来了一个问题,因为当调用\uuuu init\uuuu()
时已经太晚了,因为已经创建了一个新实例
在我的研究中,我发现了Python的\uuuuu new\uuuuuu()
方法,我实际上能够编写一个简单的工作示例,但当我尝试在现实世界的对象上使用它时,它崩溃了,我不知道为什么(我能想到的唯一一件事是,我的真实世界对象是我无法真正控制的其他对象的子类,因此这种方法存在一些不兼容之处)。这就是我所拥有的:
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()
哪个输出:
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>
制作一个新的!
139958663753808
139958663753808
做一个新的!
139958663753872
真假
谋杀
谋杀
太完美了!只为给定的两个唯一id创建了两个实例,而Flub.instances显然只列出了两个
但是,当我尝试对我正在使用的对象采用这种方法时,我得到了各种各样的荒谬错误,关于\uuuu init\uuuu()
如何只接受0个参数,而不是2个参数。所以我会改变一些事情,然后它会告诉我\uuuu init\uuuuu()
需要一个参数。完全奇怪
经过一段时间的斗争,我基本上放弃了,把所有的\uuuu new\uuuu()
黑魔法转移到一个名为get
的静态方法中,这样我就可以调用photo.get(filename)
,如果文件名不在photo.instances
中,它只会调用photo(filename)
有人知道我哪里出错了吗?有没有更好的方法
另一种思考方式是,它类似于单例,只是它不是全局单例,只是每个文件名的单例
如果您想一起查看。将
\uuuuu new\uuuuu
的参数也传递到\uuuu init\uuuuu
,因此:
def __init__(self, flubid):
...
您需要接受那里的flubid
参数,即使您没有在\uuuuu init\uuuuu
以下是有关评论摘自
关于你的问题,让我们看两点 使用备忘录 您可以使用memorization,但应该修饰类,而不是
\uuuuu init\uuuu
方法。假设我们有这个memorizator:
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
现在,您只需装饰课堂:
@memoize
class Test(object):
def __init__(self, somevalue):
self.somevalue = somevalue
让我们看看测试
tests = [Test(1), Test(2), Test(3), Test(2), Test(4)]
for test in tests:
print test.somevalue, id(test)
输出如下。请注意,相同的参数产生相同的返回对象id:
1 3072319660
2 3072319692
3 3072319724
2 3072319692
4 3072319756
无论如何,我更愿意创建一个函数来生成对象并将其记忆。对我来说似乎更干净,但这可能是一些不相关的恼怒:
class Test(object):
def __init__(self, somevalue):
self.somevalue = somevalue
@memoize
def get_test_from_value(somevalue):
return Test(somevalue)
使用\uuuu new\uuuu
:
当然,你也可以重写\uuuuuu new\uuuuuu
。几天前我发布了一个有用的帖子。基本上,它说总是将*args、**kwargs
传递给你的\uuuu new\uuuuu
方法
一、 例如,我更喜欢记忆一个创建对象的函数,甚至编写一个特定的函数,该函数将永远不会为同一个参数重新创建对象。当然,这主要是我的观点,而不是规则。我最终使用的解决方案是:
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]
然后你用这个来装饰这个类,而不是\uuu init\uuu
我发现这个概念相当微妙,但基本上当你在Python中使用装饰器时,你需要理解被装饰的东西(无论是方法还是类)实际上被装饰器本身所取代。例如,当我尝试访问photo.instances
或Camera.generate\u id()
(一种静态方法),我实际上无法访问它们,因为photo
实际上并没有引用原始的photo类,它引用了memorized
函数(来自brandizzi的示例)
为了解决这个问题,我必须创建一个decorator类,该类实际上从decorator类中获取所有属性和静态方法,并将它们公开为自己的。几乎像一个子类,只是decorator类事先不知道要修饰什么类,所以它必须在事后复制属性
最终的结果是,
memoize
类的任何实例都会成为它所修饰的实际类的几乎透明的包装器,除了尝试实例化它(但真正调用它)之外当缓存副本可用时,将为您提供缓存副本。您所说的有道理,但是我的小示例在没有定义\uuuu init\uuuuu
的情况下是如何工作的?它不应该也会给我错误的参数传递数吗?@Robru,我用typeobject.c
中给出的解释更新了我的答案。谢谢。我没有意识到你可以直接将装饰器放在类上而不是方法上。这是我所缺少的关键信息。你的memoize装饰器不是我所需要的,因为字符串不像数字那样是单态的(因此,id
s在相同的字符串之间不是唯一的),但出于简化的需要,我可以直接使用第一个参数作为键
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]