如何消除Python中仅继承不同的两个相同类的冗余代码?
问题描述
我有两个Python类,仅继承的基类不同(分别是TestCase和TransactionTestCase),但二者重写的方法完全一致。我试过用全局公共方法减少冗余,但还是过不了JSCPD检测。现在想只保留一份重写方法的实现,同时让两个类分别重写对应基类的方法,就像C++的类模板那样。当前代码能正常运行,但冗余问题和JSCPD报错必须解决。
原冗余代码如下:
import time from django.test import TestCase, TransactionTestCase LONG_TEST_THRESH_SECS = 20 LONG_TEST_ALERT_STR = f" [ALERT > {LONG_TEST_THRESH_SECS}]" class TracebaseTestCase(TestCase): maxDiff = None databases = "__all__" def setUp(self): self.testStartTime = time.time() def tearDown(self): _reportRunTime(self.id(), self.testStartTime) def setUpClass(self): self.classStartTime = time.time() def setUpTestData(self): _reportRunTime(f"{self.__class__.__name__}.setUpTestData", self.classStartTime) class Meta: abstract = True class TracebaseTransactionTestCase(TransactionTestCase): maxDiff = None databases = "__all__" def setUp(self): self.testStartTime = time.time() def tearDown(self): _reportRunTime(self.id(), self.testStartTime) def setUpClass(self): self.classStartTime = time.time() def setUpTestData(self): _reportRunTime(f"{self.__class__.__name__}.setUpTestData", self.classStartTime) class Meta: abstract = True def _reportRunTime(id, startTime): t = time.time() - startTime heads_up = "" # String to include for tests that run too long if t > LONG_TEST_THRESH_SECS: heads_up = LONG_TEST_ALERT_STR print("TEST TIME%s: %s: %.3f" % (heads_up, id, t))
我设想的实现方式是这样的,不知道可行不可行:
class abstractBaseClass(<base class input>): # 在这里定义公共方法 # 如果能实现这种"类模板",我还想把reportRunTime改成成员函数 class TracebaseTestCase(abstractBaseClass(TestCase)): pass class TracebaseTransactionTestCase(abstractBaseClass(TransactionTestCase)): pass
补充说明:两个类需要分别重写对应基类的setUp、tearDown等方法,执行时机由基类控制。
解决方案
你的思路完全可行,Python可以通过类工厂函数实现类似C++类模板的效果,这种方式直观简单,完全满足需求。
实现方式
1. 类工厂函数实现核心逻辑
写一个函数,接收基类作为参数,动态生成包含公共方法的抽象类:
import time from django.test import TestCase, TransactionTestCase LONG_TEST_THRESH_SECS = 20 LONG_TEST_ALERT_STR = f" [ALERT > {LONG_TEST_THRESH_SECS}]" def create_tracebase_test_class(base_class): class TracebaseTest(base_class): maxDiff = None databases = "__all__" def setUp(self): self.testStartTime = time.time() # 必须调用基类setUp,否则会覆盖Django测试框架的初始化逻辑 super().setUp() def tearDown(self): _reportRunTime(self.id(), self.testStartTime) # 必须调用基类tearDown,保证测试清理步骤正常执行 super().tearDown() @classmethod def setUpClass(cls): cls.classStartTime = time.time() super().setUpClass() def setUpTestData(self): _reportRunTime(f"{self.__class__.__name__}.setUpTestData", self.classStartTime) super().setUpTestData() class Meta: abstract = True return TracebaseTest # 生成两个目标类 TracebaseTestCase = create_tracebase_test_class(TestCase) TracebaseTransactionTestCase = create_tracebase_test_class(TransactionTestCase) def _reportRunTime(id, startTime): t = time.time() - startTime heads_up = "" if t > LONG_TEST_THRESH_SECS: heads_up = LONG_TEST_ALERT_STR print("TEST TIME%s: %s: %.3f" % (heads_up, id, t))
2. 把_reportRunTime封装为成员函数(可选)
如果想把时间报告逻辑内聚到类中,直接修改工厂函数里的类即可:
def create_tracebase_test_class(base_class): class TracebaseTest(base_class): maxDiff = None databases = "__all__" LONG_TEST_THRESH_SECS = 20 LONG_TEST_ALERT_STR = f" [ALERT > {LONG_TEST_THRESH_SECS}]" def setUp(self): self.testStartTime = time.time() super().setUp() def tearDown(self): self._report_run_time(self.id(), self.testStartTime) super().tearDown() @classmethod def setUpClass(cls): cls.classStartTime = time.time() super().setUpClass() def setUpTestData(self): self._report_run_time(f"{self.__class__.__name__}.setUpTestData", self.classStartTime) super().setUpTestData() def _report_run_time(self, id_str, start_time): t = time.time() - start_time heads_up = "" if t > self.LONG_TEST_THRESH_SECS: heads_up = self.LONG_TEST_ALERT_STR print("TEST TIME%s: %s: %.3f" % (heads_up, id_str, t)) class Meta: abstract = True return TracebaseTest
关键注意点
- 必须调用
super().xxx():比如setUp和tearDown方法,否则会完全覆盖Django测试基类的原有逻辑,导致测试框架的初始化、清理步骤失效。 - 工厂函数返回的是抽象类,和你原来的代码逻辑完全一致,不影响后续子类继承使用。
- 这种方式彻底消除了代码冗余,JSCPD检测不会再报重复代码的错误。
内容的提问来源于stack exchange,提问作者hepcat72
相关产品推荐
相关产品推荐

