Подкласс с наследованием переменных класса - PullRequest
0 голосов
/ 24 октября 2018

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

Но результат не тот, который я ожидал;в следующем примере у меня есть два набора родительских классов плюс дочерние классы, и некоторый код, чтобы продемонстрировать, что идет не так:

class P:
    _X = 0

    @classmethod
    def cm(cls):
        print("In p cm")
        cls._X += 1

class C1(P):
    pass

class C2(P):
    pass

class Image:
    _callbacks = {}

    @classmethod
    def registerDataFormat(cls, fmt, loader):
        if fmt in cls._callbacks.keys():
            print("The %s format has already been registered." % (fmt))
            return False

        cls._callbacks[fmt] = {}
        cls._callbacks[fmt]["loader"] = loader

class HSImage(Image):
    pass

class GT(Image):
    pass

if __name__ == '__main__':
    C1.cm()
    print(C1._X)
    print(P._X)
    C2.cm()
    print(C2._X)
    print(P._X)

    HSImage.registerDataFormat("mat", "loader 1")
    print(HSImage._callbacks)
    print(Image._callbacks)
    GT.registerDataFormat("mat", "loader 2")
    print(GT._callbacks)
    print(Image._callbacks)

Вот результаты:

In p cm
1
0
In p cm
1
0
{'mat': {'loader': 'loader 1'}}
{'mat': {'loader': 'loader 1'}}
The mat format has already been registered.
{'mat': {'loader': 'loader 1'}}
{'mat': {'loader': 'loader 1'}}

Первый примеримеет ожидаемый результат, но не второй, почему переменная класса используется совместно с родительским классом, когда я вызываю метод класса дочернего класса во втором наборе классов?

Мои ожидаемые результаты:

In p cm
1
0
In p cm
1
0
{'mat': {'loader': 'loader 1'}}
{}
{'mat': {'loader': 'loader 2'}}
{}

1 Ответ

0 голосов
/ 24 октября 2018

Разница в том, что вы мутировали в словарь .Первый простой пример с целыми числами работает с неизменяемыми целочисленными объектами.cls._X += 1 принимает значение _X (из родительского класса, если необходимо), после чего операция old + 1 создает новый целочисленный объект, который затем присваивается обратно cls._X.Назначение здесь имеет значение, так как это будет иметь место в дочернем классе.

Но вы ничего не присваивали классу со вторым регистром:

cls._callbacks[fmt] = {}
cls._callbacks[fmt]["loader"] = loader

Вы назначены наключ в словаре.Сам атрибут cls._callbacks не изменяется, это один и тот же словарь, общий для всех классов.Ссылка cls._callbacks ищется в базовом классе Image, после чего сам словарь обновляется путем добавления пары ключ-значение.Ни один из подклассов (HSImage или GT) не имеет атрибута самостоятельно.

Вам потребуется создать копию и присвоить измененную копию:

cls._callbacks = {k: dict(v) for k, v in cls._callbacks.items()}
cls._callbacks[fmt] = {'loader': loader}

Это создаетскопируйте не только внешний словарь, но и все значения, потому что все они тоже словари, перед добавлением нового словаря для fmt.Затем копия присваивается cls._callbacks, эффективно создавая новый атрибут в подклассе, если его там еще не было.

Конечно, это не так эффективно;копия создается каждый раз при регистрации загрузчика .Было бы лучше создать новый словарный объект _callback для каждого подкласса, но это становится утомительным и может быть легко забыто.Вместо этого вы можете автоматизировать , используя метод __init_subclass__ :

class Image:
    def __init_subclass__(cls):
        cls._callbacks = {}

    @classmethod
    def registerDataFormat(cls, fmt, loader):
        if fmt in cls._callbacks:
            print("The {} format has already been registered.".format(fmt))
            return

        cls._callbacks[fmt] = {'loader': loader}

Для каждого создаваемого подкласса вызывается метод __init_subclass__.

...