GeezerVison is showing github.com — gh-112068: C API: Add support of nullable arguments in PyArg_Parse (G… · python/cpython@f5f1ac8
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: Doc/c-api/arg.rst +15Lines changed: 15 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -113,14 +113,18 @@ There are three ways strings and buffers can be converted to C: | 113 | 113 | ``z`` (:class:`str` or ``None``) [const char \*] | 114 | 114 | Like ``s``, but the Python object may also be ``None``, in which case the C | 115 | 115 | pointer is set to ``NULL``. | | 116 | + It is the same as ``s?`` with the C pointer was initialized to ``NULL``. | 116 | 117 |
| 117 | 118 | ``z*`` (:class:`str`, :term:`bytes-like object` or ``None``) [Py_buffer] | 118 | 119 | Like ``s*``, but the Python object may also be ``None``, in which case the | 119 | 120 | ``buf`` member of the :c:type:`Py_buffer` structure is set to ``NULL``. | | 121 | + It is the same as ``s*?`` with the ``buf`` member of the :c:type:`Py_buffer` | | 122 | + structure was initialized to ``NULL``. | 120 | 123 |
| 121 | 124 | ``z#`` (:class:`str`, read-only :term:`bytes-like object` or ``None``) [const char \*, :c:type:`Py_ssize_t`] | 122 | 125 | Like ``s#``, but the Python object may also be ``None``, in which case the C | 123 | 126 | pointer is set to ``NULL``. | | 127 | + It is the same as ``s#?`` with the C pointer was initialized to ``NULL``. | 124 | 128 |
| 125 | 129 | ``y`` (read-only :term:`bytes-like object`) [const char \*] | 126 | 130 | This format converts a bytes-like object to a C pointer to a | @@ -377,6 +381,17 @@ Other objects | 377 | 381 | Non-tuple sequences are deprecated if *items* contains format units | 378 | 382 | which store a borrowed buffer or a borrowed reference. | 379 | 383 |
| | 384 | +``unit?`` (anything or ``None``) [*matching-variable(s)*] | | 385 | + ``?`` modifies the behavior of the preceding format unit. | | 386 | + The C variable(s) corresponding to that parameter should be initialized | | 387 | + to their default value --- when the argument is ``None``, | | 388 | + :c:func:`PyArg_ParseTuple` does not touch the contents of the corresponding | | 389 | + C variable(s). | | 390 | + If the argument is not ``None``, it is parsed according to the specified | | 391 | + format unit. | | 392 | + | | 393 | + .. versionadded:: next | | 394 | + | 380 | 395 | A few other characters have a meaning in a format string. These may not occur | 381 | 396 | inside nested parentheses. They are: | 382 | 397 |
| |
Collapse file Copy file name to clipboardExpand all lines: Doc/whatsnew/3.14.rst +5Lines changed: 5 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -1846,6 +1846,11 @@ New features | 1846 | 1846 | file. | 1847 | 1847 | (Contributed by Victor Stinner in :gh:`127350`.) | 1848 | 1848 |
| | 1849 | +* Add support of nullable arguments in :c:func:`PyArg_ParseTuple` and | | 1850 | + similar functions. | | 1851 | + Adding ``?`` after any format unit makes ``None`` be accepted as a value. | | 1852 | + (Contributed by Serhiy Storchaka in :gh:`112068`.) | | 1853 | + | 1849 | 1854 | * Add macros :c:func:`Py_PACK_VERSION` and :c:func:`Py_PACK_FULL_VERSION` for | 1850 | 1855 | bit-packing Python version numbers. | 1851 | 1856 | (Contributed by Petr Viktorin in :gh:`128629`.) | |
Collapse file Copy file name to clipboardExpand all lines: Lib/test/test_capi/test_getargs.py +117Lines changed: 117 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -1387,6 +1387,123 @@ def test_nested_sequence(self): | 1387 | 1387 | "argument 1 must be sequence of length 1, not 0"): | 1388 | 1388 | parse(([],), {}, '(' + f + ')', ['a']) | 1389 | 1389 |
| | 1390 | + def test_specific_type_errors(self): | | 1391 | + parse = _testcapi.parse_tuple_and_keywords | | 1392 | + | | 1393 | + def check(format, arg, expected, got='list'): | | 1394 | + errmsg = f'must be {expected}, not {got}' | | 1395 | + with self.assertRaisesRegex(TypeError, errmsg): | | 1396 | + parse((arg,), {}, format, ['a']) | | 1397 | + | | 1398 | + check('k', [], 'int') | | 1399 | + check('k?', [], 'int or None') | | 1400 | + check('K', [], 'int') | | 1401 | + check('K?', [], 'int or None') | | 1402 | + check('c', [], 'a byte string of length 1') | | 1403 | + check('c?', [], 'a byte string of length 1 or None') | | 1404 | + check('c', b'abc', 'a byte string of length 1', | | 1405 | + 'a bytes object of length 3') | | 1406 | + check('c?', b'abc', 'a byte string of length 1 or None', | | 1407 | + 'a bytes object of length 3') | | 1408 | + check('c', bytearray(b'abc'), 'a byte string of length 1', | | 1409 | + 'a bytearray object of length 3') | | 1410 | + check('c?', bytearray(b'abc'), 'a byte string of length 1 or None', | | 1411 | + 'a bytearray object of length 3') | | 1412 | + check('C', [], 'a unicode character') | | 1413 | + check('C?', [], 'a unicode character or None') | | 1414 | + check('C', 'abc', 'a unicode character', | | 1415 | + 'a string of length 3') | | 1416 | + check('C?', 'abc', 'a unicode character or None', | | 1417 | + 'a string of length 3') | | 1418 | + check('s', [], 'str') | | 1419 | + check('s?', [], 'str or None') | | 1420 | + check('z', [], 'str or None') | | 1421 | + check('z?', [], 'str or None') | | 1422 | + check('es', [], 'str') | | 1423 | + check('es?', [], 'str or None') | | 1424 | + check('es#', [], 'str') | | 1425 | + check('es#?', [], 'str or None') | | 1426 | + check('et', [], 'str, bytes or bytearray') | | 1427 | + check('et?', [], 'str, bytes, bytearray or None') | | 1428 | + check('et#', [], 'str, bytes or bytearray') | | 1429 | + check('et#?', [], 'str, bytes, bytearray or None') | | 1430 | + check('w*', [], 'read-write bytes-like object') | | 1431 | + check('w*?', [], 'read-write bytes-like object or None') | | 1432 | + check('S', [], 'bytes') | | 1433 | + check('S?', [], 'bytes or None') | | 1434 | + check('U', [], 'str') | | 1435 | + check('U?', [], 'str or None') | | 1436 | + check('Y', [], 'bytearray') | | 1437 | + check('Y?', [], 'bytearray or None') | | 1438 | + check('(OO)', 42, '2-item tuple', 'int') | | 1439 | + check('(OO)?', 42, '2-item tuple or None', 'int') | | 1440 | + check('(OO)', (1, 2, 3), 'tuple of length 2', '3') | | 1441 | + | | 1442 | + def test_nullable(self): | | 1443 | + parse = _testcapi.parse_tuple_and_keywords | | 1444 | + | | 1445 | + def check(format, arg, allows_none=False): | | 1446 | + # Because some format units (such as y*) require cleanup, | | 1447 | + # we force the parsing code to perform the cleanup by adding | | 1448 | + # an argument that always fails. | | 1449 | + # By checking for an exception, we ensure that the parsing | | 1450 | + # of the first argument was successful. | | 1451 | + self.assertRaises(OverflowError, parse, | | 1452 | + (arg, 256), {}, format + '?b', ['a', 'b']) | | 1453 | + self.assertRaises(OverflowError, parse, | | 1454 | + (None, 256), {}, format + '?b', ['a', 'b']) | | 1455 | + self.assertRaises(OverflowError, parse, | | 1456 | + (arg, 256), {}, format + 'b', ['a', 'b']) | | 1457 | + self.assertRaises(OverflowError if allows_none else TypeError, parse, | | 1458 | + (None, 256), {}, format + 'b', ['a', 'b']) | | 1459 | + | | 1460 | + check('b', 42) | | 1461 | + check('B', 42) | | 1462 | + check('h', 42) | | 1463 | + check('H', 42) | | 1464 | + check('i', 42) | | 1465 | + check('I', 42) | | 1466 | + check('n', 42) | | 1467 | + check('l', 42) | | 1468 | + check('k', 42) | | 1469 | + check('L', 42) | | 1470 | + check('K', 42) | | 1471 | + check('f', 2.5) | | 1472 | + check('d', 2.5) | | 1473 | + check('D', 2.5j) | | 1474 | + check('c', b'a') | | 1475 | + check('C', 'a') | | 1476 | + check('p', True, allows_none=True) | | 1477 | + check('y', b'buffer') | | 1478 | + check('y*', b'buffer') | | 1479 | + check('y#', b'buffer') | | 1480 | + check('s', 'string') | | 1481 | + check('s*', 'string') | | 1482 | + check('s#', 'string') | | 1483 | + check('z', 'string', allows_none=True) | | 1484 | + check('z*', 'string', allows_none=True) | | 1485 | + check('z#', 'string', allows_none=True) | | 1486 | + check('w*', bytearray(b'buffer')) | | 1487 | + check('U', 'string') | | 1488 | + check('S', b'bytes') | | 1489 | + check('Y', bytearray(b'bytearray')) | | 1490 | + check('O', object, allows_none=True) | | 1491 | + | | 1492 | + check('(OO)', (1, 2)) | | 1493 | + self.assertEqual(parse((((1, 2), 3),), {}, '((OO)?O)', ['a']), (1, 2, 3)) | | 1494 | + self.assertEqual(parse(((None, 3),), {}, '((OO)?O)', ['a']), (NULL, NULL, 3)) | | 1495 | + self.assertEqual(parse((((1, 2), 3),), {}, '((OO)O)', ['a']), (1, 2, 3)) | | 1496 | + self.assertRaises(TypeError, parse, ((None, 3),), {}, '((OO)O)', ['a']) | | 1497 | + | | 1498 | + parse((None,), {}, 'es?', ['a']) | | 1499 | + parse((None,), {}, 'es#?', ['a']) | | 1500 | + parse((None,), {}, 'et?', ['a']) | | 1501 | + parse((None,), {}, 'et#?', ['a']) | | 1502 | + parse((None,), {}, 'O!?', ['a']) | | 1503 | + parse((None,), {}, 'O&?', ['a']) | | 1504 | + | | 1505 | + # TODO: More tests for es?, es#?, et?, et#?, O!, O& | | 1506 | + | 1390 | 1507 | @unittest.skipIf(_testinternalcapi is None, 'needs _testinternalcapi') | 1391 | 1508 | def test_gh_119213(self): | 1392 | 1509 | rc, out, err = script_helper.assert_python_ok("-c", """if True: | |
Collapse file Copy file name to clipboardExpand all lines: Lib/test/test_mmap.py +1-1Lines changed: 1 addition & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -732,7 +732,7 @@ def test_tagname(self): | 732 | 732 | m2.close() | 733 | 733 | m1.close() | 734 | 734 |
| 735 | | - with self.assertRaisesRegex(TypeError, 'tagname'): | | 735 | + with self.assertRaisesRegex(TypeError, 'must be str or None'): | 736 | 736 | mmap.mmap(-1, 8, tagname=1) | 737 | 737 |
| 738 | 738 | @cpython_only | |
Collapse file Copy file name to clipboard +3Lines changed: 3 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +Add support of nullable arguments in :c:func:`PyArg_Parse` and similar | | 2 | +functions. Adding ``?`` after any format unit makes ``None`` be accepted as | | 3 | +a value. |
Collapse file Copy file name to clipboardExpand all lines: Modules/_ctypes/_ctypes.c +3-9Lines changed: 3 additions & 9 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -3848,9 +3848,7 @@ _validate_paramflags(ctypes_state *st, PyTypeObject *type, PyObject *paramflags) | 3848 | 3848 | PyObject *name = Py_None; | 3849 | 3849 | PyObject *defval; | 3850 | 3850 | PyObject *typ; | 3851 | | - if (!PyArg_ParseTuple(item, "i|OO", &flag, &name, &defval) || | 3852 | | - !(name == Py_None || PyUnicode_Check(name))) | 3853 | | - { | | 3851 | + if (!PyArg_ParseTuple(item, "i|U?O", &flag, &name, &defval)) { | 3854 | 3852 | PyErr_SetString(PyExc_TypeError, | 3855 | 3853 | "paramflags must be a sequence of (int [,string [,value]]) tuples"); | 3856 | 3854 | return 0; | @@ -3915,10 +3913,8 @@ PyCFuncPtr_FromDll(PyTypeObject *type, PyObject *args, PyObject *kwds) | 3915 | 3913 | void *handle; | 3916 | 3914 | PyObject *paramflags = NULL; | 3917 | 3915 |
| 3918 | | - if (!PyArg_ParseTuple(args, "O|O", &ftuple, ¶mflags)) | | 3916 | + if (!PyArg_ParseTuple(args, "O|O?", &ftuple, ¶mflags)) | 3919 | 3917 | return NULL; | 3920 | | - if (paramflags == Py_None) | 3921 | | - paramflags = NULL; | 3922 | 3918 |
| 3923 | 3919 | ftuple = PySequence_Tuple(ftuple); | 3924 | 3920 | if (!ftuple) | @@ -4050,10 +4046,8 @@ PyCFuncPtr_FromVtblIndex(PyTypeObject *type, PyObject *args, PyObject *kwds) | 4050 | 4046 | GUID *iid = NULL; | 4051 | 4047 | Py_ssize_t iid_len = 0; | 4052 | 4048 |
| 4053 | | - if (!PyArg_ParseTuple(args, "is|Oz#", &index, &name, ¶mflags, &iid, &iid_len)) | | 4049 | + if (!PyArg_ParseTuple(args, "is|O?z#", &index, &name, ¶mflags, &iid, &iid_len)) | 4054 | 4050 | return NULL; | 4055 | | - if (paramflags == Py_None) | 4056 | | - paramflags = NULL; | 4057 | 4051 |
| 4058 | 4052 | ctypes_state *st = get_module_state_by_def(Py_TYPE(type)); | 4059 | 4053 | if (!_validate_paramflags(st, type, paramflags)) { | |
Collapse file Copy file name to clipboardExpand all lines: Modules/_interpretersmodule.c +3-6Lines changed: 3 additions & 6 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -1252,14 +1252,11 @@ interp_get_config(PyObject *self, PyObject *args, PyObject *kwds) | 1252 | 1252 | PyObject *idobj = NULL; | 1253 | 1253 | int restricted = 0; | 1254 | 1254 | if (!PyArg_ParseTupleAndKeywords(args, kwds, | 1255 | | - "O|$p:get_config", kwlist, | | 1255 | + "O?|$p:get_config", kwlist, | 1256 | 1256 | &idobj, &restricted)) | 1257 | 1257 | { | 1258 | 1258 | return NULL; | 1259 | 1259 | } | 1260 | | - if (idobj == Py_None) { | 1261 | | - idobj = NULL; | 1262 | | - } | 1263 | 1260 |
| 1264 | 1261 | int reqready = 0; | 1265 | 1262 | PyInterpreterState *interp = \ | @@ -1376,14 +1373,14 @@ capture_exception(PyObject *self, PyObject *args, PyObject *kwds) | 1376 | 1373 | static char *kwlist[] = {"exc", NULL}; | 1377 | 1374 | PyObject *exc_arg = NULL; | 1378 | 1375 | if (!PyArg_ParseTupleAndKeywords(args, kwds, | 1379 | | - "|O:capture_exception", kwlist, | | 1376 | + "|O?:capture_exception", kwlist, | 1380 | 1377 | &exc_arg)) | 1381 | 1378 | { | 1382 | 1379 | return NULL; | 1383 | 1380 | } | 1384 | 1381 |
| 1385 | 1382 | PyObject *exc = exc_arg; | 1386 | | - if (exc == NULL || exc == Py_None) { | | 1383 | + if (exc == NULL) { | 1387 | 1384 | exc = PyErr_GetRaisedException(); | 1388 | 1385 | if (exc == NULL) { | 1389 | 1386 | Py_RETURN_NONE; | |
Collapse file Copy file name to clipboardExpand all lines: Modules/_json.c +3-10Lines changed: 3 additions & 10 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -1228,23 +1228,16 @@ encoder_new(PyTypeObject *type, PyObject *args, PyObject *kwds) | 1228 | 1228 | static char *kwlist[] = {"markers", "default", "encoder", "indent", "key_separator", "item_separator", "sort_keys", "skipkeys", "allow_nan", NULL}; | 1229 | 1229 |
| 1230 | 1230 | PyEncoderObject *s; | 1231 | | - PyObject *markers, *defaultfn, *encoder, *indent, *key_separator; | | 1231 | + PyObject *markers = Py_None, *defaultfn, *encoder, *indent, *key_separator; | 1232 | 1232 | PyObject *item_separator; | 1233 | 1233 | int sort_keys, skipkeys, allow_nan; | 1234 | 1234 |
| 1235 | | - if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOOOUUppp:make_encoder", kwlist, | 1236 | | - &markers, &defaultfn, &encoder, &indent, | | 1235 | + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!?OOOUUppp:make_encoder", kwlist, | | 1236 | + &PyDict_Type, &markers, &defaultfn, &encoder, &indent, | 1237 | 1237 | &key_separator, &item_separator, | 1238 | 1238 | &sort_keys, &skipkeys, &allow_nan)) | 1239 | 1239 | return NULL; | 1240 | 1240 |
| 1241 | | - if (markers != Py_None && !PyDict_Check(markers)) { | 1242 | | - PyErr_Format(PyExc_TypeError, | 1243 | | - "make_encoder() argument 1 must be dict or None, " | 1244 | | - "not %.200s", Py_TYPE(markers)->tp_name); | 1245 | | - return NULL; | 1246 | | - } | 1247 | | - | 1248 | 1241 | s = (PyEncoderObject *)type->tp_alloc(type, 0); | 1249 | 1242 | if (s == NULL) | 1250 | 1243 | return NULL; | |
Collapse file Copy file name to clipboardExpand all lines: Modules/_threadmodule.c +5-13Lines changed: 5 additions & 13 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -651,12 +651,12 @@ PyThreadHandleObject_join(PyObject *op, PyObject *args) | 651 | 651 | PyThreadHandleObject *self = PyThreadHandleObject_CAST(op); | 652 | 652 |
| 653 | 653 | PyObject *timeout_obj = NULL; | 654 | | - if (!PyArg_ParseTuple(args, "|O:join", &timeout_obj)) { | | 654 | + if (!PyArg_ParseTuple(args, "|O?:join", &timeout_obj)) { | 655 | 655 | return NULL; | 656 | 656 | } | 657 | 657 |
| 658 | 658 | PyTime_t timeout_ns = -1; | 659 | | - if (timeout_obj != NULL && timeout_obj != Py_None) { | | 659 | + if (timeout_obj != NULL) { | 660 | 660 | if (_PyTime_FromSecondsObject(&timeout_ns, timeout_obj, | 661 | 661 | _PyTime_ROUND_TIMEOUT) < 0) { | 662 | 662 | return NULL; | @@ -1919,10 +1919,10 @@ thread_PyThread_start_joinable_thread(PyObject *module, PyObject *fargs, | 1919 | 1919 | PyObject *func = NULL; | 1920 | 1920 | int daemon = 1; | 1921 | 1921 | thread_module_state *state = get_thread_state(module); | 1922 | | - PyObject *hobj = NULL; | | 1922 | + PyObject *hobj = Py_None; | 1923 | 1923 | if (!PyArg_ParseTupleAndKeywords(fargs, fkwargs, | 1924 | | - "O|Op:start_joinable_thread", keywords, | 1925 | | - &func, &hobj, &daemon)) { | | 1924 | + "O|O!?p:start_joinable_thread", keywords, | | 1925 | + &func, state->thread_handle_type, &hobj, &daemon)) { | 1926 | 1926 | return NULL; | 1927 | 1927 | } | 1928 | 1928 |
| @@ -1932,14 +1932,6 @@ thread_PyThread_start_joinable_thread(PyObject *module, PyObject *fargs, | 1932 | 1932 | return NULL; | 1933 | 1933 | } | 1934 | 1934 |
| 1935 | | - if (hobj == NULL) { | 1936 | | - hobj = Py_None; | 1937 | | - } | 1938 | | - else if (hobj != Py_None && !Py_IS_TYPE(hobj, state->thread_handle_type)) { | 1939 | | - PyErr_SetString(PyExc_TypeError, "'handle' must be a _ThreadHandle"); | 1940 | | - return NULL; | 1941 | | - } | 1942 | | - | 1943 | 1935 | if (PySys_Audit("_thread.start_joinable_thread", "OiO", func, daemon, | 1944 | 1936 | hobj) < 0) { | 1945 | 1937 | return NULL; | |
Collapse file Copy file name to clipboardExpand all lines: Modules/mmapmodule.c +4-11Lines changed: 4 additions & 11 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 23 | 23 | #endif | 24 | 24 |
| 25 | 25 | #include <Python.h> | 26 | | -#include "pycore_abstract.h" // _Py_convert_optional_to_ssize_t() | 27 | 26 | #include "pycore_bytesobject.h" // _PyBytes_Find() | 28 | 27 | #include "pycore_fileutils.h" // _Py_stat_struct | 29 | 28 |
| @@ -516,7 +515,7 @@ mmap_read_method(PyObject *op, PyObject *args) | 516 | 515 | mmap_object *self = mmap_object_CAST(op); | 517 | 516 |
| 518 | 517 | CHECK_VALID(NULL); | 519 | | - if (!PyArg_ParseTuple(args, "|O&:read", _Py_convert_optional_to_ssize_t, &num_bytes)) | | 518 | + if (!PyArg_ParseTuple(args, "|n?:read", &num_bytes)) | 520 | 519 | return NULL; | 521 | 520 | CHECK_VALID(NULL); | 522 | 521 |
| @@ -1710,7 +1709,7 @@ new_mmap_object(PyTypeObject *type, PyObject *args, PyObject *kwdict) | 1710 | 1709 | DWORD off_lo; /* lower 32 bits of offset */ | 1711 | 1710 | DWORD size_hi; /* upper 32 bits of size */ | 1712 | 1711 | DWORD size_lo; /* lower 32 bits of size */ | 1713 | | - PyObject *tagname = Py_None; | | 1712 | + PyObject *tagname = NULL; | 1714 | 1713 | DWORD dwErr = 0; | 1715 | 1714 | int fileno; | 1716 | 1715 | HANDLE fh = 0; | @@ -1720,7 +1719,7 @@ new_mmap_object(PyTypeObject *type, PyObject *args, PyObject *kwdict) | 1720 | 1719 | "tagname", | 1721 | 1720 | "access", "offset", NULL }; | 1722 | 1721 |
| 1723 | | - if (!PyArg_ParseTupleAndKeywords(args, kwdict, "in|OiL", keywords, | | 1722 | + if (!PyArg_ParseTupleAndKeywords(args, kwdict, "in|U?iL", keywords, | 1724 | 1723 | &fileno, &map_size, | 1725 | 1724 | &tagname, &access, &offset)) { | 1726 | 1725 | return NULL; | @@ -1853,13 +1852,7 @@ new_mmap_object(PyTypeObject *type, PyObject *args, PyObject *kwdict) | 1853 | 1852 | m_obj->weakreflist = NULL; | 1854 | 1853 | m_obj->exports = 0; | 1855 | 1854 | /* set the tag name */ | 1856 | | - if (!Py_IsNone(tagname)) { | 1857 | | - if (!PyUnicode_Check(tagname)) { | 1858 | | - Py_DECREF(m_obj); | 1859 | | - return PyErr_Format(PyExc_TypeError, "expected str or None for " | 1860 | | - "'tagname', not %.200s", | 1861 | | - Py_TYPE(tagname)->tp_name); | 1862 | | - } | | 1855 | + if (tagname != NULL) { | 1863 | 1856 | m_obj->tagname = PyUnicode_AsWideCharString(tagname, NULL); | 1864 | 1857 | if (m_obj->tagname == NULL) { | 1865 | 1858 | Py_DECREF(m_obj); | |
|