FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

gh-148829: Make sentinels' repr and module customizable by JelleZijlstra · Pull Request #149654 · python/cpython · GitHub

Repository navigation

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .c  (2) .dat  (1) .h  (6) .py  (2) .rst  (3) All 5 file types selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
4 changes: 2 additions & 2 deletions Doc/c-api/sentinel.rst
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ Sentinel objects

.. versionadded:: 3.15

.. c:function:: PyObject* PySentinel_New(const char *name, const char *module_name)
.. c:function:: PyObject* PySentinel_New(const char *name, const char *module_name, const char *repr)

Return a new :class:`sentinel` object with :attr:`~sentinel.__name__` set to
*name* and :attr:`~sentinel.__module__` set to *module_name*.
*name* must not be ``NULL``. If *module_name* is ``NULL``, :attr:`~sentinel.__module__`
is set to ``None``.
is set to ``None``. If *repr* is ``NULL``, ``repr()`` returns :attr:`~sentinel.__name__`.
Return ``NULL`` with an exception set on failure.

For pickling to work, *module_name* must be the name of an importable
Expand Down
1 change: 1 addition & 0 deletions Doc/data/refcounts.dat
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -2040,6 +2040,7 @@ PySeqIter_New:PyObject*:seq:0:
PySentinel_New:PyObject*::+1:
PySentinel_New:const char*:name::
PySentinel_New:const char*:module_name::
PySentinel_New:const char*:repr::

PySequence_Check:int:::
PySequence_Check:PyObject*:o:0:
Expand Down
12 changes: 9 additions & 3 deletions Doc/library/functions.rst
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -1827,15 +1827,21 @@ are always available. They are listed here in alphabetical order.
:func:`setattr`.


.. class:: sentinel(name, /)
.. class:: sentinel(name, /, *, repr=None)

Return a new unique sentinel object. *name* must be a :class:`str`, and is
used as the returned object's representation::
used by default as the returned object's representation::

>>> MISSING = sentinel("MISSING")
>>> MISSING
MISSING

The optional *repr* argument can be used to specify a different representation::

>>> MISSING = sentinel("MISSING", repr="<MISSING>")
>>> MISSING
<MISSING>

Sentinel objects are truthy and compare equal only to themselves. They are
intended to be compared with the :keyword:`is` operator.

Expand Down Expand Up @@ -1879,7 +1885,7 @@ are always available. They are listed here in alphabetical order.

.. attribute:: __module__

The name of the module where the sentinel was created.
The name of the module where the sentinel was created. This attribute is writable.

.. versionadded:: 3.15

Expand Down
3 changes: 2 additions & 1 deletion Include/cpython/sentinelobject.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ PyAPI_DATA(PyTypeObject) PySentinel_Type;

PyAPI_FUNC(PyObject *) PySentinel_New(
const char *name,
const char *module_name);
const char *module_name,
const char *repr);

#ifdef __cplusplus
}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Include/internal/pycore_global_strings.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(repeat)
STRUCT_FOR_ID(repl)
STRUCT_FOR_ID(replace)
STRUCT_FOR_ID(repr)
STRUCT_FOR_ID(reqrefs)
STRUCT_FOR_ID(require_ready)
STRUCT_FOR_ID(reserved)
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_runtime_init_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Include/internal/pycore_unicodeobject_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 20 additions & 3 deletions Lib/test/test_builtin.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -1956,16 +1956,33 @@ def test_sentinel(self):
with self.assertRaises(TypeError):
class SubSentinel(sentinel):
pass

def test_sentinel_attributes(self):
missing = sentinel("MISSING")
with self.assertRaises(TypeError):
sentinel.attribute = "value"
with self.assertRaises(AttributeError):
missing.__name__ = "CHANGED"
missing.attribute = "value"
with self.assertRaises(AttributeError):
missing.__module__ = "changed"
missing.__name__ = "CHANGED"
missing.__module__ = "changed"
self.assertEqual(missing.__module__, "changed")
with self.assertRaises(AttributeError):
del missing.__name__
del missing.__module__
with self.assertRaises(AttributeError):
del missing.__module__
missing.__module__

def test_sentinel_repr(self):
with_repr = sentinel("WITH_REPR", repr="custom")
without_repr = sentinel("WITHOUT_REPR", repr=None)
self.assertEqual(repr(with_repr), "custom")
self.assertEqual(repr(without_repr), "WITHOUT_REPR")
self.assertEqual(str(with_repr), "custom")
self.assertEqual(str(without_repr), "WITHOUT_REPR")

with self.assertRaisesRegex(TypeError, "repr.*str or None"):
sentinel("BAD_REPR", repr=42)

def test_sentinel_pickle(self):
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
Expand Down
6 changes: 6 additions & 0 deletions Lib/test/test_capi/test_object.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ def test_pysentinel_new(self):
self.assertEqual(no_module.__name__, "NO_MODULE")
self.assertIs(no_module.__module__, None)

with_repr = _testcapi.pysentinel_new("WITH_REPR", __name__, "custom repr")
self.assertIs(type(with_repr), sentinel)
self.assertEqual(with_repr.__name__, "WITH_REPR")
self.assertEqual(with_repr.__module__, __name__)
self.assertEqual(repr(with_repr), "custom repr")

