Изменить `self` для другого экземпляра того же объекта? - PullRequest
0 голосов
/ 15 ноября 2018

Я хочу создать класс, и все объекты должны иметь уникальный идентификатор key, и если я пытаюсь создать новый экземпляр объекта с ранее существующим ключом, экземпляр должен быть таким же, как тот что уже существует.

Похоже на одноэлементный класс, но в этом случае вместо одного класса их много, но они разные.

Мой первый подход был такой

class Master:
    existent = {}
    def __init__(self, key, value=None):
        try:
            self = Master.existent[key]
            return 
        except KeyError:
            Master.existent[key] = self
        # Rest of the __init__ method

Но когда я сравниваю два объекта, что-то вроде этого A = Master('A', 0) и B = Master('B', 0), B не разделяет никаких атрибутов, которые он должен иметь, и если в мастер-классе есть _method (одно подчеркивание) , Также не появляется.

Есть идеи, как я мог это сделать?

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

EDIT:

У класса в основном есть два свойства, и это все, но многие вещи могут наследовать и / или содержать экземпляры этого типа, то есть, easy , как я думал, я мог сделать это, извлекать свойства из существующий экземпляр, соответствующий указанному key, присвоение их новому экземпляру и злоупотребление тем, что они будут иметь одинаковый вывод hash, а оператор equal будет вести себя в соответствии с хэшами, поэтому я могу использовать == и is операторов без проблем.

Эта идея решает мою проблему, но в целом я думаю, что это может быть общий или достаточно интересный сценарий для решения.

Ответы [ 3 ]

0 голосов
/ 15 ноября 2018

Вы можете использовать метод __new__, чтобы справиться с этим.Вы не хотите вызывать __init__, если только вы не хотите создать новый объект с новым ключом, и __new__ можно использовать, чтобы сначала проверить, является ли ключ уникальным, перед вызовом __init__.

class Master(object):

    instances = {}
    def __new__(cls, key, value=None):
        if key in Master.instances:
            return Master.instances[key]
        else:
            instance = super(Master, cls).__new__(cls)
            Master.instances[key] = instance
            return instance

    def __init__(self, key, value=None):
        self.value = value

Затем вы можете создавать объекты с помощью

>>> A = Master('A',0)
>>> B = Master('B',0)
>>> C = Master('A',1)

Поскольку A и C имеют одинаковый ключ, они будут указывать на один и тот же объект и будут эффективнотот же экземпляр.Поскольку C имеет тот же ключ, что и A, он обновляет свое значение.

>>> print(A.value)
1

Любые новые изменения в A будут видны в C и наоборот.

>>> A.value = 5
>>> print(C.value)
5

Но изменения A и C не влияют на B, а изменения B не влияют на A или C.

Редактировать:

Если вы хотите копировать значения, но не экземпляры, вы можете просто сохранить значения в словаре Master.instances и проверить, есть ли уже значения для ключа.

class Master(object):

    instances = {}
    def __init__(self, key, value=None):
        if key in Master.instances:
            self.value = Master.instances[key]
        else:
            self.value = value
            Master.instances[key] = value

>>> A = Master('A',0)
>>> C = Master('A',1)
>>> print(C.value)
0
0 голосов
/ 15 ноября 2018

Вдохновленный ответом от A Kruger, у меня есть другое решение, основанное на использовании метода __new__, как предложено. Основное отличие в этом ответе состоит в том, что нет необходимости создавать внутренний класс __Master. Метод __new__ вызывается автоматически, когда вызывается Master(), и ожидается, что он вернет экземпляр класса Master. В моем ответе метод __new__ возвращает новый экземпляр, если это необходимо, но возвращает экземпляр из словаря existent, если это возможно. Обратите внимание, что пользователь обращается к классу Master как обычно, то есть он просто вызывает Master('A', 0). Это стало возможным благодаря расширению класса Master object.

Вот код:

class Master(object):
    existent = {}

    def __init__(self, key, value=None):
        self.key = key
        self.value = value
        if not key in Master.existent:
            Master.existent[key] = self

    def __new__(cls, *args, **kwargs):
        key = args[0]
        if key in Master.existent:
            return Master.existent[key]
        else:
            return super(Master, cls).__new__(cls)

    def __str__(self):
        return('id: ' + str(id(self)) + ', key=' + str(self.key) + ', value=' + str(self.value))

A = Master('A', 0)
print('A = ' + str(A))
B = Master('A', 1)
print('\nAfter B created:')
print('B = ' + str(B))
print('A = ' + str(A))
B.value = 99
print('\nAfter B modified:')
print('B = ' + str(B))
print('A = ' + str(A))
C = Master('C', 3)
print('\nC = ' + str(C))

А вот и вывод:

A = id: 140023450750200, key=A, value=0

After B created:
B = id: 140023450750200, key=A, value=1
A = id: 140023450750200, key=A, value=1

After B modified:
B = id: 140023450750200, key=A, value=99
A = id: 140023450750200, key=A, value=99

C = id: 140023450750256, key=C, value=3

Обратите внимание, что A и B имеют одинаковые id (это один и тот же объект). Также обратите внимание, что изменения A или B влияют друг на друга, так как это один и тот же объект.

0 голосов
/ 15 ноября 2018

Я не думаю, что вы можете сделать это, используя метод __init__(), потому что новый экземпляр класса уже был создан при вызове этого метода.Вам, вероятно, нужно создать метод фабричного типа, например:

class Master:
    existent = {}
    init_OK = False

    def __init__(self, key, value=None):
        if not Master.init_OK:
            raise Exception('Direct call to Master() is not allowed')
        Master.init_OK = False
        self.key = key
        self.value = value
        Master.existent[key] = self

    @staticmethod
    def make(key, value=None):
        try:
            inst = Master.existent[key]
        except:
            Master.init_OK = True
            inst = Master(key, value=value)
        return inst
...