Skip to content
Draft
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
3 changes: 2 additions & 1 deletion Doc/library/stdtypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ objects considered false:
* zero of any numeric type: ``0``, ``0.0``, ``0j``, ``Decimal(0)``,
``Fraction(0, 1)``

* empty sequences and collections: ``''``, ``()``, ``[]``, ``{}``, ``set()``,
* empty sequences and collections: ``''``, ``()``, ``[]``, ``{}``, ``{/}``,
``range(0)``

.. index::
Expand Down Expand Up @@ -4668,6 +4668,7 @@ The constructors for both classes work the same:

Sets can be created by several means:

* Use a slash within braces, for the empty set: ``{/}``
* Use a comma-separated list of elements within braces: ``{'jack', 'sjoerd'}``
* Use a set comprehension: ``{c for c in 'abracadabra' if c not in 'abc'}``
* Use the type constructor: ``set()``, ``set('foobar')``, ``set(['a', 'b', 'foo'])``
Expand Down
13 changes: 8 additions & 5 deletions Doc/reference/datamodel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -450,11 +450,14 @@

Sets
.. index:: pair: object; set

These represent a mutable set. They are created by the built-in :func:`set`
constructor and can be modified afterwards by several methods, such as
:meth:`~set.add`.

pair: empty; set

The items of a set are arbitrary Python objects.

Check warning on line 455 in Doc/reference/datamodel.rst

View workflow job for this annotation

GitHub Actions / Docs / Docs

py:meth reference target not found: set.add [ref.meth]
Sets are formed by placing a comma-separated list of expressions in
curly brackets. For sets of length 1, the comma may be ommitted.
An empty set can be formed by ``{/}``.
Sets are mutable (see section :ref:`set`) and can be modified afterwards
by several methods, such as :meth:`~set.add`.

Frozen sets
.. index:: pair: object; frozenset
Expand Down
18 changes: 9 additions & 9 deletions Doc/reference/expressions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -356,25 +356,25 @@ Set displays
.. index::
pair: set; display
pair: set; comprehensions
pair: empty; set
pair: object; set
single: {/}; set display
single: {} (curly brackets); set expression
single: , (comma); expression list
single: / (fowarard slash); in set displays

A set display is denoted by curly braces and distinguishable from dictionary
displays by the lack of colons separating keys and values:

.. productionlist:: python-grammar
set_display: "{" (`flexible_expression_list` | `comprehension`) "}"
set_display: "{" ("/" | `flexible_expression_list` | `comprehension`) "}"

A set display yields a new mutable set object, the contents being specified by
either a sequence of expressions or a comprehension. When a comma-separated
list of expressions is supplied, its elements are evaluated from left to right
and added to the set object. When a comprehension is supplied, the set is
constructed from the elements resulting from the comprehension.

An empty set cannot be constructed with ``{}``; this literal constructs an empty
dictionary.

either a slash (for the empty set), a sequence of expressions, or a comprehension.
When a comma-separated list of expressions is supplied, its elements
are evaluated from left to right and added to the set object.
When a comprehension is supplied, the set is constructed from
the elements resulting from the comprehension.

.. _dict:

Expand Down
8 changes: 5 additions & 3 deletions Doc/tutorial/datastructures.rst
Original file line number Diff line number Diff line change
Expand Up @@ -450,9 +450,11 @@ with no duplicate elements. Basic uses include membership testing and
eliminating duplicate entries. Set objects also support mathematical operations
like union, intersection, difference, and symmetric difference.

Curly braces or the :func:`set` function can be used to create sets. Note: to
create an empty set you have to use ``set()``, not ``{}``; the latter creates an
empty dictionary, a data structure that we discuss in the next section.
Curly braces or the :func:`set` function can be used to create sets.
``{/}`` is a special case that means the empty set, similar to :math:`\emptyset`.

.. hint:: ``{}`` will create an empty dictionary, not an empty set.
We will discuss this data structure in the next section.

Here is a brief demonstration::

Expand Down
27 changes: 27 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,33 @@
New features
============

.. _whatsnew315-pep-802:

Display Syntax for the Empty Set
--------------------------------

There is a new notation for the empty set, ``{/}``.

Check warning on line 78 in Doc/whatsnew/3.15.rst

View workflow job for this annotation

GitHub Actions / Docs / Docs

