Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cannot in-place update set with keys_view using |= #101478

Closed
akeeman opened this issue Jan 31, 2023 · 4 comments
Closed

cannot in-place update set with keys_view using |= #101478

akeeman opened this issue Jan 31, 2023 · 4 comments
Labels
type-bug An unexpected behavior, bug, or error

Comments

@akeeman
Copy link

akeeman commented Jan 31, 2023

Bug report

Updating a set with an instance of keys_view using the |= operator results in a new object, instead of an updated one.

This is unlike updating a set with an other set or a frozenset using the same operator, or using the update method. Given the docs I expect all cases to behave the same, and expect the following snippet to not fail. Yet it fails.

x = set()
y = x

y |= {13: 37}.keys()

assert x is y

Note that a keys_view is not a set, but it does implement the AbstracrSet api, so that isinstance(<keys_view inst>, AbstractSet) is True.

Your environment

  • CPython versions tested on: 3.7.16, 3.8.10, 3.8.16, 3.9.16, 3.10.6, 3.11.1,
  • Operating system and architecture: windows, linux (64 bit)
@akeeman akeeman added the type-bug An unexpected behavior, bug, or error label Jan 31, 2023
@rhettinger
Copy link
Contributor

Offhand, I don't think it was intended that y |= {13: 37}.keys() would work at all, much less doing so in place. As stated a little higher up in the docs, the rule is that the set operators require their arguments to be actual sets or frozensets (note this is a different rule than for abstract sets where the operators accept any iterable). Right now, I thinking that the dictviews_to_set() optimization may have inadvertently leaked this behavior.

@SimpleArt
Copy link

Offhand, I don't think it was intended that y |= {13: 37}.keys() would work at all, much less doing so in place. As stated a little higher up in the docs, the rule is that the set operators require their arguments to be actual sets or frozensets (note this is a different rule than for abstract sets where the operators accept any iterable). Right now, I thinking that the dictviews_to_set() optimization may have inadvertently leaked this behavior.

Are you saying that set | dict_keys is unintended, and doing so in-place even moreso? As far as I understood, in-place may be questionable but set | dict_keys should be supported at least, otherwise using dict_keys and similar sets with set becomes troublesome or error-prone. For example, I've used & to test if a dictionary had multiple specific keys, and | can be used in the same vein to consider default keys or similar.

@akeeman
Copy link
Author

akeeman commented Feb 6, 2023

@rhettinger I think with this

As stated a little higher up in the docs, the rule is that the set operators require their arguments to be actual sets or frozensets

you're targeting the following sencence from the docs:

In contrast, their operator based counterparts require their arguments to be sets.

So it actually says "sets", not mentioning "frozensets". Obviously frozensets work, but a literal frozenset is not a literal set, so we must take what is meant by "set" more broadly. That raises the question what qualifies as a set in this sense. As I read it, everything that conforms to AbstractSet should work then.

In any other case I expect at least an error, an never for the left hand side to refer to a different object after the operation.

@rhettinger
Copy link
Contributor

So it actually says "sets", not mentioning "frozensets" ... As I read it, everything that conforms to AbstractSet should work then.

That is an incorrect reading. The operator arguments are required to be either a set, frozenset, or one of their subclasses. This was a deliberate design choice. The choice was present in the original sets.py implementation and in the C implementation. We specifically test for this behavior.

In any other case I expect at least an error, an never for the left hand side to refer to a different object after the operation.

In principle, I agree. However, I don't see a way to fix the dict view implementations.

In general, __ror__ methods have no way to know whether the upstream call is __or__ which should produce a new object or __ior__ which should update in place. This appears to be a fundamental limitation and was known at the outset when augmented assignments were introduced. Per PEP 203:

There is no right-hand-side variant of __iadd__, because that would require for y to know how to in-place modify x, which is unsafe to say the least.

I'm going to close this as a known limitation. Fortunately, there is a simple workaround. Just use set.update instead of the |= operator. That is the intended way for sets/frozensets to interact with any object that isn't a concrete set, frozenset, or a direct subclass. Abstract sets and key views are excluded.

@rhettinger rhettinger closed this as not planned Won't fix, can't repro, duplicate, stale Feb 11, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

3 participants