如何在Python中记忆类实例化?

如何在Python中记忆类实例化?,python,caching,singleton,unique,memoization,Python,Caching,Singleton,Unique,Memoization,好的,这里是真实世界的场景:我正在编写一个应用程序,我有一个表示某种类型文件的类(在我的例子中,这是照片,但细节与问题无关)。photo类的每个实例对于照片的文件名都应该是唯一的 问题是,当用户告诉我的应用程序加载文件时,我需要能够识别文件已加载的时间,并使用该文件名的现有实例,而不是在同一文件名上创建重复实例 对我来说,这似乎是一个很好的使用记忆的情况,并且有很多这样的例子,但在这种情况下,我不仅仅是在记忆一个普通函数,我需要记忆\uuuu init\uu()。这带来了一个问题,因为当调用\u

好的,这里是真实世界的场景:我正在编写一个应用程序,我有一个表示某种类型文件的类(在我的例子中,这是照片,但细节与问题无关)。photo类的每个实例对于照片的文件名都应该是唯一的

问题是,当用户告诉我的应用程序加载文件时,我需要能够识别文件已加载的时间,并使用该文件名的现有实例,而不是在同一文件名上创建重复实例

对我来说,这似乎是一个很好的使用记忆的情况,并且有很多这样的例子,但在这种情况下,我不仅仅是在记忆一个普通函数,我需要记忆
\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]