Commit 3c9e7f25 authored by Stefan Behnel's avatar Stefan Behnel

cache underlying PyCFunction of calls to unbound methods of builtins in...

cache underlying PyCFunction of calls to unbound methods of builtins in utility code (should eventually become a general feature)
parent 4ae39058
...@@ -413,6 +413,35 @@ class UtilityCode(UtilityCodeBase): ...@@ -413,6 +413,35 @@ class UtilityCode(UtilityCodeBase):
assert 'PYIDENT(' not in impl assert 'PYIDENT(' not in impl
return bool(replacements), impl return bool(replacements), impl
def inject_unbound_methods(self, impl, output):
"""Replace 'UNBOUND_METHOD(type, "name")' by a constant Python identifier cname.
"""
if 'CALL_UNBOUND_METHOD(' not in impl:
return False, impl
replacements = []
def externalise(matchobj):
type_cname, method_name, args = matchobj.groups()
args = [arg.strip() for arg in args[1:].split(',')]
if len(args) == 1:
call = '__Pyx_CallUnboundCMethod0'
flag = 'METH_NOARGS'
output.use_utility_code(UtilityCode.load_cached("CallUnboundCMethod0", "ObjectHandling.c"))
elif len(args) == 2:
call = '__Pyx_CallUnboundCMethod1'
flag = 'METH_O'
output.use_utility_code(UtilityCode.load_cached("CallUnboundCMethod1", "ObjectHandling.c"))
else:
assert False, "CALL_UNBOUND_METHOD() requires 1 or 2 call arguments"
cname = output.get_cached_unbound_method(type_cname, method_name, flag)
replacements.append(cname)
return '%s(&%s, %s)' % (call, cname, ', '.join(args))
impl = re.sub(r'CALL_UNBOUND_METHOD\(([a-zA-Z_]+),\s*"([^"]+)"(,\s*[^),]+)+\)', externalise, impl)
assert 'CALL_UNBOUND_METHOD(' not in impl
return bool(replacements), impl
def wrap_c_strings(self, impl): def wrap_c_strings(self, impl):
"""Replace CSTRING('''xyz''') by a C compatible string """Replace CSTRING('''xyz''') by a C compatible string
""" """
...@@ -439,8 +468,9 @@ class UtilityCode(UtilityCodeBase): ...@@ -439,8 +468,9 @@ class UtilityCode(UtilityCodeBase):
'%s_proto' % self.name) '%s_proto' % self.name)
if self.impl: if self.impl:
impl = self.format_code(self.wrap_c_strings(self.impl)) impl = self.format_code(self.wrap_c_strings(self.impl))
is_specialised, impl = self.inject_string_constants(impl, output) is_specialised1, impl = self.inject_string_constants(impl, output)
if not is_specialised: is_specialised2, impl = self.inject_unbound_methods(impl, output)
if not (is_specialised1 or is_specialised2):
# no module specific adaptations => can be reused # no module specific adaptations => can be reused
output['utility_code_def'].put_or_include( output['utility_code_def'].put_or_include(
impl, '%s_impl' % self.name) impl, '%s_impl' % self.name)
...@@ -932,6 +962,7 @@ class GlobalState(object): ...@@ -932,6 +962,7 @@ class GlobalState(object):
'typeinfo', 'typeinfo',
'before_global_var', 'before_global_var',
'global_var', 'global_var',
'string_decls',
'decls', 'decls',
'all_the_rest', 'all_the_rest',
'pystring_table', 'pystring_table',
...@@ -965,6 +996,7 @@ class GlobalState(object): ...@@ -965,6 +996,7 @@ class GlobalState(object):
self.pyunicode_ptr_const_index = {} self.pyunicode_ptr_const_index = {}
self.num_const_index = {} self.num_const_index = {}
self.py_constants = [] self.py_constants = []
self.cached_cmethods = {}
assert writer.globalstate is None assert writer.globalstate is None
writer.globalstate = self writer.globalstate = self
...@@ -1187,6 +1219,15 @@ class GlobalState(object): ...@@ -1187,6 +1219,15 @@ class GlobalState(object):
prefix = Naming.const_prefix prefix = Naming.const_prefix
return "%s%s" % (prefix, name_suffix) return "%s%s" % (prefix, name_suffix)
def get_cached_unbound_method(self, type_cname, method_name, flag):
key = (type_cname, method_name, flag)
try:
cname = self.cached_cmethods[key]
except KeyError:
cname = self.cached_cmethods[key] = self.new_const_cname(
'umethod', '%s_%s' % (type_cname, method_name))
return cname
def add_cached_builtin_decl(self, entry): def add_cached_builtin_decl(self, entry):
if entry.is_builtin and entry.is_const: if entry.is_builtin and entry.is_const:
if self.should_declare(entry.cname, entry): if self.should_declare(entry.cname, entry):
...@@ -1218,6 +1259,7 @@ class GlobalState(object): ...@@ -1218,6 +1259,7 @@ class GlobalState(object):
w.error_goto(pos))) w.error_goto(pos)))
def generate_const_declarations(self): def generate_const_declarations(self):
self.generate_cached_methods_decls()
self.generate_string_constants() self.generate_string_constants()
self.generate_num_constants() self.generate_num_constants()
self.generate_object_constant_decls() self.generate_object_constant_decls()
...@@ -1231,13 +1273,30 @@ class GlobalState(object): ...@@ -1231,13 +1273,30 @@ class GlobalState(object):
decls_writer.putln( decls_writer.putln(
"static %s;" % c.type.declaration_code(cname)) "static %s;" % c.type.declaration_code(cname))
def generate_cached_methods_decls(self):
if not self.cached_cmethods:
return
w = self.parts['decls']
cnames = []
for (type_cname, method_name, flag), cname in sorted(self.cached_cmethods.iteritems()):
cnames.append(cname)
method_name_cname = self.get_interned_identifier(StringEncoding.EncodedString(method_name)).cname
w.putln('static __Pyx_CachedCFunction %s = {(PyObject*)&%s, &%s, 0, 0, %s};' % (
cname, type_cname, method_name_cname, flag))
if Options.generate_cleanup_code:
cleanup = self.parts['cleanup_globals']
for cname in cnames:
cleanup.putln("Py_CLEAR(%s.method);" % cname)
def generate_string_constants(self): def generate_string_constants(self):
c_consts = [ (len(c.cname), c.cname, c) c_consts = [ (len(c.cname), c.cname, c)
for c in self.string_const_index.values() ] for c in self.string_const_index.values() ]
c_consts.sort() c_consts.sort()
py_strings = [] py_strings = []
decls_writer = self.parts['decls'] decls_writer = self.parts['string_decls']
for _, cname, c in c_consts: for _, cname, c in c_consts:
conditional = False conditional = False
if c.py_versions and (2 not in c.py_versions or 3 not in c.py_versions): if c.py_versions and (2 not in c.py_versions or 3 not in c.py_versions):
......
...@@ -59,6 +59,7 @@ interned_prefixes = { ...@@ -59,6 +59,7 @@ interned_prefixes = {
'codeobj': pyrex_prefix + "codeobj_", 'codeobj': pyrex_prefix + "codeobj_",
'slice': pyrex_prefix + "slice_", 'slice': pyrex_prefix + "slice_",
'ustring': pyrex_prefix + "ustring_", 'ustring': pyrex_prefix + "ustring_",
'umethod': pyrex_prefix + "umethod_",
} }
ctuple_type_prefix = pyrex_prefix + "ctuple_" ctuple_type_prefix = pyrex_prefix + "ctuple_"
......
...@@ -293,11 +293,10 @@ static long __Pyx__PyObject_Ord(PyObject* c) { ...@@ -293,11 +293,10 @@ static long __Pyx__PyObject_Ord(PyObject* c) {
static CYTHON_INLINE PyObject* __Pyx_PyDict_Keys(PyObject* d); /*proto*/ static CYTHON_INLINE PyObject* __Pyx_PyDict_Keys(PyObject* d); /*proto*/
//////////////////// py_dict_keys //////////////////// //////////////////// py_dict_keys ////////////////////
//@requires: ObjectHandling.c::PyObjectCallMethod1
static CYTHON_INLINE PyObject* __Pyx_PyDict_Keys(PyObject* d) { static CYTHON_INLINE PyObject* __Pyx_PyDict_Keys(PyObject* d) {
if (PY_MAJOR_VERSION >= 3) if (PY_MAJOR_VERSION >= 3)
return __Pyx_PyObject_CallMethod1((PyObject*)&PyDict_Type, PYIDENT("keys"), d); return CALL_UNBOUND_METHOD(PyDict_Type, "keys", d);
else else
return PyDict_Keys(d); return PyDict_Keys(d);
} }
...@@ -307,11 +306,10 @@ static CYTHON_INLINE PyObject* __Pyx_PyDict_Keys(PyObject* d) { ...@@ -307,11 +306,10 @@ static CYTHON_INLINE PyObject* __Pyx_PyDict_Keys(PyObject* d) {
static CYTHON_INLINE PyObject* __Pyx_PyDict_Values(PyObject* d); /*proto*/ static CYTHON_INLINE PyObject* __Pyx_PyDict_Values(PyObject* d); /*proto*/
//////////////////// py_dict_values //////////////////// //////////////////// py_dict_values ////////////////////
//@requires: ObjectHandling.c::PyObjectCallMethod1
static CYTHON_INLINE PyObject* __Pyx_PyDict_Values(PyObject* d) { static CYTHON_INLINE PyObject* __Pyx_PyDict_Values(PyObject* d) {
if (PY_MAJOR_VERSION >= 3) if (PY_MAJOR_VERSION >= 3)
return __Pyx_PyObject_CallMethod1((PyObject*)&PyDict_Type, PYIDENT("values"), d); return CALL_UNBOUND_METHOD(PyDict_Type, "values", d);
else else
return PyDict_Values(d); return PyDict_Values(d);
} }
...@@ -321,11 +319,10 @@ static CYTHON_INLINE PyObject* __Pyx_PyDict_Values(PyObject* d) { ...@@ -321,11 +319,10 @@ static CYTHON_INLINE PyObject* __Pyx_PyDict_Values(PyObject* d) {
static CYTHON_INLINE PyObject* __Pyx_PyDict_Items(PyObject* d); /*proto*/ static CYTHON_INLINE PyObject* __Pyx_PyDict_Items(PyObject* d); /*proto*/
//////////////////// py_dict_items //////////////////// //////////////////// py_dict_items ////////////////////
//@requires: ObjectHandling.c::PyObjectCallMethod1
static CYTHON_INLINE PyObject* __Pyx_PyDict_Items(PyObject* d) { static CYTHON_INLINE PyObject* __Pyx_PyDict_Items(PyObject* d) {
if (PY_MAJOR_VERSION >= 3) if (PY_MAJOR_VERSION >= 3)
return __Pyx_PyObject_CallMethod1((PyObject*)&PyDict_Type, PYIDENT("items"), d); return CALL_UNBOUND_METHOD(PyDict_Type, "items", d);
else else
return PyDict_Items(d); return PyDict_Items(d);
} }
...@@ -335,10 +332,12 @@ static CYTHON_INLINE PyObject* __Pyx_PyDict_Items(PyObject* d) { ...@@ -335,10 +332,12 @@ static CYTHON_INLINE PyObject* __Pyx_PyDict_Items(PyObject* d) {
static CYTHON_INLINE PyObject* __Pyx_PyDict_IterKeys(PyObject* d); /*proto*/ static CYTHON_INLINE PyObject* __Pyx_PyDict_IterKeys(PyObject* d); /*proto*/
//////////////////// py_dict_iterkeys //////////////////// //////////////////// py_dict_iterkeys ////////////////////
//@requires: ObjectHandling.c::PyObjectCallMethod0
static CYTHON_INLINE PyObject* __Pyx_PyDict_IterKeys(PyObject* d) { static CYTHON_INLINE PyObject* __Pyx_PyDict_IterKeys(PyObject* d) {
return __Pyx_PyObject_CallMethod0(d, (PY_MAJOR_VERSION >= 3) ? PYIDENT("keys") : PYIDENT("iterkeys")); if (PY_MAJOR_VERSION >= 3)
return CALL_UNBOUND_METHOD(PyDict_Type, "keys", d);
else
return CALL_UNBOUND_METHOD(PyDict_Type, "iterkeys", d);
} }
//////////////////// py_dict_itervalues.proto //////////////////// //////////////////// py_dict_itervalues.proto ////////////////////
...@@ -346,10 +345,12 @@ static CYTHON_INLINE PyObject* __Pyx_PyDict_IterKeys(PyObject* d) { ...@@ -346,10 +345,12 @@ static CYTHON_INLINE PyObject* __Pyx_PyDict_IterKeys(PyObject* d) {
static CYTHON_INLINE PyObject* __Pyx_PyDict_IterValues(PyObject* d); /*proto*/ static CYTHON_INLINE PyObject* __Pyx_PyDict_IterValues(PyObject* d); /*proto*/
//////////////////// py_dict_itervalues //////////////////// //////////////////// py_dict_itervalues ////////////////////
//@requires: ObjectHandling.c::PyObjectCallMethod0
static CYTHON_INLINE PyObject* __Pyx_PyDict_IterValues(PyObject* d) { static CYTHON_INLINE PyObject* __Pyx_PyDict_IterValues(PyObject* d) {
return __Pyx_PyObject_CallMethod0(d, (PY_MAJOR_VERSION >= 3) ? PYIDENT("values") : PYIDENT("itervalues")); if (PY_MAJOR_VERSION >= 3)
return CALL_UNBOUND_METHOD(PyDict_Type, "values", d);
else
return CALL_UNBOUND_METHOD(PyDict_Type, "itervalues", d);
} }
//////////////////// py_dict_iteritems.proto //////////////////// //////////////////// py_dict_iteritems.proto ////////////////////
...@@ -357,10 +358,12 @@ static CYTHON_INLINE PyObject* __Pyx_PyDict_IterValues(PyObject* d) { ...@@ -357,10 +358,12 @@ static CYTHON_INLINE PyObject* __Pyx_PyDict_IterValues(PyObject* d) {
static CYTHON_INLINE PyObject* __Pyx_PyDict_IterItems(PyObject* d); /*proto*/ static CYTHON_INLINE PyObject* __Pyx_PyDict_IterItems(PyObject* d); /*proto*/
//////////////////// py_dict_iteritems //////////////////// //////////////////// py_dict_iteritems ////////////////////
//@requires: ObjectHandling.c::PyObjectCallMethod0
static CYTHON_INLINE PyObject* __Pyx_PyDict_IterItems(PyObject* d) { static CYTHON_INLINE PyObject* __Pyx_PyDict_IterItems(PyObject* d) {
return __Pyx_PyObject_CallMethod0(d, (PY_MAJOR_VERSION >= 3) ? PYIDENT("items") : PYIDENT("iteritems")); if (PY_MAJOR_VERSION >= 3)
return CALL_UNBOUND_METHOD(PyDict_Type, "items", d);
else
return CALL_UNBOUND_METHOD(PyDict_Type, "iteritems", d);
} }
//////////////////// py_dict_viewkeys.proto //////////////////// //////////////////// py_dict_viewkeys.proto ////////////////////
...@@ -371,10 +374,12 @@ static CYTHON_INLINE PyObject* __Pyx_PyDict_IterItems(PyObject* d) { ...@@ -371,10 +374,12 @@ static CYTHON_INLINE PyObject* __Pyx_PyDict_IterItems(PyObject* d) {
static CYTHON_INLINE PyObject* __Pyx_PyDict_ViewKeys(PyObject* d); /*proto*/ static CYTHON_INLINE PyObject* __Pyx_PyDict_ViewKeys(PyObject* d); /*proto*/
//////////////////// py_dict_viewkeys //////////////////// //////////////////// py_dict_viewkeys ////////////////////
//@requires: ObjectHandling.c::PyObjectCallMethod0
static CYTHON_INLINE PyObject* __Pyx_PyDict_ViewKeys(PyObject* d) { static CYTHON_INLINE PyObject* __Pyx_PyDict_ViewKeys(PyObject* d) {
return __Pyx_PyObject_CallMethod0(d, (PY_MAJOR_VERSION >= 3) ? PYIDENT("keys") : PYIDENT("viewkeys")); if (PY_MAJOR_VERSION >= 3)
return CALL_UNBOUND_METHOD(PyDict_Type, "keys", d);
else
return CALL_UNBOUND_METHOD(PyDict_Type, "viewkeys", d);
} }
//////////////////// py_dict_viewvalues.proto //////////////////// //////////////////// py_dict_viewvalues.proto ////////////////////
...@@ -385,10 +390,12 @@ static CYTHON_INLINE PyObject* __Pyx_PyDict_ViewKeys(PyObject* d) { ...@@ -385,10 +390,12 @@ static CYTHON_INLINE PyObject* __Pyx_PyDict_ViewKeys(PyObject* d) {
static CYTHON_INLINE PyObject* __Pyx_PyDict_ViewValues(PyObject* d); /*proto*/ static CYTHON_INLINE PyObject* __Pyx_PyDict_ViewValues(PyObject* d); /*proto*/
//////////////////// py_dict_viewvalues //////////////////// //////////////////// py_dict_viewvalues ////////////////////
//@requires: ObjectHandling.c::PyObjectCallMethod0
static CYTHON_INLINE PyObject* __Pyx_PyDict_ViewValues(PyObject* d) { static CYTHON_INLINE PyObject* __Pyx_PyDict_ViewValues(PyObject* d) {
return __Pyx_PyObject_CallMethod0(d, (PY_MAJOR_VERSION >= 3) ? PYIDENT("values") : PYIDENT("viewvalues")); if (PY_MAJOR_VERSION >= 3)
return CALL_UNBOUND_METHOD(PyDict_Type, "values", d);
else
return CALL_UNBOUND_METHOD(PyDict_Type, "viewvalues", d);
} }
//////////////////// py_dict_viewitems.proto //////////////////// //////////////////// py_dict_viewitems.proto ////////////////////
...@@ -399,10 +406,12 @@ static CYTHON_INLINE PyObject* __Pyx_PyDict_ViewValues(PyObject* d) { ...@@ -399,10 +406,12 @@ static CYTHON_INLINE PyObject* __Pyx_PyDict_ViewValues(PyObject* d) {
static CYTHON_INLINE PyObject* __Pyx_PyDict_ViewItems(PyObject* d); /*proto*/ static CYTHON_INLINE PyObject* __Pyx_PyDict_ViewItems(PyObject* d); /*proto*/
//////////////////// py_dict_viewitems //////////////////// //////////////////// py_dict_viewitems ////////////////////
//@requires: ObjectHandling.c::PyObjectCallMethod0
static CYTHON_INLINE PyObject* __Pyx_PyDict_ViewItems(PyObject* d) { static CYTHON_INLINE PyObject* __Pyx_PyDict_ViewItems(PyObject* d) {
return __Pyx_PyObject_CallMethod0(d, (PY_MAJOR_VERSION >= 3) ? PYIDENT("items") : PYIDENT("viewitems")); if (PY_MAJOR_VERSION >= 3)
return CALL_UNBOUND_METHOD(PyDict_Type, "items", d);
else
return CALL_UNBOUND_METHOD(PyDict_Type, "viewitems", d);
} }
//////////////////// pyfrozenset_new.proto //////////////////// //////////////////// pyfrozenset_new.proto ////////////////////
......
...@@ -1101,6 +1101,110 @@ static CYTHON_INLINE int __Pyx_PyObject_SetAttrStr(PyObject* obj, PyObject* attr ...@@ -1101,6 +1101,110 @@ static CYTHON_INLINE int __Pyx_PyObject_SetAttrStr(PyObject* obj, PyObject* attr
#endif #endif
/////////////// UnpackUnboundCMethod.proto ///////////////
typedef struct {
PyObject *type;
PyObject **method_name;
PyCFunction func; // set on first access (direct C function pointer)
PyObject *method; // set on first access (fallback)
int flag;
} __Pyx_CachedCFunction;
/////////////// UnpackUnboundCMethod ///////////////
//@requires: PyObjectGetAttrStr
static int __Pyx_TryUnpackUnboundCMethod(__Pyx_CachedCFunction* target) {
PyObject *method;
method = __Pyx_PyObject_GetAttrStr(target->type, *target->method_name);
if (unlikely(!method))
return -1;
target->method = method;
#if CYTHON_COMPILING_IN_CPYTHON
assert (target->flag == METH_NOARGS || target->flag == METH_O);
#if PY_MAJOR_VERSION >= 3
assert (PyObject_TypeCheck(method, &PyMethodDescr_Type));
#endif
{
PyMethodDescrObject *descr = (PyMethodDescrObject*) method;
if (!(descr->d_method->ml_flags & (METH_VARARGS | METH_KEYWORDS)) && (descr->d_method->ml_flags & target->flag)) {
target->func = descr->d_method->ml_meth;
}
}
#endif
return 0;
}
/////////////// CallUnboundCMethod0.proto ///////////////
static PyObject* __Pyx__CallUnboundCMethod0(__Pyx_CachedCFunction* cfunc, PyObject* self); /*proto*/
#if CYTHON_COMPILING_IN_CPYTHON
#define __Pyx_CallUnboundCMethod0(cfunc, self) \
((likely((cfunc)->func)) ? (*((cfunc)->func))(self, NULL) : __Pyx__CallUnboundCMethod0(cfunc, self))
#else
#define __Pyx_CallUnboundCMethod0(cfunc, self) __Pyx__CallUnboundCMethod0(cfunc, self)
#endif
/////////////// CallUnboundCMethod0 ///////////////
//@requires: UnpackUnboundCMethod
//@requires: PyObjectCall
static PyObject* __Pyx__CallUnboundCMethod0(__Pyx_CachedCFunction* cfunc, PyObject* self) {
PyObject *args, *result = NULL;
if (unlikely(!cfunc->method) && unlikely(__Pyx_TryUnpackUnboundCMethod(cfunc) < 0)) return NULL;
#if CYTHON_COMPILING_IN_CPYTHON
args = PyTuple_New(1);
if (unlikely(!args)) goto bad;
Py_INCREF(self);
PyTuple_SET_ITEM(args, 0, self);
#else
args = PyTuple_Pack(1, self);
if (unlikely(!args)) goto bad;
#endif
result = __Pyx_PyObject_Call(cfunc->method, args, NULL);
Py_DECREF(args);
bad:
return result;
}
/////////////// CallUnboundCMethod1.proto ///////////////
static PyObject* __Pyx__CallUnboundCMethod1(__Pyx_CachedCFunction* cfunc, PyObject* self, PyObject* arg); /*proto*/
#if CYTHON_COMPILING_IN_CPYTHON
#define __Pyx_CallUnboundCMethod1(cfunc, self, arg) \
((likely((cfunc)->func)) ? (*((cfunc)->func))(self, arg) : __Pyx__CallUnboundCMethod1(cfunc, self, arg))
#else
#define __Pyx_CallUnboundCMethod1(cfunc, self, arg) __Pyx__CallUnboundCMethod1(cfunc, self, arg)
#endif
/////////////// CallUnboundCMethod1 ///////////////
//@requires: UnpackUnboundCMethod
//@requires: PyObjectCall
static PyObject* __Pyx__CallUnboundCMethod1(__Pyx_CachedCFunction* cfunc, PyObject* self, PyObject* arg){
PyObject *args, *result = NULL;
if (unlikely(!cfunc->method) && unlikely(__Pyx_TryUnpackUnboundCMethod(cfunc) < 0)) return NULL;
#if CYTHON_COMPILING_IN_CPYTHON
args = PyTuple_New(2);
if (unlikely(!args)) goto bad;
Py_INCREF(self);
PyTuple_SET_ITEM(args, 0, self);
Py_INCREF(arg);
PyTuple_SET_ITEM(args, 1, arg);
#else
args = PyTuple_Pack(2, self, arg);
if (unlikely(!args)) goto bad;
#endif
result = __Pyx_PyObject_Call(cfunc->method, args, NULL);
bad:
Py_XDECREF(args);
return result;
}
/////////////// PyObjectCallMethod0.proto /////////////// /////////////// PyObjectCallMethod0.proto ///////////////
static PyObject* __Pyx_PyObject_CallMethod0(PyObject* obj, PyObject* method_name); /*proto*/ static PyObject* __Pyx_PyObject_CallMethod0(PyObject* obj, PyObject* method_name); /*proto*/
......
...@@ -109,7 +109,7 @@ static CYTHON_INLINE PyObject* __Pyx_PyList_Pop(PyObject* L) { ...@@ -109,7 +109,7 @@ static CYTHON_INLINE PyObject* __Pyx_PyList_Pop(PyObject* L) {
Py_SIZE(L) -= 1; Py_SIZE(L) -= 1;
return PyList_GET_ITEM(L, PyList_GET_SIZE(L)); return PyList_GET_ITEM(L, PyList_GET_SIZE(L));
} }
return __Pyx_PyObject_CallMethod0(L, PYIDENT("pop")); return CALL_UNBOUND_METHOD(PyList_Type, "pop", L);
} }
#endif #endif
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment