From 5a53f368e61a5535571362e36c451827ee7d3a27 Mon Sep 17 00:00:00 2001 From: Eli Bendersky Date: Fri, 25 Nov 2011 15:07:50 +0200 Subject: [PATCH 01/10] fix some typos in Doc/c-api/memoryview.rst --- Doc/c-api/memoryview.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/c-api/memoryview.rst b/Doc/c-api/memoryview.rst index 34ecd121897..6b49cdf70a3 100644 --- a/Doc/c-api/memoryview.rst +++ b/Doc/c-api/memoryview.rst @@ -17,7 +17,7 @@ any other object. Create a memoryview object from an object that provides the buffer interface. If *obj* supports writable buffer exports, the memoryview object will be - readable and writable, other it will be read-only. + readable and writable, otherwise it will be read-only. .. c:function:: PyObject *PyMemoryView_FromBuffer(Py_buffer *view) @@ -33,7 +33,7 @@ any other object. Create a memoryview object to a contiguous chunk of memory (in either 'C' or 'F'ortran *order*) from an object that defines the buffer interface. If memory is contiguous, the memoryview object points to the - original memory. Otherwise copy is made and the memoryview points to a + original memory. Otherwise, a copy is made and the memoryview points to a new bytes object. From fd9ebd4a361805607baea3e038652f207575ced8 Mon Sep 17 00:00:00 2001 From: Antoine Pitrou Date: Fri, 25 Nov 2011 16:33:53 +0100 Subject: [PATCH 02/10] Clarify concatenation behaviour of immutable strings, and remove explicit mention of the CPython optimization hack. --- Doc/faq/programming.rst | 26 ++++++++++++++++++++++++++ Doc/library/stdtypes.rst | 19 +++++++++++-------- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/Doc/faq/programming.rst b/Doc/faq/programming.rst index d1a3dafce86..f157a943a43 100644 --- a/Doc/faq/programming.rst +++ b/Doc/faq/programming.rst @@ -989,6 +989,32 @@ What does 'UnicodeDecodeError' or 'UnicodeEncodeError' error mean? See the :ref:`unicode-howto`. +What is the most efficient way to concatenate many strings together? +-------------------------------------------------------------------- + +:class:`str` and :class:`bytes` objects are immutable, therefore concatenating +many strings together is inefficient as each concatenation creates a new +object. In the general case, the total runtime cost is quadratic in the +total string length. + +To accumulate many :class:`str` objects, the recommended idiom is to place +them into a list and call :meth:`str.join` at the end:: + + chunks = [] + for s in my_strings: + chunks.append(s) + result = ''.join(chunks) + +(another reasonably efficient idiom is to use :class:`io.StringIO`) + +To accumulate many :class:`bytes` objects, the recommended idiom is to extend +a :class:`bytearray` object using in-place concatenation (the ``+=`` operator):: + + result = bytearray() + for b in my_bytes_objects: + result += b + + Sequences (Tuples/Lists) ======================== diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index af1e44a941c..5b54b09ed46 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -964,15 +964,18 @@ Notes: If *k* is ``None``, it is treated like ``1``. (6) - .. impl-detail:: + Concatenating immutable strings always results in a new object. This means + that building up a string by repeated concatenation will have a quadratic + runtime cost in the total string length. To get a linear runtime cost, + you must switch to one of the alternatives below: - If *s* and *t* are both strings, some Python implementations such as - CPython can usually perform an in-place optimization for assignments of - the form ``s = s + t`` or ``s += t``. When applicable, this optimization - makes quadratic run-time much less likely. This optimization is both - version and implementation dependent. For performance sensitive code, it - is preferable to use the :meth:`str.join` method which assures consistent - linear concatenation performance across versions and implementations. + * if concatenating :class:`str` objects, you can build a list and use + :meth:`str.join` at the end; + + * if concatenating :class:`bytes` objects, you can similarly use + :meth:`bytes.join`, or you can do in-place concatenation with a + :class:`bytearray` object. :class:`bytearray` objects are mutable and + have an efficient overallocation mechanism. .. _string-methods: From 0e86a5842d0fadff37c299e8a1c03535c6727b19 Mon Sep 17 00:00:00 2001 From: Antoine Pitrou Date: Fri, 25 Nov 2011 18:03:09 +0100 Subject: [PATCH 03/10] Issue #9957: SpooledTemporaryFile.truncate() now accepts an optional size parameter, as other file-like objects. Patch by Ryan Kelly. --- Lib/tempfile.py | 9 +++++++-- Lib/test/test_tempfile.py | 21 +++++++++++++++++++++ Misc/NEWS | 4 +++- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/Lib/tempfile.py b/Lib/tempfile.py index 3ec6b4a3da9..e3afa3b0ac0 100644 --- a/Lib/tempfile.py +++ b/Lib/tempfile.py @@ -578,8 +578,13 @@ class SpooledTemporaryFile: def tell(self): return self._file.tell() - def truncate(self): - self._file.truncate() + def truncate(self, size=None): + if size is None: + self._file.truncate() + else: + if size > self._max_size: + self.rollover() + self._file.truncate(size) def write(self, s): file = self._file diff --git a/Lib/test/test_tempfile.py b/Lib/test/test_tempfile.py index 014fca01a99..5b0bca797e9 100644 --- a/Lib/test/test_tempfile.py +++ b/Lib/test/test_tempfile.py @@ -846,6 +846,27 @@ class test_SpooledTemporaryFile(TC): pass self.assertRaises(ValueError, use_closed) + def test_truncate_with_size_parameter(self): + # A SpooledTemporaryFile can be truncated to zero size + f = tempfile.SpooledTemporaryFile(max_size=10) + f.write(b'abcdefg\n') + f.seek(0) + f.truncate() + self.assertFalse(f._rolled) + self.assertEqual(f._file.getvalue(), b'') + # A SpooledTemporaryFile can be truncated to a specific size + f = tempfile.SpooledTemporaryFile(max_size=10) + f.write(b'abcdefg\n') + f.truncate(4) + self.assertFalse(f._rolled) + self.assertEqual(f._file.getvalue(), b'abcd') + # A SpooledTemporaryFile rolls over if truncated to large size + f = tempfile.SpooledTemporaryFile(max_size=10) + f.write(b'abcdefg\n') + f.truncate(20) + self.assertTrue(f._rolled) + if has_stat: + self.assertEqual(os.fstat(f.fileno()).st_size, 20) test_classes.append(test_SpooledTemporaryFile) diff --git a/Misc/NEWS b/Misc/NEWS index f78a31eec70..93169e4608b 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -386,10 +386,12 @@ Core and Builtins - Issue #12380: The rjust, ljust and center methods of bytes and bytearray now accept a bytearray argument. - Library ------- +- Issue #9957: SpooledTemporaryFile.truncate() now accepts an optional size + parameter, as other file-like objects. Patch by Ryan Kelly. + - Issue #13458: Fix a memory leak in the ssl module when decoding a certificate with a subjectAltName. Patch by Robert Xiao. From 86a36b500a7f7581194382ce4df914e4bb487873 Mon Sep 17 00:00:00 2001 From: Antoine Pitrou Date: Fri, 25 Nov 2011 18:56:07 +0100 Subject: [PATCH 04/10] PEP 3155 / issue #13448: Qualified name for classes and functions. --- Doc/c-api/function.rst | 10 +++ Doc/data/refcounts.dat | 5 ++ Doc/glossary.rst | 18 +++++ Doc/library/stdtypes.rst | 7 ++ Doc/reference/datamodel.rst | 5 ++ Include/funcobject.h | 2 + Include/object.h | 2 +- Lib/pydoc.py | 2 +- Lib/test/test_code.py | 2 +- Lib/test/test_descr.py | 11 ++- Lib/test/test_dis.py | 1 + Lib/test/test_funcattrs.py | 27 +++++++ Lib/test/test_metaclass.py | 8 ++- Lib/test/test_reprlib.py | 13 ++-- Lib/test/test_sys.py | 4 +- Misc/NEWS | 2 + Objects/funcobject.c | 44 +++++++++++- Objects/typeobject.c | 56 ++++++++++++++- Python/ceval.c | 4 +- Python/compile.c | 139 +++++++++++++++++++++++++++++++----- Python/import.c | 3 +- 21 files changed, 322 insertions(+), 43 deletions(-) diff --git a/Doc/c-api/function.rst b/Doc/c-api/function.rst index 31805fd0ad1..ad9832233f7 100644 --- a/Doc/c-api/function.rst +++ b/Doc/c-api/function.rst @@ -38,6 +38,16 @@ There are a few functions specific to Python functions. object, the argument defaults and closure are set to *NULL*. +.. c:function:: PyObject* PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname) + + As :c:func:`PyFunction_New`, but also allows to set the function object's + ``__qualname__`` attribute. *qualname* should be a unicode object or NULL; + if NULL, the ``__qualname__`` attribute is set to the same value as its + ``__name__`` attribute. + + .. versionadded:: 3.3 + + .. c:function:: PyObject* PyFunction_GetCode(PyObject *op) Return the code object associated with the function object *op*. diff --git a/Doc/data/refcounts.dat b/Doc/data/refcounts.dat index c7d7bd140af..a1004ad48df 100644 --- a/Doc/data/refcounts.dat +++ b/Doc/data/refcounts.dat @@ -465,6 +465,11 @@ PyFunction_New:PyObject*::+1: PyFunction_New:PyObject*:code:+1: PyFunction_New:PyObject*:globals:+1: +PyFunction_NewWithQualName:PyObject*::+1: +PyFunction_NewWithQualName:PyObject*:code:+1: +PyFunction_NewWithQualName:PyObject*:globals:+1: +PyFunction_NewWithQualName:PyObject*:qualname:+1: + PyFunction_SetClosure:int::: PyFunction_SetClosure:PyObject*:op:0: PyFunction_SetClosure:PyObject*:closure:+1: diff --git a/Doc/glossary.rst b/Doc/glossary.rst index 04b3fbba4ca..f5ca0d9c8dc 100644 --- a/Doc/glossary.rst +++ b/Doc/glossary.rst @@ -544,6 +544,24 @@ Glossary for piece in food: print(piece) + qualified name + A dotted name showing the "path" from a module's global scope to a + class, function or method defined in that module, as defined in + :pep:`3155`. For top-level functions and classes, the qualified name + is the same as the object's name:: + + >>> class C: + ... class D: + ... def meth(self): + ... pass + ... + >>> C.__qualname__ + 'C' + >>> C.D.__qualname__ + 'C.D' + >>> C.D.meth.__qualname__ + 'C.D.meth' + reference count The number of references to an object. When the reference count of an object drops to zero, it is deallocated. Reference counting is diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index 5bb4324d62a..ee76cd34013 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -2824,6 +2824,13 @@ types, where they are relevant. Some of these are not reported by the The name of the class or type. +.. attribute:: class.__qualname__ + + The :term:`qualified name` of the class or type. + + .. versionadded:: 3.3 + + .. attribute:: class.__mro__ This attribute is a tuple of classes that are considered when looking for diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index a93c09afe1d..55fd76bd793 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -448,6 +448,11 @@ Callable types +-------------------------+-------------------------------+-----------+ | :attr:`__name__` | The function's name | Writable | +-------------------------+-------------------------------+-----------+ + | :attr:`__qualname__` | The function's | Writable | + | | :term:`qualified name` | | + | | | | + | | .. versionadded:: 3.3 | | + +-------------------------+-------------------------------+-----------+ | :attr:`__module__` | The name of the module the | Writable | | | function was defined in, or | | | | ``None`` if unavailable. | | diff --git a/Include/funcobject.h b/Include/funcobject.h index 521d87bf9f1..cc1426cdc2e 100644 --- a/Include/funcobject.h +++ b/Include/funcobject.h @@ -31,6 +31,7 @@ typedef struct { PyObject *func_weakreflist; /* List of weak references */ PyObject *func_module; /* The __module__ attribute, can be anything */ PyObject *func_annotations; /* Annotations, a dict or NULL */ + PyObject *func_qualname; /* The qualified name */ /* Invariant: * func_closure contains the bindings for func_code->co_freevars, so @@ -44,6 +45,7 @@ PyAPI_DATA(PyTypeObject) PyFunction_Type; #define PyFunction_Check(op) (Py_TYPE(op) == &PyFunction_Type) PyAPI_FUNC(PyObject *) PyFunction_New(PyObject *, PyObject *); +PyAPI_FUNC(PyObject *) PyFunction_NewWithQualName(PyObject *, PyObject *, PyObject *); PyAPI_FUNC(PyObject *) PyFunction_GetCode(PyObject *); PyAPI_FUNC(PyObject *) PyFunction_GetGlobals(PyObject *); PyAPI_FUNC(PyObject *) PyFunction_GetModule(PyObject *); diff --git a/Include/object.h b/Include/object.h index b97f716933a..3cd52975f3f 100644 --- a/Include/object.h +++ b/Include/object.h @@ -418,7 +418,7 @@ typedef struct _heaptypeobject { a given operator (e.g. __getitem__). see add_operators() in typeobject.c . */ PyBufferProcs as_buffer; - PyObject *ht_name, *ht_slots; + PyObject *ht_name, *ht_slots, *ht_qualname; /* here are optional user slots, followed by the members. */ } PyHeapTypeObject; diff --git a/Lib/pydoc.py b/Lib/pydoc.py index caae46ad108..b5dbde651c2 100755 --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -166,7 +166,7 @@ def visiblename(name, all=None, obj=None): if name in {'__builtins__', '__doc__', '__file__', '__path__', '__module__', '__name__', '__slots__', '__package__', '__cached__', '__author__', '__credits__', '__date__', - '__version__'}: + '__version__', '__qualname__'}: return 0 # Private names are hidden, but special names are displayed. if name.startswith('__') and name.endswith('__'): return 1 diff --git a/Lib/test/test_code.py b/Lib/test/test_code.py index e1c7a783de1..3377a7b07a8 100644 --- a/Lib/test/test_code.py +++ b/Lib/test/test_code.py @@ -16,7 +16,7 @@ cellvars: ('x',) freevars: () nlocals: 2 flags: 3 -consts: ('None', '') +consts: ('None', '', "'f..g'") >>> dump(f(4).__code__) name: g diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index 15219db5703..4a7a9d21977 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -4492,9 +4492,14 @@ class DictProxyTests(unittest.TestCase): self.assertEqual(type(C.__dict__), type(B.__dict__)) def test_repr(self): - # Testing dict_proxy.__repr__ - dict_ = {k: v for k, v in self.C.__dict__.items()} - self.assertEqual(repr(self.C.__dict__), 'dict_proxy({!r})'.format(dict_)) + # Testing dict_proxy.__repr__. + # We can't blindly compare with the repr of another dict as ordering + # of keys and values is arbitrary and may differ. + r = repr(self.C.__dict__) + self.assertTrue(r.startswith('dict_proxy('), r) + self.assertTrue(r.endswith(')'), r) + for k, v in self.C.__dict__.items(): + self.assertIn('{!r}: {!r}'.format(k, v), r) class PTypesLongInitTest(unittest.TestCase): diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index 4b0b02af266..be380079822 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -339,6 +339,7 @@ Flags: OPTIMIZED, NEWLOCALS, VARARGS, VARKEYWORDS, GENERATOR Constants: 0: None 1: + 2: 'tricky..f' Variable names: 0: x 1: y diff --git a/Lib/test/test_funcattrs.py b/Lib/test/test_funcattrs.py index 4d1936879ec..f4a38b961d8 100644 --- a/Lib/test/test_funcattrs.py +++ b/Lib/test/test_funcattrs.py @@ -2,6 +2,15 @@ from test import support import types import unittest + +def global_function(): + def inner_function(): + class LocalClass: + pass + return LocalClass + return lambda: inner_function + + class FuncAttrsTest(unittest.TestCase): def setUp(self): class F: @@ -96,6 +105,24 @@ class FunctionPropertiesTest(FuncAttrsTest): self.assertEqual(self.fi.a.__name__, 'a') self.cannot_set_attr(self.fi.a, "__name__", 'a', AttributeError) + def test___qualname__(self): + # PEP 3155 + self.assertEqual(self.b.__qualname__, 'FuncAttrsTest.setUp..b') + self.assertEqual(FuncAttrsTest.setUp.__qualname__, 'FuncAttrsTest.setUp') + self.assertEqual(global_function.__qualname__, 'global_function') + self.assertEqual(global_function().__qualname__, + 'global_function..') + self.assertEqual(global_function()().__qualname__, + 'global_function..inner_function') + self.assertEqual(global_function()()().__qualname__, + 'global_function..inner_function..LocalClass') + self.b.__qualname__ = 'c' + self.assertEqual(self.b.__qualname__, 'c') + self.b.__qualname__ = 'd' + self.assertEqual(self.b.__qualname__, 'd') + # __qualname__ must be a string + self.cannot_set_attr(self.b, '__qualname__', 7, TypeError) + def test___code__(self): num_one, num_two = 7, 8 def a(): pass diff --git a/Lib/test/test_metaclass.py b/Lib/test/test_metaclass.py index 6862900b010..e6fe20a107c 100644 --- a/Lib/test/test_metaclass.py +++ b/Lib/test/test_metaclass.py @@ -159,6 +159,7 @@ Use a __prepare__ method that returns an instrumented dict. ... bar = 123 ... d['__module__'] = 'test.test_metaclass' + d['__qualname__'] = 'C' d['foo'] = 4 d['foo'] = 42 d['bar'] = 123 @@ -177,12 +178,12 @@ Use a metaclass that doesn't derive from type. ... b = 24 ... meta: C () - ns: [('__module__', 'test.test_metaclass'), ('a', 42), ('b', 24)] + ns: [('__module__', 'test.test_metaclass'), ('__qualname__', 'C'), ('a', 42), ('b', 24)] kw: [] >>> type(C) is dict True >>> print(sorted(C.items())) - [('__module__', 'test.test_metaclass'), ('a', 42), ('b', 24)] + [('__module__', 'test.test_metaclass'), ('__qualname__', 'C'), ('a', 42), ('b', 24)] >>> And again, with a __prepare__ attribute. @@ -199,11 +200,12 @@ And again, with a __prepare__ attribute. ... prepare: C () [('other', 'booh')] d['__module__'] = 'test.test_metaclass' + d['__qualname__'] = 'C' d['a'] = 1 d['a'] = 2 d['b'] = 3 meta: C () - ns: [('__module__', 'test.test_metaclass'), ('a', 2), ('b', 3)] + ns: [('__module__', 'test.test_metaclass'), ('__qualname__', 'C'), ('a', 2), ('b', 3)] kw: [('other', 'booh')] >>> diff --git a/Lib/test/test_reprlib.py b/Lib/test/test_reprlib.py index 439fa337947..0365cea5ff1 100644 --- a/Lib/test/test_reprlib.py +++ b/Lib/test/test_reprlib.py @@ -129,8 +129,8 @@ class ReprTests(unittest.TestCase): self.assertIn(s.find("..."), [12, 13]) def test_lambda(self): - self.assertTrue(repr(lambda x: x).startswith( - ".func_module = module; } + if (qualname) + op->func_qualname = qualname; + else + op->func_qualname = op->func_name; + Py_INCREF(op->func_qualname); } else return NULL; @@ -61,6 +66,12 @@ PyFunction_New(PyObject *code, PyObject *globals) return (PyObject *)op; } +PyObject * +PyFunction_New(PyObject *code, PyObject *globals) +{ + return PyFunction_NewWithQualName(code, globals, NULL); +} + PyObject * PyFunction_GetCode(PyObject *op) { @@ -333,6 +344,32 @@ func_set_name(PyFunctionObject *op, PyObject *value) return 0; } +static PyObject * +func_get_qualname(PyFunctionObject *op) +{ + Py_INCREF(op->func_qualname); + return op->func_qualname; +} + +static int +func_set_qualname(PyFunctionObject *op, PyObject *value) +{ + PyObject *tmp; + + /* Not legal to del f.__qualname__ or to set it to anything + * other than a string object. */ + if (value == NULL || !PyUnicode_Check(value)) { + PyErr_SetString(PyExc_TypeError, + "__qualname__ must be set to a string object"); + return -1; + } + tmp = op->func_qualname; + Py_INCREF(value); + op->func_qualname = value; + Py_DECREF(tmp); + return 0; +} + static PyObject * func_get_defaults(PyFunctionObject *op) { @@ -441,6 +478,7 @@ static PyGetSetDef func_getsetlist[] = { (setter)func_set_annotations}, {"__dict__", (getter)func_get_dict, (setter)func_set_dict}, {"__name__", (getter)func_get_name, (setter)func_set_name}, + {"__qualname__", (getter)func_get_qualname, (setter)func_set_qualname}, {NULL} /* Sentinel */ }; @@ -561,6 +599,7 @@ func_dealloc(PyFunctionObject *op) Py_XDECREF(op->func_dict); Py_XDECREF(op->func_closure); Py_XDECREF(op->func_annotations); + Py_XDECREF(op->func_qualname); PyObject_GC_Del(op); } @@ -568,7 +607,7 @@ static PyObject* func_repr(PyFunctionObject *op) { return PyUnicode_FromFormat("", - op->func_name, op); + op->func_qualname, op); } static int @@ -584,6 +623,7 @@ func_traverse(PyFunctionObject *f, visitproc visit, void *arg) Py_VISIT(f->func_dict); Py_VISIT(f->func_closure); Py_VISIT(f->func_annotations); + Py_VISIT(f->func_qualname); return 0; } diff --git a/Objects/typeobject.c b/Objects/typeobject.c index d53ae93ee70..010120a4e80 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -242,6 +242,19 @@ type_name(PyTypeObject *type, void *context) } } +static PyObject * +type_qualname(PyTypeObject *type, void *context) +{ + if (type->tp_flags & Py_TPFLAGS_HEAPTYPE) { + PyHeapTypeObject* et = (PyHeapTypeObject*)type; + Py_INCREF(et->ht_qualname); + return et->ht_qualname; + } + else { + return type_name(type, context); + } +} + static int type_set_name(PyTypeObject *type, PyObject *value, void *context) { @@ -286,6 +299,25 @@ type_set_name(PyTypeObject *type, PyObject *value, void *context) return 0; } +static int +type_set_qualname(PyTypeObject *type, PyObject *value, void *context) +{ + PyHeapTypeObject* et; + + if (!PyUnicode_Check(value)) { + PyErr_Format(PyExc_TypeError, + "can only assign string to %s.__qualname__, not '%s'", + type->tp_name, Py_TYPE(value)->tp_name); + return -1; + } + + et = (PyHeapTypeObject*)type; + Py_INCREF(value); + Py_DECREF(et->ht_qualname); + et->ht_qualname = value; + return 0; +} + static PyObject * type_module(PyTypeObject *type, void *context) { @@ -631,6 +663,7 @@ type___subclasscheck__(PyObject *type, PyObject *inst) static PyGetSetDef type_getsets[] = { {"__name__", (getter)type_name, (setter)type_set_name, NULL}, + {"__qualname__", (getter)type_qualname, (setter)type_set_qualname, NULL}, {"__bases__", (getter)type_get_bases, (setter)type_set_bases, NULL}, {"__module__", (getter)type_module, (setter)type_set_module, NULL}, {"__abstractmethods__", (getter)type_abstractmethods, @@ -652,7 +685,7 @@ type_repr(PyTypeObject *type) Py_DECREF(mod); mod = NULL; } - name = type_name(type, NULL); + name = type_qualname(type, NULL); if (name == NULL) return NULL; @@ -1955,7 +1988,7 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds) { PyObject *name, *bases, *dict; static char *kwlist[] = {"name", "bases", "dict", 0}; - PyObject *slots, *tmp, *newslots; + PyObject *qualname, *slots, *tmp, *newslots; PyTypeObject *type, *base, *tmptype, *winner; PyHeapTypeObject *et; PyMemberDef *mp; @@ -2032,6 +2065,18 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds) return NULL; } + /* Check for a __qualname__ variable in dict */ + qualname = PyDict_GetItemString(dict, "__qualname__"); + if (qualname == NULL) { + qualname = name; + } + else { + if (PyDict_DelItemString(dict, "__qualname__") < 0) { + Py_DECREF(bases); + return NULL; + } + } + /* Check for a __slots__ sequence variable in dict, and count it */ slots = PyDict_GetItemString(dict, "__slots__"); nslots = 0; @@ -2185,7 +2230,9 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds) /* Keep name and slots alive in the extended type object */ et = (PyHeapTypeObject *)type; Py_INCREF(name); + Py_INCREF(qualname); et->ht_name = name; + et->ht_qualname = qualname; et->ht_slots = slots; /* Initialize tp_flags */ @@ -2369,6 +2416,8 @@ PyObject* PyType_FromSpec(PyType_Spec *spec) res->ht_name = PyUnicode_FromString(spec->name); if (!res->ht_name) goto fail; + res->ht_qualname = res->ht_name; + Py_INCREF(res->ht_qualname); res->ht_type.tp_name = _PyUnicode_AsString(res->ht_name); if (!res->ht_type.tp_name) goto fail; @@ -2568,6 +2617,7 @@ type_dealloc(PyTypeObject *type) */ PyObject_Free((char *)type->tp_doc); Py_XDECREF(et->ht_name); + Py_XDECREF(et->ht_qualname); Py_XDECREF(et->ht_slots); Py_TYPE(type)->tp_free((PyObject *)type); } @@ -2983,7 +3033,7 @@ object_repr(PyObject *self) Py_DECREF(mod); mod = NULL; } - name = type_name(type, NULL); + name = type_qualname(type, NULL); if (name == NULL) return NULL; if (mod != NULL && PyUnicode_CompareWithASCIIString(mod, "builtins")) diff --git a/Python/ceval.c b/Python/ceval.c index 8d980fdc939..ed82b9411a0 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -2687,9 +2687,11 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag) int kwdefaults = (oparg>>8) & 0xff; int num_annotations = (oparg >> 16) & 0x7fff; + w = POP(); /* qualname */ v = POP(); /* code object */ - x = PyFunction_New(v, f->f_globals); + x = PyFunction_NewWithQualName(v, f->f_globals, w); Py_DECREF(v); + Py_DECREF(w); if (x != NULL && opcode == MAKE_CLOSURE) { v = POP(); diff --git a/Python/compile.c b/Python/compile.c index 906772289dd..849f48785dc 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -90,6 +90,13 @@ struct fblockinfo { basicblock *fb_block; }; +enum { + COMPILER_SCOPE_MODULE, + COMPILER_SCOPE_CLASS, + COMPILER_SCOPE_FUNCTION, + COMPILER_SCOPE_COMPREHENSION, +}; + /* The following items change on entry and exit of code blocks. They must be saved and restored when returning to a block. */ @@ -97,6 +104,9 @@ struct compiler_unit { PySTEntryObject *u_ste; PyObject *u_name; + PyObject *u_qualname; /* dot-separated qualified name (lazy) */ + int u_scope_type; + /* The following fields are dicts that map objects to the index of them in co_XXX. The index is used as the argument for opcodes that refer to those collections. @@ -149,7 +159,7 @@ struct compiler { PyArena *c_arena; /* pointer to memory allocation arena */ }; -static int compiler_enter_scope(struct compiler *, identifier, void *, int); +static int compiler_enter_scope(struct compiler *, identifier, int, void *, int); static void compiler_free(struct compiler *); static basicblock *compiler_new_block(struct compiler *); static int compiler_next_instr(struct compiler *, basicblock *); @@ -457,6 +467,7 @@ compiler_unit_free(struct compiler_unit *u) } Py_CLEAR(u->u_ste); Py_CLEAR(u->u_name); + Py_CLEAR(u->u_qualname); Py_CLEAR(u->u_consts); Py_CLEAR(u->u_names); Py_CLEAR(u->u_varnames); @@ -467,8 +478,8 @@ compiler_unit_free(struct compiler_unit *u) } static int -compiler_enter_scope(struct compiler *c, identifier name, void *key, - int lineno) +compiler_enter_scope(struct compiler *c, identifier name, + int scope_type, void *key, int lineno) { struct compiler_unit *u; @@ -479,6 +490,7 @@ compiler_enter_scope(struct compiler *c, identifier name, void *key, return 0; } memset(u, 0, sizeof(struct compiler_unit)); + u->u_scope_type = scope_type; u->u_argcount = 0; u->u_kwonlyargcount = 0; u->u_ste = PySymtable_Lookup(c->c_st, key); @@ -566,6 +578,59 @@ compiler_exit_scope(struct compiler *c) } +static PyObject * +compiler_scope_qualname(struct compiler *c) +{ + Py_ssize_t stack_size, i; + _Py_static_string(dot, "."); + _Py_static_string(locals, ""); + struct compiler_unit *u; + PyObject *capsule, *name, *seq, *dot_str, *locals_str; + + u = c->u; + if (u->u_qualname != NULL) { + Py_INCREF(u->u_qualname); + return u->u_qualname; + } + + seq = PyList_New(0); + if (seq == NULL) + return NULL; + + stack_size = PyList_GET_SIZE(c->c_stack); + for (i = 0; i < stack_size; i++) { + capsule = PyList_GET_ITEM(c->c_stack, i); + u = (struct compiler_unit *)PyCapsule_GetPointer(capsule, COMPILER_CAPSULE_NAME_COMPILER_UNIT); + assert(u); + if (u->u_scope_type == COMPILER_SCOPE_MODULE) + continue; + if (PyList_Append(seq, u->u_name)) + goto _error; + if (u->u_scope_type == COMPILER_SCOPE_FUNCTION) { + locals_str = _PyUnicode_FromId(&locals); + if (locals_str == NULL) + goto _error; + if (PyList_Append(seq, locals_str)) + goto _error; + } + } + u = c->u; + if (PyList_Append(seq, u->u_name)) + goto _error; + dot_str = _PyUnicode_FromId(&dot); + if (dot_str == NULL) + goto _error; + name = PyUnicode_Join(dot_str, seq); + Py_DECREF(seq); + u->u_qualname = name; + Py_XINCREF(name); + return name; + +_error: + Py_XDECREF(seq); + return NULL; +} + /* Allocate a new block and return a pointer to it. Returns NULL on error. */ @@ -862,9 +927,9 @@ opcode_stack_effect(int opcode, int oparg) case CALL_FUNCTION_VAR_KW: return -NARGS(oparg)-2; case MAKE_FUNCTION: - return -NARGS(oparg) - ((oparg >> 16) & 0xffff); + return -1 -NARGS(oparg) - ((oparg >> 16) & 0xffff); case MAKE_CLOSURE: - return -1 - NARGS(oparg) - ((oparg >> 16) & 0xffff); + return -2 - NARGS(oparg) - ((oparg >> 16) & 0xffff); #undef NARGS case BUILD_SLICE: if (oparg == 3) @@ -1194,7 +1259,7 @@ compiler_mod(struct compiler *c, mod_ty mod) return NULL; } /* Use 0 for firstlineno initially, will fixup in assemble(). */ - if (!compiler_enter_scope(c, module, mod, 0)) + if (!compiler_enter_scope(c, module, COMPILER_SCOPE_MODULE, mod, 0)) return NULL; switch (mod->kind) { case Module_kind: @@ -1270,11 +1335,15 @@ compiler_lookup_arg(PyObject *dict, PyObject *name) } static int -compiler_make_closure(struct compiler *c, PyCodeObject *co, int args) +compiler_make_closure(struct compiler *c, PyCodeObject *co, int args, PyObject *qualname) { int i, free = PyCode_GetNumFree(co); + if (qualname == NULL) + qualname = co->co_name; + if (free == 0) { ADDOP_O(c, LOAD_CONST, (PyObject*)co, consts); + ADDOP_O(c, LOAD_CONST, qualname, consts); ADDOP_I(c, MAKE_FUNCTION, args); return 1; } @@ -1311,6 +1380,7 @@ compiler_make_closure(struct compiler *c, PyCodeObject *co, int args) } ADDOP_I(c, BUILD_TUPLE, free); ADDOP_O(c, LOAD_CONST, (PyObject*)co, consts); + ADDOP_O(c, LOAD_CONST, qualname, consts); ADDOP_I(c, MAKE_CLOSURE, args); return 1; } @@ -1452,7 +1522,7 @@ static int compiler_function(struct compiler *c, stmt_ty s) { PyCodeObject *co; - PyObject *first_const = Py_None; + PyObject *qualname, *first_const = Py_None; arguments_ty args = s->v.FunctionDef.args; expr_ty returns = s->v.FunctionDef.returns; asdl_seq* decos = s->v.FunctionDef.decorator_list; @@ -1478,7 +1548,8 @@ compiler_function(struct compiler *c, stmt_ty s) return 0; assert((num_annotations & 0xFFFF) == num_annotations); - if (!compiler_enter_scope(c, s->v.FunctionDef.name, (void *)s, + if (!compiler_enter_scope(c, s->v.FunctionDef.name, + COMPILER_SCOPE_FUNCTION, (void *)s, s->lineno)) return 0; @@ -1500,14 +1571,19 @@ compiler_function(struct compiler *c, stmt_ty s) VISIT_IN_SCOPE(c, stmt, st); } co = assemble(c, 1); + qualname = compiler_scope_qualname(c); compiler_exit_scope(c); - if (co == NULL) + if (qualname == NULL || co == NULL) { + Py_XDECREF(qualname); + Py_XDECREF(co); return 0; + } arglength = asdl_seq_LEN(args->defaults); arglength |= kw_default_count << 8; arglength |= num_annotations << 16; - compiler_make_closure(c, co, arglength); + compiler_make_closure(c, co, arglength, qualname); + Py_DECREF(qualname); Py_DECREF(co); /* decorators */ @@ -1542,7 +1618,8 @@ compiler_class(struct compiler *c, stmt_ty s) */ /* 1. compile the class body into a code object */ - if (!compiler_enter_scope(c, s->v.ClassDef.name, (void *)s, s->lineno)) + if (!compiler_enter_scope(c, s->v.ClassDef.name, + COMPILER_SCOPE_CLASS, (void *)s, s->lineno)) return 0; /* this block represents what we do in the new scope */ { @@ -1572,6 +1649,21 @@ compiler_class(struct compiler *c, stmt_ty s) return 0; } Py_DECREF(str); + /* store the __qualname__ */ + str = compiler_scope_qualname(c); + if (!str) { + compiler_exit_scope(c); + return 0; + } + ADDOP_O(c, LOAD_CONST, str, consts); + Py_DECREF(str); + str = PyUnicode_InternFromString("__qualname__"); + if (!str || !compiler_nameop(c, str, Store)) { + Py_XDECREF(str); + compiler_exit_scope(c); + return 0; + } + Py_DECREF(str); /* compile the body proper */ if (!compiler_body(c, s->v.ClassDef.body)) { compiler_exit_scope(c); @@ -1608,7 +1700,7 @@ compiler_class(struct compiler *c, stmt_ty s) ADDOP(c, LOAD_BUILD_CLASS); /* 3. load a function (or closure) made from the code object */ - compiler_make_closure(c, co, 0); + compiler_make_closure(c, co, 0, NULL); Py_DECREF(co); /* 4. load class name */ @@ -1659,6 +1751,7 @@ static int compiler_lambda(struct compiler *c, expr_ty e) { PyCodeObject *co; + PyObject *qualname; static identifier name; int kw_default_count = 0, arglength; arguments_ty args = e->v.Lambda.args; @@ -1678,7 +1771,8 @@ compiler_lambda(struct compiler *c, expr_ty e) } if (args->defaults) VISIT_SEQ(c, expr, args->defaults); - if (!compiler_enter_scope(c, name, (void *)e, e->lineno)) + if (!compiler_enter_scope(c, name, COMPILER_SCOPE_FUNCTION, + (void *)e, e->lineno)) return 0; /* Make None the first constant, so the lambda can't have a @@ -1696,13 +1790,15 @@ compiler_lambda(struct compiler *c, expr_ty e) ADDOP_IN_SCOPE(c, RETURN_VALUE); } co = assemble(c, 1); + qualname = compiler_scope_qualname(c); compiler_exit_scope(c); - if (co == NULL) + if (qualname == NULL || co == NULL) return 0; arglength = asdl_seq_LEN(args->defaults); arglength |= kw_default_count << 8; - compiler_make_closure(c, co, arglength); + compiler_make_closure(c, co, arglength, qualname); + Py_DECREF(qualname); Py_DECREF(co); return 1; @@ -2916,11 +3012,13 @@ compiler_comprehension(struct compiler *c, expr_ty e, int type, identifier name, { PyCodeObject *co = NULL; expr_ty outermost_iter; + PyObject *qualname = NULL; outermost_iter = ((comprehension_ty) asdl_seq_GET(generators, 0))->iter; - if (!compiler_enter_scope(c, name, (void *)e, e->lineno)) + if (!compiler_enter_scope(c, name, COMPILER_SCOPE_COMPREHENSION, + (void *)e, e->lineno)) goto error; if (type != COMP_GENEXP) { @@ -2953,12 +3051,14 @@ compiler_comprehension(struct compiler *c, expr_ty e, int type, identifier name, } co = assemble(c, 1); + qualname = compiler_scope_qualname(c); compiler_exit_scope(c); - if (co == NULL) + if (qualname == NULL || co == NULL) goto error; - if (!compiler_make_closure(c, co, 0)) + if (!compiler_make_closure(c, co, 0, qualname)) goto error; + Py_DECREF(qualname); Py_DECREF(co); VISIT(c, expr, outermost_iter); @@ -2968,6 +3068,7 @@ compiler_comprehension(struct compiler *c, expr_ty e, int type, identifier name, error_in_scope: compiler_exit_scope(c); error: + Py_XDECREF(qualname); Py_XDECREF(co); return 0; } diff --git a/Python/import.c b/Python/import.c index 6c6d1f697a5..71992300987 100644 --- a/Python/import.c +++ b/Python/import.c @@ -103,6 +103,7 @@ typedef unsigned short mode_t; tag: cpython-32 Python 3.2a2 3180 (add DELETE_DEREF) Python 3.3a0 3190 __class__ super closure changed + Python 3.3a0 3200 (__qualname__ added) */ /* MAGIC must change whenever the bytecode emitted by the compiler may no @@ -115,7 +116,7 @@ typedef unsigned short mode_t; #define STRIFY(name) QUOTE(name) #define MAJOR STRIFY(PY_MAJOR_VERSION) #define MINOR STRIFY(PY_MINOR_VERSION) -#define MAGIC (3190 | ((long)'\r'<<16) | ((long)'\n'<<24)) +#define MAGIC (3200 | ((long)'\r'<<16) | ((long)'\n'<<24)) #define TAG "cpython-" MAJOR MINOR; #define CACHEDIR "__pycache__" /* Current magic word and string tag as globals. */ From 6bbd76b0a0bb1b98e310b187f82aa2b93eb6ac29 Mon Sep 17 00:00:00 2001 From: Antoine Pitrou Date: Fri, 25 Nov 2011 19:10:05 +0100 Subject: [PATCH 05/10] Update What's new for PEP 3155 --- Doc/whatsnew/3.3.rst | 59 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/Doc/whatsnew/3.3.rst b/Doc/whatsnew/3.3.rst index 0c3be15627e..6c2041840a1 100644 --- a/Doc/whatsnew/3.3.rst +++ b/Doc/whatsnew/3.3.rst @@ -189,6 +189,65 @@ inspection of exception attributes:: print("You are not allowed to read document.txt") +PEP 3155: Qualified name for classes and functions +================================================== + +:pep:`3155` - Qualified name for classes and functions + PEP written and implemented by Antoine Pitrou. + +Functions and class objects have a new ``__qualname__`` attribute representing +the "path" from the module top-level to their definition. For global functions +and classes, this is the same as ``__name__``. For other functions and classes, +it provides better information about where they were actually defined, and +how they might be accessible from the global scope. + +Example with (non-bound) methods:: + + >>> class C: + ... def meth(self): + ... pass + >>> C.meth.__name__ + 'meth' + >>> C.meth.__qualname__ + 'C.meth' + +Example with nested classes:: + + >>> class C: + ... class D: + ... def meth(self): + ... pass + ... + >>> C.D.__name__ + 'D' + >>> C.D.__qualname__ + 'C.D' + >>> C.D.meth.__name__ + 'meth' + >>> C.D.meth.__qualname__ + 'C.D.meth' + +Example with nested functions:: + + >>> def outer(): + ... def inner(): + ... pass + ... return inner + ... + >>> outer().__name__ + 'inner' + >>> outer().__qualname__ + 'outer..inner' + +The string representation of those objects is also changed to included the +new, more precise information:: + + >>> str(C.D) + "" + >>> str(C.D.meth) + '' + + Other Language Changes ====================== From e7ede067576e9beaf0787e1fb3104cf6202d8aa0 Mon Sep 17 00:00:00 2001 From: Antoine Pitrou Date: Fri, 25 Nov 2011 19:11:26 +0100 Subject: [PATCH 06/10] Typo --- Doc/whatsnew/3.3.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.3.rst b/Doc/whatsnew/3.3.rst index 6c2041840a1..0a1d0a3750a 100644 --- a/Doc/whatsnew/3.3.rst +++ b/Doc/whatsnew/3.3.rst @@ -239,7 +239,7 @@ Example with nested functions:: >>> outer().__qualname__ 'outer..inner' -The string representation of those objects is also changed to included the +The string representation of those objects is also changed to include the new, more precise information:: >>> str(C.D) From 6345be9a141642c2e95eec417844f8702775b700 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 25 Nov 2011 20:09:01 +0100 Subject: [PATCH 07/10] Close #13093: PyUnicode_EncodeDecimal() doesn't support error handlers different than "strict" anymore. The caller was unable to compute the size of the output buffer: it depends on the error handler. --- Lib/test/test_unicode.py | 18 ++---- Misc/NEWS | 4 ++ Objects/unicodeobject.c | 131 ++++++--------------------------------- 3 files changed, 26 insertions(+), 127 deletions(-) diff --git a/Lib/test/test_unicode.py b/Lib/test/test_unicode.py index b20f8781490..72aae8f1a4a 100644 --- a/Lib/test/test_unicode.py +++ b/Lib/test/test_unicode.py @@ -1816,20 +1816,10 @@ class UnicodeTest(string_tests.CommonTest, b' 3.14 ') self.assertRaises(UnicodeEncodeError, unicode_encodedecimal, "123\u20ac", "strict") - self.assertEqual(unicode_encodedecimal("123\u20ac", "replace"), - b'123?') - self.assertEqual(unicode_encodedecimal("123\u20ac", "ignore"), - b'123') - self.assertEqual(unicode_encodedecimal("123\u20ac", "xmlcharrefreplace"), - b'123€') - self.assertEqual(unicode_encodedecimal("123\u20ac", "backslashreplace"), - b'123\\u20ac') - self.assertEqual(unicode_encodedecimal("123\u20ac\N{EM SPACE}", "replace"), - b'123? ') - self.assertEqual(unicode_encodedecimal("123\u20ac\u20ac", "replace"), - b'123??') - self.assertEqual(unicode_encodedecimal("123\u20ac\u0660", "replace"), - b'123?0') + self.assertRaisesRegex( + ValueError, + "^'decimal' codec can't encode character", + unicode_encodedecimal, "123\u20ac", "replace") def test_transform_decimal(self): from _testcapi import unicode_transformdecimaltoascii as transform_decimal diff --git a/Misc/NEWS b/Misc/NEWS index b789b5f4f0d..d919e2e9994 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -10,6 +10,10 @@ What's New in Python 3.3 Alpha 1? Core and Builtins ----------------- +- Issue #13093: PyUnicode_EncodeDecimal() doesn't support error handlers + different than "strict" anymore. The caller was unable to compute the + size of the output buffer: it depends on the error handler. + - PEP 3155 / issue #13448: Qualified name for classes and functions. - Issue #13436: Fix a bogus error message when an AST object was passed diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 2fefdbe1709..a9bf677fa3e 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -8839,15 +8839,8 @@ PyUnicode_EncodeDecimal(Py_UNICODE *s, char *output, const char *errors) { - PyObject *errorHandler = NULL; - PyObject *exc = NULL; PyObject *unicode; - const char *encoding = "decimal"; - const char *reason = "invalid decimal Unicode string"; - /* the following variable is used for caching string comparisons - * -1=not initialized, 0=unknown, 1=strict, 2=replace, 3=ignore, 4=xmlcharrefreplace */ - int known_errorHandler = -1; - Py_ssize_t i, j; + Py_ssize_t i; enum PyUnicode_Kind kind; void *data; @@ -8860,15 +8853,20 @@ PyUnicode_EncodeDecimal(Py_UNICODE *s, if (unicode == NULL) return -1; - if (PyUnicode_READY(unicode) < 0) - goto onError; + if (PyUnicode_READY(unicode) < 0) { + Py_DECREF(unicode); + return -1; + } kind = PyUnicode_KIND(unicode); data = PyUnicode_DATA(unicode); for (i=0; i < length; ) { - Py_UCS4 ch = PyUnicode_READ(kind, data, i); + PyObject *exc; + Py_UCS4 ch; int decimal; - Py_ssize_t startpos, endpos; + Py_ssize_t startpos; + + ch = PyUnicode_READ(kind, data, i); if (Py_UNICODE_ISSPACE(ch)) { *output++ = ' '; @@ -8886,113 +8884,20 @@ PyUnicode_EncodeDecimal(Py_UNICODE *s, i++; continue; } - /* All other characters are considered unencodable */ + startpos = i; - endpos = i+1; - for (; endpos < length; endpos++) { - ch = PyUnicode_READ(kind, data, endpos); - if ((0 < ch && ch < 256) || - Py_UNICODE_ISSPACE(ch) || - 0 <= Py_UNICODE_TODECIMAL(ch)) - break; - } - /* cache callback name lookup - * (if not done yet, i.e. it's the first error) */ - if (known_errorHandler==-1) { - if ((errors==NULL) || (!strcmp(errors, "strict"))) - known_errorHandler = 1; - else if (!strcmp(errors, "replace")) - known_errorHandler = 2; - else if (!strcmp(errors, "ignore")) - known_errorHandler = 3; - else if (!strcmp(errors, "xmlcharrefreplace")) - known_errorHandler = 4; - else - known_errorHandler = 0; - } - switch (known_errorHandler) { - case 1: /* strict */ - raise_encode_exception(&exc, encoding, unicode, startpos, endpos, reason); - goto onError; - case 2: /* replace */ - for (j=startpos; j < endpos; j++) - *output++ = '?'; - i = endpos; - break; - case 3: /* ignore */ - i = endpos; - break; - case 4: /* xmlcharrefreplace */ - /* generate replacement */ - for (j=startpos; j < endpos; j++) { - ch = PyUnicode_READ(kind, data, i); - output += sprintf(output, "&#%d;", (int)ch); - i++; - } - break; - default: - { - PyObject *repunicode; - Py_ssize_t repsize, newpos, k; - enum PyUnicode_Kind repkind; - void *repdata; - - repunicode = unicode_encode_call_errorhandler(errors, &errorHandler, - encoding, reason, unicode, &exc, - startpos, endpos, &newpos); - if (repunicode == NULL) - goto onError; - if (!PyUnicode_Check(repunicode)) { - /* Byte results not supported, since they have no decimal property. */ - PyErr_SetString(PyExc_TypeError, "error handler should return unicode"); - Py_DECREF(repunicode); - goto onError; - } - if (PyUnicode_READY(repunicode) < 0) { - Py_DECREF(repunicode); - goto onError; - } - repkind = PyUnicode_KIND(repunicode); - repdata = PyUnicode_DATA(repunicode); - - /* generate replacement */ - repsize = PyUnicode_GET_SIZE(repunicode); - for (k=0; k= 0) - *output++ = '0' + decimal; - else if (0 < ch && ch < 256) - *output++ = (char)ch; - else { - Py_DECREF(repunicode); - raise_encode_exception(&exc, encoding, - unicode, startpos, endpos, - reason); - goto onError; - } - } - } - i = newpos; - Py_DECREF(repunicode); - } - } + exc = NULL; + raise_encode_exception(&exc, "decimal", unicode, + startpos, startpos+1, + "invalid decimal Unicode string"); + Py_XDECREF(exc); + Py_DECREF(unicode); + return -1; } /* 0-terminate the output string */ *output++ = '\0'; - Py_XDECREF(exc); - Py_XDECREF(errorHandler); Py_DECREF(unicode); return 0; - - onError: - Py_XDECREF(exc); - Py_XDECREF(errorHandler); - Py_DECREF(unicode); - return -1; } /* --- Helpers ------------------------------------------------------------ */ From cad939bf9d83cd7d9f0b74076e81c3b1de16d88d Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 25 Nov 2011 20:11:54 +0100 Subject: [PATCH 08/10] NEWS: fix the issue number for PyUnicode_EncodeDecimal change --- Misc/NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS b/Misc/NEWS index d919e2e9994..c0d64f41cba 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -10,7 +10,7 @@ What's New in Python 3.3 Alpha 1? Core and Builtins ----------------- -- Issue #13093: PyUnicode_EncodeDecimal() doesn't support error handlers +- Issue #13452: PyUnicode_EncodeDecimal() doesn't support error handlers different than "strict" anymore. The caller was unable to compute the size of the output buffer: it depends on the error handler. From 4558bad7d64a7599b46fb56ea2df52319437f3a0 Mon Sep 17 00:00:00 2001 From: Antoine Pitrou Date: Fri, 25 Nov 2011 21:28:15 +0100 Subject: [PATCH 09/10] Issue #12856: Ensure child processes do not inherit the parent's random seed for filename generation in the tempfile module. Patch by Brian Harring. --- Lib/tempfile.py | 9 +++++++-- Lib/test/test_tempfile.py | 32 ++++++++++++++++++++++++++++++++ Misc/NEWS | 4 ++++ 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/Lib/tempfile.py b/Lib/tempfile.py index 48b77a87e21..34dff30468b 100644 --- a/Lib/tempfile.py +++ b/Lib/tempfile.py @@ -112,8 +112,13 @@ class _RandomNameSequence: characters = "abcdefghijklmnopqrstuvwxyz0123456789_" - def __init__(self): - self.rng = _Random() + @property + def rng(self): + cur_pid = _os.getpid() + if cur_pid != getattr(self, '_rng_pid', None): + self._rng = _Random() + self._rng_pid = cur_pid + return self._rng def __iter__(self): return self diff --git a/Lib/test/test_tempfile.py b/Lib/test/test_tempfile.py index f7f5bdadbd3..50cf3b49cb8 100644 --- a/Lib/test/test_tempfile.py +++ b/Lib/test/test_tempfile.py @@ -1,6 +1,7 @@ # tempfile.py unit tests. import tempfile import os +import signal import sys import re import warnings @@ -135,6 +136,37 @@ class test__RandomNameSequence(TC): except: self.failOnException("iteration") + @unittest.skipUnless(hasattr(os, 'fork'), + "os.fork is required for this test") + def test_process_awareness(self): + # ensure that the random source differs between + # child and parent. + read_fd, write_fd = os.pipe() + pid = None + try: + pid = os.fork() + if not pid: + os.close(read_fd) + os.write(write_fd, next(self.r).encode("ascii")) + os.close(write_fd) + # bypass the normal exit handlers- leave those to + # the parent. + os._exit(0) + parent_value = next(self.r) + child_value = os.read(read_fd, len(parent_value)).decode("ascii") + finally: + if pid: + # best effort to ensure the process can't bleed out + # via any bugs above + try: + os.kill(pid, signal.SIGKILL) + except EnvironmentError: + pass + os.close(read_fd) + os.close(write_fd) + self.assertNotEqual(child_value, parent_value) + + test_classes.append(test__RandomNameSequence) diff --git a/Misc/NEWS b/Misc/NEWS index f65fafe2eaf..fba2c3d7d98 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -83,6 +83,10 @@ Core and Builtins Library ------- +- Issue #12856: Ensure child processes do not inherit the parent's random + seed for filename generation in the tempfile module. Patch by Brian + Harring. + - Issue #13458: Fix a memory leak in the ssl module when decoding a certificate with a subjectAltName. Patch by Robert Xiao. From 0fdfceb782424dcddca848357736f24ef40c91be Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 25 Nov 2011 22:10:02 +0100 Subject: [PATCH 10/10] Issue #12567: The curses module uses Unicode functions for Unicode arguments when it is linked to the ncurses library. It encodes also Unicode strings to the locale encoding instead of UTF-8. --- Doc/library/curses.rst | 12 +- Doc/whatsnew/3.3.rst | 5 + Include/py_curses.h | 1 + Lib/test/test_curses.py | 41 ++- Misc/NEWS | 4 + Modules/_cursesmodule.c | 580 ++++++++++++++++++++++++++++++++-------- 6 files changed, 521 insertions(+), 122 deletions(-) diff --git a/Doc/library/curses.rst b/Doc/library/curses.rst index d6a35c3d169..ff3a7938d69 100644 --- a/Doc/library/curses.rst +++ b/Doc/library/curses.rst @@ -653,7 +653,7 @@ Window Objects -------------- Window objects, as returned by :func:`initscr` and :func:`newwin` above, have -the following methods: +the following methods and attributes: .. method:: window.addch([y, x,] ch[, attr]) @@ -834,6 +834,16 @@ the following methods: event. +.. attribute:: window.encoding + + Encoding used to encode method arguments (Unicode strings and characters). + The encoding attribute is inherited from by parent window when a subwindow + is created, for example with :meth:`window.subwin`. By default, the locale + encoding is used (see :func:`locale.getpreferredencoding`). + + .. versionadded:: 3.3 + + .. method:: window.erase() Clear the window. diff --git a/Doc/whatsnew/3.3.rst b/Doc/whatsnew/3.3.rst index 0a1d0a3750a..eb41cca96dc 100644 --- a/Doc/whatsnew/3.3.rst +++ b/Doc/whatsnew/3.3.rst @@ -333,6 +333,11 @@ function to the :mod:`crypt` module. curses ------ + * If the :mod:`curses` module is linked to the ncursesw library, use Unicode + functions when Unicode strings or characters are passed (e.g. + :c:func:`waddwstr`), and bytes functions otherwise (e.g. :c:func:`waddstr`). + * Use the locale encoding instead of ``utf-8`` to encode Unicode strings. + * :class:`curses.window` has a new :attr:`curses.window.encoding` attribute. * The :class:`curses.window` class has a new :meth:`~curses.window.get_wch` method to get a wide character * The :mod:`curses` module has a new :meth:`~curses.unget_wch` function to diff --git a/Include/py_curses.h b/Include/py_curses.h index a891c42be65..f2c08f6413a 100644 --- a/Include/py_curses.h +++ b/Include/py_curses.h @@ -76,6 +76,7 @@ extern "C" { typedef struct { PyObject_HEAD WINDOW *win; + char *encoding; } PyCursesWindowObject; #define PyCursesWindow_Check(v) (Py_TYPE(v) == &PyCursesWindow_Type) diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py index 72be3e7b512..b416403e900 100644 --- a/Lib/test/test_curses.py +++ b/Lib/test/test_curses.py @@ -267,24 +267,42 @@ def test_issue6243(stdscr): def test_unget_wch(stdscr): if not hasattr(curses, 'unget_wch'): return - ch = 'a' - curses.unget_wch(ch) - read = stdscr.get_wch() - read = chr(read) - if read != ch: - raise AssertionError("%r != %r" % (read, ch)) + for ch in ('a', '\xe9', '\u20ac', '\U0010FFFF'): + curses.unget_wch(ch) + read = stdscr.get_wch() + read = chr(read) + if read != ch: + raise AssertionError("%r != %r" % (read, ch)) - ch = ord('a') - curses.unget_wch(ch) - read = stdscr.get_wch() - if read != ch: - raise AssertionError("%r != %r" % (read, ch)) + code = ord(ch) + curses.unget_wch(code) + read = stdscr.get_wch() + if read != code: + raise AssertionError("%r != %r" % (read, code)) def test_issue10570(): b = curses.tparm(curses.tigetstr("cup"), 5, 3) assert type(b) is bytes curses.putp(b) +def test_encoding(stdscr): + import codecs + encoding = stdscr.encoding + codecs.lookup(encoding) + try: + stdscr.encoding = 10 + except TypeError: + pass + else: + raise AssertionError("TypeError not raised") + stdscr.encoding = encoding + try: + del stdscr.encoding + except TypeError: + pass + else: + raise AssertionError("TypeError not raised") + def main(stdscr): curses.savetty() try: @@ -295,6 +313,7 @@ def main(stdscr): test_issue6243(stdscr) test_unget_wch(stdscr) test_issue10570() + test_encoding(stdscr) finally: curses.resetty() diff --git a/Misc/NEWS b/Misc/NEWS index 11f3de2ae86..1b8d0cb7195 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -395,6 +395,10 @@ Core and Builtins Library ------- +- Issue #12567: The curses module uses Unicode functions for Unicode arguments + when it is linked to the ncurses library. It encodes also Unicode strings to + the locale encoding instead of UTF-8. + - Issue #12856: Ensure child processes do not inherit the parent's random seed for filename generation in the tempfile module. Patch by Brian Harring. diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c index cfa5b7a571c..166a93a6ecd 100644 --- a/Modules/_cursesmodule.c +++ b/Modules/_cursesmodule.c @@ -121,6 +121,10 @@ extern int setupterm(char *,int,int *); #include #endif +#ifdef HAVE_LANGINFO_H +#include +#endif + #if !defined(HAVE_NCURSES_H) && (defined(sgi) || defined(__sun) || defined(SCO5)) #define STRICT_SYSV_CURSES /* Don't use ncurses extensions */ typedef chtype attr_t; /* No attr_t type is available */ @@ -143,6 +147,8 @@ static int initialised = FALSE; /* Tells whether start_color() has been called to initialise color usage. */ static int initialisedcolors = FALSE; +static char *screen_encoding = NULL; + /* Utility Macros */ #define PyCursesSetupTermCalled \ if (initialised_setupterm != TRUE) { \ @@ -175,7 +181,7 @@ static int initialisedcolors = FALSE; */ static PyObject * -PyCursesCheckERR(int code, char *fname) +PyCursesCheckERR(int code, const char *fname) { if (code != ERR) { Py_INCREF(Py_None); @@ -190,28 +196,193 @@ PyCursesCheckERR(int code, char *fname) } } +/* Convert an object to a byte (an integer of type chtype): + + - int + - bytes of length 1 + - str of length 1 + + Return 1 on success, 0 on error (invalid type or integer overflow). */ static int -PyCurses_ConvertToChtype(PyObject *obj, chtype *ch) +PyCurses_ConvertToChtype(PyCursesWindowObject *win, PyObject *obj, chtype *ch) { - if (PyLong_CheckExact(obj)) { - int overflow; - /* XXX should the truncation by the cast also be reported - as an error? */ - *ch = (chtype) PyLong_AsLongAndOverflow(obj, &overflow); - if (overflow) + long value; + if(PyBytes_Check(obj) && PyBytes_Size(obj) == 1) { + value = (unsigned char)PyBytes_AsString(obj)[0]; + } + else if (PyUnicode_Check(obj)) { + if (PyUnicode_GetLength(obj) != 1) { + PyErr_Format(PyExc_TypeError, + "expect bytes or str of length 1, or int, " + "got a str of length %zi", + PyUnicode_GET_LENGTH(obj)); return 0; - } else if(PyBytes_Check(obj) - && (PyBytes_Size(obj) == 1)) { - *ch = (chtype) *PyBytes_AsString(obj); - } else if (PyUnicode_Check(obj) && PyUnicode_GET_LENGTH(obj) == 1) { - Py_UCS4 ucs = PyUnicode_READ(PyUnicode_KIND(obj), - PyUnicode_DATA(obj), - 0); - *ch = (chtype)ucs; - } else { + } + value = PyUnicode_READ_CHAR(obj, 0); + if (128 < value) { + PyObject *bytes; + const char *encoding; + if (win) + encoding = win->encoding; + else + encoding = screen_encoding; + bytes = PyUnicode_AsEncodedObject(obj, encoding, NULL); + if (bytes == NULL) + return 0; + if (PyBytes_GET_SIZE(bytes) == 1) + value = (unsigned char)PyBytes_AS_STRING(bytes)[0]; + else + value = -1; + Py_DECREF(bytes); + if (value < 0) + goto overflow; + } + } + else if (PyLong_CheckExact(obj)) { + int long_overflow; + value = PyLong_AsLongAndOverflow(obj, &long_overflow); + if (long_overflow) + goto overflow; + } + else { + PyErr_Format(PyExc_TypeError, + "expect bytes or str of length 1, or int, got %s", + Py_TYPE(obj)->tp_name); return 0; } + *ch = (chtype)value; + if ((long)*ch != value) + goto overflow; return 1; + +overflow: + PyErr_SetString(PyExc_OverflowError, + "byte doesn't fit in chtype"); + return 0; +} + +/* Convert an object to a byte (chtype) or a character (cchar_t): + + - int + - bytes of length 1 + - str of length 1 + + Return: + + - 2 if obj is a character (written into *wch) + - 1 if obj is a byte (written into *ch) + - 0 on error: raise an exception */ +static int +PyCurses_ConvertToCchar_t(PyCursesWindowObject *win, PyObject *obj, + chtype *ch +#ifdef HAVE_NCURSESW + , cchar_t *wch +#endif + ) +{ + int ret = 0; + long value; +#ifdef HAVE_NCURSESW + wchar_t buffer[2]; +#endif + + if (PyUnicode_Check(obj)) { +#ifdef HAVE_NCURSESW + if (PyUnicode_AsWideChar(obj, buffer, 2) != 1) { + PyErr_Format(PyExc_TypeError, + "expect bytes or str of length 1, or int, " + "got a str of length %zi", + PyUnicode_GET_LENGTH(obj)); + return 0; + } + memset(wch->chars, 0, sizeof(wch->chars)); + wch->chars[0] = buffer[0]; + return 2; +#else + return PyCurses_ConvertToChtype(win, obj, ch); +#endif + } + else if(PyBytes_Check(obj) && PyBytes_Size(obj) == 1) { + value = (unsigned char)PyBytes_AsString(obj)[0]; + ret = 1; + } + else if (PyLong_CheckExact(obj)) { + int overflow; + value = PyLong_AsLongAndOverflow(obj, &overflow); + if (overflow) { + PyErr_SetString(PyExc_OverflowError, + "int doesn't fit in long"); + return 0; + } +#ifdef HAVE_NCURSESW + ret = 2; +#else + ret = 1; +#endif + } + else { + PyErr_Format(PyExc_TypeError, + "expect bytes or str of length 1, or int, got %s", + Py_TYPE(obj)->tp_name); + return 0; + } +#ifdef HAVE_NCURSESW + if (ret == 2) { + memset(wch->chars, 0, sizeof(wch->chars)); + wch->chars[0] = (wchar_t)value; + if ((long)wch->chars[0] != value) { + PyErr_Format(PyExc_OverflowError, + "character doesn't fit in wchar_t"); + return 0; + } + } + else +#endif + { + *ch = (chtype)value; + if ((long)*ch != value || value < 0 || value > 255) { + PyErr_Format(PyExc_OverflowError, + "byte doesn't fit in chtype"); + return 0; + } + } + return ret; +} + +/* Convert an object to a byte string (char*) or a wide character string + (wchar_t*). Return: + + - 2 if obj is a character string (written into *wch) + - 1 if obj is a byte string (written into *bytes) + - 0 on error: raise an exception */ +static int +PyCurses_ConvertToString(PyCursesWindowObject *win, PyObject *obj, + PyObject **bytes, wchar_t **wstr) +{ + if (PyUnicode_Check(obj)) { +#ifdef HAVE_NCURSESW + assert (wstr != NULL); + *wstr = PyUnicode_AsWideCharString(obj, NULL); + if (*wstr == NULL) + return 0; + return 2; +#else + assert (wstr == NULL); + *bytes = PyUnicode_AsEncodedObject(obj, win->encoding, NULL); + if (*bytes == NULL) + return 0; + return 1; +#endif + } + else if (PyBytes_Check(obj)) { + Py_INCREF(obj); + *bytes = obj; + return 1; + } + + PyErr_Format(PyExc_TypeError, "expect bytes or str, got %s", + Py_TYPE(obj)->tp_name); + return 0; } /* Function versions of the 3 functions for testing whether curses has been @@ -357,13 +528,37 @@ Window_TwoArgNoReturnFunction(wresize, int, "ii;lines,columns") /* Allocation and deallocation of Window Objects */ static PyObject * -PyCursesWindow_New(WINDOW *win) +PyCursesWindow_New(WINDOW *win, const char *encoding) { PyCursesWindowObject *wo; + if (encoding == NULL) { +#if defined(MS_WINDOWS) + char *buffer[100]; + UINT cp; + cp = GetConsoleOutputCP(); + if (cp != 0) { + PyOS_snprintf(buffer, sizeof(buffer), "cp%u", cp); + encoding = buffer; + } +#elif defined(CODESET) + const char *codeset = nl_langinfo(CODESET); + if (codeset != NULL && codeset[0] != 0) + encoding = codeset; +#endif + if (encoding == NULL) + encoding = "utf-8"; + } + wo = PyObject_NEW(PyCursesWindowObject, &PyCursesWindow_Type); if (wo == NULL) return NULL; wo->win = win; + wo->encoding = strdup(encoding); + if (wo->encoding == NULL) { + Py_DECREF(wo); + PyErr_NoMemory(); + return NULL; + } return (PyObject *)wo; } @@ -371,6 +566,8 @@ static void PyCursesWindow_Dealloc(PyCursesWindowObject *wo) { if (wo->win != stdscr) delwin(wo->win); + if (wo->encoding != NULL) + free(wo->encoding); PyObject_DEL(wo); } @@ -380,29 +577,34 @@ static PyObject * PyCursesWindow_AddCh(PyCursesWindowObject *self, PyObject *args) { int rtn, x, y, use_xy = FALSE; - PyObject *temp; - chtype ch = 0; + PyObject *chobj; + int type; + chtype ch; +#ifdef HAVE_NCURSESW + cchar_t wch; +#endif attr_t attr = A_NORMAL; long lattr; + const char *funcname; switch (PyTuple_Size(args)) { case 1: - if (!PyArg_ParseTuple(args, "O;ch or int", &temp)) + if (!PyArg_ParseTuple(args, "O;ch or int", &chobj)) return NULL; break; case 2: - if (!PyArg_ParseTuple(args, "Ol;ch or int,attr", &temp, &lattr)) + if (!PyArg_ParseTuple(args, "Ol;ch or int,attr", &chobj, &lattr)) return NULL; attr = lattr; break; case 3: - if (!PyArg_ParseTuple(args,"iiO;y,x,ch or int", &y, &x, &temp)) + if (!PyArg_ParseTuple(args,"iiO;y,x,ch or int", &y, &x, &chobj)) return NULL; use_xy = TRUE; break; case 4: if (!PyArg_ParseTuple(args,"iiOl;y,x,ch or int, attr", - &y, &x, &temp, &lattr)) + &y, &x, &chobj, &lattr)) return NULL; attr = lattr; use_xy = TRUE; @@ -412,17 +614,33 @@ PyCursesWindow_AddCh(PyCursesWindowObject *self, PyObject *args) return NULL; } - if (!PyCurses_ConvertToChtype(temp, &ch)) { - PyErr_SetString(PyExc_TypeError, "argument 1 or 3 must be a ch or an int"); +#ifdef HAVE_NCURSESW + type = PyCurses_ConvertToCchar_t(self, chobj, &ch, &wch); + if (type == 2) { + funcname = "add_wch"; + wch.attr = attr; + if (use_xy == TRUE) + rtn = mvwadd_wch(self->win,y,x, &wch); + else { + rtn = wadd_wch(self->win, &wch); + } + } + else +#else + type = PyCurses_ConvertToCchar_t(self, chobj, &ch); +#endif + if (type == 1) { + funcname = "addch"; + if (use_xy == TRUE) + rtn = mvwaddch(self->win,y,x, ch | attr); + else { + rtn = waddch(self->win, ch | attr); + } + } + else { return NULL; } - - if (use_xy == TRUE) - rtn = mvwaddch(self->win,y,x, ch | attr); - else { - rtn = waddch(self->win, ch | attr); - } - return PyCursesCheckERR(rtn, "addch"); + return PyCursesCheckERR(rtn, funcname); } static PyObject * @@ -430,29 +648,34 @@ PyCursesWindow_AddStr(PyCursesWindowObject *self, PyObject *args) { int rtn; int x, y; - char *str; + int strtype; + PyObject *strobj, *bytesobj; +#ifdef HAVE_NCURSESW + wchar_t *wstr = NULL; +#endif attr_t attr = A_NORMAL , attr_old = A_NORMAL; long lattr; int use_xy = FALSE, use_attr = FALSE; + const char *funcname; switch (PyTuple_Size(args)) { case 1: - if (!PyArg_ParseTuple(args,"s;str", &str)) + if (!PyArg_ParseTuple(args,"O;str", &strobj)) return NULL; break; case 2: - if (!PyArg_ParseTuple(args,"sl;str,attr", &str, &lattr)) + if (!PyArg_ParseTuple(args,"Ol;str,attr", &strobj, &lattr)) return NULL; attr = lattr; use_attr = TRUE; break; case 3: - if (!PyArg_ParseTuple(args,"iis;int,int,str", &y, &x, &str)) + if (!PyArg_ParseTuple(args,"iiO;int,int,str", &y, &x, &strobj)) return NULL; use_xy = TRUE; break; case 4: - if (!PyArg_ParseTuple(args,"iisl;int,int,str,attr", &y, &x, &str, &lattr)) + if (!PyArg_ParseTuple(args,"iiOl;int,int,str,attr", &y, &x, &strobj, &lattr)) return NULL; attr = lattr; use_xy = use_attr = TRUE; @@ -461,47 +684,74 @@ PyCursesWindow_AddStr(PyCursesWindowObject *self, PyObject *args) PyErr_SetString(PyExc_TypeError, "addstr requires 1 to 4 arguments"); return NULL; } - +#ifdef HAVE_NCURSESW + strtype = PyCurses_ConvertToString(self, strobj, &bytesobj, &wstr); +#else + strtype = PyCurses_ConvertToString(self, strobj, &bytesobj, NULL); +#endif + if (strtype == 0) + return NULL; if (use_attr == TRUE) { attr_old = getattrs(self->win); (void)wattrset(self->win,attr); } - if (use_xy == TRUE) - rtn = mvwaddstr(self->win,y,x,str); +#ifdef HAVE_NCURSESW + if (strtype == 2) { + funcname = "addwstr"; + if (use_xy == TRUE) + rtn = mvwaddwstr(self->win,y,x,wstr); + else + rtn = waddwstr(self->win,wstr); + PyMem_Free(wstr); + } else - rtn = waddstr(self->win,str); +#endif + { + char *str = PyBytes_AS_STRING(bytesobj); + funcname = "addstr"; + if (use_xy == TRUE) + rtn = mvwaddstr(self->win,y,x,str); + else + rtn = waddstr(self->win,str); + Py_DECREF(bytesobj); + } if (use_attr == TRUE) (void)wattrset(self->win,attr_old); - return PyCursesCheckERR(rtn, "addstr"); + return PyCursesCheckERR(rtn, funcname); } static PyObject * PyCursesWindow_AddNStr(PyCursesWindowObject *self, PyObject *args) { int rtn, x, y, n; - char *str; + int strtype; + PyObject *strobj, *bytesobj; +#ifdef HAVE_NCURSESW + wchar_t *wstr = NULL; +#endif attr_t attr = A_NORMAL , attr_old = A_NORMAL; long lattr; int use_xy = FALSE, use_attr = FALSE; + const char *funcname; switch (PyTuple_Size(args)) { case 2: - if (!PyArg_ParseTuple(args,"si;str,n", &str, &n)) + if (!PyArg_ParseTuple(args,"Oi;str,n", &strobj, &n)) return NULL; break; case 3: - if (!PyArg_ParseTuple(args,"sil;str,n,attr", &str, &n, &lattr)) + if (!PyArg_ParseTuple(args,"Oil;str,n,attr", &strobj, &n, &lattr)) return NULL; attr = lattr; use_attr = TRUE; break; case 4: - if (!PyArg_ParseTuple(args,"iisi;y,x,str,n", &y, &x, &str, &n)) + if (!PyArg_ParseTuple(args,"iiOi;y,x,str,n", &y, &x, &strobj, &n)) return NULL; use_xy = TRUE; break; case 5: - if (!PyArg_ParseTuple(args,"iisil;y,x,str,n,attr", &y, &x, &str, &n, &lattr)) + if (!PyArg_ParseTuple(args,"iiOil;y,x,str,n,attr", &y, &x, &strobj, &n, &lattr)) return NULL; attr = lattr; use_xy = use_attr = TRUE; @@ -510,18 +760,41 @@ PyCursesWindow_AddNStr(PyCursesWindowObject *self, PyObject *args) PyErr_SetString(PyExc_TypeError, "addnstr requires 2 to 5 arguments"); return NULL; } +#ifdef HAVE_NCURSESW + strtype = PyCurses_ConvertToString(self, strobj, &bytesobj, &wstr); +#else + strtype = PyCurses_ConvertToString(self, strobj, &bytesobj, NULL); +#endif + if (strtype == 0) + return NULL; if (use_attr == TRUE) { attr_old = getattrs(self->win); (void)wattrset(self->win,attr); } - if (use_xy == TRUE) - rtn = mvwaddnstr(self->win,y,x,str,n); +#ifdef HAVE_NCURSESW + if (strtype == 2) { + funcname = "addnwstr"; + if (use_xy == TRUE) + rtn = mvwaddnwstr(self->win,y,x,wstr,n); + else + rtn = waddnwstr(self->win,wstr,n); + PyMem_Free(wstr); + } else - rtn = waddnstr(self->win,str,n); +#endif + { + char *str = PyBytes_AS_STRING(bytesobj); + funcname = "addnstr"; + if (use_xy == TRUE) + rtn = mvwaddnstr(self->win,y,x,str,n); + else + rtn = waddnstr(self->win,str,n); + Py_DECREF(bytesobj); + } if (use_attr == TRUE) (void)wattrset(self->win,attr_old); - return PyCursesCheckERR(rtn, "addnstr"); + return PyCursesCheckERR(rtn, funcname); } static PyObject * @@ -547,10 +820,8 @@ PyCursesWindow_Bkgd(PyCursesWindowObject *self, PyObject *args) return NULL; } - if (!PyCurses_ConvertToChtype(temp, &bkgd)) { - PyErr_SetString(PyExc_TypeError, "argument 1 or 3 must be a ch or an int"); + if (!PyCurses_ConvertToChtype(self, temp, &bkgd)) return NULL; - } return PyCursesCheckERR(wbkgd(self->win, bkgd | attr), "bkgd"); } @@ -605,10 +876,8 @@ PyCursesWindow_BkgdSet(PyCursesWindowObject *self, PyObject *args) return NULL; } - if (!PyCurses_ConvertToChtype(temp, &bkgd)) { - PyErr_SetString(PyExc_TypeError, "argument 1 must be a ch or an int"); + if (!PyCurses_ConvertToChtype(self, temp, &bkgd)) return NULL; - } wbkgdset(self->win, bkgd | attr); return PyCursesCheckERR(0, "bkgdset"); @@ -633,11 +902,8 @@ PyCursesWindow_Border(PyCursesWindowObject *self, PyObject *args) return NULL; for(i=0; i<8; i++) { - if (temp[i] != NULL && !PyCurses_ConvertToChtype(temp[i], &ch[i])) { - PyErr_Format(PyExc_TypeError, - "argument %i must be a ch or an int", i+1); + if (temp[i] != NULL && !PyCurses_ConvertToChtype(self, temp[i], &ch[i])) return NULL; - } } wborder(self->win, @@ -782,7 +1048,7 @@ PyCursesWindow_DerWin(PyCursesWindowObject *self, PyObject *args) return NULL; } - return (PyObject *)PyCursesWindow_New(win); + return (PyObject *)PyCursesWindow_New(win, NULL); } static PyObject * @@ -810,10 +1076,8 @@ PyCursesWindow_EchoChar(PyCursesWindowObject *self, PyObject *args) return NULL; } - if (!PyCurses_ConvertToChtype(temp, &ch)) { - PyErr_SetString(PyExc_TypeError, "argument 1 must be a ch or an int"); + if (!PyCurses_ConvertToChtype(self, temp, &ch)) return NULL; - } #ifdef WINDOW_HAS_FLAGS if (self->win->_flags & _ISPAD) @@ -1034,11 +1298,8 @@ PyCursesWindow_Hline(PyCursesWindowObject *self, PyObject *args) } if (code != ERR) { - if (!PyCurses_ConvertToChtype(temp, &ch)) { - PyErr_SetString(PyExc_TypeError, - "argument 1 or 3 must be a ch or an int"); + if (!PyCurses_ConvertToChtype(self, temp, &ch)) return NULL; - } return PyCursesCheckERR(whline(self->win, ch | attr, n), "hline"); } else return PyCursesCheckERR(code, "wmove"); @@ -1079,11 +1340,8 @@ PyCursesWindow_InsCh(PyCursesWindowObject *self, PyObject *args) return NULL; } - if (!PyCurses_ConvertToChtype(temp, &ch)) { - PyErr_SetString(PyExc_TypeError, - "argument 1 or 3 must be a ch or an int"); + if (!PyCurses_ConvertToChtype(self, temp, &ch)) return NULL; - } if (use_xy == TRUE) rtn = mvwinsch(self->win,y,x, ch | attr); @@ -1154,29 +1412,34 @@ PyCursesWindow_InsStr(PyCursesWindowObject *self, PyObject *args) { int rtn; int x, y; - char *str; + int strtype; + PyObject *strobj, *bytesobj; +#ifdef HAVE_NCURSESW + wchar_t *wstr = NULL; +#endif attr_t attr = A_NORMAL , attr_old = A_NORMAL; long lattr; int use_xy = FALSE, use_attr = FALSE; + const char *funcname; switch (PyTuple_Size(args)) { case 1: - if (!PyArg_ParseTuple(args,"s;str", &str)) + if (!PyArg_ParseTuple(args,"O;str", &strobj)) return NULL; break; case 2: - if (!PyArg_ParseTuple(args,"sl;str,attr", &str, &lattr)) + if (!PyArg_ParseTuple(args,"Ol;str,attr", &strobj, &lattr)) return NULL; attr = lattr; use_attr = TRUE; break; case 3: - if (!PyArg_ParseTuple(args,"iis;y,x,str", &y, &x, &str)) + if (!PyArg_ParseTuple(args,"iiO;y,x,str", &y, &x, &strobj)) return NULL; use_xy = TRUE; break; case 4: - if (!PyArg_ParseTuple(args,"iisl;y,x,str,attr", &y, &x, &str, &lattr)) + if (!PyArg_ParseTuple(args,"iiOl;y,x,str,attr", &y, &x, &strobj, &lattr)) return NULL; attr = lattr; use_xy = use_attr = TRUE; @@ -1186,46 +1449,75 @@ PyCursesWindow_InsStr(PyCursesWindowObject *self, PyObject *args) return NULL; } +#ifdef HAVE_NCURSESW + strtype = PyCurses_ConvertToString(self, strobj, &bytesobj, &wstr); +#else + strtype = PyCurses_ConvertToString(self, strobj, &bytesobj, NULL); +#endif + if (strtype == 0) + return NULL; + if (use_attr == TRUE) { attr_old = getattrs(self->win); (void)wattrset(self->win,attr); } - if (use_xy == TRUE) - rtn = mvwinsstr(self->win,y,x,str); +#ifdef HAVE_NCURSESW + if (strtype == 2) { + funcname = "inswstr"; + if (use_xy == TRUE) + rtn = mvwins_wstr(self->win,y,x,wstr); + else + rtn = wins_wstr(self->win,wstr); + PyMem_Free(wstr); + } else - rtn = winsstr(self->win,str); +#endif + { + char *str = PyBytes_AS_STRING(bytesobj); + funcname = "insstr"; + if (use_xy == TRUE) + rtn = mvwinsstr(self->win,y,x,str); + else + rtn = winsstr(self->win,str); + Py_DECREF(bytesobj); + } if (use_attr == TRUE) (void)wattrset(self->win,attr_old); - return PyCursesCheckERR(rtn, "insstr"); + return PyCursesCheckERR(rtn, funcname); } static PyObject * PyCursesWindow_InsNStr(PyCursesWindowObject *self, PyObject *args) { int rtn, x, y, n; - char *str; + int strtype; + PyObject *strobj, *bytesobj; +#ifdef HAVE_NCURSESW + wchar_t *wstr = NULL; +#endif attr_t attr = A_NORMAL , attr_old = A_NORMAL; long lattr; int use_xy = FALSE, use_attr = FALSE; + const char *funcname; switch (PyTuple_Size(args)) { case 2: - if (!PyArg_ParseTuple(args,"si;str,n", &str, &n)) + if (!PyArg_ParseTuple(args,"Oi;str,n", &strobj, &n)) return NULL; break; case 3: - if (!PyArg_ParseTuple(args,"sil;str,n,attr", &str, &n, &lattr)) + if (!PyArg_ParseTuple(args,"Oil;str,n,attr", &strobj, &n, &lattr)) return NULL; attr = lattr; use_attr = TRUE; break; case 4: - if (!PyArg_ParseTuple(args,"iisi;y,x,str,n", &y, &x, &str, &n)) + if (!PyArg_ParseTuple(args,"iiOi;y,x,str,n", &y, &x, &strobj, &n)) return NULL; use_xy = TRUE; break; case 5: - if (!PyArg_ParseTuple(args,"iisil;y,x,str,n,attr", &y, &x, &str, &n, &lattr)) + if (!PyArg_ParseTuple(args,"iiOil;y,x,str,n,attr", &y, &x, &strobj, &n, &lattr)) return NULL; attr = lattr; use_xy = use_attr = TRUE; @@ -1235,17 +1527,41 @@ PyCursesWindow_InsNStr(PyCursesWindowObject *self, PyObject *args) return NULL; } +#ifdef HAVE_NCURSESW + strtype = PyCurses_ConvertToString(self, strobj, &bytesobj, &wstr); +#else + strtype = PyCurses_ConvertToString(self, strobj, &bytesobj, NULL); +#endif + if (strtype == 0) + return NULL; + if (use_attr == TRUE) { attr_old = getattrs(self->win); (void)wattrset(self->win,attr); } - if (use_xy == TRUE) - rtn = mvwinsnstr(self->win,y,x,str,n); +#ifdef HAVE_NCURSESW + if (strtype == 2) { + funcname = "insn_wstr"; + if (use_xy == TRUE) + rtn = mvwins_nwstr(self->win,y,x,wstr,n); + else + rtn = wins_nwstr(self->win,wstr,n); + PyMem_Free(wstr); + } else - rtn = winsnstr(self->win,str,n); +#endif + { + char *str = PyBytes_AS_STRING(bytesobj); + funcname = "insnstr"; + if (use_xy == TRUE) + rtn = mvwinsnstr(self->win,y,x,str,n); + else + rtn = winsnstr(self->win,str,n); + Py_DECREF(bytesobj); + } if (use_attr == TRUE) (void)wattrset(self->win,attr_old); - return PyCursesCheckERR(rtn, "insnstr"); + return PyCursesCheckERR(rtn, funcname); } static PyObject * @@ -1528,7 +1844,7 @@ PyCursesWindow_SubWin(PyCursesWindowObject *self, PyObject *args) return NULL; } - return (PyObject *)PyCursesWindow_New(win); + return (PyObject *)PyCursesWindow_New(win, self->encoding); } static PyObject * @@ -1604,16 +1920,51 @@ PyCursesWindow_Vline(PyCursesWindowObject *self, PyObject *args) } if (code != ERR) { - if (!PyCurses_ConvertToChtype(temp, &ch)) { - PyErr_SetString(PyExc_TypeError, - "argument 1 or 3 must be a ch or an int"); + if (!PyCurses_ConvertToChtype(self, temp, &ch)) return NULL; - } return PyCursesCheckERR(wvline(self->win, ch | attr, n), "vline"); } else return PyCursesCheckERR(code, "wmove"); } +static PyObject * +PyCursesWindow_get_encoding(PyCursesWindowObject *self, void *closure) +{ + return PyUnicode_FromString(self->encoding); +} + +static int +PyCursesWindow_set_encoding(PyCursesWindowObject *self, PyObject *value) +{ + PyObject *ascii; + char *encoding; + + /* It is illegal to del win.encoding */ + if (value == NULL) { + PyErr_SetString(PyExc_TypeError, + "encoding may not be deleted"); + return -1; + } + + if (!PyUnicode_Check(value)) { + PyErr_SetString(PyExc_TypeError, + "setting encoding to a non-string"); + return -1; + } + ascii = PyUnicode_AsASCIIString(value); + if (ascii == NULL) + return -1; + encoding = strdup(PyBytes_AS_STRING(ascii)); + if (encoding == NULL) { + PyErr_NoMemory(); + return -1; + } + free(self->encoding); + self->encoding = encoding; + return 0; +} + + static PyMethodDef PyCursesWindow_Methods[] = { {"addch", (PyCFunction)PyCursesWindow_AddCh, METH_VARARGS}, {"addnstr", (PyCFunction)PyCursesWindow_AddNStr, METH_VARARGS}, @@ -1701,6 +2052,13 @@ static PyMethodDef PyCursesWindow_Methods[] = { {NULL, NULL} /* sentinel */ }; +static PyGetSetDef PyCursesWindow_getsets[] = { + {"encoding", + (getter)PyCursesWindow_get_encoding, + (setter)PyCursesWindow_set_encoding, + "the typecode character used to create the array"} +}; + /* -------------------------------------------------------*/ PyTypeObject PyCursesWindow_Type = { @@ -1733,6 +2091,8 @@ PyTypeObject PyCursesWindow_Type = { 0, /*tp_iter*/ 0, /*tp_iternext*/ PyCursesWindow_Methods, /*tp_methods*/ + 0, /* tp_members */ + PyCursesWindow_getsets, /* tp_getset */ }; /********************************************************************* @@ -1956,7 +2316,7 @@ PyCurses_GetWin(PyCursesWindowObject *self, PyObject *stream) PyErr_SetString(PyCursesError, catchall_NULL); return NULL; } - return PyCursesWindow_New(win); + return PyCursesWindow_New(win, NULL); } static PyObject * @@ -2034,10 +2394,11 @@ static PyObject * PyCurses_InitScr(PyObject *self) { WINDOW *win; + PyCursesWindowObject *winobj; if (initialised == TRUE) { wrefresh(stdscr); - return (PyObject *)PyCursesWindow_New(stdscr); + return (PyObject *)PyCursesWindow_New(stdscr, NULL); } win = initscr(); @@ -2129,7 +2490,9 @@ PyCurses_InitScr(PyObject *self) SetDictInt("LINES", LINES); SetDictInt("COLS", COLS); - return (PyObject *)PyCursesWindow_New(win); + winobj = (PyCursesWindowObject *)PyCursesWindow_New(win, NULL); + screen_encoding = winobj->encoding; + return (PyObject *)winobj; } static PyObject * @@ -2331,7 +2694,7 @@ PyCurses_NewPad(PyObject *self, PyObject *args) return NULL; } - return (PyObject *)PyCursesWindow_New(win); + return (PyObject *)PyCursesWindow_New(win, NULL); } static PyObject * @@ -2363,7 +2726,7 @@ PyCurses_NewWindow(PyObject *self, PyObject *args) return NULL; } - return (PyObject *)PyCursesWindow_New(win); + return (PyObject *)PyCursesWindow_New(win, NULL); } static PyObject * @@ -2680,10 +3043,8 @@ PyCurses_UnCtrl(PyObject *self, PyObject *args) if (!PyArg_ParseTuple(args,"O;ch or int",&temp)) return NULL; - if (!PyCurses_ConvertToChtype(temp, &ch)) { - PyErr_SetString(PyExc_TypeError, "argument must be a ch or an int"); + if (!PyCurses_ConvertToChtype(NULL, temp, &ch)) return NULL; - } return PyBytes_FromString(unctrl(ch)); } @@ -2696,12 +3057,11 @@ PyCurses_UngetCh(PyObject *self, PyObject *args) PyCursesInitialised; - if (!PyArg_ParseTuple(args,"O;ch or int",&temp)) return NULL; - - if (!PyCurses_ConvertToChtype(temp, &ch)) { - PyErr_SetString(PyExc_TypeError, "argument must be a ch or an int"); + if (!PyArg_ParseTuple(args,"O;ch or int",&temp)) + return NULL; + + if (!PyCurses_ConvertToChtype(NULL, temp, &ch)) return NULL; - } return PyCursesCheckERR(ungetch(ch), "ungetch"); }