py:func reference target not found: :str [ref.func]
As part of this, the :func:`:str` and :func:`repr` of an empty :class:`set`
will now return ``'{/}'``.

This complements the existing notation for empty tuples, lists, and
dictionaries, which use ``()``, ``[]``, and ``{}`` respectively.

.. code-block:: python

Check warning on line 85 in Doc/whatsnew/3.15.rst

View workflow job for this annotation

GitHub Actions / Docs / Docs

Lexing literal_block " >>> type({/})\n <class 'set'>\n >>> {/} == set()\n True\n>>> repr({/})\n '{/}'\n >>> str(set())\n '{/}'\n >>> str({/}) == str(set()) == repr({/}) == repr(set())\n True" as "python" resulted in an error at token: "'". Retrying in relaxed mode. [misc.highlighting_failure]

>>> type({/})
<class 'set'>
>>> {/} == set()
True
>>> repr({/})
'{/}'
>>> str(set())
'{/}'
>>> str({/}) == str(set()) == repr({/}) == repr(set())
True

(Contributed by Adam Turner in :pep:`802`.)

.. _whatsnew315-sampling-profiler:

High frequency statistical sampling profiler
Expand Down
14 changes: 13 additions & 1 deletion Grammar/python.gram
Original file line number Diff line number Diff line change
Expand Up @@ -993,13 +993,17 @@ strings[expr_ty] (memo):
| a[asdl_expr_seq*]=tstring+ { _PyPegen_concatenate_tstrings(p, a, EXTRA) }

list[expr_ty]:
| invalid_list_slash
| '[' a=[star_named_expressions] ']' { _PyAST_List(a, Load, EXTRA) }

tuple[expr_ty]:
| invalid_tuple_slash
| '(' a=[y=star_named_expression ',' z=[star_named_expressions] { _PyPegen_seq_insert_in_front(p, y, z) } ] ')' {
_PyAST_Tuple(a, Load, EXTRA) }

set[expr_ty]: '{' a=star_named_expressions '}' { _PyAST_Set(a, EXTRA) }
set[expr_ty]:
| '{' '/' '}' { _PyAST_Set(NULL, EXTRA) }
| '{' a=star_named_expressions '}' { _PyAST_Set(a, EXTRA) }

# Dicts
# -----
Expand Down Expand Up @@ -1572,3 +1576,11 @@ invalid_type_params:
RAISE_SYNTAX_ERROR_STARTING_FROM(
token,
"Type parameter list cannot be empty")}

invalid_list_slash:
| '[' a='/' ']' { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a,
"invalid syntax. Perhaps you meant '[]' instead of '[/]'?") }

invalid_tuple_slash:
| '(' a='/' ')' { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a,
"invalid syntax. Perhaps you meant '()' instead of '(/)'?") }
4 changes: 1 addition & 3 deletions Lib/_ast_unparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -772,9 +772,7 @@ def visit_Set(self, node):
with self.delimit("{", "}"):
self.interleave(lambda: self.write(", "), self.traverse, node.elts)
else:
# `{}` would be interpreted as a dictionary literal, and
# `set` might be shadowed. Thus:
self.write('{*()}')
self.write('{/}')

def visit_Dict(self, node):
def write_key_value_pair(k, v):
Expand Down
2 changes: 1 addition & 1 deletion Lib/reprlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ def repr_array(self, x, level):

