Я использую 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? Полностью ли я отношусь к своим нуждам неправильно?