Python类:覆盖`self`

2022-03-13 00:00:00 python python-3.x self globals storage

问题描述

在我的python脚本中,我有一个存储Processo对象的全局存储(一个简单的全局dict)。它是在我的程序执行过程中填满的。由于性能原因,它的存在是为了避免创建重复的Processo对象。

因此,对于class Processo,我希望在创建期间验证它是否已在全局存储上。

在这种情况下,我只想将其复制到self。为此,我使用getfromStorage()

class Processo:
    def __init__(self, name, ...): # ... for simplicity
       self.processoname = name
       self = getfromStorage(self)

不知道它是否有用,但是.

def getfromStorage(processo):
    if processo.processoname in process_storage:
        return process_storage[processo.processoname]
    return processo

我如何实现这一点?是我遗漏了什么,还是我的设计有误?


解决方案

无法使用__init__合理地完成此模式,因为__init__仅初始化已存在的对象,并且您无法更改调用方将获得的内容(您可以重新绑定self,但这只会将您与正在创建的对象切断,调用方有自己的单独别名,不受影响)。

正确的做法是覆盖实际的构造函数__new__,它允许您返回您可能创建也可能不创建的新实例:

class Processo:
    def __new__(cls, name, ...): # ... for simplicity
       try:
           # Try to return existing instance from storage
           return getfromStorage(name)
       except KeyError:
           pass

       # No instance existed, so create new object
       self = super().__new__(cls)  # Calls parent __new__ to make empty object

       # Assign attributes as normal
       self.processoname = name

       # Optionally insert into storage here, e.g. with:
       self = process_storage.setdefault(name, self)
       # which will (at least for name of built-in type) atomically get either then newly
       # constructed self, or an instance that was inserted by another thread
       # between your original test and now
       # If you're not on CPython, or name is a user-defined type where __hash__
       # is implemented in Python and could allow the GIL to swap, then use a lock
       # around this line, e.g. with process_storage_lock: to guarantee no races

       # Return newly constructed object
       return self

为了减少开销,我略微重写了getfromStorage,所以它只接受名称并执行查找,如果失败则允许泡沫异常:

def getfromStorage(processoname):
    return process_storage[processoname]

这意味着,当可以使用缓存的实例时,不需要重新构造不必要的self对象。

注意:如果这样做,通常最好根本不定义__init__;对象的构造是通过调用类的__new__,然后对结果隐式调用__init__来完成的。对于缓存的实例,您不希望重新初始化它们,因此需要一个空的__init__(这样缓存的实例就不会因为从缓存中检索而被修改)。将所有的__init__类行为都放在__new__内部构造和返回新对象的代码中,并且只对新对象执行,以避免此问题。

相关文章