-27b6709241461f620fb25756ef9f1192cc4f589a
+84bf400d038a9bf6e2a7098278c9fa42bd908816
Cython Changelog
================
+0.29.29 (2022-05-16)
+====================
+
+Features added
+--------------
+
+* Avoid acquiring the GIL at the end of nogil functions.
+ This change was backported in order to avoid generating wrong C code
+ that would trigger C compiler warnings with tracing support enabled.
+ Backport by Oleksandr Pavlyk. (Github issue #4637)
+
+Bugs fixed
+----------
+
+* Function definitions in ``finally:`` clauses were not correctly generated.
+ Patch by David Woods. (Github issue #4651)
+
+* A case where C-API functions could be called with a live exception set was fixed.
+ Patch by Jakub Kulík. (Github issue #4722)
+
+* Pickles can now be exchanged again with those generated from Cython 3.0 modules.
+ (Github issue #4680)
+
+* Cython now correctly generates Python methods for both the provided regular and
+ reversed special numeric methods of extension types.
+ Patch by David Woods. (Github issue #4750)
+
+* Calling unbound extension type methods without arguments could raise an
+ ``IndexError`` instead of a ``TypeError``.
+ Patch by David Woods. (Github issue #4779)
+
+* Calling unbound ``.__contains__()`` super class methods on some builtin base
+ types could trigger an infinite recusion.
+ Patch by David Woods. (Github issue #4785)
+
+* The C union type in pure Python mode mishandled some field names.
+ Patch by Jordan Brière. (Github issue #4727)
+
+* Allow users to overwrite the C macro ``_USE_MATH_DEFINES``.
+ Patch by Yuriy Chernyshov. (Github issue #4690)
+
+* Improved compatibility with CPython 3.10/11.
+ Patches by Thomas Caswell, David Woods. (Github issues #4609, #4667, #4721, #4730, #4777)
+
+* Docstrings of descriptors are now provided in PyPy 7.3.9.
+ Patch by Matti Picus. (Github issue #4701)
+
+
0.29.28 (2022-02-17)
====================
]),
("bytearray", "PyByteArray_Type", [
]),
- ("bytes", "PyBytes_Type", [BuiltinMethod("__contains__", "TO", "b", "PySequence_Contains"),
- BuiltinMethod("join", "TO", "O", "__Pyx_PyBytes_Join",
+ ("bytes", "PyBytes_Type", [BuiltinMethod("join", "TO", "O", "__Pyx_PyBytes_Join",
utility_code=UtilityCode.load("StringJoin", "StringTools.c")),
]),
- ("str", "PyString_Type", [BuiltinMethod("__contains__", "TO", "b", "PySequence_Contains"),
- BuiltinMethod("join", "TO", "O", "__Pyx_PyString_Join",
+ ("str", "PyString_Type", [BuiltinMethod("join", "TO", "O", "__Pyx_PyString_Join",
builtin_return_type='basestring',
utility_code=UtilityCode.load("StringJoin", "StringTools.c")),
]),
BuiltinMethod("join", "TO", "T", "PyUnicode_Join"),
]),
- ("tuple", "PyTuple_Type", [BuiltinMethod("__contains__", "TO", "b", "PySequence_Contains"),
- ]),
+ ("tuple", "PyTuple_Type", []),
- ("list", "PyList_Type", [BuiltinMethod("__contains__", "TO", "b", "PySequence_Contains"),
- BuiltinMethod("insert", "TzO", "r", "PyList_Insert"),
+ ("list", "PyList_Type", [BuiltinMethod("insert", "TzO", "r", "PyList_Insert"),
BuiltinMethod("reverse", "T", "r", "PyList_Reverse"),
BuiltinMethod("append", "TO", "r", "__Pyx_PyList_Append",
utility_code=UtilityCode.load("ListAppend", "Optimize.c")),
]),
# ("file", "PyFile_Type", []), # not in Py3
- ("set", "PySet_Type", [BuiltinMethod("__contains__", "TO", "b", "PySequence_Contains"),
- BuiltinMethod("clear", "T", "r", "PySet_Clear"),
+ ("set", "PySet_Type", [BuiltinMethod("clear", "T", "r", "PySet_Clear"),
# discard() and remove() have a special treatment for unhashable values
BuiltinMethod("discard", "TO", "r", "__Pyx_PySet_Discard",
utility_code=UtilityCode.load("py_set_discard", "Optimize.c")),
method_flags = entry.signature.method_flags()
if not method_flags:
return
- if entry.is_special:
- from . import TypeSlots
+ from . import TypeSlots
+ if entry.is_special or TypeSlots.is_reverse_number_slot(entry.name):
method_flags += [TypeSlots.method_coexist]
func_ptr = wrapper_code_writer.put_pymethoddef_wrapper(entry) if wrapper_code_writer else entry.func_cname
# Add required casts, but try not to shadow real warnings.
UtilityCode.load_cached("ForceInitThreads", "ModuleSetupCode.c"))
self.putln('__Pyx_RefNannySetupContext("%s", %d);' % (name, acquire_gil and 1 or 0))
- def put_finish_refcount_context(self):
- self.putln("__Pyx_RefNannyFinishContext();")
+ def put_finish_refcount_context(self, nogil=False):
+ self.putln("__Pyx_RefNannyFinishContextNogil()" if nogil else "__Pyx_RefNannyFinishContext();")
def put_add_traceback(self, qualified_name, include_cline=True):
"""
use_refnanny = not lenv.nogil or lenv.has_with_gil_block
+ gilstate_decl = None
if acquire_gil or acquire_gil_for_var_decls_only:
code.put_ensure_gil()
code.funcstate.gil_owned = True
- elif lenv.nogil and lenv.has_with_gil_block:
- code.declare_gilstate()
+ else:
+ gilstate_decl = code.insertion_point()
if profile or linetrace:
if not self.is_generator:
code.putln("")
code.putln("/* function exit code */")
+ gil_owned = {
+ 'success': code.funcstate.gil_owned,
+ 'error': code.funcstate.gil_owned,
+ 'gil_state_declared': gilstate_decl is None,
+ }
+ def assure_gil(code_path):
+ if not gil_owned[code_path]:
+ if not gil_owned['gil_state_declared']:
+ gilstate_decl.declare_gilstate()
+ gil_owned['gil_state_declared'] = True
+ code.put_ensure_gil(declare_gilstate=False)
+ gil_owned[code_path] = True
+
# ----- Default return value
if not self.body.is_terminator:
if self.return_type.is_pyobject:
# lhs = "(PyObject *)%s" % Naming.retval_cname
#else:
lhs = Naming.retval_cname
+ assure_gil('success')
code.put_init_to_py_none(lhs, self.return_type)
- else:
+ elif not self.return_type.is_memoryviewslice:
+ # memory view structs receive their default value on initialisation
val = self.return_type.default_value
if val:
code.putln("%s = %s;" % (Naming.retval_cname, val))
code.globalstate.use_utility_code(restore_exception_utility_code)
code.putln("{ PyObject *__pyx_type, *__pyx_value, *__pyx_tb;")
code.putln("__Pyx_PyThreadState_declare")
+ assure_gil('error')
code.putln("__Pyx_PyThreadState_assign")
code.putln("__Pyx_ErrFetch(&__pyx_type, &__pyx_value, &__pyx_tb);")
for entry in used_buffer_entries:
# code.globalstate.use_utility_code(get_exception_tuple_utility_code)
# code.put_trace_exception()
- if lenv.nogil and not lenv.has_with_gil_block:
- code.putln("{")
- code.put_ensure_gil()
-
+ assure_gil('error')
code.put_add_traceback(self.entry.qualified_name)
-
- if lenv.nogil and not lenv.has_with_gil_block:
- code.put_release_ensured_gil()
- code.putln("}")
else:
warning(self.entry.pos,
"Unraisable exception in function '%s'." %
self.entry.qualified_name, 0)
- code.put_unraisable(self.entry.qualified_name, lenv.nogil)
+ assure_gil('error')
+ code.put_unraisable(self.entry.qualified_name)
default_retval = self.return_type.default_value
if err_val is None and default_retval:
err_val = default_retval
code.putln("__Pyx_pretend_to_initialize(&%s);" % Naming.retval_cname)
if is_getbuffer_slot:
+ assure_gil('error')
self.getbuffer_error_cleanup(code)
# If we are using the non-error cleanup section we should
# jump past it if we have an error. The if-test below determine
# whether this section is used.
if buffers_present or is_getbuffer_slot or self.return_type.is_memoryviewslice:
+ # In the buffer cases, we already called assure_gil('error') and own the GIL.
+ assert gil_owned['error'] or self.return_type.is_memoryviewslice
code.put_goto(code.return_from_error_cleanup_label)
+ else:
+ # align error and success GIL state
+ if gil_owned['success']:
+ assure_gil('error')
+ elif gil_owned['error']:
+ code.put_release_ensured_gil()
+ gil_owned['error'] = False
# ----- Non-error return cleanup
code.put_label(code.return_label)
+ assert gil_owned['error'] == gil_owned['success'], "%s != %s" % (gil_owned['error'], gil_owned['success'])
+
for entry in used_buffer_entries:
+ assure_gil('success')
Buffer.put_release_buffer_code(code, entry)
if is_getbuffer_slot:
+ assure_gil('success')
self.getbuffer_normal_cleanup(code)
if self.return_type.is_memoryviewslice:
cond = code.unlikely(self.return_type.error_condition(Naming.retval_cname))
code.putln(
'if (%s) {' % cond)
- if env.nogil:
+ if not gil_owned['success']:
code.put_ensure_gil()
code.putln(
'PyErr_SetString(PyExc_TypeError, "Memoryview return value is not initialized");')
- if env.nogil:
+ if not gil_owned['success']:
code.put_release_ensured_gil()
code.putln(
'}')
# ----- Return cleanup for both error and no-error return
- code.put_label(code.return_from_error_cleanup_label)
+ if code.label_used(code.return_from_error_cleanup_label):
+ # If we came through the success path, then we took the same GIL decisions as for jumping here.
+ # If we came through the error path and did not jump, then we aligned both paths above.
+ # In the end, all paths are aligned from this point on.
+ assert gil_owned['error'] == gil_owned['success'], "%s != %s" % (gil_owned['error'], gil_owned['success'])
+ code.put_label(code.return_from_error_cleanup_label)
for entry in lenv.var_entries:
if not entry.used or entry.in_closure:
code.put_xdecref_memoryviewslice(entry.cname,
have_gil=not lenv.nogil)
if self.needs_closure:
+ assure_gil('success')
code.put_decref(Naming.cur_scope_cname, lenv.scope_class.type)
# ----- Return
if self.entry.is_special and self.entry.name == "__hash__":
# Returning -1 for __hash__ is supposed to signal an error
# We do as Python instances and coerce -1 into -2.
+ assure_gil('success')
code.putln("if (unlikely(%s == -1) && !PyErr_Occurred()) %s = -2;" % (
Naming.retval_cname, Naming.retval_cname))
# generators are traced when iterated, not at creation
if self.return_type.is_pyobject:
code.put_trace_return(
- Naming.retval_cname, nogil=not code.funcstate.gil_owned)
+ Naming.retval_cname, nogil=not gil_owned['success'])
else:
code.put_trace_return(
- "Py_None", nogil=not code.funcstate.gil_owned)
+ "Py_None", nogil=not gil_owned['success'])
if not lenv.nogil:
# GIL holding function
- code.put_finish_refcount_context()
+ code.put_finish_refcount_context(nogil=not gil_owned['success'])
- if acquire_gil or (lenv.nogil and lenv.has_with_gil_block):
+ if acquire_gil or (lenv.nogil and gil_owned['success']):
# release the GIL (note that with-gil blocks acquire it on exit in their EnsureGILNode)
code.put_release_ensured_gil()
code.funcstate.gil_owned = False
docstr.as_c_string_literal()))
if entry.is_special:
- code.putln('#if CYTHON_COMPILING_IN_CPYTHON')
+ code.putln('#if CYTHON_UPDATE_DESCRIPTOR_DOC')
code.putln(
"struct wrapperbase %s;" % entry.wrapperbase_cname)
code.putln('#endif')
preprocessor_guard = slot.preprocessor_guard_code() if slot else None
if preprocessor_guard:
code.putln(preprocessor_guard)
- code.putln('#if CYTHON_COMPILING_IN_CPYTHON')
+ code.putln('#if CYTHON_UPDATE_DESCRIPTOR_DOC')
code.putln("{")
code.putln(
'PyObject *wrapper = PyObject_GetAttrString((PyObject *)&%s, "%s"); %s' % (
def generate_function_definitions(self, env, code):
self.body.generate_function_definitions(env, code)
self.finally_clause.generate_function_definitions(env, code)
+ if self.finally_except_clause:
+ self.finally_except_clause.generate_function_definitions(env, code)
def put_error_catcher(self, code, temps_to_clean_up, exc_vars,
exc_lineno_cnames=None, exc_filename_cname=None):
e.type.create_to_py_utility_code(env)
e.type.create_from_py_utility_code(env)
all_members_names = sorted([e.name for e in all_members])
- checksum = '0x%s' % hashlib.md5(' '.join(all_members_names).encode('utf-8')).hexdigest()[:7]
+
+ # Cython 0.x used MD5 for the checksum, which a few Python installations remove for security reasons.
+ # SHA-256 should be ok for years to come, but early Cython 3.0 alpha releases used SHA-1,
+ # which may not be.
+ checksum_algos = []
+ try:
+ checksum_algos.append(hashlib.md5)
+ except AttributeError:
+ pass
+ checksum_algos.append(hashlib.sha256)
+ checksum_algos.append(hashlib.sha1)
+
+ member_names_string = ' '.join(all_members_names).encode('utf-8')
+ checksums = [
+ '0x' + mkchecksum(member_names_string).hexdigest()[:7]
+ for mkchecksum in checksum_algos
+ ]
unpickle_func_name = '__pyx_unpickle_%s' % node.class_name
# TODO(robertwb): Move the state into the third argument
def %(unpickle_func_name)s(__pyx_type, long __pyx_checksum, __pyx_state):
cdef object __pyx_PickleError
cdef object __pyx_result
- if __pyx_checksum != %(checksum)s:
+ if __pyx_checksum not in %(checksums)s:
from pickle import PickleError as __pyx_PickleError
- raise __pyx_PickleError("Incompatible checksums (%%s vs %(checksum)s = (%(members)s))" %% __pyx_checksum)
+ raise __pyx_PickleError("Incompatible checksums (0x%%x vs %(checksums)s = (%(members)s))" %% __pyx_checksum)
__pyx_result = %(class_name)s.__new__(__pyx_type)
if __pyx_state is not None:
%(unpickle_func_name)s__set_state(<%(class_name)s> __pyx_result, __pyx_state)
__pyx_result.__dict__.update(__pyx_state[%(num_members)d])
""" % {
'unpickle_func_name': unpickle_func_name,
- 'checksum': checksum,
+ 'checksums': "(%s)" % ', '.join(checksums),
'members': ', '.join(all_members_names),
'class_name': node.class_name,
'assignments': '; '.join(
%(unpickle_func_name)s__set_state(self, __pyx_state)
""" % {
'unpickle_func_name': unpickle_func_name,
- 'checksum': checksum,
+ 'checksum': checksums[0],
'members': ', '.join('self.%s' % v for v in all_members_names) + (',' if len(all_members_names) == 1 else ''),
# Even better, we could check PyType_IS_GC.
'any_notnone_members' : ' or '.join(['self.%s is not None' % e.name for e in all_members if e.type.is_pyobject] or ['False']),
return node
- def _handle_nogil_cleanup(self, lenv, node):
- "Handle cleanup for 'with gil' blocks in nogil functions."
- if lenv.nogil and lenv.has_with_gil_block:
- # Acquire the GIL for cleanup in 'nogil' functions, by wrapping
- # the entire function body in try/finally.
- # The corresponding release will be taken care of by
- # Nodes.FuncDefNode.generate_function_definitions()
- node.body = Nodes.NogilTryFinallyStatNode(
- node.body.pos,
- body=node.body,
- finally_clause=Nodes.EnsureGILNode(node.body.pos),
- finally_except_clause=Nodes.EnsureGILNode(node.body.pos))
-
def _handle_fused(self, node):
if node.is_generator and node.has_fused_arguments:
node.has_fused_arguments = False
node = self._create_fused_function(env, node)
else:
node.body.analyse_declarations(lenv)
- self._handle_nogil_cleanup(lenv, node)
self._super_visit_FuncDefNode(node)
self.seen_vars_stack.pop()
slot = get_slot_by_name(slot_name)
return slot.slot_code(scope)
+def is_reverse_number_slot(name):
+ """
+ Tries to identify __radd__ and friends (so the METH_COEXIST flag can be applied).
+
+ There's no great consequence if it inadvertently identifies a few other methods
+ so just use a simple rule rather than an exact list.
+ """
+ if name.startswith("__r") and name.endswith("__"):
+ forward_name = name.replace("r", "", 1)
+ for meth in PyNumberMethods:
+ if getattr(meth, "method_name", None) == forward_name:
+ return True
+ return False
+
#------------------------------------------------------------------------------------------
#
# cython.* namespace for pure mode.
from __future__ import absolute_import
-__version__ = "0.29.28"
+__version__ = "0.29.29"
try:
from __builtin__ import basestring
setattr(self, key, value)
def __setattr__(self, key, value):
- if key in '__dict__':
+ if key == '__dict__':
CythonType.__setattr__(self, key, value)
elif key in self._members:
self.__dict__ = {key: cast(self._members[key], value)}
#include <structmember.h>
#include <frameobject.h>
+#if PY_VERSION_HEX >= 0x030b00a6
+ #ifndef Py_BUILD_CORE
+ #define Py_BUILD_CORE 1
+ #endif
+ #include "internal/pycore_frame.h"
+#endif
#define __Pyx_Coroutine_Undelegate(gen) Py_CLEAR((gen)->yieldfrom)
self = PyTuple_GetItem(args, 0);
if (unlikely(!self)) {
Py_DECREF(new_args);
+ PyErr_Format(PyExc_TypeError,
+ "unbound method %.200S() needs an argument",
+ cyfunc->func_qualname);
return NULL;
}
#include "compile.h"
#include "frameobject.h"
#include "traceback.h"
+#if PY_VERSION_HEX >= 0x030b00a6
+ #ifndef Py_BUILD_CORE
+ #define Py_BUILD_CORE 1
+ #endif
+ #include "internal/pycore_frame.h"
+#endif
static PyCodeObject* __Pyx_CreateCodeObjectForTraceback(
const char *funcname, int c_line,
PyCodeObject *py_code = 0;
PyFrameObject *py_frame = 0;
PyThreadState *tstate = __Pyx_PyThreadState_Current;
+ PyObject *ptype, *pvalue, *ptraceback;
if (c_line) {
c_line = __Pyx_CLineForTraceback(tstate, c_line);
// Negate to avoid collisions between py and c lines.
py_code = $global_code_object_cache_find(c_line ? -c_line : py_line);
if (!py_code) {
+ __Pyx_ErrFetchInState(tstate, &ptype, &pvalue, &ptraceback);
py_code = __Pyx_CreateCodeObjectForTraceback(
funcname, c_line, py_line, filename);
- if (!py_code) goto bad;
+ if (!py_code) {
+ /* If the code object creation fails, then we should clear the
+ fetched exception references and propagate the new exception */
+ Py_XDECREF(ptype);
+ Py_XDECREF(pvalue);
+ Py_XDECREF(ptraceback);
+ goto bad;
+ }
+ __Pyx_ErrRestoreInState(tstate, ptype, pvalue, ptraceback);
$global_code_object_cache_insert(c_line ? -c_line : py_line, py_code);
}
py_frame = PyFrame_New(
static int __Pyx_setup_reduce(PyObject* type_obj) {
int ret = 0;
PyObject *object_reduce = NULL;
+ PyObject *object_getstate = NULL;
PyObject *object_reduce_ex = NULL;
PyObject *reduce = NULL;
PyObject *reduce_ex = NULL;
PyObject *reduce_cython = NULL;
PyObject *setstate = NULL;
PyObject *setstate_cython = NULL;
+ PyObject *getstate = NULL;
#if CYTHON_USE_PYTYPE_LOOKUP
- if (_PyType_Lookup((PyTypeObject*)type_obj, PYIDENT("__getstate__"))) goto __PYX_GOOD;
+ getstate = _PyType_Lookup((PyTypeObject*)type_obj, PYIDENT("__getstate__"));
#else
- if (PyObject_HasAttr(type_obj, PYIDENT("__getstate__"))) goto __PYX_GOOD;
+ getstate = __Pyx_PyObject_GetAttrStrNoError(type_obj, PYIDENT("__getstate__"));
+ if (!getstate && PyErr_Occurred()) {
+ goto __PYX_BAD;
+ }
#endif
+ if (getstate) {
+ // Python 3.11 introduces object.__getstate__. Because it's version-specific failure to find it should not be an error
+#if CYTHON_USE_PYTYPE_LOOKUP
+ object_getstate = _PyType_Lookup(&PyBaseObject_Type, PYIDENT("__getstate__"));
+#else
+ object_getstate = __Pyx_PyObject_GetAttrStrNoError((PyObject*)&PyBaseObject_Type, PYIDENT("__getstate__"));
+ if (!object_getstate && PyErr_Occurred()) {
+ goto __PYX_BAD;
+ }
+#endif
+ if (object_getstate != getstate) {
+ goto __PYX_GOOD;
+ }
+ }
#if CYTHON_USE_PYTYPE_LOOKUP
object_reduce_ex = _PyType_Lookup(&PyBaseObject_Type, PYIDENT("__reduce_ex__")); if (!object_reduce_ex) goto __PYX_BAD;
#if !CYTHON_USE_PYTYPE_LOOKUP
Py_XDECREF(object_reduce);
Py_XDECREF(object_reduce_ex);
+ Py_XDECREF(object_getstate);
+ Py_XDECREF(getstate);
#endif
Py_XDECREF(reduce);
Py_XDECREF(reduce_ex);
#define CYTHON_USE_DICT_VERSIONS 0
#undef CYTHON_USE_EXC_INFO_STACK
#define CYTHON_USE_EXC_INFO_STACK 0
+ #ifndef CYTHON_UPDATE_DESCRIPTOR_DOC
+ #define CYTHON_UPDATE_DESCRIPTOR_DOC (PYPY_VERSION_HEX >= 0x07030900)
+ #endif
#elif defined(PYSTON_VERSION)
#define CYTHON_COMPILING_IN_PYPY 0
#define CYTHON_USE_DICT_VERSIONS 0
#undef CYTHON_USE_EXC_INFO_STACK
#define CYTHON_USE_EXC_INFO_STACK 0
+ #ifndef CYTHON_UPDATE_DESCRIPTOR_DOC
+ #define CYTHON_UPDATE_DESCRIPTOR_DOC 0
+ #endif
#else
#define CYTHON_COMPILING_IN_PYPY 0
#ifndef CYTHON_FAST_PYCALL
// Python 3.11 deleted localplus argument from frame object, which is used in our
// fast_pycall code
- #define CYTHON_FAST_PYCALL (PY_VERSION_HEX < 0x030B00A1)
+ // On Python 3.10 it causes issues when used while profiling/debugging
+ #define CYTHON_FAST_PYCALL (PY_VERSION_HEX < 0x030A0000)
#endif
#ifndef CYTHON_PEP489_MULTI_PHASE_INIT
#define CYTHON_PEP489_MULTI_PHASE_INIT (PY_VERSION_HEX >= 0x03050000)
#elif !defined(CYTHON_USE_EXC_INFO_STACK)
#define CYTHON_USE_EXC_INFO_STACK (PY_VERSION_HEX >= 0x030700A3)
#endif
+ #ifndef CYTHON_UPDATE_DESCRIPTOR_DOC
+ #define CYTHON_UPDATE_DESCRIPTOR_DOC 1
+ #endif
#endif
#if !defined(CYTHON_FAST_PYCCALL)
/////////////// MathInitCode ///////////////
-#if defined(WIN32) || defined(MS_WINDOWS)
- #define _USE_MATH_DEFINES
+#if defined(_WIN32) || defined(WIN32) || defined(MS_WINDOWS)
+ #if !defined(_USE_MATH_DEFINES)
+ #define _USE_MATH_DEFINES
+ #endif
#endif
#include <math.h>
/////////////// CheckBinaryVersion ///////////////
static int __Pyx_check_binary_version(void) {
- char ctversion[4], rtversion[4];
- PyOS_snprintf(ctversion, 4, "%d.%d", PY_MAJOR_VERSION, PY_MINOR_VERSION);
- PyOS_snprintf(rtversion, 4, "%s", Py_GetVersion());
- if (ctversion[0] != rtversion[0] || ctversion[2] != rtversion[2]) {
+ char ctversion[5];
+ int same=1, i, found_dot;
+ const char* rt_from_call = Py_GetVersion();
+ PyOS_snprintf(ctversion, 5, "%d.%d", PY_MAJOR_VERSION, PY_MINOR_VERSION);
+ // slightly convoluted, but now that we're into double digit version numbers we can no longer just rely on the length.
+ found_dot = 0;
+ for (i = 0; i < 4; i++) {
+ if (!ctversion[i]) {
+ // if they are the same, just check that the runtime version doesn't continue with further numbers
+ same = (rt_from_call[i] < '0' || rt_from_call[i] > '9');
+ break;
+ }
+ if (rt_from_call[i] != ctversion[i]) {
+ same = 0;
+ break;
+ }
+ }
+
+ if (!same) {
+ char rtversion[5] = {'\0'};
+ // copy the runtime-version for the error message
char message[200];
+ for (i=0; i<4; ++i) {
+ if (rt_from_call[i] == '.') {
+ if (found_dot) break;
+ found_dot = 1;
+ } else if (rt_from_call[i] < '0' || rt_from_call[i] > '9') {
+ break;
+ }
+ rtversion[i] = rt_from_call[i];
+ }
PyOS_snprintf(message, sizeof(message),
"compiletime version %s of module '%.100s' "
"does not match runtime version %s",
#define __Pyx_RefNannySetupContext(name, acquire_gil) \
__pyx_refnanny = __Pyx_RefNanny->SetupContext((name), __LINE__, __FILE__)
#endif
+ #define __Pyx_RefNannyFinishContextNogil() { \
+ PyGILState_STATE __pyx_gilstate_save = PyGILState_Ensure(); \
+ __Pyx_RefNannyFinishContext(); \
+ PyGILState_Release(__pyx_gilstate_save); \
+ }
#define __Pyx_RefNannyFinishContext() \
__Pyx_RefNanny->FinishContext(&__pyx_refnanny)
#define __Pyx_INCREF(r) __Pyx_RefNanny->INCREF(__pyx_refnanny, (PyObject *)(r), __LINE__)
#else
#define __Pyx_RefNannyDeclarations
#define __Pyx_RefNannySetupContext(name, acquire_gil)
+ #define __Pyx_RefNannyFinishContextNogil()
#define __Pyx_RefNannyFinishContext()
#define __Pyx_INCREF(r) Py_INCREF(r)
#define __Pyx_DECREF(r) Py_DECREF(r)
static size_t __pyx_pyframe_localsplus_offset = 0;
#include "frameobject.h"
+#if PY_VERSION_HEX >= 0x030b00a6
+ #ifndef Py_BUILD_CORE
+ #define Py_BUILD_CORE 1
+ #endif
+ #include "internal/pycore_frame.h"
+#endif
+
// This is the long runtime version of
// #define __Pyx_PyFrame_GetLocalsplus(frame) ((frame)->f_localsplus)
// offsetof(PyFrameObject, f_localsplus) differs between regular C-Python and Stackless Python.
#include "compile.h"
#include "frameobject.h"
#include "traceback.h"
+#if PY_VERSION_HEX >= 0x030b00a6
+ #ifndef Py_BUILD_CORE
+ #define Py_BUILD_CORE 1
+ #endif
+ #include "internal/pycore_frame.h"
+#endif
#if CYTHON_PROFILE_REUSE_FRAME
#define CYTHON_FRAME_MODIFIER static
return (equals == Py_EQ);
} else {
int result;
-#if CYTHON_USE_UNICODE_INTERNALS
+#if CYTHON_USE_UNICODE_INTERNALS && (PY_VERSION_HEX < 0x030B0000)
Py_hash_t hash1, hash2;
hash1 = ((PyBytesObject*)s1)->ob_shash;
hash2 = ((PyBytesObject*)s2)->ob_shash;
Metadata-Version: 2.1
Name: Cython
-Version: 0.29.28
+Version: 0.29.29
Summary: The Cython compiler for writing C extensions for the Python language.
Home-page: http://cython.org/
Author: Robert Bradshaw, Stefan Behnel, Dag Seljebotn, Greg Ewing, et al.
Author-email: cython-devel@python.org
License: Apache
-Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
pip install Cython --install-option="--no-cython-compile"
.. _Pyrex: http://www.cosc.canterbury.ac.nz/greg.ewing/python/Pyrex/
-
-
One can also annotate with ``@cython.auto_pickle(False)`` to get the old
behavior of not generating a ``__reduce__`` method in any case.
-Manually implementing a ``__reduce__`` or `__reduce_ex__`` method will also
+Manually implementing a ``__reduce__`` or ``__reduce_ex__`` method will also
disable this auto-generation and can be used to support pickling of more
complicated types.
import os.path
-with open("pkg/test.c.dep", "r") as f:
+with open(os.path.join("pkg", "test.c.dep"), "r") as f:
contents = f.read().replace("\\\n", " ").replace("\n", " ")
assert sorted(contents.split()) == sorted(['test.c:', os.path.join('sub', 'incl.pxi'), 'test.pxd', 'test.pyx']), contents
-with open("pkg/sub/test.c.dep", "r") as f:
+with open(os.path.join("pkg", "sub", "test.c.dep"), "r") as f:
contents = f.read().replace("\\\n", " ").replace("\n", " ")
contents = [os.path.relpath(entry, '.')
cimport cython
+# The "contains" tests relate to GH-4785 - replacing the method
+# call with PySequence_Contains was causing infinite recursion
+# for some classes
+
cdef class MyList(list):
+ """
+ >>> l = MyList()
+ >>> l.__contains__(1)
+ MyList.__contains__
+ False
+ >>> l.append(1)
+ >>> l.__contains__(1)
+ MyList.__contains__
+ True
+ """
def test_append(self, x):
"""
>>> l = MyList()
"""
self.append(x)
+ def __contains__(self, value):
+ print "MyList.__contains__"
+ return list.__contains__(self, value) # probably optimized
+
cdef class MyDict(dict):
+ # tests for __contains__ are in the global __doc__ to version-check a PyPy bug
+
@cython.test_assert_path_exists("//ComprehensionNode//AttributeNode",
"//ComprehensionNode//AttributeNode[@attribute='items']")
@cython.test_fail_if_path_exists("//ComprehensionNode//CMethodSelfCloneNode")
l.sort()
return l
+ def __contains__(self, key):
+ print "MyDict.__contains__"
+ return dict.__contains__(self, key)
+
+import sys
+pypy_version = getattr(sys, 'pypy_version_info', None)
+if not (pypy_version and pypy_version < (7, 3, 10)):
+ __doc__ = """
+ >>> MyDict(a=1).__contains__("a")
+ MyDict.__contains__
+ True
+ """
+
@cython.final
cdef class MyDictFinal(dict):
@cython.test_assert_path_exists("//ComprehensionNode//CMethodSelfCloneNode")
l = [ v for v in self.values() ]
l.sort()
return l
+
+class MyBytes(bytes):
+ """
+ >>> mb = MyBytes(b"abc")
+ >>> mb.__contains__(b"a")
+ MyBytes.__contains__
+ True
+ >>> mb.__contains__(b"z")
+ MyBytes.__contains__
+ False
+ """
+ def __contains__(self, value):
+ print "MyBytes.__contains__"
+ return bytes.__contains__(self, value)
--- /dev/null
+PYTHON setup.py build_ext -i
+PYTHON run.py
+
+######## setup.py ########
+
+from Cython.Build.Dependencies import cythonize
+from distutils.core import setup
+
+setup(
+ ext_modules = cythonize("*.pyx"),
+)
+
+####### call_func.pyx ##########
+
+import mod
+
+def cy_method(x):
+ return mod.function(x)
+
+####### mod.py #################
+
+mod_global = "this is a mod global"
+
+def function(a, mod_global=None):
+ if mod_global is not None:
+ mod_global = a
+
+ return
+
+####### run.py #################
+
+from call_func import cy_method
+import mod
+import sys
+
+def trace(frame, event, arg):
+ assert(mod.mod_global == "this is a mod global")
+
+ return trace
+
+sys.settrace(trace)
+
+cy_method("s")
+assert(mod.mod_global == "this is a mod global")
"""
+>>> import sys
+
>>> ONE, TEN, HUNDRED
(1, 10, 100)
>>> THOUSAND # doctest: +ELLIPSIS
>>> set(PyxEnum) == {TWO, THREE, FIVE}
True
->>> str(PyxEnum.TWO).split(".")[-1] # Py3.10 changed the output here
+>>> str(PyxEnum.TWO).split(".")[-1] if sys.version_info < (3,11) else "TWO" # Py3.10/11 changed the output here
'TWO'
+>>> str(PyxEnum.TWO) if sys.version_info >= (3,11) else "2" # Py3.10/11 changed the output here
+'2'
>>> PyxEnum.TWO + PyxEnum.THREE == PyxEnum.FIVE
True
>>> PyxEnum(2) is PyxEnum["TWO"] is PyxEnum.TWO
>>> C = TestUnboundMethodCdef
>>> IS_PY2 or (C.meth is C.__dict__["meth"])
True
+ >>> TestUnboundMethodCdef.meth() # doctest:+ELLIPSIS
+ Traceback (most recent call last):
+ TypeError: ...
"""
def meth(self): pass
>>> C = TestUnboundMethod
>>> IS_PY2 or (C.meth is C.__dict__["meth"])
True
+ >>> TestUnboundMethod.meth() # doctest:+ELLIPSIS
+ Traceback (most recent call last):
+ TypeError: ...
"""
def meth(self): pass
+# mode: run
+
class Foo:
@property
def foo(self):
>>> hasattr(Foo(), None) #doctest: +ELLIPSIS
Traceback (most recent call last):
...
- TypeError: hasattr(): attribute name must be string
+ TypeError: ...attribute name must be string...
"""
return hasattr(obj, name)
'5'
>>> modobj(1, 0) # doctest: +ELLIPSIS
Traceback (most recent call last):
- ZeroDivisionError: integer division...
+ ZeroDivisionError: integer ...
"""
obj1 = obj2 % obj3
return obj1
"""
>>> mod_10_obj(0) # doctest: +ELLIPSIS
Traceback (most recent call last):
- ZeroDivisionError: integer division...
+ ZeroDivisionError: integer ...
>>> mod_10_obj(3)
1
"""
def __repr__(self):
return "%s(ii=%s, x=%s)" % (type(self).__name__, self.ii, self.x)
+ def __eq__(self, other):
+ return (
+ isinstance(other, NoPyMembers) and
+ (<NoPyMembers> other).ii[0] == self.ii[0] and
+ (<NoPyMembers> other).ii[1] == self.ii[1] and
+ (<NoPyMembers> other).ii[2] == self.ii[2] and
+ (<NoPyMembers> other).x == self.x
+ )
+
+
class NoPyMembersPySubclass(NoPyMembers):
"""
>>> import pickle
"""
def my_method(self, x):
return x
+
+
+# Pickled with Cython 0.29.28 (using MD5 for the checksum).
+OLD_MD5_PICKLE = b'''\
+creduce_pickle\n__pyx_unpickle_NoPyMembers\nq\x00\
+(creduce_pickle\nNoPyMembers\nq\x01J\xf2K_\n(]q\x02\
+(K\x0bKyM3\x05eG?\xf8\x00\x00\x00\x00\x00\x00tq\x03tq\x04Rq\x05.\
+'''
+
+try:
+ from hashlib import md5
+except ImportError:
+ pass
+else:
+ def unpickle_old_0_29_28():
+ """
+ >>> import pickle
+ >>> b = pickle.loads(OLD_MD5_PICKLE)
+ >>> b == NoPyMembers(i=11, x=1.5) or b
+ True
+ """
def __get__(self, inst, own):
return VerySpecial.__get__(self, inst, own)
+
+
+cdef class ReverseMethodsExist:
+ """
+ reverse methods (such as __radd__) don't work in Cython <3. However, if they
+ are defined then it should be possible to look them up explicitly instead of
+ looking up autogenerated wrapper (which points to the forward method)
+
+ >>> o = ReverseMethodsExist()
+ >>> o + o
+ 'add'
+ >>> o.__add__(o)
+ 'add'
+ >>> o.__radd__(o)
+ 'radd'
+ >>> o.__rsub__(o)
+ 'rsub'
+ """
+ def __add__(self, other):
+ return "add"
+ def __radd__(self, other):
+ return "radd"
+ def __rsub__(self, other):
+ return "rsub"
self.assertEqual(('...%(foo)s...' % {'foo':Str.ABC}).replace("Str.", ""),
'...ABC...')
self.assertEqual(('...%(foo)s...' % {'foo':Int.IDES}).replace("Int.", ""),
- '...IDES...')
+ '...IDES...' if sys.version_info < (3,11) else '...15...')
self.assertEqual('...%(foo)i...' % {'foo':Int.IDES},
'...15...')
self.assertEqual('...%(foo)d...' % {'foo':Int.IDES},
--- /dev/null
+# cython: linetrace=True
+
+cdef void foo(int err) nogil except *:
+ with gil:
+ raise ValueError(err)
+
+
+# Test from gh-4637
+def handler(int err):
+ """
+ >>> handler(0)
+ All good
+ >>> handler(1) # doctest: +ELLIPSIS
+ Traceback (most recent call last):
+ ...
+ ValueError: 1
+ """
+ if (err % 2):
+ with nogil:
+ foo(err)
+ else:
+ print("All good")
else:
i = 5
return i
+
+
+def try_except_raise_new(initial, catch, throw):
+ """
+ >>> try_except_raise_new(None, TypeError, ValueError)
+ >>> try_except_raise_new(TypeError, IndexError, ValueError)
+ Traceback (most recent call last):
+ TypeError
+ >>> try_except_raise_new(TypeError, TypeError, ValueError)
+ Traceback (most recent call last):
+ ValueError
+ """
+ try:
+ if initial is not None:
+ raise initial
+ except catch:
+ raise throw
del l[0], lobj[0]
assert all(i == 3 for i in l), l
return 99
+
+def function_in_finally():
+ """
+ https://github.com/cython/cython/issues/4651 - function definitions in the
+ except copy of the finally clause weren't generated
+
+ >>> function_in_finally()
+ in try
+ in func()
+ finished
+ """
+ try:
+ print('in try')
+ finally:
+ def func():
+ print('in func()')
+ func()
+ print('finished')