Более эффективный подход к принуждению пользователей абстрактного класса к определению значения c для атрибута класса - PullRequest
0 голосов
/ 08 февраля 2020

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

Все эти подклассы должны иметь атрибут экземпляра (my_value) и атрибут класса с именем LENGTH.

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

Первая попытка получить этот результат состояла в том, чтобы использовать в аннотации класс, декораторы @property, @classmethod и @abstractmethod таким образом:

class SuperAbstractClass(ABC):
[...]
    @property
    @classmethod
    @abstractmethod
    def LENGTH(cls):
        return NotImplementedError

Таким образом, я решил проблему: когда вы определяете подкласс, вы также должны определить LENGTH.

Я также добавил новую проблему, связанную с тем, что в абстрактном классе я также переопределяю метод __setattr__ magi c, чтобы убедиться, что значение подкласса всегда соответствует значению LENGTH заполнение my_value нулями (используя метод zfill):

    def __setattr__(self, name, value):
        '''
        Every time a user will assign a value to "my_value", "__setattr__" will ensure
        that the value get filled by zeros to match the "LENGTH" value
        (example: with LENGTH=4, the value "A" will become "000A")
        '''
        if name == 'my_value':
            super(SuperAbstractClass, self).__setattr__(
                                                        'my_value',
                                                        value.zfill(SuperAbstractClass.LENGTH)
                                                        )
        else: # all the other assignment will not be modified
            super(SuperAbstractClass, self).__setattr__(name, value)

Таким образом, я получаю сообщение об ошибке, потому что zfill говорит мне, что нужно int и в то время LENGTH - это свойство.

Теперь я нашел решение, которое не является идеальным: пользователи моего абстрактного класса имеют для переопределения __init__, но в идеале они могут избежать определения LENGTH (в этом примере LENGTH теперь называется length_to_reach), а также у меня есть один и тот же код для каждого подкласса:

from abc import ABC, abstractmethod

class SuperAbstractClass(ABC):
    '''
    We want to force all subclasses to redefine "__init__" adding an hardcoded
    length to the "my_value" attribute: ideally the subclasses users should
    only be able define "my_value", not the "length_to_reach"
    '''
    @abstractmethod
    def __init__(self, my_value, length_to_reach):
        self.length_to_reach = length_to_reach
        self.my_value = my_value # NB: assignation managed by __setattr__

    def __setattr__(self, name, value):
        '''
        Every time a user will assign a value to "my_value", "__setattr__" will ensure
        that the value get filled by zeros to match the "length_to_reach" value
        (example: with length_to_reach=4, the value "A" will become "000A")
        '''
        if name == 'my_value':
            super(SuperAbstractClass, self).__setattr__(
                                                        'my_value',
                                                        value.zfill(self.length_to_reach)
                                                        )
        else: # all the other assignment will not be modified
            super(SuperAbstractClass, self).__setattr__(name, value)


class SubClassOne(SuperAbstractClass):
    LENGTH = 12
    def __init__(self, my_value):
        super(SubClassOne, self).__init__(my_value, SubClassOne.LENGTH)

class SubClassTwo(SuperAbstractClass):
    LENGTH = 9
    def __init__(self, my_value):
        super(SubClassTwo, self).__init__(my_value, SubClassTwo.LENGTH)


if __name__ == '__main__':
    a = SubClassOne("ValueOne")
    b = SubClassTwo("ValueTwo")
    print(f'\na.my_value: {a.my_value} is now filled to the length of {a.LENGTH}')
    print(f'\nb.my_value: {b.my_value} is now filled to the length of {b.LENGTH}')

#*Output:*
#
#a.my_value: 0000ValueOne is now filled to the length of 12
#
#b.my_value: 0ValueTwo is now filled to the length of 9

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

...