summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>2020-11-21 20:20:26 (GMT)
committerGitHub <noreply@github.com>2020-11-21 20:20:26 (GMT)
commit89d74d0acde25eed953dbfd56b1fafd0df78da7e (patch)
treea4d5d3e5f4979fab55480378256d312bcfd0ccb9
parent3b5b1c82a30506e2194fd1c9d9070e7215d79f41 (diff)
downloadcpython-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.rst2
-rw-r--r--Lib/test/test_collections.py56
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):