globals()["CAPI_SENTINEL"] = marker
self.addCleanup(globals().pop, "CAPI_SENTINEL", None)
self.assertIs(pickle.loads(pickle.dumps(marker)), marker)
Expand Down
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:class:`sentinel` objects now support a ``repr=`` argument and their
:attr:`~sentinel.__module__` attribute is writable.
5 changes: 3 additions & 2 deletions Modules/_testcapi/object.c
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -560,10 +560,11 @@ pysentinel_new(PyObject *self, PyObject *args)
{
const char *name;
const char *module_name = NULL;
if (!PyArg_ParseTuple(args, "s|s", &name, &module_name)) {
const char *repr = NULL;
if (!PyArg_ParseTuple(args, "s|ss", &name, &module_name, &repr)) {
return NULL;
}
return PySentinel_New(name, module_name);
return PySentinel_New(name, module_name, repr);
}

static PyObject *
Expand Down
62 changes: 50 additions & 12 deletions Objects/clinic/sentinelobject.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 34 additions & 8 deletions Objects/sentinelobject.c
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ typedef struct {
PyObject_HEAD
PyObject *name;
PyObject *module;
PyObject *repr;
} sentinelobject;

#define sentinelobject_CAST(op) \
Expand Down Expand Up @@ -46,7 +47,7 @@ caller(void)
}

static PyObject *
sentinel_new_with_module(PyTypeObject *type, PyObject *name, PyObject *module)
sentinel_new_with_module(PyTypeObject *type, PyObject *name, PyObject *module, PyObject *repr)
{
assert(PyUnicode_Check(name));

Expand All @@ -56,6 +57,7 @@ sentinel_new_with_module(PyTypeObject *type, PyObject *name, PyObject *module)
}
self->name = Py_NewRef(name);
self->module = Py_NewRef(module);
self->repr = Py_XNewRef(repr);
_PyObject_GC_TRACK(self);
return (PyObject *)self;
}
Expand All @@ -66,37 +68,56 @@ sentinel.__new__ as sentinel_new

name: object(subclass_of='&PyUnicode_Type')
/
*
repr: object = None
[clinic start generated code]*/

static PyObject *
sentinel_new_impl(PyTypeObject *type, PyObject *name)
/*[clinic end generated code: output=4af55c6048bed30d input=3ab75704f39c119c]*/
sentinel_new_impl(PyTypeObject *type, PyObject *name, PyObject *repr)
/*[clinic end generated code: output=1eb7fab52e57d8c8 input=28cab6c468997b35]*/
{
if (repr == Py_None) {
repr = NULL;
}
else if (!PyUnicode_Check(repr)) {
_PyArg_BadArgument("sentinel", "argument 'repr'", "str or None", repr);
return NULL;
}
PyObject *module = caller();
PyObject *self = sentinel_new_with_module(type, name, module);
PyObject *self = sentinel_new_with_module(type, name, module, repr);
Py_DECREF(module);
return self;
}

PyObject *
PySentinel_New(const char *name, const char *module_name)
PySentinel_New(const char *name, const char *module_name, const char *repr)
{
PyObject *name_obj = PyUnicode_FromString(name);
if (name_obj == NULL) {
return NULL;
}
PyObject *repr_obj = NULL;
if (repr != NULL) {
repr_obj = PyUnicode_FromString(repr);
if (repr_obj == NULL) {
Py_DECREF(name_obj);
return NULL;
}
}
PyObject *module_obj = module_name == NULL
? Py_None
: PyUnicode_FromString(module_name);
if (module_obj == NULL) {
Py_DECREF(name_obj);
Py_XDECREF(repr_obj);
return NULL;
}

PyObject *sentinel = sentinel_new_with_module(
&PySentinel_Type, name_obj, module_obj);
&PySentinel_Type, name_obj, module_obj, repr_obj);
Py_DECREF(module_obj);
Py_DECREF(name_obj);
Py_XDECREF(repr_obj);
return sentinel;
}

Expand All @@ -106,6 +127,7 @@ sentinel_clear(PyObject *op)
sentinelobject *self = sentinelobject_CAST(op);
Py_CLEAR(self->name);
Py_CLEAR(self->module);
Py_CLEAR(self->repr);
return 0;
}

Expand All @@ -123,13 +145,17 @@ sentinel_traverse(PyObject *op, visitproc visit, void *arg)
sentinelobject *self = sentinelobject_CAST(op);
Py_VISIT(self->name);
Py_VISIT(self->module);
Py_VISIT(self->repr);
return 0;
}

static PyObject *
sentinel_repr(PyObject *op)
{
sentinelobject *self = sentinelobject_CAST(op);
if (self->repr != NULL) {
return Py_NewRef(self->repr);
}
return Py_NewRef(self->name);
}

Expand Down Expand Up @@ -161,7 +187,7 @@ static PyMethodDef sentinel_methods[] = {

static PyMemberDef sentinel_members[] = {
{"__name__", Py_T_OBJECT_EX, offsetof(sentinelobject, name), Py_READONLY},
{"__module__", Py_T_OBJECT_EX, offsetof(sentinelobject, module), Py_READONLY},
{"__module__", Py_T_OBJECT_EX, offsetof(sentinelobject, module), 0},
{NULL}
};

Expand All @@ -170,7 +196,7 @@ static PyNumberMethods sentinel_as_number = {
};

PyDoc_STRVAR(sentinel_doc,
"sentinel(name, /)\n"
"sentinel(name, /, *, repr=None)\n"
"--\n\n"
"Create a unique sentinel object with the given name.");

Expand Down
Loading

Back | FazBrowse Home | New Git URL