如何在 Python 中记住类实例化? [英] How can I memoize a class instantiation in Python?

查看:22
本文介绍了如何在 Python 中记住类实例化?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Ok, here is the real world scenario: I'm writing an application, and I have a class that represents a certain type of files (in my case this is photographs but that detail is irrelevant to the problem). Each instance of the Photograph class should be unique to the photo's filename.

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

The problem is, when a user tells my application to load a file, I need to be able to identify when files are already loaded, and use the existing instance for that filename, rather than create duplicate instances on the same filename.

对我来说,这似乎是使用记忆的好方法,并且有很多这样的例子,但在这种情况下,我不仅仅是记忆一个普通的函数,我需要记忆 __init__().这带来了一个问题,因为当 __init__() 被调用时已经太晚了,因为已经创建了一个新实例.

To me this seems like a good situation to use memoization, and there's a lot of examples of that out there, but in this case I'm not just memoizing an ordinary function, I need to be memoizing __init__(). This poses a problem, because by the time __init__() gets called it's already too late as there's a new instance created already.

在我的研究中,我发现了 Python 的 __new__() 方法,并且我实际上能够编写一个有效的简单示例,但是当我尝试在我的真实世界对象上使用它时它崩溃了,而且我不知道为什么(我唯一能想到的是我的现实世界对象是我无法真正控制的其他对象的子类,因此与这种方法存在一些不兼容).这就是我所拥有的:

In my research I found Python's __new__() method, and I was actually able to write a working trivial example, but it fell apart when I tried to use it on my real-world objects, and I'm not sure why (the only thing I can think of is that my real world objects were subclasses of other objects that I can't really control, and so there were some incompatibilities with this approach). This is what I had:

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>

太完美了!只为给定的两个唯一 id 创建了两个实例,而 Flub.instances 显然只列出了两个.

It's perfect! Only two instances were made for the two unique id's given, and Flub.instances clearly only has two listed.

但是当我尝试对我正在使用的对象采用这种方法时,我遇到了关于 __init__() 如何只采用 0 个参数而不是 2 个参数的各种荒谬错误.所以我会改变一些事情然后它会告诉我 __init__() 需要一个参数.太奇怪了.

But when I tried to take this approach with the objects I was using, I got all kinds of nonsensical errors about how __init__() took only 0 arguments, not 2. So I'd change some things around and then it would tell me that __init__() needed an argument. Totally bizarre.

在与它斗争了一段时间后,我基本上只是放弃并将所有的__new__() 黑魔法移动到一个名为get 的静态方法中,这样我就可以调用Photograph.get(filename) 如果文件名不在 Photograph.instances 中,它只会调用 Photograph(filename).

After a while of fighting with it, I basically just gave up and moved all the __new__() black magic into a staticmethod called get, such that I could call Photograph.get(filename) and it would only call Photograph(filename) if filename wasn't already in Photograph.instances.

有人知道我哪里出错了吗?有没有更好的方法来做到这一点?

Does anybody know where I went wrong here? Is there some better way to do this?

另一种思考方式是它类似于单例,只是它不是全局单例,只是每个文件名单例.

Another way of thinking about it is that it's similar to a singleton, except it's not globally singleton, just singleton-per-filename.

如果需要,这是我使用静态方法 get 的真实代码一起看.

推荐答案

让我们看看关于您的问题的两点.

Let us see two points about your question.

你可以使用记忆化,但你应该装饰,而不是__init__方法.假设我们有这个备忘录:

You can use memoization, but you should decorate the class, not the __init__ method. Suppose we have this memoizator:

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

现在你只需要装饰这个类:

Now you just need to decorate the class:

@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:

The output is below. Note that the same parameters yield the same id of the returned object:

1 3072319660
2 3072319692
3 3072319724
2 3072319692
4 3072319756

无论如何,我更愿意创建一个函数来生成对象并记住它.对我来说似乎更干净,但它可能是一些无关紧要的宠物:

Anyway, I would prefer to create a function to generate the objects and memoize it. Seems cleaner to me, but it may be some irrelevant pet peeve:

class Test(object):
    def __init__(self, somevalue):
        self.somevalue = somevalue

@memoize
def get_test_from_value(somevalue):
    return Test(somevalue)

使用__new__:

或者,当然,您可以覆盖 __new__.几天前,我发布了 关于覆盖 __new__ 的来龙去脉和最佳实践的答案可能会有所帮助.基本上,它说总是将 *args, **kwargs 传递给您的 __new__ 方法.

Using __new__:

Or, of course, you can override __new__. Some days ago I posted an answer about the ins, outs and best practices of overriding __new__ that can be helpful. Basically, it says to always pass *args, **kwargs to your __new__ method.

例如,我更愿意记住一个创建对象的函数,或者甚至编写一个特定的函数来处理永远不会重新创建相同参数的对象.当然,但是,这主要是我的意见,而不是规则.

I, for one, would prefer to memoize a function which creates the objects, or even write a specific function which would take care of never recreating a object to the same parameter. Of course, however, this is mostly a opinion of mine, not a rule.

这篇关于如何在 Python 中记住类实例化?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