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

module-level factory functions #24

Merged
merged 1 commit into from
Feb 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion benchmarks/test_benchmark_immutablelist.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import random
import pytest
from immutablecollections import ImmutableList
from immutablecollections import ImmutableDict, ImmutableList

empty_tuple = ()
Expand Down
50 changes: 36 additions & 14 deletions benchmarks/test_benchmark_immutableset.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,67 @@
import pytest
import random

from immutablecollections import ImmutableDict, ImmutableSet
from immutablecollections import ImmutableDict, immutableset, ImmutableSet

print(type(immutableset))

empty_tuple = ()
empty_list = []
empty_set = set()
empty_frozenset = frozenset()

small_tuple = (3, 1, 2)
small_list = [3, 1, 2]
small_set = set(small_tuple)
small_frozen_set = set(small_list)

rand = random.Random(0)

big_list = list(range(10000))
rand.shuffle(big_list)
big_tuple = tuple(big_list)
big_set = set(big_list)
big_frozen_set = frozenset(big_list)

input_types = ImmutableDict.of(
(
("empty set", empty_set),
("empty frozenset", empty_frozenset),
("empty tuple", empty_tuple),
("empty list", empty_list),
("small set", small_set),
("small frozenset", small_frozen_set),
("small tuple", small_tuple),
("small list", small_list),
("big set", big_set),
("big frozenset", big_frozen_set),
("big tuple", big_tuple),
("big list", big_list),
)
)


def function_call_overhead_no_arg():
pass


def function_call_overhead_arg(iterable):
pass


no_arg_constructors = ImmutableDict.of(
(
("set", set),
("frozenset", frozenset),
("immutableset", immutableset),
("function_call", function_call_overhead_no_arg),
)
)


@pytest.mark.parametrize("constructor", no_arg_constructors.items())
def test_create_empty(constructor, benchmark):
benchmark.name = constructor[0]
benchmark.group = f"Create empty"
benchmark(constructor[1])


constructors = ImmutableDict.of(
(("set", set), ("frozenset", frozenset), ("ImmutableSet", ImmutableSet.of))
(
("set", set),
("frozenset", frozenset),
("ImmutableSet", ImmutableSet.of),
("immutableset", immutableset),
("function_call", function_call_overhead_arg),
)
)


Expand Down
13 changes: 9 additions & 4 deletions immutablecollections/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,14 @@
from attr.exceptions import FrozenInstanceError

from immutablecollections.immutablecollection import ImmutableCollection
from immutablecollections.immutabledict import ImmutableDict
from immutablecollections.immutablelist import ImmutableList
from immutablecollections.immutableset import ImmutableSet
from immutablecollections.utils import ImmutableMixin
from immutablecollections._immutabledict import immutabledict, ImmutableDict
from immutablecollections._immutablelist import immutablelist, ImmutableList
from immutablecollections._immutableset import immutableset, ImmutableSet
from immutablecollections._immutablemultidict import (
ImmutableSetMultiDict,
ImmutableListMultiDict,
immutablelistmultidict,
immutablesetmultidict,
)

from immutablecollections.version import version as __version__
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from abc import ABCMeta
from typing import (
Dict,
Iterable,
Mapping,
TypeVar,
Tuple,
Iterator,
Optional,
Union,
Generic,
Callable,
Expand All @@ -15,6 +17,7 @@
from frozendict import frozendict

from immutablecollections.immutablecollection import ImmutableCollection
from immutablecollections._utils import DICT_ITERATION_IS_DETERMINISTIC

KT = TypeVar("KT")
VT = TypeVar("VT")
Expand All @@ -27,6 +30,47 @@

SelfType = TypeVar("SelfType") # pylint:disable=invalid-name

AllowableDictType = Union[ # pylint:disable=invalid-name
"ImmutableDict[KT, VT]", Dict[KT, VT]
]


def immutabledict(
iterable: Optional[Union[Iterable[Tuple[KT, VT]], AllowableDictType]] = None
) -> "ImmutableDict[KT, VT]":
"""
Create an immutable dictionary with the given mappings.

Mappings may be specified as a sequence of key-value pairs or as another ``ImmutableDict`` or
(on Python 3.7+ and CPython 3.6+) as a built-in ``dict``.

The iteration order of the created keys, values, and items of the resulting ``ImmutableDict``
will match *iterable*.

If *iterable* is already an ``ImmutableDict``, *iterable* itself will be returned.
"""
# immutabledict() should return an empty set
if iterable is None:
return _EMPTY

if isinstance(iterable, ImmutableDict):
# if an ImmutableDict is input, we can safely just return it,
# since the object can safely be shared
return iterable

if isinstance(iterable, Dict) and not DICT_ITERATION_IS_DETERMINISTIC:
raise ValueError(
"ImmutableDicts can only be initialized from built-in dicts when the "
"iteration of built-in dicts is guaranteed to be deterministic (Python "
"3.7+; CPython 3.6+)"
)

ret: ImmutableDict[KT, VT] = _FrozenDictBackedImmutableDict(iterable)
if ret:
return ret
else:
return _EMPTY


class ImmutableDict(ImmutableCollection[KT], Mapping[KT, VT], metaclass=ABCMeta):
__slots__ = ()
Expand All @@ -35,17 +79,27 @@ class ImmutableDict(ImmutableCollection[KT], Mapping[KT, VT], metaclass=ABCMeta)
# pylint: disable = arguments-differ
@staticmethod
def of(dict_: Union[Mapping[KT, VT], Iterable[IT]]) -> "ImmutableDict[KT, VT]":
"""
Deprecated - prefer ``immutabledict`` module-level factory
"""
if isinstance(dict_, ImmutableDict):
return dict_
else:
return ImmutableDict.builder().put_all(dict_).build() # type:ignore

@staticmethod
def empty() -> "ImmutableDict[KT, VT]":
"""
Deprecated - prefer the ``immutabledict`` module-level factory with no arguments.
"""
return _EMPTY

@staticmethod
def builder() -> "ImmutableDict.Builder[KT, VT]":
"""
Deprecated - prefer to build a list of tuples and pass them to the ``immutabledict``
module-level factory
"""
return ImmutableDict.Builder()

@staticmethod
Expand All @@ -58,12 +112,7 @@ def index(
The result is an `ImmutableDict` where each given item appears as a value which
is mapped to from the result of applying `key_function` to the value.
"""
ret: ImmutableDict.Builder[KT, VT] = ImmutableDict.builder()

for item in items:
ret.put(key_function(item), item)

return ret.build()
return immutabledict((key_function(item), item) for item in items)

def modified_copy_builder(self) -> "ImmutableDict.Builder[KT, VT]":
return ImmutableDict.Builder(source=self)
Expand All @@ -79,14 +128,11 @@ def filter_keys(self, predicate: Callable[[KT], bool]) -> "ImmutableDict[KT, VT]
ImmutableDict in general is updated to maintain order) and allows us not to do any
copying if all keys pass the filter
"""
retained_keys = [key for key in self.keys() if predicate(key)]
if len(retained_keys) == len(self.keys()):
new_items = [item for item in self.items() if predicate(item[0])]
if len(new_items) == len(self):
return self
else:
ret: ImmutableDict.Builder[KT, VT] = ImmutableDict.builder()
for key in retained_keys:
ret.put(key, self[key])
return ret.build()
return immutabledict(new_items)

def __repr__(self):
return "i" + str(self)
Expand Down Expand Up @@ -144,13 +190,13 @@ def build(self) -> "ImmutableDict[KT2, VT2]":
# objects can be safely shared
return self.source
if self._dict:
return FrozenDictBackedImmutableDict(self._dict)
return _FrozenDictBackedImmutableDict(self._dict)
else:
return _EMPTY


@attrs(frozen=True, slots=True, repr=False)
class FrozenDictBackedImmutableDict(ImmutableDict[KT, VT]):
class _FrozenDictBackedImmutableDict(ImmutableDict[KT, VT]):

# Mypy does not believe this is a valid converter, but it is
_dict = attrib(converter=frozendict) # type:ignore
Expand All @@ -170,4 +216,4 @@ def __contains__(self, x: object) -> bool:


# Singleton instance for empty
_EMPTY: ImmutableDict = FrozenDictBackedImmutableDict({})
_EMPTY: ImmutableDict = _FrozenDictBackedImmutableDict({})
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
Iterable,
Iterator,
List,
Optional,
Sequence,
Sized,
Tuple,
Expand All @@ -20,6 +21,32 @@
T2 = TypeVar("T2")


def immutablelist(iterable: Optional[Iterable[T]] = None) -> "ImmutableList[T]":
"""
Create an immutable list with the given contents.

The iteration order of the created list will match *iterable*. If *iterable* is `None`, an
empty `ImmutableList` will be returned.

If *iterable* is already an ``ImmutableList``, *iterable* itself will be returned.
"""
# immutablelist() should return an empty set
if iterable is None:
return _EMPTY_IMMUTABLE_LIST

if isinstance(iterable, ImmutableList):
# if an ImmutableList is input, we can safely just return it,
# since the object can safely be shared
return iterable

values_as_tuple = tuple(iterable)

if values_as_tuple:
return _TupleBackedImmutableList(iterable)
else:
return _EMPTY_IMMUTABLE_LIST


class ImmutableList(
Generic[T], ImmutableCollection[T], Sequence[T], Iterable[T], metaclass=ABCMeta
):
Expand All @@ -29,20 +56,27 @@ class ImmutableList(
# pylint: disable = arguments-differ
@staticmethod
def of(seq: Iterable[T]) -> "ImmutableList[T]":
"""
Use of this method is deprecated. Prefer the module-level ``immutablelist``.
"""
if isinstance(seq, ImmutableList):
return seq
else:
return ImmutableList.builder().add_all(seq).build() # type: ignore

@staticmethod
def empty() -> "ImmutableList[T]":
return EMPTY_IMMUTABLE_LIST
return _EMPTY_IMMUTABLE_LIST

@staticmethod
def builder() -> "ImmutableList.Builder[T]":
return ImmutableList.Builder()

class Builder(Generic[T2], Sized):
"""
Use of this builder is deprecated. Prefer to pass a regular ``list`` to ``immutablelist``
"""

def __init__(self):
self._list: List[T2] = []

Expand All @@ -61,7 +95,7 @@ def build(self) -> "ImmutableList[T2]":
if self._list:
return _TupleBackedImmutableList(self._list)
else:
return EMPTY_IMMUTABLE_LIST
return _EMPTY_IMMUTABLE_LIST

def __repr__(self):
return "i" + str(self)
Expand Down Expand Up @@ -99,4 +133,4 @@ def __len__(self):


# Singleton instance for empty
EMPTY_IMMUTABLE_LIST: ImmutableList = _TupleBackedImmutableList(())
_EMPTY_IMMUTABLE_LIST: ImmutableList = _TupleBackedImmutableList(())
Loading