Skip to content

weird behavior with tuples indirect parameterization #8281

Description

@giladkr

I found the combination of python 3.7 and current version of pytest (6.2.2) to act weird among indirect parameterization with tuples.

The following test:

import pytest

@pytest.fixture(scope="session")
def something(request):
    print(f'\ninitialized with {request.param}')
    return request.param
    
@pytest.mark.parametrize('something', ['abcd', (1,2)], indirect=True)
def test_one(something):
    print(f'test_one {something}')

@pytest.mark.parametrize('something', ['abcd', (1,2)], indirect=True)
def test_two(something):
    print(f'test_two {something}')

class TestSomething:
    @pytest.mark.parametrize('something', ['abcd', (1,2)], indirect=True)
    def test_three(self, something):
        print(f'test_three {something}')

Outputs:

$ pytest -s
============================= test session starts ==============================
platform linux -- Python 3.7.9, pytest-6.2.2, py-1.10.0, pluggy-0.13.0
rootdir: /home/ubuntu/Desktop/tmp
collected 6 items                                                              

test_something.py 
initialized with abcd
test_one abcd
.test_two abcd
.test_three abcd
.
initialized with (1, 2)
test_one (1, 2)
.test_two (1, 2)
.
initialized with (1, 2)
test_three (1, 2)
.

============================== 6 passed in 0.01s ===============================

The fixture was created twice with the same parameter (1, 2) although it is scoped to the session.

I've tested this in python 3.8 also, and there the fixture is initialized once per parameter (as expected).

