小能豆

为什么一个 Pytest 中实例化的对象会影响另一个 Pytest 中实例化的另一个对象?

python

我正在测试一个包含一些处理性能计数器的方法的类。这是一个普通的 Python 类:

class S3Db:
    data = {}
    fs = None
    s3_path = None

    def __init__(self, s3_api_key, s3_api_secret, bucket_name, filename, bucket_path='/'):
        if s3_api_key != 'test':
            self.fs = s3fs.S3FileSystem(s3_api_key, secret=s3_api_secret)
            self.s3_path = f's3://{bucket_name}{bucket_path}{filename}'
            self.data = read_json_s3_path(self.fs, self.s3_path)
        pass

    def increment_timed_counter(self, counter_name: str):
        ...

我的测试如下所示(请注意,在每个测试开始时都会再次创建测试对象):

def test_can_increment_timed_counter():
    s3db = S3Db('test', 'testsecret', 'bucket', 'filename')

    s3db.init_timed_counter('Test Counter 1', 1000)

    val = s3db.increment_timed_counter('Test Counter 1')
    assert val == 1
    assert s3db.get_timed_counter('Test Counter 1') == 1


def test_can_init_timed_counters():
    # NOTE I CREATE A NEW INSTANCE HERE, SHOULD BE BRAND NEW?
    s3db = S3Db('test', 'testsecret', 'bucket', 'filename')

    s3db.init_timed_counter('Test Counter 2', 1000)

    assert s3db.data == {
        'timed_counters': {
            'Test Counter 2': {
                'name': 'Test Counter 2',
                'expiration_ms': 1000,
                'values': []
            }
        }
    }

但第二个测试失败了:

E            'timed_counters': {'Test Counter 2': {'expiration_ms': 1000,
E                                                'name': 'Test Counter 2',
E         -                                      'values': []}},
E         ?                                                   -
E         +                                      'values': []},
E         +                     'Test Counter 1': {'expiration_ms': 1000,
E         +                                        'name': 'Test Counter 1',
E         +                                        'values': [854055045839803]}},
E           }

指示在第一次测试中创建的计数器保留在第二次测试中正在测试的对象上。

如果我在第二次测试中创建一个新对象,为什么会发生这种情况?难道第一个对象不应该被垃圾收集而第二个对象是该类的全新实例吗?

我使用的是Python 3.9


阅读 139

收藏
2023-07-21

共1个答案

小能豆

在你的代码中,问题出现在类变量data的定义。类变量是在类的定义中创建,并在类的所有实例之间共享。因此,data在每个S3Db实例之间是相同的,即使你在__init__方法中重新定义了data为一个空字典,它仍然是类变量,会影响所有实例。

解决这个问题的方法是将data定义为实例变量,这样每个实例都会有自己的data字典。修改代码如下:

class S3Db:
    fs = None
    s3_path = None

    def __init__(self, s3_api_key, s3_api_secret, bucket_name, filename, bucket_path='/'):
        self.data = {}  # Move data from class level to instance level
        if s3_api_key != 'test':
            self.fs = s3fs.S3FileSystem(s3_api_key, secret=s3_api_secret)
            self.s3_path = f's3://{bucket_name}{bucket_path}{filename}'
            self.data = read_json_s3_path(self.fs, self.s3_path)
        pass

    def increment_timed_counter(self, counter_name: str):
        ...

# Rest of the code remains unchanged

在这个修正后的版本中,data成为一个实例变量,它只与每个实例相关,而不是与整个类相关。这样,每次创建S3Db对象时,都会有一个新的、空的data字典。

在进行测试时,你会发现现在第二个测试应该通过了,因为每个测试都有一个独立的实例,并且它们的data不会相互干扰。

2023-07-21