def repr_set(self, x, level):
if not x:
return 'set()'
return '{/}'
x = _possibly_sorted(x)
return self._repr_iterable(x, level, '{', '}', self.maxset)

Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_ast/data/ast_repr.txt
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ Module(body=[Expr(value=Lambda(args=arguments(...), body=Constant(...)))], type_
Module(body=[Expr(value=Dict(keys=[Constant(...)], values=[Constant(...)]))], type_ignores=[])
Module(body=[Expr(value=Dict(keys=[], values=[]))], type_ignores=[])
Module(body=[Expr(value=Set(elts=[Constant(...)]))], type_ignores=[])
Module(body=[Expr(value=Set(elts=[]))], type_ignores=[])
Module(body=[Expr(value=Dict(keys=[Constant(...)], values=[Constant(...)]))], type_ignores=[])
Module(body=[Expr(value=List(elts=[Constant(...), Constant(...)], ctx=Load(...)))], type_ignores=[])
Module(body=[Expr(value=Tuple(elts=[Constant(...)], ctx=Load(...)))], type_ignores=[])
Expand Down
3 changes: 3 additions & 0 deletions Lib/test/test_ast/snippets.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,8 @@
"{}",
# Set
"{None,}",
# Empty set
"{/}",
# Multiline dict (test for .lineno & .col_offset)
"""{
1
Expand Down Expand Up @@ -552,6 +554,7 @@ def main():
('Expression', ('Dict', (1, 0, 1, 7), [('Constant', (1, 2, 1, 3), 1, None)], [('Constant', (1, 4, 1, 5), 2, None)])),
('Expression', ('Dict', (1, 0, 1, 2), [], [])),
('Expression', ('Set', (1, 0, 1, 7), [('Constant', (1, 1, 1, 5), None, None)])),
('Expression', ('Set', (1, 0, 1, 3), [])),
('Expression', ('Dict', (1, 0, 5, 6), [('Constant', (2, 6, 2, 7), 1, None)], [('Constant', (4, 10, 4, 11), 2, None)])),
('Expression', ('List', (1, 0, 5, 6), [('Constant', (2, 6, 2, 7), 1, None), ('Constant', (4, 8, 4, 9), 1, None)], ('Load',))),
('Expression', ('Tuple', (1, 0, 4, 6), [('Constant', (2, 6, 2, 7), 1, None)], ('Load',))),
Expand Down
3 changes: 2 additions & 1 deletion Lib/test/test_ast/test_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -1908,7 +1908,8 @@
self.assertEqual(ast.literal_eval('(True, False, None)'), (True, False, None))
self.assertEqual(ast.literal_eval('{1, 2, 3}'), {1, 2, 3})
self.assertEqual(ast.literal_eval('b"hi"'), b"hi")
self.assertEqual(ast.literal_eval('set()'), set())
self.assertEqual(ast.literal_eval('{/}'), {/})

Check failure on line 1911 in Lib/test/test_ast/test_ast.py

View workflow job for this annotation

GitHub Actions / lint

Ruff

Lib/test/test_ast/test_ast.py:1911:53: SyntaxError: Expected an expression

Check failure on line 1911 in Lib/test/test_ast/test_ast.py

View workflow job for this annotation

GitHub Actions / lint

Ruff

Lib/test/test_ast/test_ast.py:1911:52: SyntaxError: Expected an expression
self.assertEqual(ast.literal_eval('set()'), {/})

Check failure on line 1912 in Lib/test/test_ast/test_ast.py

View workflow job for this annotation

GitHub Actions / lint

Ruff

Lib/test/test_ast/test_ast.py:1912:55: SyntaxError: Expected an expression

Check failure on line 1912 in Lib/test/test_ast/test_ast.py

View workflow job for this annotation

GitHub Actions / lint

Ruff

Lib/test/test_ast/test_ast.py:1912:54: SyntaxError: Expected an expression
self.assertRaises(ValueError, ast.literal_eval, 'foo()')
self.assertEqual(ast.literal_eval('6'), 6)
self.assertEqual(ast.literal_eval('+6'), 6)
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_free_threading/test_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def repr_set():
t.join()

for set_repr in set_reprs:
self.assertIn(set_repr, ("set()", "{1, 2, 3, 4, 5, 6, 7, 8}"))
self.assertIn(set_repr, ("{/}", "{1, 2, 3, 4, 5, 6, 7, 8}"))


if __name__ == "__main__":
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_gdb/test_pretty_print.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ def test_sets(self):
'Verify the pretty-printing of sets'
if GDB_VERSION < (7, 3):
self.skipTest("pretty-printing of sets needs gdb 7.3 or later")
self.assertGdbRepr(set(), "set()")
self.assertGdbRepr(set(), "{/}")
self.assertGdbRepr(set(['a']), "{'a'}")
# PYTHONHASHSEED is need to get the exact frozenset item order
if not sys.flags.ignore_environment:
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_grammar.py
Original file line number Diff line number Diff line change
Expand Up @@ -1685,6 +1685,7 @@ def test_atoms(self):
x = {'one': 1, 'two': 2,}
x = {'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 'six': 6}

x = {/}
x = {'one'}
x = {'one', 1,}
x = {'one', 'two', 'three'}
Expand Down
3 changes: 2 additions & 1 deletion Lib/test/test_pprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -833,7 +833,8 @@
self.assertEqual(dotted_printer.pformat(o2), exp2)

def test_set_reprs(self):
self.assertEqual(pprint.pformat(set()), 'set()')
self.assertEqual(pprint.pformat({/}), '{/}')

Check failure on line 836 in Lib/test/test_pprint.py

View workflow job for this annotation

GitHub Actions / lint

Ruff

Lib/test/test_pprint.py:836:43: SyntaxError: Expected an expression

Check failure on line 836 in Lib/test/test_pprint.py

View workflow job for this annotation

GitHub Actions / lint

Ruff

Lib/test/test_pprint.py:836:42: SyntaxError: Expected an expression
self.assertEqual(pprint.pformat(set()), '{/}')
self.assertEqual(pprint.pformat(set(range(3))), '{0, 1, 2}')
self.assertEqual(pprint.pformat(set(range(7)), width=20), '''\
{0,
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_reprlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def test_container(self):
eq(r([1, 2, 3, 4, 5, 6, 7]), "[1, 2, 3, 4, 5, 6, ...]")

# Sets give up after 6 as well
eq(r(set([])), "set()")
eq(r(set([])), "{/}")
eq(r(set([1])), "{1}")
eq(r(set([1, 2, 3])), "{1, 2, 3}")
eq(r(set([1, 2, 3, 4, 5, 6])), "{1, 2, 3, 4, 5, 6}")
Expand Down
4 changes: 2 additions & 2 deletions Lib/test/test_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -864,7 +864,7 @@

# Tests taken from test_sets.py =============================================

empty_set = set()
empty_set = {/}

Check failure on line 867 in Lib/test/test_set.py

View workflow job for this annotation

GitHub Actions / lint

Ruff

Lib/test/test_set.py:867:15: SyntaxError: Expected an expression

Check failure on line 867 in Lib/test/test_set.py

View workflow job for this annotation

GitHub Actions / lint

Ruff

Lib/test/test_set.py:867:14: SyntaxError: Expected an expression

#==============================================================================

Expand Down Expand Up @@ -981,7 +981,7 @@
self.set = set(self.values)
self.dup = set(self.values)
self.length = 0
self.repr = "set()"
self.repr = "{/}"

#------------------------------------------------------------------------------

Expand Down
19 changes: 19 additions & 0 deletions Lib/test/test_syntax.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,25 @@
Traceback (most recent call last):
SyntaxError: invalid syntax. Perhaps you forgot a comma?

# Suggest the correct form of an empty literal collection.
# A slash ('/') is only valid for sets.

>>> [/]
Traceback (most recent call last):
SyntaxError: invalid syntax. Perhaps you meant '[]' instead of '[/]'?

>>> [ / ]
Traceback (most recent call last):
SyntaxError: invalid syntax. Perhaps you meant '[]' instead of '[/]'?

>>> (/)
Traceback (most recent call last):
SyntaxError: invalid syntax. Perhaps you meant '()' instead of '(/)'?

>>> ( / )
Traceback (most recent call last):
SyntaxError: invalid syntax. Perhaps you meant '()' instead of '(/)'?

# Make sure soft keywords constructs don't raise specialized
# errors regarding missing commas or other spezialiced errors

Expand Down
5 changes: 1 addition & 4 deletions Lib/test/test_unparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,10 +301,7 @@ def test_set_literal(self):
self.check_ast_roundtrip("{'a', 'b', 'c'}")

def test_empty_set(self):
self.assertASTEqual(
ast.parse(ast.unparse(ast.Set(elts=[]))),
ast.parse('{*()}')
)
self.check_ast_roundtrip("{/}")

def test_set_comprehension(self):
self.check_ast_roundtrip("{x for x in range(5)}")
Expand Down
3 changes: 3 additions & 0 deletions Objects/setobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,9 @@ set_repr_lock_held(PySetObject *so)
/* shortcut for the empty set */
if (!so->used) {
Py_ReprLeave((PyObject*)so);
if (PySet_CheckExact(so)) {
return PyUnicode_FromString("{/}");
}
return PyUnicode_FromFormat("%s()", Py_TYPE(so)->tp_name);
}

Expand Down
Loading
Loading