Python Класс: перезаписать `self` - PullRequest
1 голос
/ 03 марта 2020

В моем скрипте python у меня есть глобальное хранилище (простое глобальное dict), в котором хранятся Processo объекты. Он заполняется во время исполнения моей программы. Он существует, чтобы избежать создания повторяющихся 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

Как мне этого добиться? Я что-то упустил или мой дизайн не так?

Ответы [ 2 ]

5 голосов
/ 04 марта 2020

Этот шаблон не может быть выполнен разумно с __init__, потому что __init__ только инициализирует уже существующий объект, и вы не можете изменить то, что получит вызывающий (вы можете выполнить повторную привязку * 1005) *, но это просто отсекает вас от создаваемого объекта, вызывающая сторона имеет свой собственный отдельный псевдоним, который не изменяется).

Правильный способ сделать это - переопределить фактический конструктор , __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

       # Return newly constructed object
       return self

Чтобы уменьшить накладные расходы, я слегка переписал getfromStorage, поэтому просто берет имя и выполняет поиск, позволяя исключению всплыть в случае сбоя:

def getfromStorage(processoname):
    return process_storage[processoname]

, что означает, что при использовании кэшированного экземпляра не требуется заново создавать ненужный объект self.

Примечание. Если вы это сделаете, обычно лучше вообще не определять __init__; построение объекта выполняется путем вызова класса __new__, а затем неявным образом вызова __init__ результата. Для кэшированных экземпляров вы не хотите, чтобы они были повторно инициализированы, поэтому вам нужен пустой __init__ (поэтому кэшированный экземпляр не изменяется в силу того, что он извлекается из кэша). Поместите все __init__ -подобное поведение в код, который создает и возвращает новый объект внутри __new__, и выполняйте его только для новых объектов, чтобы избежать этой проблемы.

2 голосов
/ 04 марта 2020

Первый вопрос, который я хотел бы задать, - почему метод getfromStorage не предоставляется пользователю с реализацией:

def getfromStorage(name):
    if name in process_storage:
        return process_storage[name]
    return Processo(name)

И Processo определяется как:

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

Похоже, это решит вашу проблему. Но если вы не хотите или не можете раскрыть основное кэширование, тогда:

Другой способ, аналогичный тому, что вы пытаетесь сделать

Я считаю, что шаблон, который подходит Ваша потребность в так называемом паттерне Bridge, также известном как паттерн Handle / Body. Цитируя из Design Patterns by Gamma и др. , этот шаблон используется в различных обстоятельствах, среди которых, например, «когда вы хотите поделиться реализацией между несколькими объектами и этот факт должен быть скрыт от клиента или вы всегда хотите, чтобы клиент обращался к фактической реализации "body" через объект "handle" по какой-то другой причине. Объект "handle" сохраняет ссылку на реализация "body" и делегирует все вызовы этой реализации. Конечно, несколько дескрипторов могут ссылаться на одно и то же тело. В приведенном ниже примере я использовал абстрактный базовый класс, который действительно не нужен с утилитой Python

from abc import ABC, abstractmethod

class Processo(ABC):

    @abstractmethod
    def __init__(self, name): raise NotImplementedError

    @abstractmethod
    def get_name(self): raise NotImplementedError

    @abstractmethod
    def foo(self): raise NotImplementedError


class Processo_Handle(Processo): # the handle class
    def __init__(self, name):
        self.processo = getfromStorage(name)

    def get_name(self):
        return self.processo.get_name() # delegate to "body" object

    def foo(self):
        self.processo.foo() # delegate to "body" object
        return self # don't return result of the call to foo because it's the wrong instance (i.e. the body)


class Processo_Body(Processo): # the bodyclass
    def __init__(self, name):
        self.name = name

    def get_name(self):
        return self.name

    def foo(self):
        """ do whatever it takes """
        return self

process_storage = {} # dummy implementation    
def getfromStorage(processoname):
    if processoname in process_storage:
        return process_storage[processoname]
    return Processo_Body(processoname)
...