-
Notifications
You must be signed in to change notification settings - Fork 2
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
Provide ImmutableDict & ImmutableMultiDict factory methods which ban duplicate additions (Closes issue #53) #55
Changes from all commits
b17b722
b3c3ac5
71f214f
972754d
7757f50
6b56310
8c71b5d
14080a9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,8 @@ | |
Mapping, | ||
TypeVar, | ||
Tuple, | ||
Set, | ||
List, | ||
Iterator, | ||
Optional, | ||
Union, | ||
|
@@ -32,7 +34,7 @@ | |
|
||
|
||
def immutabledict( | ||
iterable: Optional[AllowableSourceType] = None | ||
iterable: Optional[AllowableSourceType] = None, *, forbid_duplicate_keys: bool = False | ||
) -> "ImmutableDict[KT, VT]": | ||
""" | ||
Create an immutable dictionary with the given mappings. | ||
|
@@ -51,7 +53,8 @@ def immutabledict( | |
|
||
if isinstance(iterable, ImmutableDict): | ||
# if an ImmutableDict is input, we can safely just return it, | ||
# since the object can safely be shared | ||
# since the object can safely be shared. | ||
# It is also guaranteed not to have repeat keys | ||
return iterable | ||
|
||
if isinstance(iterable, Dict) and not DICT_ITERATION_IS_DETERMINISTIC: | ||
|
@@ -66,13 +69,46 @@ def immutabledict( | |
f"Cannot create an immutabledict from {type(iterable)}, only {InstantiationTypes}" | ||
) | ||
|
||
if forbid_duplicate_keys: | ||
keys: List[KT] | ||
# `for x in dict` grabs keys, but `for x in pairs` grabs pairs, so we must branch | ||
if isinstance(iterable, Mapping): | ||
# duplicate keys are possible if input is e.g. a multidict | ||
keys = list(iterable.keys()) | ||
else: | ||
# iterable must be a (key, value) pair iterable | ||
iterable = list(iterable) # in case iterable is consumed by iteration | ||
keys = [key for key, value in iterable] | ||
seen: Set[KT] = set() | ||
duplicated: Set[KT] = set() | ||
for key in keys: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, this is really super-slow compared to the call to
|
||
if key in seen: | ||
duplicated.add(key) | ||
else: | ||
seen.add(key) | ||
if duplicated: | ||
raise ValueError( | ||
"forbid_duplicate_keys=True, but some keys " | ||
"occur multiple times in input: {}".format(duplicated) | ||
) | ||
|
||
ret: ImmutableDict[KT, VT] = _RegularDictBackedImmutableDict(iterable) | ||
if ret: | ||
return ret | ||
else: | ||
return _EMPTY | ||
|
||
|
||
def immutabledict_from_unique_keys( | ||
iterable: Optional[AllowableSourceType] = None | ||
) -> "ImmutableDict[KT, VT]": | ||
""" | ||
Create an immutable dictionary with the given mappings, but raise ValueError if | ||
*iterable* contains the same item twice. More information in `immutabledict` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The docstring here is not quite accurate - we throw on a duplicate key, not a duplicate item |
||
""" | ||
return immutabledict(iterable, forbid_duplicate_keys=True) | ||
|
||
|
||
class ImmutableDict(ImmutableCollection[KT], Mapping[KT, VT], metaclass=ABCMeta): | ||
""" | ||
A ``Mapping`` implementation which is locally immutable. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -92,16 +92,21 @@ def immutableset( | |
"iteration order, specify disable_order_check=True" | ||
) | ||
|
||
duplicated = set() # only used if forbid_duplicate_elements=True | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No reason to spend time allocating this unless |
||
iteration_order = [] | ||
containment_set: MutableSet[T] = set() | ||
for value in iterable: | ||
if value not in containment_set: | ||
containment_set.add(value) | ||
iteration_order.append(value) | ||
elif forbid_duplicate_elements: | ||
raise ValueError( | ||
"Input collection has duplicate items and forbid_duplicate_elements=False" | ||
) | ||
duplicated.add(value) | ||
|
||
if forbid_duplicate_elements and duplicated: | ||
raise ValueError( | ||
"forbid_duplicate_elements=True, but some elements " | ||
"occur multiple times in input: {}".format(duplicated) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prefer f-strings to |
||
) | ||
|
||
if iteration_order: | ||
if len(iteration_order) == 1: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mapping
guarantees/requires that.keys
return a set (and ourMultiDict
s don't implementMapping
)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What types should we check for then?
dict_keys
has someset
behavior but failsisinstance
:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could branch on whether
iterable
has.keys
, or we could have a weaker debug message that just says some unknown duplicate keys were presentThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code is actually fine; it's just the comment which is wrong :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@qpwo : We do want to include the duplicate elements. But we want to keep the "happy path" (which will be by far the most common) very fast; once we know there is an error, then we can do the work to figure out what the duplicate elements are (because no one cares if their crash takes an extra millisecond longer :-) )