]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
Clarify that Set._from_iterable is not required to be a classmethod. (GH-23272)
authorRichard Levasseur <richardlev@gmail.com>
Sat, 21 Nov 2020 19:56:24 +0000 (11:56 -0800)
committerGitHub <noreply@github.com>
Sat, 21 Nov 2020 19:56:24 +0000 (11:56 -0800)
Doc/library/collections.abc.rst
Lib/test/test_collections.py

index db0e25bb0772eb21e3f939d0e15d4042ab681362..2345e78a17e4f573c9409d95e13dc3aaeecc631a 100644 (file)
@@ -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)
index 7c7f8655b0fbdd47018ffcdb3a733f457a0f4e06..150c2a1c0e3498e45c5d2080592a958aad48419d 100644 (file)
@@ -1559,6 +1559,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):