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

add magic methods as seen in set() and elsewhere #21

Open
isaacimholt opened this issue Feb 21, 2019 · 6 comments
Open

add magic methods as seen in set() and elsewhere #21

isaacimholt opened this issue Feb 21, 2019 · 6 comments

Comments

@isaacimholt
Copy link
Contributor

Add functionality such that
>>> omdict(a=1, b=2) |= {'a': 'hello world'}
fully merges/updates (add all members) the omdict
>>> omdict([('a', 1), ('b', 2), ('a', 'hello world')])
and similar functionality for | but returns a new object instead.

I suggest also then to use
>>> omdict(a=1, b=2) += {'a': 'hello world'}
to produce a more "regular" update
>>> omdict([('b', 2), ('a', 'hello world')])
and once again + produces a new object instead

newer versions of python will have dictionary insertion order as default, but this is still useful in the case that order does not particularly matter, but then if it does these operations should obviously also work with OrderedDict and other omdict instances to preserve order.

@isaacimholt
Copy link
Contributor Author

Shall I submit a pr? Is this feature in line with the project's goals?

@gruns
Copy link
Owner

gruns commented Apr 16, 2019

Thank you for the bump. I appreciate it.

Shall I submit a pr? Is this feature in line with the project's goals?

That'd be awesome. Binary operators like | and + would be a great addition
to orderedmultidict.

Before you wrangle your spade and break ground: to be explicit, which operators
will you add support for?

  • + (via __add__()): Binary add with key replacement.
  • | (via __or__()): Binary union with key addendum, not key replacement.

Any others?

@isaacimholt
Copy link
Contributor Author

isaacimholt commented Apr 17, 2019

Yes I was thinking of what else could be added.
I was basing myself largely on existing operators for sets (https://docs.python.org/2/library/sets.html#set-objects), but the difficulty was merging the ideas of set operators with dictionaries, but I suppose there could be a good mental model: consider k:v pairs to be single items that could be added/removed from omdict. Therefore, briefly:

  • del omdict["a"] obviously removes the "a" key and all its values (existing behavior)
  • however omdict -= {"a": "my value"} removes only "my value" if it exists for key "a"

Other operators could be added, but it's not clear to me how useful they might be:

  • omdict ^= {"a": "my value"} return omdict with k:v elements from omdict or other dictionary but not both
  • omdict &= {"a": "my value"} return omdict keeping only k:v elements found in both omdict and other dictionary
  • In both previous cases, the order of the first object is canonical. any new elements are added to the end of the first one, in whatever order they appear from the second one.

The interesting thing here is that since dictionaries are limited to unique keys, if you wanted to remove/update/etc multiple values for the same key, you could do omdict -= {"a": "my value"} -= {"a": "my other value"}, or use any other multidict implementation (or another omdict obviously) omdict -= omdict(("a", "my value"), ("a", "my other value")) as the implementation should simply iterate over the k:v pairs of the other object and work.

Also considering subset/superset semantics (<=, >=) but not sure if this would be correct. Should order be considered when comparing?

Another possible point for discussion is that set operators are useful for sets... which have no order. Are there other possible operators that could be added that would do useful things considering omdict ordering? What other "rich" objects exist in python-land that have order-aware operators? I prefer to avoid creating new semantics for these operators and instead re-use existing conventions, and only break from them where it makes sense for this use-case.

To be explicit here are the methods to be added:

  • object.__add__(self, other)
  • object.__sub__(self, other)
  • object.__xor__(self, other)
  • object.__or__(self, other)
  • object.__and__(self, other)
  • object.__iadd__(self, other)
  • object.__isub__(self, other)
  • object.__ixor__(self, other)
  • object.__ior__(self, other)
  • object.__iand__(self, other)

possibly add "regular" methods?

  • object.add(other)
  • object.union(other)
  • object.intersection(other)
  • object.difference(other)
  • object.symmetric_difference(other)
  • object.update(other)
  • object.intersection_update(other)
  • object.difference_update(other)
  • object.symmetric_difference_update(other)

possibly right-hand operators?

  • object.__radd__(self, other)
  • object.__rsub__(self, other)
  • object.__rxor__(self, other)
  • object.__ror__(self, other)
  • object.__rand__(self, other)

I expect that some of these (add, update) exist already and should be updated to support this new functionality. As a first implementation I would limit myself to the 1st group of methods e.g. object.__add__(self, other) and object.__iadd__(self, other).

@isaacimholt
Copy link
Contributor Author

By the way, I realized afterwards that most of the previous post is incorrect, since obviously you can have multiple duplicate values per key. |= and += are still valuable shortcuts to have however, and -= would be useful as well. Following the principle of least surprise, it should remove a single k:v:

omdict((1, 'a'), (1, 'a), (1, 'b')) - {1: 'a'} == omdict((1, 'a'), (1, 'b'))
omdict((1, 'a'), (1, 'a), (1, 'b')) - omdict((1, 'a'), (1, 'a)) == omdict((1, 'b'),)

however the order is not defined at the moment, I will try to stick to whatever default order is used in the project (remove first matching element or last).

@gruns
Copy link
Owner

gruns commented Jul 11, 2019

however the order is not defined at the moment, I will try to stick to
whatever default order is used in the project (remove first matching element
or last).

By default, the last item is removed. A la popitem()

popitem(fromall=False, last=True)

The other big question is whether to remove all matching items or just the
last one. Which do you think is the more expected behavior? I'm torn.

@isaacimholt
Copy link
Contributor Author

The other big question is whether to remove all matching items or just the
last one. Which do you think is the more expected behavior? I'm torn.

I really don't know, I would do whatever might be more useful for url manipulation purposes, but it's been a while since I've worked with them. Rethinking things, I might suggest the removal of all matching items. I may not know how many items of that type exist, but I may know that I only want 1 to be present, so I might do this: omdict((1, 'a'), (1, 'a'), (1, 'a'), ..., (1, 'b')) - {1: 'a'} + {1: 'a'} == omdict((1, 'a'), (1, 'b')) but I am having difficulty imagining a concrete usage scenario for one implementation over the other.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants