diff options
author | Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> | 2020-11-21 20:20:26 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-11-21 20:20:26 (GMT) |
commit | 89d74d0acde25eed953dbfd56b1fafd0df78da7e (patch) | |
tree | a4d5d3e5f4979fab55480378256d312bcfd0ccb9 | |
parent | 3b5b1c82a30506e2194fd1c9d9070e7215d79f41 (diff) | |
download | cpython-89d74d0acde25eed953dbfd56b1fafd0df78da7e.zip cpython-89d74d0acde25eed953dbfd56b1fafd0df78da7e.tar.gz cpython-89d74d0acde25eed953dbfd56b1fafd0df78da7e.tar.bz2 |
Clarify that Set._from_iterable is not required to be a classmethod. (GH-23272) (GH-23450)
-rw-r--r-- | Doc/library/collections.abc.rst | 2 | ||||
-rw-r--r-- | Lib/test/test_collections.py | 56 |
2 files changed, 57 insertions, 1 deletions
diff --git a/Doc/library/collections.abc.rst b/Doc/library/collections.abc.rst index db0e25b..2345e78 100644 --- a/Doc/library/collections.abc.rst +++ b/Doc/library/collections.abc.rst @@ -291,7 +291,7 @@ Notes on using :class:`Set` and :class:`MutableSet` as a mixin: :meth:`_from_iterable` which calls ``cls(iterable)`` to produce a new set. If the :class:`Set` mixin is being used in a class with a different constructor signature, you will need to override :meth:`_from_iterable` - with a classmethod that can construct new instances from + with a classmethod or regular method that can construct new instances from an iterable argument. (2) diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index a8d3337..6aa8979 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -1558,6 +1558,62 @@ class TestCollectionABCs(ABCTestCase): # coerce both to a real set then check equality self.assertSetEqual(set(s1), set(s2)) + def test_Set_from_iterable(self): + """Verify _from_iterable overriden to an instance method works.""" + class SetUsingInstanceFromIterable(MutableSet): + def __init__(self, values, created_by): + if not created_by: + raise ValueError(f'created_by must be specified') + self.created_by = created_by + self._values = set(values) + + def _from_iterable(self, values): + return type(self)(values, 'from_iterable') + + def __contains__(self, value): + return value in self._values + + def __iter__(self): + yield from self._values + + def __len__(self): + return len(self._values) + + def add(self, value): + self._values.add(value) + + def discard(self, value): + self._values.discard(value) + + impl = SetUsingInstanceFromIterable([1, 2, 3], 'test') + + actual = impl - {1} + self.assertIsInstance(actual, SetUsingInstanceFromIterable) + self.assertEqual('from_iterable', actual.created_by) + self.assertEqual({2, 3}, actual) + + actual = impl | {4} + self.assertIsInstance(actual, SetUsingInstanceFromIterable) + self.assertEqual('from_iterable', actual.created_by) + self.assertEqual({1, 2, 3, 4}, actual) + + actual = impl & {2} + self.assertIsInstance(actual, SetUsingInstanceFromIterable) + self.assertEqual('from_iterable', actual.created_by) + self.assertEqual({2}, actual) + + actual = impl ^ {3, 4} + self.assertIsInstance(actual, SetUsingInstanceFromIterable) + self.assertEqual('from_iterable', actual.created_by) + self.assertEqual({1, 2, 4}, actual) + + # NOTE: ixor'ing with a list is important here: internally, __ixor__ + # only calls _from_iterable if the other value isn't already a Set. + impl ^= [3, 4] + self.assertIsInstance(impl, SetUsingInstanceFromIterable) + self.assertEqual('test', impl.created_by) + self.assertEqual({1, 2, 4}, impl) + def test_Set_interoperability_with_real_sets(self): # Issue: 8743 class ListSet(Set): |