# \[split out\] Deprecating arbitrary equality

**URL:** <https://discuss.python.org/t/split-out-deprecating-arbitrary-equality/104935>\
**Category:** Standards\
**Created:** [November 16, 2025, 5:02pm UTC](https://discuss.python.org/t/split-out-deprecating-arbitrary-equality/104935 "2025-11-16T17:02:31Z")\
**Posts on this page:** 16\
**Page:** 1

<div class="post-metadata">

**Author:** ![dimbleby](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/dimbleby/32/25970_2.png) [@dimbleby](https://discuss.python.org/u/dimbleby)\
**Post date:** [November 16, 2025, 5:02pm UTC](https://discuss.python.org/t/split-out-deprecating-arbitrary-equality/104935/1 "2025-11-16T17:02:31Z")

</div>

Are there any existing tools that support arbitrary equality as the spec describes it?

Trying the spec example of `===foobar`, all of pip and uv and poetry fail, with an error message indicating that this is an invalid version number.

What are we even trying to be backwards-compatible with?

If no-one has implemented this for all these years then I guess that is no objection to changing the text.

However I am tempted to counter-propose that arbitrary equality just be removed altogether…

---

<div class="post-metadata">

**Author:** ![pf\_moore](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/pf_moore/32/35_2.png) [@pf\_moore](https://discuss.python.org/u/pf_moore)\
**Post date:** [November 16, 2025, 5:05pm UTC](https://discuss.python.org/t/split-out-deprecating-arbitrary-equality/104935/2 "2025-11-16T17:05:28Z")

</div>

> [@dimbleby](#):
>
> However I am tempted to counter-propose that arbitrary equality just be removed altogether…

That _would_ require a PEP. Are you willing to write one?

---

<div class="post-metadata">

**Author:** ![dimbleby](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/dimbleby/32/25970_2.png) [@dimbleby](https://discuss.python.org/u/dimbleby)\
**Post date:** [November 16, 2025, 5:08pm UTC](https://discuss.python.org/t/split-out-deprecating-arbitrary-equality/104935/3 "2025-11-16T17:08:29Z")

</div>

Lets start with the other part! What are we even trying to be backwards compatible with?

While I sort of feel as though no-one will care what an unimplemented part of the spec says, I also feel that specs should not be moving targets.

If no-one is using this feature anyway, then I am not sure that changing its meaning is well justified.

---

<div class="post-metadata">

**Author:** ![notatallshaw](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/notatallshaw/32/10861_2.png) [@notatallshaw](https://discuss.python.org/u/notatallshaw)\
**Post date:** [November 16, 2025, 5:56pm UTC](https://discuss.python.org/t/split-out-deprecating-arbitrary-equality/104935/4 "2025-11-16T17:56:28Z")

</div>

> [@dimbleby](#):
>
> Are there any existing tools that support arbitrary equality as the spec describes it?
> 
> Trying the spec example of `===foobar`, all of pip and uv and poetry fail, with an error message indicating that this is an invalid version number.
> 
> What are we even trying to be backwards-compatible with?

I’m pretty sure this used to work on pip prior to v24.1, and as described in the OP, setuptool’s `parse_version` and packaging’s `LegacyVersion` are what I’m trying to match backwards compatibility with.

I could not tell you the real world usage, but from my personal experience there are a large number of users still on legacy versions of packaging tools.

> [@dimbleby](#):
>
> However I am tempted to counter-propose that arbitrary equality just be removed altogether…

Please start a new thread for that then, I don’t want this thread to get confused with splintering discussions.

---

<div class="post-metadata">

**Author:** ![dimbleby](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/dimbleby/32/25970_2.png) [@dimbleby](https://discuss.python.org/u/dimbleby)\
**Post date:** [November 16, 2025, 6:10pm UTC](https://discuss.python.org/t/split-out-deprecating-arbitrary-equality/104935/5 "2025-11-16T18:10:04Z")

</div>

> [@notatallshaw](#):
>
> I’m pretty sure this used to work on pip prior to v24.1

That is easily checked: and yes it did.

Nevertheless - if this has already been not working for 18 months (and my cursory search found no issue reports) - that somewhat weakens the case for backwards compatibility being important.

---

<div class="post-metadata">

**Author:** ![jwodder](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jwodder/32/236_2.png) [@jwodder](https://discuss.python.org/u/jwodder)\
**Post date:** [November 16, 2025, 6:10pm UTC](https://discuss.python.org/t/split-out-deprecating-arbitrary-equality/104935/6 "2025-11-16T18:10:32Z")

</div>

> [@notatallshaw](#):
>
> setuptool’s `parse_version` and packaging’s `LegacyVersion` are what I’m trying to match backwards compatibility with.

Do those actually _currently_ support non-PEP 440 versions? `packaging` removed `LooseVersion` a year or two ago (I can’t find this mentioned in the changelog), and `setuptools` has rejected non-PEP 440 versions since v64, released in August 2022.

---

<div class="post-metadata">

**Author:** ![notatallshaw](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/notatallshaw/32/10861_2.png) [@notatallshaw](https://discuss.python.org/u/notatallshaw)\
**Post date:** [November 16, 2025, 6:36pm UTC](https://discuss.python.org/t/split-out-deprecating-arbitrary-equality/104935/7 "2025-11-16T18:36:35Z")

</div>

> [@dimbleby](#):
>
> if this has already been not working for 18 months

What’s the relevancy of that to the spec?

And in my own practical experience, I’ve worked with a team that has standardized on packaging tools over 5 years old, especially if they are using LTE redistributions of packaging tools.

> [@dimbleby](#):
>
> that somewhat weakens the case for backwards compatibility being important.

I never made _that_ case for backwards compatibility, I made the case (multiple times, including in the OP) that backward compatibility is important because the spec explicitly says that’s the intent of arbitrary equality:

> This operator is special and acts as an escape hatch to allow someone using a tool which implements this specification to still install a **legacy version** which is otherwise incompatible with this specification.

In general, I think your points are will suited to arguing that arbitrary equality be removed from the spec, but aren’t really that relevant to the discussion of clarifying the intent of the existing spec.

> [@Clarify that arbitrary equality is supposed to be case insensitive](https://discuss.python.org/t/clarify-that-arbitrary-equality-is-supposed-to-be-case-insensitive/104885/19):
>
> Do those actually _currently_ support non-PEP 440 versions? `packaging` removed `LooseVersion` a year or two ago (I can’t find this mentioned in the changelog), and `setuptools` has rejected non-PEP 440 versions since v64, released in August 2022.

`packaging` removed `LegacyVersion` not `LooseVersion` (that was an old distutils class), but `Specifier` still accepts arbitrary strings, and still accepts arbitrary equality.

As for `setuptools`, that’s a tool UX choice, and I am supportive of that, it is not, however it is not a specification question, which is what I am trying to address here.

---

<div class="post-metadata">

**Author:** ![konstin](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/konstin/32/20207_2.png) [@konstin](https://discuss.python.org/u/konstin)\
**Post date:** [November 17, 2025, 12:10pm UTC](https://discuss.python.org/t/split-out-deprecating-arbitrary-equality/104935/8 "2025-11-17T12:10:08Z")

</div>

The arbitrary equality operator is “heavily discouraged” by spec and may display a warning (it’s implemented in uv but hidden by default). I’m not aware of it being used anyway, and over a decade after PEP 440, we’re not seeing non-PEP 440 legacy versions anymore. I’d favor deprecating arbitrary equality properly over investing into it.

---

<div class="post-metadata">

**Author:** ![pf\_moore](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/pf_moore/32/35_2.png) [@pf\_moore](https://discuss.python.org/u/pf_moore)\
**Post date:** [November 17, 2025, 12:48pm UTC](https://discuss.python.org/t/split-out-deprecating-arbitrary-equality/104935/9 "2025-11-17T12:48:50Z")

</div>

I repeat - if you want to do that, by all means open a new thread.

~~For _this_ thread, I would say “do nothing” - apart from the fact that the `packaging` behaviour is at a minimum not clearly allowed by the spec, and is arguably in violation. The minimum\[1\] reasonable fix is a quick text-only PEP change, and that involves very little investment, _except_ for the time being spent addressing “let’s not do this, let’s deprecate arbitrary equality instead” comments 🙁~~

I’m not against deprecating arbitrary equality, I just personally don’t care enough to invest time into doing so (although obviously as PEP delegate, I will invest time if someone else writes a PEP).

Edit: Updated this post to reflect that it was split off into a new thread.

* * *

1. Changing `packaging` is a much bigger change

---

<div class="post-metadata">

**Author:** ![jamestwebber](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jamestwebber/32/12799_2.png) [@jamestwebber](https://discuss.python.org/u/jamestwebber)\
**Post date:** [November 18, 2025, 10:41pm UTC](https://discuss.python.org/t/split-out-deprecating-arbitrary-equality/104935/10 "2025-11-18T22:41:45Z")

</div>

> [@pf\_moore](#):
>
> I repeat - if you want to do that, by all means open a new thread.
> 
> For _this_ thread, I would say “do nothing”

In case anyone else is confused by this and other comments – @brettcannon split these posts into this thread and gave it the title!

---

<div class="post-metadata">

**Author:** ![notatallshaw](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/notatallshaw/32/10861_2.png) [@notatallshaw](https://discuss.python.org/u/notatallshaw)\
**Post date:** [November 18, 2025, 10:44pm UTC](https://discuss.python.org/t/split-out-deprecating-arbitrary-equality/104935/11 "2025-11-18T22:44:34Z")

</div>

> [@jamestwebber](#):
>
> @brettcannon split these posts into this thread and gave it the title!

Yeah, I appreciate trying to clean up the other thread my comments don’t make much sense in this context. I am not against deprecating arbitrary equality but it’s also not something I am trying to achieve or spend my time on.

I may delete all my comments in this thread, in case someone later reads this and wonders why I am being quoted but not appearing on this thread.

---

<div class="post-metadata">

**Author:** ![sirosen](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/sirosen/32/8478_2.png) [@sirosen](https://discuss.python.org/u/sirosen)\
**Post date:** [November 19, 2025, 3:59am UTC](https://discuss.python.org/t/split-out-deprecating-arbitrary-equality/104935/12 "2025-11-19T03:59:05Z")

</div>

Maybe edit the posts to add a footer, rather than remove them? My concern would be that things get even more confusing.

* * *

To this new thread, about deprecating arbitrary equality, it seems like a reasonable change, but I question the value of pursuing it.

Arbitrary equality doesn’t significantly impact the complexity of tooling, does it? So we’d be removing it in pursuit of spec purity, not for any practical gain?

If there’s a better reason than “theoretical purity” to remove it, I’d be willing to help write or co-author the requisite PEP and coach the change through tools.

---

<div class="post-metadata">

**Author:** ![notatallshaw](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/notatallshaw/32/10861_2.png) [@notatallshaw](https://discuss.python.org/u/notatallshaw)\
**Post date:** [November 21, 2025, 8:38pm UTC](https://discuss.python.org/t/split-out-deprecating-arbitrary-equality/104935/13 "2025-11-21T20:38:43Z")

</div>

> [@sirosen](#):
>
> Arbitrary equality doesn’t significantly impact the complexity of tooling, does it? So we’d be removing it in pursuit of spec purity, not for any practical gain?

Working on a static [unsatisfiability check](https://github.com/pypa/packaging/issues/940) in packaging, arbitrary equality is a minor annoyance (and what led me to wanting to clarify the spec) but not a major one. If we’re getting rid of vestigial parts of the spec for cleanliness I would also remove epochs.

The real major annoyance for tooling is the complexity around exclusive ordering operators, pre-releases, and post-releases. For example:

- The ordering `0.9b1 < 1.0b1 < 1.0b2 < 1.0` is true
- Intuitively, out these versions, `0.9b1` and `1.0b1` matches the specifier `<1.0b2`
- But, out of these versions, only `0.9b1` matches the specifier `<1.0`

These kind of quirky behaviors make implementations error prone, and it possibly difficult to translate to literature on satisfiability and other solutions in other ecosystems to Python versions and specifiers.

However, reworking these parts of the spec would need broad agreement from tooling authors, and tool design or other spec choices would need to be made to ensure a good user experience.

---

<div class="post-metadata">

**Author:** ![f3flight](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/f3flight/32/32680_2.png) [@f3flight](https://discuss.python.org/u/f3flight)\
**Post date:** [November 22, 2025, 1:11am UTC](https://discuss.python.org/t/split-out-deprecating-arbitrary-equality/104935/14 "2025-11-22T01:11:33Z")

</div>

I would like to highlight one (two) use case where using arbitrary equality works (with pip and sort of with uv, with a caveat) and is necessary right now - handling build suffixes and sub-patch (internal patched) versions.  
Example:

Index contains files:

- torch-1.2.3.tar.gz (using tar.gz for example simplicity)
- torch-1.2.3+cpu.tar.gz
- torch-1.2.4.0+cpu.tar.gz
- torch-1.2.4.0+cu128.tar.gz

Running “uv pip compile \<(echo ‘torch==1.2.3’)” results in “torch-1.2.3+cpu” picked up. The only way to resolve “torch-1.2.3.tar.gz” is to use “pip install torch===1.2.3” or “uv pip compile \<(echo ‘torch===1.2.3’) . This works.  
The other case is - if user tries to install a package which is not in the index, but they need that exact package version. Trying to resolve “torch==1.2.4” results in torch-1.2.4.0+cu128.tar.gz. Here also === helps. However I found that behavior deviates between pip and uv - uv will pick up .0 version as long as suffix matches, even if === is used. Pip will not.

Some examples from real life:

```python
(venv) [/tmp/tmp34u1frv7/venv]$ pip download torch==2.9.1 --no-deps
Looking in indexes: <redacted>
Collecting torch==2.9.1
  Downloading <redacted>/torch-2.9.1.0%2Bcu128-cp312-cp312-linux_x86_64.whl.metadata (29 kB)
Downloading <redacted>/torch-2.9.1.0%2Bcu128-cp312-cp312-linux_x86_64.whl (614.2 MB)
   ━━━━━━━━━━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━ 201.3/614.2 MB 106.8 MB/s eta 0:00:04
# Ctrl+C
ERROR: Operation cancelled by user

(venv) [/tmp/tmp34u1frv7/venv]$ pip download torch===2.9.1 --no-deps
Looking in indexes: <redacted>
ERROR: Could not find a version that satisfies the requirement torch===2.9.1 (from versions: 2.9.1.0+cpu, 2.9.1.0+cu128)
ERROR: No matching distribution found for torch===2.9.1

(venv) [/tmp/tmp34u1frv7/venv]$ pip download torch===2.9.1+cpu --no-deps
Looking in indexes: <redacted>
ERROR: Could not find a version that satisfies the requirement torch===2.9.1+cpu (from versions: 2.9.1.0+cpu, 2.9.1.0+cu128)
ERROR: No matching distribution found for torch===2.9.1+cpu

(venv) [/tmp/tmp34u1frv7/venv]$ pip download torch==2.9.1+cpu --no-deps
Looking in indexes: <redacted>
Collecting torch==2.9.1+cpu
  Downloading <redacted>/torch-2.9.1.0%2Bcpu-cp312-cp312-linux_x86_64.whl.metadata (29 kB)
Downloading <redacted>/torch-2.9.1.0%2Bcpu-cp312-cp312-linux_x86_64.whl (151.9 MB)
   ━━━━━━━━━━━━━━━━━━━╺━━━━━━━━━━━━━━━━━━━━ 73.7/151.9 MB 100.2 MB/s eta 0:00:01
# Ctrl+C
ERROR: Operation cancelled by user

(venv) [/tmp/tmp34u1frv7/venv]$ uv pip compile <(echo "torch==2.9.1")
Resolved 10 packages in 716ms
# ...
torch==2.9.1.0+cu128
# ...

(venv) [/tmp/tmp34u1frv7/venv]$ uv pip compile <(echo "torch===2.9.1")
  × No solution found when resolving dependencies:
  ╰─▶ Because there is no version of torch==2.9.1 and you require torch==2.9.1, we can conclude that your requirements are unsatisfiable.
  
(venv) [/tmp/tmp34u1frv7/venv]$ uv pip compile <(echo "torch==2.9.1+cpu")
Resolved 10 packages in 456ms
# ...
torch==2.9.1.0+cpu
# ...

# DEVIATION BETWEEN PIP AND UV:
(venv) [/tmp/tmp34u1frv7/venv]$ uv pip compile <(echo "torch===2.9.1+cpu")
Resolved 10 packages in 411ms
# ...
torch==2.9.1.0+cpu
# ...

```

---

<div class="post-metadata">

**Author:** ![ncoghlan](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ncoghlan/32/14266_2.png) [@ncoghlan](https://discuss.python.org/u/ncoghlan)\
**Post date:** [July 30, 2026, 9:23am UTC](https://discuss.python.org/t/split-out-deprecating-arbitrary-equality/104935/15 "2026-07-30T09:23:08Z")

</div>

> [@notatallshaw](#):
>
> These kind of quirky behaviors make implementations error prone, and it possibly difficult to translate to literature on satisfiability and other solutions in other ecosystems to Python versions and specifiers.

Pre-releases and post-releases being considered equivalent to their base release for inequality checks is intentional (it’s more obvious why if you consider the appropriate way to handle release candidates and post releases that are behaviourally identical to the unqualified release).

I don’t actually have super strong opinions about keeping arbitrary equality clauses, though. We put it in PEP 440 because we _knew_ we didn’t have 100% coverage of all the versions on PyPI (exact numbers are in the PEP), and “those versions can’t be specified anymore” wasn’t a fight we wanted to have at the time.

---

<div class="post-metadata">

**Author:** ![notatallshaw](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/notatallshaw/32/10861_2.png) [@notatallshaw](https://discuss.python.org/u/notatallshaw)\
**Post date:** [July 30, 2026, 12:40pm UTC](https://discuss.python.org/t/split-out-deprecating-arbitrary-equality/104935/16 "2026-07-30T12:40:19Z")

</div>

I understand they’re intentional, and I don’t think changing them is worth it, but they do make Python versions more complex to implement than I believe any other ecosystem.

In 2026, a dozen years after pep 440, I am still finding differences between the spec and the implementation of packaging around this (same issues existed in uv also and I reported to them):

- [Narrow \<V.postN pre-release exclusion to match spec by notatallshaw · Pull Request #1140 · pypa/packaging · GitHub](https://github.com/pypa/packaging/pull/1140)
- [Narrow \>V post-release exclusion to match spec by notatallshaw · Pull Request #1141 · pypa/packaging · GitHub](https://github.com/pypa/packaging/pull/1141)

It gets more complex when trying to implement a set algebra of Python packaging vesions, required for modern resolver algorithms. In the upcoming packaging, 26.3 there is a new VersionRange object that supports intersection, union, compliment, and difference. Getting implied pre-releases logically correct took multiple attempts and still had to pick and choose what Boolean rules it would comply with, e.g. [feat(ranges): clip pre-release opt-in to bounds to prevent pre-release leaks by notatallshaw · Pull Request #1311 · pypa/packaging · GitHub](https://github.com/pypa/packaging/pull/1311)
