Add a list.swap_values method

I would like to propose adding a new built-in method to Python lists for swapping two specific values: list.swap_values(v1, v2, all_occurrences=True).

Currently, swapping values within a list requires complex loops or long list comprehensions. I have designed a solution that allows developers to easily swap all matching values or choose to swap only the first occurrence of each value by setting all_occurrences=False.

Here is the code that shows how this feature should behave:

class Switchlist:
    def __init__(self,existing_list):
        self.list = existing_list 

    def swap_values(self,v1,v2,all_occurrences=True):
        if all_occurrences:
            return [v2 if x==v1 else v1 if x==v2 else x for x in self.list]
        new_list=[]
        changed_v1=False
        changed_v2=False
        for item in self.list:
            if item==v1 and not changed_v1:
                new_list.append(v2)
                changed_v1=True
            elif item==v2 and not changed_v2:
                new_list.append(v1)
                changed_v2=True
            else:
                new_list.append(item)
        return new_list

# Example for both cases:
my_list=["a","b","c","a","b","d"]
sm_all=Switchlist(my_list).swap_values("a","b")
print(f"Original list: {my_list}")
print(f"New list (all): {sm_all}")
# Output: ['b','a','c','b','a','d']

# Swapping only the first occurrence
sm_first=Switchlist(my_list).swap_values("a","b",all_occurrences=False)
print(f"New list (first only): {sm_first}")
# Output: ['b','a','c','a','b','d']

I believe making this a built-in method would make the code more readable for everyone and especially beginners

1 Like

This seems like an incredibly niche requirement. I don’t think I’ve ever seen code that has needed to do this. Obviously, any individual codebase can implement the functionality as a local function, so the question is whether it’s important enough to justify being in the stdlib.

Do you have evidence that this is a common requirement - i.e., that there are a lot of projects that are currently implementing this for themselves? Because without such evidence, it’s unlikely that it’ll be added to the stdlib, much less added as a method on the core list type.

5 Likes

i can think of a few things this method can help with:

Imagine a board game or a grid-based game where two players swap positions on the map, or we need to switch active pieces/states (e.g., swapping “Player 1” and “Player 2”, or changing tile types). With the current approach:

grid = [“Player1”, “Empty”, “Player2”, “Empty”]

grid = [“Player2” if x == “Player1” else “Player1” if x == “Player2” else x for x in grid]

With `swap_values`:

grid = [“Player1”, “Empty”, “Player2”, “Empty”]

grid.swap_values(“Player1”, “Player2”)

With that approach it’s way more readable and obvious

2. When processing datasets, it’s incredibly common to find inconsistent or flipped labels. For example, if a data entry error swapped “Male” and “Female” codes, or “Passed” and “Failed” statuses in a specific batch of records. Instead of writing a complex loop to fix the corrupted batch: status_records = [“Passed”, “Passed”, “Failed”, “Passed”]

status_records.swap_values(“Passed”, “Failed”)

While those examples aren’t ‘evidence’ it is a situation that can exist in a code and i think it’s a fast and more readable solution.

Thank you for the feedback.

The list methods that result in a changed list all do so in place, by mutation of the input list. They return None. A new changed-list method should do the same. From the title, I expected the proposal was to swap a pair of adjacent elements. Still, one might identify the pair either by position or value, and the identified item might be switched either forward or backward. More variations are possible.

As for examples, I would expect tile changing to usually be a replacement, not a swap. In my decade of experience with datasets from multiple investigators, I can’t really remember an instance of flipped labels.

Your list comprehension doesn’t look long to me. The loop also not that complex, though I might do one of these instead:

        a = self.list
        b = a.copy()
        with suppress(ValueError):
            b[a.index(v1)] = v2
        with suppress(ValueError):
            b[a.index(v2)] = v1
        return b
        b = self.list + [v1]
        i1 = b.index(v1)
        b[-1] = v2
        b[b.index(v2)] = v1
        b[i1] = v2
        b.pop()
        return b
1 Like

I’ve encountered similar label-swapping cases in small data-cleaning scripts, but the list-comprehension form is already concise and keeps the operation explicit. A built-in would need a very strong, broadly applicable use case to justify adding another method to the core list API.

1 Like

If all members of the list can be dict keys, (are hashable), then you can do this:

my_list=["a","b","c","a","b","d"]

switcher = {'a': 'b', 'b': 'a'}
converted = [switcher.get(item, item) for item in my_list]

assert converted == ['b', 'a', 'c', 'b', 'a', 'd']
3 Likes

And for the “only once” case:

list(map({v1: v2, v2: v1}.pop, my_list, my_list))

In OO theory, you should only add methods (rather than bare functions) for things that cannot be done with the public interface. By that principle, this should not be a method.