Activity

  1. unxmaster commented on Jan 27, 2021

    @unxmaster

    I have it with different python and pytest versions and the behavior between the versions is inconsistent

    • I have added additional test_four inside the class exactly the same as test_three
    • Python version 3.9 behaves the same as python 3.8
    • Pytest version 5.4.3 behaves the same as version 6.2.2

    Looks like pytest 4.6.11 is the only one that behave consistently in different python versions

    import pytest
    
    @pytest.fixture(scope="session")
    def something(request):
        print(f'\n\tinitialized with {request.param}')
        return request.param
    
    
    @pytest.mark.parametrize('something', ['abcd', (1,2), [1,2]], indirect=True)
    def test_one(something):
        print(f'test_one {something}')
    
    @pytest.mark.parametrize('something', ['abcd', (1,2), [1,2]], indirect=True)
    def test_two(something):
        print(f'test_two {something}')
    
    
    class TestSomething:
        @pytest.mark.parametrize('something', ['abcd', (1,2), [1,2]], indirect=True)
        def test_three(self, something):
            print(f'test_three {something}')
    
        @pytest.mark.parametrize('something', ['abcd', (1,2), [1,2]], indirect=True)
        def test_four(self, something):
            print(f'test_four {something}')
    ============================= test session starts ==============================
    platform linux -- Python 3.6.12, pytest-4.6.11, py-1.10.0, pluggy-0.13.1
    rootdir: /home
    collected 12 items
    
    test_something.py
    	initialized with abcd
    test_one abcd
    .test_two abcd
    .test_three abcd
    .test_four abcd
    .
    	initialized with (1, 2)
    test_one (1, 2)
    .test_two (1, 2)
    .test_three (1, 2)
    .test_four (1, 2)
    .
    	initialized with [1, 2]
    test_one [1, 2]
    .test_two [1, 2]
    .test_three [1, 2]
    .test_four [1, 2]
    .
    
    ========================== 12 passed in 0.06 seconds ===========================
    
    ============================= test session starts ==============================
    platform linux -- Python 3.6.12, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
    rootdir: /home
    collected 12 items
    
    test_something.py
    	initialized with abcd
    test_one abcd
    .test_two abcd
    .test_three abcd
    .test_four abcd
    .
    	initialized with (1, 2)
    test_one (1, 2)
    .
    	initialized with (1, 2)
    test_two (1, 2)
    .
    	initialized with (1, 2)
    test_three (1, 2)
    .
    	initialized with (1, 2)
    test_four (1, 2)
    .
    	initialized with [1, 2]
    test_one [1, 2]
    .
    	initialized with [1, 2]
    test_two [1, 2]
    .
    	initialized with [1, 2]
    test_three [1, 2]
    .
    	initialized with [1, 2]
    test_four [1, 2]
    .
    
    ============================== 12 passed in 0.04s ==============================
    
    ============================= test session starts ==============================
    platform linux -- Python 3.7.9, pytest-4.6.11, py-1.10.0, pluggy-0.13.1
    rootdir: /home
    collected 12 items
    
    test_something.py
    	initialized with abcd
    test_one abcd
    .test_two abcd
    .test_three abcd
    .test_four abcd
    .
    	initialized with (1, 2)
    test_one (1, 2)
    .test_two (1, 2)
    .test_three (1, 2)
    .test_four (1, 2)
    .
    	initialized with [1, 2]
    test_one [1, 2]
    .test_two [1, 2]
    .test_three [1, 2]
    .test_four [1, 2]
    .
    
    ========================== 12 passed in 0.05 seconds ===========================
    
    ============================= test session starts ==============================
    platform linux -- Python 3.7.9, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
    rootdir: /home
    collected 12 items
    
    test_something.py
    	initialized with abcd
    test_one abcd
    .test_two abcd
    .test_three abcd
    .test_four abcd
    .
    	initialized with (1, 2)
    test_one (1, 2)
    .test_two (1, 2)
    .
    	initialized with (1, 2)
    test_three (1, 2)
    .test_four (1, 2)
    .
    	initialized with [1, 2]
    test_one [1, 2]
    .
    	initialized with [1, 2]
    test_two [1, 2]
    .
    	initialized with [1, 2]
    test_three [1, 2]
    .
    	initialized with [1, 2]
    test_four [1, 2]
    .
    
    ============================== 12 passed in 0.06s ==============================
    
    ============================= test session starts ==============================
    platform linux -- Python 3.8.7, pytest-4.6.11, py-1.10.0, pluggy-0.13.1
    rootdir: /home
    collected 12 items
    
    test_something.py
    	initialized with abcd
    test_one abcd
    .test_two abcd
    .test_three abcd
    .test_four abcd
    .
    	initialized with (1, 2)
    test_one (1, 2)
    .test_two (1, 2)
    .test_three (1, 2)
    .test_four (1, 2)
    .
    	initialized with [1, 2]
    test_one [1, 2]
    .test_two [1, 2]
    .test_three [1, 2]
    .test_four [1, 2]
    .
    
    ========================== 12 passed in 0.08 seconds ===========================
    
    ============================= test session starts ==============================
    platform linux -- Python 3.8.7, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
    rootdir: /home
    collected 12 items
    
    test_something.py
    	initialized with abcd
    test_one abcd
    .test_two abcd
    .test_three abcd
    .test_four abcd
    .
    	initialized with (1, 2)
    test_one (1, 2)
    .test_two (1, 2)
    .test_three (1, 2)
    .test_four (1, 2)
    .
    	initialized with [1, 2]
    test_one [1, 2]
    .
    	initialized with [1, 2]
    test_two [1, 2]
    .
    	initialized with [1, 2]
    test_three [1, 2]
    .
    	initialized with [1, 2]
    test_four [1, 2]
    .
    
    ============================== 12 passed in 0.04s ==============================
    
  2. bluetech commented on Jan 28, 2021

    @bluetech
    Member

    I haven't read the issue in detail yet, but this might be similar to what's discussed here: #8102 (comment)

    Specifically, pytest uses is to check whether to reexecute a fixture. And python does not guarantee that two equal tuples will have the same identity. You can check what happens if you pull out the tuple to a global variable e.g. TUPLE = (1, 2) and replace all of the distinct (1, 2)s with it.

  3. Zac-HD commented on Jul 22, 2026

    @Zac-HD
    Member

    Closing as resolved by #12600 (released in pytest 8.3.0): fixture cache-key comparison was changed from is (object identity) back to == (value equality, with an is fallback only if == raises), so an equal-but-distinct (1, 2) tuple across indirect-parametrize calls now hits the cache instead of re-initializing the fixture, exactly as this issue described.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    topic: parametrizerelated to @pytest.mark.parametrizetype: bugproblem that needs to be addressed

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions