Skip to content

Commit 68f68fa

Browse files
authored
Clarify that Set._from_iterable is not required to be a classmethod. (GH-23272)
1 parent ea97eba commit 68f68fa

File tree

2 files changed

+57
-1
lines changed

2 files changed

+57
-1
lines changed

Doc/library/collections.abc.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ Notes on using :class:`Set` and :class:`MutableSet` as a mixin:
291291
:meth:`_from_iterable` which calls ``cls(iterable)`` to produce a new set.
292292
If the :class:`Set` mixin is being used in a class with a different
293293
constructor signature, you will need to override :meth:`_from_iterable`
294-
with a classmethod that can construct new instances from
294+
with a classmethod or regular method that can construct new instances from
295295
an iterable argument.
296296

297297
(2)

Lib/test/test_collections.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1559,6 +1559,62 @@ def assertSameSet(self, s1, s2):
15591559
# coerce both to a real set then check equality
15601560
self.assertSetEqual(set(s1), set(s2))
15611561

1562+
def test_Set_from_iterable(self):
1563+
"""Verify _from_iterable overriden to an instance method works."""
1564+
class SetUsingInstanceFromIterable(MutableSet):
1565+
def __init__(self, values, created_by):
1566+
if not created_by:
1567+
raise ValueError(f'created_by must be specified')
1568+
self.created_by = created_by
1569+
self._values = set(values)
1570+
1571+
def _from_iterable(self, values):
1572+
return type(self)(values, 'from_iterable')
1573+
1574+
def __contains__(self, value):
1575+
return value in self._values
1576+
1577+
def __iter__(self):
1578+
yield from self._values
1579+
1580+
def __len__(self):
1581+
return len(self._values)
1582+
1583+
def add(self, value):
1584+
self._values.add(value)
1585+
1586+
def discard(self, value):
1587+
self._values.discard(value)
1588+
1589+
impl = SetUsingInstanceFromIterable([1, 2, 3], 'test')
1590+
1591+
actual = impl - {1}
1592+
self.assertIsInstance(actual, SetUsingInstanceFromIterable)
1593+
self.assertEqual('from_iterable', actual.created_by)
1594+
self.assertEqual({2, 3}, actual)
1595+
1596+
actual = impl | {4}
1597+
self.assertIsInstance(actual, SetUsingInstanceFromIterable)
1598+
self.assertEqual('from_iterable', actual.created_by)
1599+
self.assertEqual({1, 2, 3, 4}, actual)
1600+
1601+
actual = impl & {2}
1602+
self.assertIsInstance(actual, SetUsingInstanceFromIterable)
1603+
self.assertEqual('from_iterable', actual.created_by)
1604+
self.assertEqual({2}, actual)
1605+
1606+
actual = impl ^ {3, 4}
1607+
self.assertIsInstance(actual, SetUsingInstanceFromIterable)
1608+
self.assertEqual('from_iterable', actual.created_by)
1609+
self.assertEqual({1, 2, 4}, actual)
1610+
1611+
# NOTE: ixor'ing with a list is important here: internally, __ixor__
1612+
# only calls _from_iterable if the other value isn't already a Set.
1613+
impl ^= [3, 4]
1614+
self.assertIsInstance(impl, SetUsingInstanceFromIterable)
1615+
self.assertEqual('test', impl.created_by)
1616+
self.assertEqual({1, 2, 4}, impl)
1617+
15621618
def test_Set_interoperability_with_real_sets(self):
15631619
# Issue: 8743
15641620
class ListSet(Set):

0 commit comments

Comments
 (0)