Commit fd565517 authored by Stefan Behnel's avatar Stefan Behnel

implement coercion between C array and list/tuple

--HG--
rename : Cython/Utility/CFuncConvert.pyx => Cython/Utility/CConvert.pyx
parent a9a4b5f5
......@@ -3895,10 +3895,17 @@ class SliceIndexNode(ExprNode):
elif base_type.is_ptr:
self.type = base_type
elif base_type.is_array:
# we need a ptr type here instead of an array type, as
# array types can result in invalid type casts in the C
# code
self.type = PyrexTypes.CPtrType(base_type.base_type)
if getting:
# we need a ptr type here instead of an array type, as
# array types can result in invalid type casts in the C
# code
self.type = PyrexTypes.CPtrType(base_type.base_type)
else:
# try to assign 'by value' (i.e. make a copy)
if not self.start and not self.stop:
self.type = base_type
else:
self.type = PyrexTypes.CPtrType(base_type.base_type)
else:
self.base = self.base.coerce_to_pyobject(env)
self.type = py_object_type
......@@ -6528,7 +6535,7 @@ class ListNode(SequenceNode):
error(self.pos, "Cannot coerce list to type '%s'" % dst_type)
elif self.mult_factor:
error(self.pos, "Cannot coerce multiplied list to '%s'" % dst_type)
elif dst_type.is_ptr and dst_type.base_type is not PyrexTypes.c_void_type:
elif (dst_type.is_array or dst_type.is_ptr) and dst_type.base_type is not PyrexTypes.c_void_type:
base_type = dst_type.base_type
self.type = PyrexTypes.CArrayType(base_type, len(self.args))
for i in range(len(self.original_args)):
......@@ -11110,6 +11117,7 @@ class CoerceToPyTypeNode(CoercionNode):
# to a Python object.
type = py_object_type
target_type = py_object_type
is_temp = 1
def __init__(self, arg, env, type=py_object_type):
......@@ -11129,22 +11137,17 @@ class CoerceToPyTypeNode(CoercionNode):
self.type = unicode_type
elif arg.type.is_complex:
self.type = Builtin.complex_type
self.target_type = self.type
elif arg.type.is_string or arg.type.is_cpp_string:
if (type not in (bytes_type, bytearray_type)
and not env.directives['c_string_encoding']):
error(arg.pos,
"default encoding required for conversion from '%s' to '%s'" %
(arg.type, type))
self.type = type
self.type = self.target_type = type
else:
# FIXME: check that the target type and the resulting type are compatible
pass
if arg.type.is_memoryviewslice:
# Register utility codes at this point
arg.type.get_to_py_function(env, arg)
self.env = env
self.target_type = type
gil_message = "Converting to Python object"
......@@ -11172,21 +11175,11 @@ class CoerceToPyTypeNode(CoercionNode):
return self
def generate_result_code(self, code):
arg_type = self.arg.type
if arg_type.is_memoryviewslice:
funccall = arg_type.get_to_py_function(self.env, self.arg)
else:
func = arg_type.to_py_function
if arg_type.is_string or arg_type.is_cpp_string:
if self.type in (bytes_type, str_type, unicode_type):
func = func.replace("Object", self.type.name.title(), 1)
elif self.type is bytearray_type:
func = func.replace("Object", "ByteArray", 1)
funccall = "%s(%s)" % (func, self.arg.result() or 'NULL')
code.putln('%s = %s; %s' % (
self.result(),
funccall,
code.putln('%s; %s' % (
self.arg.type.to_py_call_code(
self.arg.result(),
self.result(),
self.target_type),
code.error_goto_if_null(self.result(), self.pos)))
code.put_gotref(self.py_result())
......@@ -11257,15 +11250,8 @@ class CoerceFromPyTypeNode(CoercionNode):
return self.type.is_ptr and self.arg.is_ephemeral()
def generate_result_code(self, code):
function = self.type.from_py_function
operand = self.arg.py_result()
rhs = "%s(%s)" % (function, operand)
if self.type.is_enum:
rhs = typecast(self.type, c_long_type, rhs)
code.putln('%s = %s; %s' % (
self.result(),
rhs,
code.error_goto_if(self.type.error_condition(self.result()), self.pos)))
code.putln(self.type.from_py_call_code(
self.arg.py_result(), self.result(), self.pos, code))
if self.type.is_pyobject:
code.put_gotref(self.py_result())
......
......@@ -4,6 +4,7 @@
from __future__ import absolute_import
import re
import copy
from .Code import UtilityCode, LazyUtilityCode, TempitaUtilityCode
......@@ -422,6 +423,21 @@ class CTypedefType(BaseType):
# delegation
return self.typedef_base_type.create_from_py_utility_code(env)
def to_py_call_code(self, source_code, result_code, result_type, to_py_function=None):
if to_py_function is None:
to_py_function = self.to_py_function
return self.typedef_base_type.to_py_call_code(
source_code, result_code, result_type, to_py_function)
def from_py_call_code(self, source_code, result_code, error_pos, code,
from_py_function=None, error_condition=None):
if from_py_function is None:
from_py_function = self.from_py_function
if error_condition is None:
error_condition = self.error_condition(result_code)
return self.typedef_base_type.from_py_call_code(
source_code, result_code, error_pos, code, from_py_function, error_condition)
def overflow_check_binop(self, binop, env, const_rhs=False):
env.use_utility_code(UtilityCode.load("Common", "Overflow.c"))
type = self.declaration_code("")
......@@ -695,16 +711,18 @@ class MemoryViewSliceType(PyrexType):
return True
def create_to_py_utility_code(self, env):
self._dtype_to_py_func, self._dtype_from_py_func = self.dtype_object_conversion_funcs(env)
return True
def get_to_py_function(self, env, obj):
to_py_func, from_py_func = self.dtype_object_conversion_funcs(env)
to_py_func = "(PyObject *(*)(char *)) " + to_py_func
from_py_func = "(int (*)(char *, PyObject *)) " + from_py_func
def to_py_call_code(self, source_code, result_code, result_type, to_py_function=None):
assert self._dtype_to_py_func
assert self._dtype_from_py_func
to_py_func = "(PyObject *(*)(char *)) " + self._dtype_to_py_func
from_py_func = "(int (*)(char *, PyObject *)) " + self._dtype_from_py_func
tup = (obj.result(), self.ndim, to_py_func, from_py_func,
self.dtype.is_pyobject)
return "__pyx_memoryview_fromslice(%s, %s, %s, %s, %d);" % tup
tup = (result_code, source_code, self.ndim, to_py_func, from_py_func, self.dtype.is_pyobject)
return "%s = __pyx_memoryview_fromslice(%s, %s, %s, %s, %d);" % tup
def dtype_object_conversion_funcs(self, env):
get_function = "__pyx_memview_get_%s" % self.dtype_name
......@@ -1217,6 +1235,29 @@ class CType(PyrexType):
else:
return 0
def to_py_call_code(self, source_code, result_code, result_type, to_py_function=None):
func = self.to_py_function if to_py_function is None else to_py_function
assert func
if self.is_string or self.is_cpp_string:
if result_type.is_builtin_type:
result_type_name = result_type.name
if result_type_name in ('bytes', 'str', 'unicode'):
func = func.replace("Object", result_type_name.title(), 1)
elif result_type_name == 'bytearray':
func = func.replace("Object", "ByteArray", 1)
return '%s = %s(%s)' % (
result_code,
func,
source_code or 'NULL')
def from_py_call_code(self, source_code, result_code, error_pos, code,
from_py_function=None, error_condition=None):
return '%s = %s(%s); %s' % (
result_code,
from_py_function or self.from_py_function,
source_code,
code.error_goto_if(error_condition or self.error_condition(result_code), error_pos))
class CConstType(BaseType):
......@@ -2141,6 +2182,7 @@ class CArrayType(CPointerBaseType):
# size integer or None Number of elements
is_array = 1
to_tuple_function = None
def __init__(self, base_type, size):
super(CArrayType, self).__init__(base_type)
......@@ -2163,8 +2205,8 @@ class CArrayType(CPointerBaseType):
or other_type is error_type)
def assignable_from_resolved_type(self, src_type):
# Can't assign to a variable of an array type
return 0
# Can't assign to a variable of an array type, except from Python containers
return src_type.is_pyobject
def element_ptr_type(self):
return c_ptr_type(self.base_type)
......@@ -2192,7 +2234,7 @@ class CArrayType(CPointerBaseType):
if base_type == self.base_type:
return self
else:
return CArrayType(base_type)
return CArrayType(base_type, self.size)
def deduce_template_params(self, actual):
if isinstance(actual, CArrayType):
......@@ -2200,6 +2242,76 @@ class CArrayType(CPointerBaseType):
else:
return None
def create_to_py_utility_code(self, env):
if self.to_py_function is not None:
return self.to_py_function
if not self.base_type.create_to_py_utility_code(env):
return False
base_type = self.base_type.declaration_code("", pyrex=1)
safe_typename = re.sub('[^a-zA-Z0-9]', '__', base_type)
to_py_function = "__Pyx_carray_to_py_%s" % safe_typename
to_tuple_function = "__Pyx_carray_to_tuple_%s" % safe_typename
from .UtilityCode import CythonUtilityCode
context = {
'cname': to_py_function,
'to_tuple_cname': to_tuple_function,
'base_type': base_type,
'to_py_func': self.base_type.to_py_function,
}
env.use_utility_code(CythonUtilityCode.load(
"carray.to_py", "CConvert.pyx",
outer_module_scope=env.global_scope(), # need access to types declared in module
context=context, compiler_directives=dict(env.global_scope().directives)))
self.to_tuple_function = to_tuple_function
self.to_py_function = to_py_function
return True
def to_py_call_code(self, source_code, result_code, result_type, to_py_function=None):
func = self.to_py_function if to_py_function is None else to_py_function
if self.is_string or self.is_pyunicode_ptr:
return '%s = %s(%s)' % (
result_code,
func,
source_code)
target_is_tuple = result_type.is_builtin_type and result_type.name == 'tuple'
return '%s = %s(%s, %s)' % (
result_code,
self.to_tuple_function if target_is_tuple else func,
source_code,
self.size)
def create_from_py_utility_code(self, env):
if self.from_py_function is not None:
return self.from_py_function
if not self.base_type.create_from_py_utility_code(env):
return False
base_type = self.base_type.declaration_code("", pyrex=1)
safe_typename = re.sub('[^a-zA-Z0-9]', '__', base_type)
from_py_function = "__Pyx_carray_from_py_%s" % safe_typename
from .UtilityCode import CythonUtilityCode
context = {
'cname': from_py_function,
'base_type': base_type,
'from_py_func': self.base_type.from_py_function,
}
env.use_utility_code(CythonUtilityCode.load(
"carray.from_py", "CConvert.pyx",
outer_module_scope=env.global_scope(), # need access to types declared in module
context=context, compiler_directives=dict(env.global_scope().directives)))
self.from_py_function = from_py_function
return True
def from_py_call_code(self, source_code, result_code, error_pos, code,
from_py_function=None, error_condition=None):
call_code = "%s(%s, %s, %s)" % (
from_py_function or self.from_py_function,
source_code, result_code, self.size)
return code.error_goto_if_neg(call_code, error_pos)
class CPtrType(CPointerBaseType):
# base_type CType Reference type
......@@ -2275,6 +2387,7 @@ class CPtrType(CPointerBaseType):
return self.base_type.find_cpp_operation_type(operator, operand_type)
return None
class CNullPtrType(CPtrType):
is_null_ptr = 1
......@@ -2678,7 +2791,6 @@ class CFuncType(CType):
if not self.can_coerce_to_pyobject(env):
return False
from .UtilityCode import CythonUtilityCode
import re
safe_typename = re.sub('[^a-zA-Z0-9]', '__', self.declaration_code("", pyrex=1))
to_py_function = "__Pyx_CFunc_%s_to_py" % safe_typename
......@@ -2732,9 +2844,9 @@ class CFuncType(CType):
}
# FIXME: directives come from first defining environment and do not adapt for reuse
env.use_utility_code(CythonUtilityCode.load(
"cfunc.to_py", "CFuncConvert.pyx",
"cfunc.to_py", "CConvert.pyx",
outer_module_scope=env.global_scope(), # need access to types declared in module
context=context, compiler_directives=dict(env.directives)))
context=context, compiler_directives=dict(env.global_scope().directives)))
self.to_py_function = to_py_function
return True
......@@ -2884,11 +2996,12 @@ class ToPyStructUtilityCode(object):
requires = None
def __init__(self, type, forward_decl):
def __init__(self, type, forward_decl, env):
self.type = type
self.header = "static PyObject* %s(%s)" % (type.to_py_function,
type.declaration_code('s'))
self.forward_decl = forward_decl
self.env = env
def __eq__(self, other):
return isinstance(other, ToPyStructUtilityCode) and self.header == other.header
......@@ -2909,8 +3022,8 @@ class ToPyStructUtilityCode(object):
code.putln("res = PyDict_New(); if (res == NULL) return NULL;")
for member in self.type.scope.var_entries:
nameconst_cname = code.get_py_string_const(member.name, identifier=True)
code.putln("member = %s(s.%s); if (member == NULL) goto bad;" % (
member.type.to_py_function, member.cname))
code.putln("%s; if (member == NULL) goto bad;" % (
member.type.to_py_call_code('s.%s' % member.cname, 'member', member.type)))
code.putln("if (PyDict_SetItem(res, %s, member) < 0) goto bad;" % nameconst_cname)
code.putln("Py_DECREF(member);")
code.putln("return res;")
......@@ -2951,9 +3064,8 @@ class CStructOrUnionType(CType):
self.scope = scope
self.typedef_flag = typedef_flag
self.is_struct = kind == 'struct'
if self.is_struct:
self.to_py_function = "%s_to_py_%s" % (Naming.convert_func_prefix, self.cname)
self.from_py_function = "%s_from_py_%s" % (Naming.convert_func_prefix, self.cname)
self.to_py_function = "%s_to_py_%s" % (Naming.convert_func_prefix, self.cname)
self.from_py_function = "%s_from_py_%s" % (Naming.convert_func_prefix, self.cname)
self.exception_check = True
self._convert_to_py_code = None
self._convert_from_py_code = None
......@@ -2972,8 +3084,8 @@ class CStructOrUnionType(CType):
self.to_py_function = None
self._convert_to_py_code = False
return False
forward_decl = (self.entry.visibility != 'extern')
self._convert_to_py_code = ToPyStructUtilityCode(self, forward_decl)
forward_decl = self.entry.visibility != 'extern' and not self.typedef_flag
self._convert_to_py_code = ToPyStructUtilityCode(self, forward_decl, env)
env.use_utility_code(self._convert_to_py_code)
return True
......@@ -2993,12 +3105,15 @@ class CStructOrUnionType(CType):
return False
context = dict(
struct_type_decl=self.declaration_code(""),
struct_name=self.name,
var_entries=self.scope.var_entries,
funcname=self.from_py_function,
)
self._convert_from_py_code = TempitaUtilityCode.load(
"FromPyStructUtility", "TypeConversion.c", context=context)
from .UtilityCode import CythonUtilityCode
self._convert_from_py_code = CythonUtilityCode.load(
"FromPyStructUtility", "CConvert.pyx",
outer_module_scope=env.global_scope(), # need access to types declared in module
context=context)
env.use_utility_code(self._convert_from_py_code)
return True
......@@ -3165,7 +3280,8 @@ class CppClassType(CType):
'type': self.cname,
}
from .UtilityCode import CythonUtilityCode
env.use_utility_code(CythonUtilityCode.load(cls.replace('unordered_', '') + ".from_py", "CppConvert.pyx", context=context))
env.use_utility_code(CythonUtilityCode.load(
cls.replace('unordered_', '') + ".from_py", "CppConvert.pyx", context=context))
self.from_py_function = cname
return True
......@@ -3371,6 +3487,7 @@ class TemplatePlaceholderType(CType):
else:
return False
class CEnumType(CType):
# name string
# cname string or None
......@@ -3407,6 +3524,17 @@ class CEnumType(CType):
base_code = public_decl(base_code, dll_linkage)
return self.base_declaration_code(base_code, entity_code)
def from_py_call_code(self, source_code, result_code, error_pos, code,
from_py_function=None, error_condition=None):
rhs = "%s(%s)" % (
from_py_function or self.from_py_function,
source_code)
return '%s = %s;%s' % (
result_code,
typecast(self, c_long_type, rhs),
' %s' % code.error_goto_if(error_condition or self.error_condition(result_code), error_pos))
class UnspecifiedType(PyrexType):
# Used as a placeholder until the type can be determined.
......
#################### FromPyStructUtility ####################
cdef extern from *:
ctypedef struct PyTypeObject:
char* tp_name
PyTypeObject *Py_TYPE(obj)
bint PyMapping_Check(obj)
object PyErr_Format(exc, const char *format, ...)
@cname("{{funcname}}")
cdef {{struct_name}} {{funcname}}(obj) except *:
cdef {{struct_name}} result
if not PyMapping_Check(obj):
PyErr_Format(TypeError, b"Expected %.16s, got %.200s", b"a mapping", Py_TYPE(obj).tp_name)
{{for member in var_entries:}}
try:
value = obj['{{member.name}}']
except KeyError:
raise ValueError("No value specified for struct attribute '{{member.name}}'")
result.{{member.cname}}{{'[:]' if member.type.is_array else ''}} = value
{{endfor}}
return result
#################### cfunc.to_py ####################
@cname("{{cname}}")
cdef object {{cname}}({{return_type.ctype}} (*f)({{ ', '.join(arg.type_cname for arg in args) }}) {{except_clause}}):
def wrap({{ ', '.join('{arg.ctype} {arg.name}'.format(arg=arg) for arg in args) }}):
"""wrap({{', '.join(('{arg.name}: {arg.type_displayname}'.format(arg=arg) if arg.type_displayname else arg.name) for arg in args)}}){{if return_type.type_displayname}} -> {{return_type.type_displayname}}{{endif}}"""
{{'' if return_type.type.is_void else 'return '}}f({{ ', '.join(arg.name for arg in args) }})
return wrap
#################### carray.from_py ####################
cdef extern from *:
object PyErr_Format(exc, const char *format, ...)
@cname("{{cname}}")
cdef int {{cname}}(object o, {{base_type}} *v, Py_ssize_t length) except -1:
cdef Py_ssize_t i = length
try:
i = len(o)
except (TypeError, OverflowError):
pass
if i == length:
for i, item in enumerate(o):
if i >= length:
break
v[i] = item
else:
i += 1 # convert index to length
if i == length:
return 0
PyErr_Format(
IndexError,
("too many values found during array assignment, expected %zd"
if i >= length else
"not enough values found during array assignment, expected %zd, got %zd"),
length, i)
#################### carray.to_py ####################
cdef extern from *:
ctypedef struct PyObject
PyObject* {{to_py_func}}({{base_type}}) except NULL
tuple PyTuple_New(Py_ssize_t size)
void PyTuple_SET_ITEM(object p, Py_ssize_t pos, PyObject* o)
list PyList_New(Py_ssize_t size)
void PyList_SET_ITEM(object p, Py_ssize_t pos, PyObject* o)
@cname("{{cname}}")
cdef inline list {{cname}}({{base_type}} *v, Py_ssize_t length):
cdef size_t i
l = PyList_New(length)
for i in range(<size_t>length):
PyList_SET_ITEM(l, i, {{to_py_func}}(v[i]))
return l
@cname("{{to_tuple_cname}}")
cdef inline tuple {{to_tuple_cname}}({{base_type}} *v, Py_ssize_t length):
cdef size_t i
t = PyTuple_New(length)
for i in range(<size_t>length):
PyTuple_SET_ITEM(t, i, {{to_py_func}}(v[i]))
return t
#################### cfunc.to_py ####################
@cname("{{cname}}")
cdef object {{cname}}({{return_type.ctype}} (*f)({{ ', '.join(arg.type_cname for arg in args) }}) {{except_clause}}):
def wrap({{ ', '.join('{arg.ctype} {arg.name}'.format(arg=arg) for arg in args) }}):
"""wrap({{', '.join(('{arg.name}: {arg.type_displayname}'.format(arg=arg) if arg.type_displayname else arg.name) for arg in args)}}){{if return_type.type_displayname}} -> {{return_type.type_displayname}}{{endif}}"""
{{'' if return_type.type.is_void else 'return '}}f({{ ', '.join(arg.name for arg in args) }})
return wrap
......@@ -311,42 +311,6 @@ static CYTHON_INLINE PyObject * __Pyx_PyInt_FromSize_t(size_t ival) {
}
/////////////// FromPyStructUtility.proto ///////////////
{{struct_type_decl}};
static {{struct_type_decl}} {{funcname}}(PyObject *);
/////////////// FromPyStructUtility ///////////////
static {{struct_type_decl}} {{funcname}}(PyObject * o) {
{{struct_type_decl}} result;
PyObject *value = NULL;
if (!PyMapping_Check(o)) {
PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "a mapping", Py_TYPE(o)->tp_name);
goto bad;
}
{{for member in var_entries:}}
{{py:attr = "result." + member.cname}}
value = PyObject_GetItem(o, PYIDENT("{{member.name}}"));
if (!value) {
PyErr_Format(PyExc_ValueError, \
"No value specified for struct attribute '%.{{max(200, len(member.name))}}s'", "{{member.name}}");
goto bad;
}
{{attr}} = {{member.type.from_py_function}}(value);
if ({{member.type.error_condition(attr)}})
goto bad;
Py_DECREF(value);
{{endfor}}
return result;
bad:
Py_XDECREF(value);
return result;
}
/////////////// ObjectAsUCS4.proto ///////////////
static CYTHON_INLINE Py_UCS4 __Pyx_PyObject_AsPy_UCS4(PyObject*);
......
......@@ -1935,9 +1935,9 @@ def test_borrowed_slice():
5
5
"""
cdef int i, carray[10]
for i in range(10):
carray[i] = i
cdef int i
cdef int[10] carray
carray[:] = range(10)
_borrowed(carray)
_not_borrowed(carray)
_not_borrowed2(carray)
......
def from_int_array():
"""
>>> from_int_array()
[1, 2, 3]
"""
cdef int[3] v
v[0] = 1
v[1] = 2
v[2] = 3
return v
cpdef tuple tuple_from_int_array():
"""
>>> tuple_from_int_array()
(1, 2, 3)
"""
cdef int[3] v
v[0] = 1
v[1] = 2
v[2] = 3
assert isinstance(<tuple>v, tuple)
return v
cdef extern from "stdint.h":
ctypedef unsigned long uint32_t
def from_typedef_int_array():
"""
>>> from_typedef_int_array()
[1, 2, 3]
"""
cdef uint32_t[3] v
v[0] = 1
v[1] = 2
v[2] = 3
return v
cpdef tuple tuple_from_typedef_int_array():
"""
>>> tuple_from_typedef_int_array()
(1, 2, 3)
"""
cdef uint32_t[3] v
v[0] = 1
v[1] = 2
v[2] = 3
return v
ctypedef struct MyStructType:
int x
double y
cdef struct MyStruct:
int x
double y
def from_struct_array():
"""
>>> a, b = from_struct_array()
>>> a['x'], a['y']
(1, 2.0)
>>> b['x'], b['y']
(3, 4.0)
"""
cdef MyStructType[2] v
cdef MyStruct[2] w
v[0] = MyStructType(1, 2)
v[1] = MyStructType(3, 4)
assert isinstance(<tuple>v, tuple)
assert isinstance(v, list)
w[0] = MyStruct(1, 2)
w[1] = MyStruct(3, 4)
assert (<object>w) == v
assert w == (<object>v)
return v
def to_int_array(x):
"""
>>> to_int_array([1, 2, 3])
(1, 2, 3)
>>> to_int_array([1, 2])
Traceback (most recent call last):
IndexError: not enough values found during array assignment, expected 3, got 2
>>> to_int_array([1, 2, 3, 4])
Traceback (most recent call last):
IndexError: too many values found during array assignment, expected 3
"""
cdef int[3] v
v[:] = x[:3]
assert v[0] == x[0]
assert v[1] == x[1]
assert v[2] == x[2]
v[:3] = [0, 0, 0]
assert v[0] == 0
assert v[1] == 0
assert v[2] == 0
v[:] = x
return v[0], v[1], v[2]
def iterable_to_int_array(x):
"""
>>> iterable_to_int_array(iter([1, 2, 3]))
(1, 2, 3)
>>> iterable_to_int_array(iter([1, 2]))
Traceback (most recent call last):
IndexError: not enough values found during array assignment, expected 3, got 2
>>> iterable_to_int_array(iter([1, 2, 3, 4]))
Traceback (most recent call last):
IndexError: too many values found during array assignment, expected 3
"""
cdef int[3] v
v[:] = x
return v[0], v[1], v[2]
def to_struct_array(x):
"""
>>> a, b = to_struct_array(({'x': 1, 'y': 2}, {'x': 3, 'y': 4}))
>>> a['x'], a['y']
(1, 2.0)
>>> b['x'], b['y']
(3, 4.0)
"""
cdef MyStructType[2] v
v[:] = x
cdef MyStruct[2] w
w[:] = x
assert w[0].x == v[0].x
assert w[0].y == v[0].y
assert w[1].x == v[1].x
assert w[1].y == v[1].y
return v[0], w[1]
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