Should all of append, clear, copy, count, extend, index, insert, pop, remove, reverse and sort (that’s all the non-dunders) also not be methods then? I can do everything they do without using any of them.

1 Like

That’s a fair point :smiley: !

For better or worse, Python has deviated from that theory by adding methods to mitigate imports class class calls. It’s a matter of taste, and it’s hard to argue about taste.

Also, many of the methods in your list could only otherwise be done with fairly opaque assignment expressions, and while I agree that’s part of the public interface, it’s still worth having a method. Maybe I should have said: you can do it legibly using the public interface.

If it were up to me, I would have gone with fewer methods. Also, a lot of methods were added over time, and just because a new method supplants an older one, doesn’t mean that you can then remove the older one (unfortunately).

Anyway, for this case, I think given that this would be an extremely rare operation, the cost of an import and class call are far too small to justify making this a method. It’s not even worth making this a standard library bare function, IMO.

OO theory is often at odds with practicality, and we know from PEP 20 which side Python comes down on. IMO methods are perfectly fine when they (a) are closely tied to a specific data type, and (b) are of sufficiently general value that they would be used by a large proportion of people using that data type. The choice of whether something should be a method or a global function is often not clear-cut (for example, we have list.sort as a method, but sorted() as a function), but my second point is broadly applicable to builtin functions as well (you wouldn’t, for example, want to have to import globals() from somewhere, as it’s a pretty important function).

IMO this particular proposal doesn’t clear that bar. The generality of comprehensions makes them a little more verbose than a dedicated single-purpose function/method, but that’s also what means you don’t have fifty bajillion individual functions to have to wade through to find the one you want. Notably in this case, I don’t think all_occurrences is a good parameter here; the two code branches have exactly zero overlap, so if you’re planning to set it, just use the comprehension directly. (Personally I’d probably write it with a translation dictionary.) The situation where that’s NOT set is fairly unusual, and I really don’t think that there needs to be a builtin or method to do this.

Side note: I don’t believe that this would be an improvement for beginner programmers. When you’re starting out, you don’t read through every available method before coding; every additional method you have to read about makes things worse for beginners. (A lot of those methods are worthwhile because of how beneficial they are once you have the familiarity to use them, but that still steepens the learning curve.) If you’re teaching Python in a context where one of these specific tools is actually helpful, consider instead making a utilities library available to your students; stick things in there to help them get started, and keep it small and focused, so there’s not too much to comprehend.

3 Likes

Search for later criticisms of “OO theory” and there are many. Decide if and what of their principles work for you, and listen to the detractors too.I like Python in that I can mix-n-match between paradigms and hopefully end up with a maintainable result.

Single method classes? Deep inheritance?? Hidden mutable state and concurrency??? Factory this, singleton that, adapter whatever,AbstractSingletonProxyFactoryBean) ????

1 Like

First time posting.

Hi dev3, just to remind you on your first post here that you are welcome, and the opinions are on your idea - not on you. It can feel personal, but isn’t meant to be, it’s just technical peeps giving technical responses on the proposal.

6 Likes

Hi dev3,

if I found myself in your situation, I would consider creating a /lib/swap_list.py utility like this:

def swapped_first[T](it: Iterable[T], item1: T, item2: T) -> list[T]:
    lst = list(it)
    i = lst.index(item1)
    j = lst.index(item2)
    lst[i], lst[j] = lst[j], lst[i]
    return lst

def swapped_all[T](it: Iterable[T], item1: T, item2: T) -> list[T]:
    return [item1 if x == item2 else item2 if x == item1 else x
            for x in it]

class SwapList[T](list[T]):  # optional, if you like OO
    def swap_first(self, item1: T, item2: T) -> None:
        self[:] = swapped_first(self, item1, item2)
    def swap_all(self, item1: T, item2: T) -> None:
        self[:] = swapped_all(self, item1, item2)

Most importantly, I think the complexity of writing down these helpers is proportional to many times I would actually reach for them. Therefore, these should not become part of the standard library.


Additionally, this may be an example of an XY-problem. If you are doing a lot of swapping, maybe you chose the wrong abstractions and what you really need is some indirection?

@dataclasses.dataclass
class Slot:
    player: str

acting = Slot("Player1")
responding = Slot("Player2")
empty = Slot("")

turn = [acting, responding]
grid = [acting, empty, responding, empty]
...

# swap
acting.player, responding.player = responding.player, acting.player

Hi Paddy3118 i know the opinions are on my idea and not on me, i don’t usually take things personally and from what i read in the comments i understand that this is not the right place for this idea and i should do smth else with it. thank you for your comment!

5 Likes