Одно дополнение к ответу @nailxx:
Вы можете установить __test__ = False
в родительском классе, а затем использовать метакласс (см. Этот вопрос снекоторые блестящие объяснения) установить его обратно в True при создании подклассов.
(Наконец, я нашел оправдание использованию метакласса!)
Хотя __test__
являетсяАтрибут двойного подчеркивания, мы должны явно установить его на True
, так как его установка не заставит python просто искать атрибут дальше по MRO и оценивать его как False
.
Таким образом, нам нужно проверить в экземпляре класса, имеет ли один из родительских классов __test__ = False
.Если это так и текущее определение класса не установило __test__
, мы добавим '__test__': True
к атрибуту dict.
Полученный код выглядит следующим образом:
class TestWhenSubclassedMeta(type):
"""Metaclass that sets `__test__` back to `True` when subclassed.
Usage:
>>> class GenericTestCase(TestCase, metaclass=TestWhenSubclassed):
... __test__ = False
...
... def test_something(self):
... self.fail("This test is executed in a subclass, only.")
...
...
>>> class SpecificTestCase(GenericTestCase):
... pass
"""
def __new__(mcs, name, bases, attrs):
ATTR_NAME = '__test__'
VALUE_TO_RESET = False
RESET_VALUE = True
values = [getattr(base, ATTR_NAME) for base in bases
if hasattr(base, ATTR_NAME)]
# only reset if the first attribute is `VALUE_TO_RESET`
try:
first_value = values[0]
except IndexError:
pass
else:
if first_value == VALUE_TO_RESET and ATTR_NAME not in attrs:
attrs[ATTR_NAME] = RESET_VALUE
return super().__new__(mcs, name, bases, attrs)
Можно расширить это до некоторого более неявного поведения, например «если имя начинается с Abstract
, установите __test__ = False
автоматически», но я бы для себя оставил явное назначение для ясности.
ПозвольтеЯ вставляю простые юнит-тесты, чтобы продемонстрировать поведение - и как напоминание, что всем нужно потратить две минуты, чтобы протестировать свой код после введения функции.
from unittest import TestCase
from .base import TestWhenSubclassedMeta
class SubclassesTestCase(TestCase):
def test_subclass_resetted(self):
class Base(metaclass=TestWhenSubclassedMeta):
__test__ = False
class C(Base):
pass
self.assertTrue(C.__test__)
self.assertIn('__test__', C.__dict__)
def test_subclass_not_resetted(self):
class Base(metaclass=TestWhenSubclassedMeta):
__test__ = True
class C(Base):
pass
self.assertTrue(C.__test__)
self.assertNotIn('__test__', C.__dict__)
def test_subclass_attr_not_set(self):
class Base(metaclass=TestWhenSubclassedMeta):
pass
class C(Base):
pass
with self.assertRaises(AttributeError):
getattr(C, '__test__')