You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何消除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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 18:50:29