Commit 22a49f40 authored by scoder's avatar scoder Committed by GitHub

Rewrite the C property feature (GH-3571)

* Rewrite C property support (GH-2640) based on inline C methods.
Supersedes GH-2640 and GH-3095.
Closes GH-3521.

* Test fix for `numpy_parallel.pyx`: avoid depending on whether "nd.shape" requires the GIL or not.

* Turn NumPy's "ndarray.data" into a property to avoid direct struct access.

* Make "ndarray.size" accessible without the GIL.
parent 3bd6ccb0
......@@ -4024,12 +4024,9 @@ class IndexNode(_IndexingBaseNode):
else:
assert False, "unexpected base type in indexing: %s" % self.base.type
elif self.base.type.is_cfunction:
if self.base.entry.is_cgetter:
index_code = "(%s[%s])"
else:
return "%s<%s>" % (
self.base.result(),
",".join([param.empty_declaration_code() for param in self.type_indices]))
return "%s<%s>" % (
self.base.result(),
",".join([param.empty_declaration_code() for param in self.type_indices]))
elif self.base.type.is_ctuple:
index = self.index.constant_result
if index < 0:
......@@ -5594,6 +5591,16 @@ class SimpleCallNode(CallNode):
except Exception as e:
self.compile_time_value_error(e)
@classmethod
def for_cproperty(cls, pos, obj, entry):
# Create a call node for C property access.
property_scope = entry.scope
getter_entry = property_scope.lookup_here(entry.name)
assert getter_entry, "Getter not found in scope %s: %s" % (property_scope, property_scope.entries)
function = NameNode(pos, name=entry.name, entry=getter_entry, type=getter_entry.type)
node = cls(pos, function=function, args=[obj])
return node
def analyse_as_type(self, env):
attr = self.function.as_cython_attribute()
if attr == 'pointer':
......@@ -6789,7 +6796,7 @@ class AttributeNode(ExprNode):
is_attribute = 1
subexprs = ['obj']
_type = PyrexTypes.error_type
type = PyrexTypes.error_type
entry = None
is_called = 0
needs_none_check = True
......@@ -6797,20 +6804,6 @@ class AttributeNode(ExprNode):
is_special_lookup = False
is_py_attr = 0
@property
def type(self):
if self._type.is_cfunction and hasattr(self._type, 'entry') and self._type.entry.is_cgetter:
return self._type.return_type
return self._type
@type.setter
def type(self, value):
# XXX review where the attribute is set
# make sure it is not already a cgetter
if self._type.is_cfunction and hasattr(self._type, 'entry') and self._type.entry.is_cgetter:
error(self.pos, "%s.type already set" % self.__name__)
self._type = value
def as_cython_attribute(self):
if (isinstance(self.obj, NameNode) and
self.obj.is_cython_module and not
......@@ -6896,7 +6889,7 @@ class AttributeNode(ExprNode):
if node is None:
node = self.analyse_as_ordinary_attribute_node(env, target)
assert node is not None
if node.entry:
if (node.is_attribute or node.is_name) and node.entry:
node.entry.used = True
if node.is_attribute:
node.wrap_obj_in_nonecheck(env)
......@@ -7029,6 +7022,11 @@ class AttributeNode(ExprNode):
self.result_ctype = py_object_type
elif target and self.obj.type.is_builtin_type:
error(self.pos, "Assignment to an immutable object field")
elif self.entry and self.entry.is_cproperty:
if not target:
return SimpleCallNode.for_cproperty(self.pos, self.obj, self.entry).analyse_types(env)
# TODO: implement writable C-properties?
error(self.pos, "Assignment to a read-only property")
#elif self.type.is_memoryviewslice and not target:
# self.is_temp = True
return self
......@@ -7085,8 +7083,11 @@ class AttributeNode(ExprNode):
# fused function go through assignment synthesis
# (foo = pycfunction(foo_func_obj)) and need to go through
# regular Python lookup as well
if (entry.is_variable and not entry.fused_cfunction) or entry.is_cmethod:
self._type = entry.type
if entry.is_cproperty:
self.type = entry.type
return
elif (entry.is_variable and not entry.fused_cfunction) or entry.is_cmethod:
self.type = entry.type
self.member = entry.cname
return
else:
......@@ -7106,7 +7107,7 @@ class AttributeNode(ExprNode):
# mangle private '__*' Python attributes used inside of a class
self.attribute = env.mangle_class_private_name(self.attribute)
self.member = self.attribute
self._type = py_object_type
self.type = py_object_type
self.is_py_attr = 1
if not obj_type.is_pyobject and not obj_type.is_error:
......@@ -7188,8 +7189,6 @@ class AttributeNode(ExprNode):
obj_code = obj.result_as(obj.type)
#print "...obj_code =", obj_code ###
if self.entry and self.entry.is_cmethod:
if self.entry.is_cgetter:
return "%s(%s)" % (self.entry.func_cname, obj_code)
if obj.type.is_extension_type and not self.entry.is_builtin_cmethod:
if self.entry.final_func_cname:
return self.entry.final_func_cname
......
......@@ -21,7 +21,7 @@ from . import Naming
from . import PyrexTypes
from . import TypeSlots
from .PyrexTypes import py_object_type, error_type
from .Symtab import (ModuleScope, LocalScope, ClosureScope,
from .Symtab import (ModuleScope, LocalScope, ClosureScope, PropertyScope,
StructOrUnionScope, PyClassScope, CppClassScope, TemplateScope,
punycodify_name)
from .Code import UtilityCode
......@@ -154,6 +154,7 @@ class Node(object):
is_literal = 0
is_terminator = 0
is_wrapper = False # is a DefNode wrapper for a C function
is_cproperty = False
temps = None
# All descendants should set child_attrs to a list of the attributes
......@@ -453,8 +454,13 @@ class CDefExternNode(StatNode):
env.add_include_file(self.include_file, self.verbatim_include, late)
def analyse_expressions(self, env):
# Allow C properties, inline methods, etc. also in external types.
self.body = self.body.analyse_expressions(env)
return self
def generate_function_definitions(self, env, code):
self.body.generate_function_definitions(env, code)
def generate_execution_code(self, code):
pass
......@@ -479,6 +485,9 @@ class CDeclaratorNode(Node):
calling_convention = ""
def declared_name(self):
return None
def analyse_templates(self):
# Only C++ functions have templates.
return None
......@@ -493,6 +502,9 @@ class CNameDeclaratorNode(CDeclaratorNode):
default = None
def declared_name(self):
return self.name
def analyse(self, base_type, env, nonempty=0, visibility=None, in_pxd=False):
if nonempty and self.name == '':
# May have mistaken the name for the type.
......@@ -516,6 +528,9 @@ class CPtrDeclaratorNode(CDeclaratorNode):
child_attrs = ["base"]
def declared_name(self):
return self.base.declared_name()
def analyse_templates(self):
return self.base.analyse_templates()
......@@ -531,6 +546,9 @@ class CReferenceDeclaratorNode(CDeclaratorNode):
child_attrs = ["base"]
def declared_name(self):
return self.base.declared_name()
def analyse_templates(self):
return self.base.analyse_templates()
......@@ -603,6 +621,9 @@ class CFuncDeclaratorNode(CDeclaratorNode):
is_const_method = 0
templates = None
def declared_name(self):
return self.base.declared_name()
def analyse_templates(self):
if isinstance(self.base, CArrayDeclaratorNode):
from .ExprNodes import TupleNode, NameNode
......@@ -836,6 +857,9 @@ class CArgDeclNode(Node):
annotation = None
is_dynamic = 0
def declared_name(self):
return self.declarator.declared_name()
@property
def name_cstring(self):
return self.name.as_c_string_literal()
......@@ -1728,10 +1752,6 @@ class FuncDefNode(StatNode, BlockNode):
def generate_function_definitions(self, env, code):
from . import Buffer
if self.entry.is_cgetter:
# no code to generate
return
lenv = self.local_scope
if lenv.is_closure_scope and not lenv.is_passthrough:
outer_scope_cname = "%s->%s" % (Naming.cur_scope_cname,
......@@ -2344,7 +2364,7 @@ class CFuncDefNode(FuncDefNode):
# is_static_method whether this is a static method
# is_c_class_method whether this is a cclass method
child_attrs = ["base_type", "declarator", "body", "py_func_stat", "decorators"]
child_attrs = ["base_type", "declarator", "body", "decorators", "py_func_stat"]
outer_attrs = ["decorators", "py_func_stat"]
inline_in_pxd = False
......@@ -2359,27 +2379,15 @@ class CFuncDefNode(FuncDefNode):
def unqualified_name(self):
return self.entry.name
def declared_name(self):
return self.declarator.declared_name()
@property
def code_object(self):
# share the CodeObject with the cpdef wrapper (if available)
return self.py_func.code_object if self.py_func else None
def analyse_declarations(self, env):
is_property = 0
if self.decorators:
for decorator in self.decorators:
func = decorator.decorator
if func.is_name:
if func.name == 'property':
is_property = 1
elif func.name == 'staticmethod':
pass
else:
error(self.pos, "Cannot handle %s decorators yet" % func.name)
else:
error(self.pos,
"Cannot handle %s decorators yet" % type(func).__name__)
self.is_c_class_method = env.is_c_class_scope
if self.directive_locals is None:
self.directive_locals = {}
......@@ -2458,10 +2466,6 @@ class CFuncDefNode(FuncDefNode):
cname=cname, visibility=self.visibility, api=self.api,
defining=self.body is not None, modifiers=self.modifiers,
overridable=self.overridable)
if is_property:
self.entry.is_property = 1
env.property_entries.append(self.entry)
env.cfunc_entries.remove(self.entry)
self.entry.inline_func_in_pxd = self.inline_in_pxd
self.return_type = typ.return_type
if self.return_type.is_array and self.visibility != 'extern':
......@@ -2628,7 +2632,7 @@ class CFuncDefNode(FuncDefNode):
header = self.return_type.declaration_code(entity, dll_linkage=dll_linkage)
#print (storage_class, modifiers, header)
needs_proto = self.is_c_class_method
needs_proto = self.is_c_class_method or self.entry.is_cproperty
if self.template_declaration:
if needs_proto:
code.globalstate.parts['module_declarations'].putln(self.template_declaration)
......@@ -5332,14 +5336,13 @@ class PropertyNode(StatNode):
#
# name string
# doc EncodedString or None Doc string
# entry Symtab.Entry
# entry Symtab.Entry The Entry of the property attribute
# body StatListNode
child_attrs = ["body"]
def analyse_declarations(self, env):
self.entry = env.declare_property(self.name, self.doc, self.pos)
self.entry.scope.directives = env.directives
self.body.analyse_declarations(self.entry.scope)
def analyse_expressions(self, env):
......@@ -5356,6 +5359,44 @@ class PropertyNode(StatNode):
self.body.annotate(code)
class CPropertyNode(StatNode):
"""Definition of a C property, backed by a CFuncDefNode getter.
"""
# name string
# doc EncodedString or None Doc string of the property
# entry Symtab.Entry The Entry of the property attribute
# body StatListNode[CFuncDefNode] (for compatibility with PropertyNode)
child_attrs = ["body"]
is_cproperty = True
@property
def cfunc(self):
stats = self.body.stats
assert stats and isinstance(stats[0], CFuncDefNode), stats
return stats[0]
def analyse_declarations(self, env):
scope = PropertyScope(self.name, class_scope=env)
self.body.analyse_declarations(scope)
entry = self.entry = env.declare_property(
self.name, self.doc, self.pos, ctype=self.cfunc.return_type, property_scope=scope)
entry.getter_cname = self.cfunc.entry.cname
def analyse_expressions(self, env):
self.body = self.body.analyse_expressions(env)
return self
def generate_function_definitions(self, env, code):
self.body.generate_function_definitions(env, code)
def generate_execution_code(self, code):
pass
def annotate(self, code):
self.body.annotate(code)
class GlobalNode(StatNode):
# Global variable declaration.
#
......
......@@ -593,14 +593,12 @@ class PxdPostParse(CythonTransform, SkipDeclarations):
err = self.ERR_INLINE_ONLY
if (isinstance(node, Nodes.DefNode) and self.scope_type == 'cclass'
and node.name in ('__getbuffer__', '__releasebuffer__')):
and node.name in ('__getbuffer__', '__releasebuffer__')):
err = None # allow these slots
if isinstance(node, Nodes.CFuncDefNode):
if node.decorators and self.scope_type == 'cclass':
err = None
elif (u'inline' in node.modifiers and
self.scope_type in ('pxd', 'cclass')):
if (u'inline' in node.modifiers and
self.scope_type in ('pxd', 'cclass')):
node.inline_in_pxd = True
if node.visibility != 'private':
err = self.ERR_NOGO_WITH_INLINE % node.visibility
......@@ -1365,7 +1363,7 @@ class DecoratorTransform(ScopeTrackingTransform, SkipDeclarations):
if self._properties is None:
self._properties = []
self._properties.append({})
super(DecoratorTransform, self).visit_CClassDefNode(node)
node = super(DecoratorTransform, self).visit_CClassDefNode(node)
self._properties.pop()
return node
......@@ -1375,6 +1373,25 @@ class DecoratorTransform(ScopeTrackingTransform, SkipDeclarations):
warning(node.pos, "'property %s:' syntax is deprecated, use '@property'" % node.name, level)
return node
def visit_CFuncDefNode(self, node):
node = self.visit_FuncDefNode(node)
if self.scope_type != 'cclass' or self.scope_node.visibility != "extern" or not node.decorators:
return node
ret_node = node
decorator_node = self._find_property_decorator(node)
if decorator_node:
if decorator_node.decorator.is_name:
name = node.declared_name()
if name:
ret_node = self._add_property(node, name, decorator_node)
else:
error(decorator_node.pos, "C property decorator can only be @property")
if node.decorators:
return self._reject_decorated_property(node, node.decorators[0])
return ret_node
def visit_DefNode(self, node):
scope_type = self.scope_type
node = self.visit_FuncDefNode(node)
......@@ -1382,28 +1399,12 @@ class DecoratorTransform(ScopeTrackingTransform, SkipDeclarations):
return node
# transform @property decorators
properties = self._properties[-1]
for decorator_node in node.decorators[::-1]:
decorator_node = self._find_property_decorator(node)
if decorator_node is not None:
decorator = decorator_node.decorator
if decorator.is_name and decorator.name == 'property':
if len(node.decorators) > 1:
return self._reject_decorated_property(node, decorator_node)
name = node.name
node.name = EncodedString('__get__')
node.decorators.remove(decorator_node)
stat_list = [node]
if name in properties:
prop = properties[name]
prop.pos = node.pos
prop.doc = node.doc
prop.body.stats = stat_list
return []
prop = Nodes.PropertyNode(node.pos, name=name)
prop.doc = node.doc
prop.body = Nodes.StatListNode(node.pos, stats=stat_list)
properties[name] = prop
return [prop]
elif decorator.is_attribute and decorator.obj.name in properties:
if decorator.is_name:
return self._add_property(node, node.name, decorator_node)
else:
handler_name = self._map_property_attribute(decorator.attribute)
if handler_name:
if decorator.obj.name != node.name:
......@@ -1414,7 +1415,7 @@ class DecoratorTransform(ScopeTrackingTransform, SkipDeclarations):
elif len(node.decorators) > 1:
return self._reject_decorated_property(node, decorator_node)
else:
return self._add_to_property(properties, node, handler_name, decorator_node)
return self._add_to_property(node, handler_name, decorator_node)
# we clear node.decorators, so we need to set the
# is_staticmethod/is_classmethod attributes now
......@@ -1429,6 +1430,18 @@ class DecoratorTransform(ScopeTrackingTransform, SkipDeclarations):
node.decorators = None
return self.chain_decorators(node, decs, node.name)
def _find_property_decorator(self, node):
properties = self._properties[-1]
for decorator_node in node.decorators[::-1]:
decorator = decorator_node.decorator
if decorator.is_name and decorator.name == 'property':
# @property
return decorator_node
elif decorator.is_attribute and decorator.obj.name in properties:
# @prop.setter etc.
return decorator_node
return None
@staticmethod
def _reject_decorated_property(node, decorator_node):
# restrict transformation to outermost decorator as wrapped properties will probably not work
......@@ -1437,9 +1450,42 @@ class DecoratorTransform(ScopeTrackingTransform, SkipDeclarations):
error(deco.pos, "Property methods with additional decorators are not supported")
return node
@staticmethod
def _add_to_property(properties, node, name, decorator):
def _add_property(self, node, name, decorator_node):
if len(node.decorators) > 1:
return self._reject_decorated_property(node, decorator_node)
node.decorators.remove(decorator_node)
properties = self._properties[-1]
is_cproperty = isinstance(node, Nodes.CFuncDefNode)
body = Nodes.StatListNode(node.pos, stats=[node])
if is_cproperty:
if name in properties:
error(node.pos, "C property redeclared")
if 'inline' not in node.modifiers:
error(node.pos, "C property method must be declared 'inline'")
prop = Nodes.CPropertyNode(node.pos, doc=node.doc, name=name, body=body)
elif name in properties:
prop = properties[name]
if prop.is_cproperty:
error(node.pos, "C property redeclared")
else:
node.name = EncodedString("__get__")
prop.pos = node.pos
prop.doc = node.doc
prop.body.stats = [node]
return None
else:
node.name = EncodedString("__get__")
prop = Nodes.PropertyNode(
node.pos, name=name, doc=node.doc, body=body)
properties[name] = prop
return prop
def _add_to_property(self, node, name, decorator):
properties = self._properties[-1]
prop = properties[node.name]
if prop.is_cproperty:
error(node.pos, "C property redeclared")
return None
node.name = name
node.decorators.remove(decorator)
stats = prop.body.stats
......@@ -1449,7 +1495,7 @@ class DecoratorTransform(ScopeTrackingTransform, SkipDeclarations):
break
else:
stats.append(node)
return []
return None
@staticmethod
def chain_decorators(node, decorators, name):
......@@ -2274,29 +2320,6 @@ class AnalyseExpressionsTransform(CythonTransform):
node = node.base
return node
class ReplacePropertyNode(CythonTransform):
def visit_CFuncDefNode(self, node):
if not node.decorators:
return node
decorator = self.find_first_decorator(node, 'property')
if decorator:
# transform class functions into c-getters
if len(node.decorators) > 1:
# raises
self._reject_decorated_property(node, decorator_node)
node.entry.is_cgetter = True
# Add a func_cname to be output instead of the attribute
node.entry.func_cname = node.body.stats[0].value.function.name
node.decorators.remove(decorator)
return node
def find_first_decorator(self, node, name):
for decorator_node in node.decorators[::-1]:
decorator = decorator_node.decorator
if decorator.is_name and decorator.name == name:
return decorator_node
return None
class FindInvalidUseOfFusedTypes(CythonTransform):
......
......@@ -146,7 +146,7 @@ def create_pipeline(context, mode, exclude_classes=()):
from .ParseTreeTransforms import CreateClosureClasses, MarkClosureVisitor, DecoratorTransform
from .ParseTreeTransforms import TrackNumpyAttributes, InterpretCompilerDirectives, TransformBuiltinMethods
from .ParseTreeTransforms import ExpandInplaceOperators, ParallelRangeTransform
from .ParseTreeTransforms import CalculateQualifiedNamesTransform, ReplacePropertyNode
from .ParseTreeTransforms import CalculateQualifiedNamesTransform
from .TypeInference import MarkParallelAssignments, MarkOverflowingArithmetic
from .ParseTreeTransforms import AdjustDefByDirectives, AlignFunctionDefinitions
from .ParseTreeTransforms import RemoveUnreachableCode, GilCheck
......@@ -198,7 +198,6 @@ def create_pipeline(context, mode, exclude_classes=()):
AnalyseDeclarationsTransform(context),
AutoTestDictTransform(context),
EmbedSignature(context),
ReplacePropertyNode(context),
EarlyReplaceBuiltinCalls(context), ## Necessary?
TransformBuiltinMethods(context),
MarkParallelAssignments(context),
......
......@@ -1691,15 +1691,21 @@ class PythranExpr(CType):
self.scope = scope = Symtab.CClassScope('', None, visibility="extern")
scope.parent_type = self
scope.directives = {}
scope.declare_cgetter(
"shape",
CPtrType(c_long_type),
pos=None,
cname="__Pyx_PythranShapeAccessor",
visibility="extern",
nogil=True)
scope.declare_var("ndim", c_long_type, pos=None, cname="value", is_cdef=True)
shape_type = c_ptr_type(c_long_type)
shape_entry = scope.declare_property(
"shape", doc="Pythran array shape", ctype=shape_type, pos=None)
shape_entry.scope.declare_cfunction(
name="shape",
type=CFuncType(shape_type, [CFuncTypeArg("self", self, pos=None)], nogil=True),
cname="__Pyx_PythranShapeAccessor",
visibility='extern',
pos=None,
)
return True
def __eq__(self, other):
......
......@@ -109,6 +109,7 @@ class Entry(object):
# doc_cname string or None C const holding the docstring
# getter_cname string C func for getting property
# setter_cname string C func for setting or deleting property
# is_cproperty boolean Is an inline property of an external type
# is_self_arg boolean Is the "self" arg of an exttype method
# is_arg boolean Is the arg of a method
# is_local boolean Is a local variable
......@@ -183,6 +184,7 @@ class Entry(object):
is_cpp_class = 0
is_const = 0
is_property = 0
is_cproperty = 0
doc_cname = None
getter_cname = None
setter_cname = None
......@@ -2395,15 +2397,18 @@ class CClassScope(ClassScope):
entry.as_variable = var_entry
return entry
def declare_property(self, name, doc, pos):
def declare_property(self, name, doc, pos, ctype=None, property_scope=None):
entry = self.lookup_here(name)
if entry is None:
entry = self.declare(name, name, py_object_type, pos, 'private')
entry.is_property = 1
entry = self.declare(name, name, py_object_type if ctype is None else ctype, pos, 'private')
entry.is_property = True
if ctype is not None:
entry.is_cproperty = True
entry.doc = doc
entry.scope = PropertyScope(name,
outer_scope = self.global_scope(), parent_scope = self)
entry.scope.parent_type = self.parent_type
if property_scope is None:
entry.scope = PropertyScope(name, class_scope=self)
else:
entry.scope = property_scope
self.property_entries.append(entry)
return entry
......@@ -2607,6 +2612,31 @@ class PropertyScope(Scope):
is_property_scope = 1
def __init__(self, name, class_scope):
# outer scope is None for some internal properties
outer_scope = class_scope.global_scope() if class_scope.outer_scope else None
Scope.__init__(self, name, outer_scope, parent_scope=class_scope)
self.parent_type = class_scope.parent_type
self.directives = class_scope.directives
def declare_cfunction(self, name, type, pos, *args, **kwargs):
"""Declare a C property function.
"""
if type.return_type.is_void:
error(pos, "C property method cannot return 'void'")
if type.args and type.args[0].type is py_object_type:
# Set 'self' argument type to extension type.
type.args[0].type = self.parent_scope.parent_type
elif len(type.args) != 1:
error(pos, "C property method must have a single (self) argument")
elif not (type.args[0].type.is_pyobject or type.args[0].type is self.parent_scope.parent_type):
error(pos, "C property method must have a single (object) argument")
entry = Scope.declare_cfunction(self, name, type, pos, *args, **kwargs)
entry.is_cproperty = True
return entry
def declare_pyfunction(self, name, pos, allow_redefine=False):
# Add an entry for a method.
signature = get_property_accessor_signature(name)
......
......@@ -244,25 +244,33 @@ cdef extern from "numpy/arrayobject.h":
cdef:
# Only taking a few of the most commonly used and stable fields.
char *data
dtype descr # deprecated since NumPy 1.7 !
PyObject* base
dtype descr # deprecated since NumPy 1.7 !
PyObject* base # NOT PUBLIC, DO NOT USE !
@property
cdef int ndim(self):
cdef inline int ndim(self):
return PyArray_NDIM(self)
@property
cdef npy_intp *shape(self):
cdef inline npy_intp *shape(self):
return PyArray_DIMS(self)
@property
cdef npy_intp *strides(self):
cdef inline npy_intp *strides(self):
return PyArray_STRIDES(self)
@property
cdef npy_intp size(self):
return PyArray_SIZE(ndarray)
cdef inline npy_intp size(self) nogil:
return PyArray_SIZE(self)
@property
cdef inline char* data(self) nogil:
"""The pointer to the data buffer as a char*.
This is provided for legacy reasons to avoid direct struct field access.
For new code that needs this access, you probably want to cast the result
of `PyArray_DATA()` instead.
"""
return PyArray_BYTES(self)
# Note: This syntax (function definition in pxd files) is an
......@@ -449,8 +457,9 @@ cdef extern from "numpy/arrayobject.h":
bint PyArray_ISFORTRAN(ndarray)
int PyArray_FORTRANIF(ndarray)
void* PyArray_DATA(ndarray)
char* PyArray_BYTES(ndarray)
void* PyArray_DATA(ndarray) nogil
char* PyArray_BYTES(ndarray) nogil
npy_intp* PyArray_DIMS(ndarray)
npy_intp* PyArray_STRIDES(ndarray)
npy_intp PyArray_DIM(ndarray, size_t)
......@@ -551,7 +560,7 @@ cdef extern from "numpy/arrayobject.h":
bint PyArray_CheckAnyScalar(object)
ndarray PyArray_GETCONTIGUOUS(ndarray)
bint PyArray_SAMESHAPE(ndarray, ndarray)
npy_intp PyArray_SIZE(ndarray)
npy_intp PyArray_SIZE(ndarray) nogil
npy_intp PyArray_NBYTES(ndarray)
object PyArray_FROM_O(object)
......
# mode: run
# tag: cgetter, property
"""
PYTHON setup.py build_ext --inplace
PYTHON -c "import runner"
PYTHON run_failure_tests.py
PYTHON runner.py
"""
######## setup.py ########
from Cython.Build.Dependencies import cythonize
from Cython.Compiler.Errors import CompileError
from distutils.core import setup
# force the build order
# Enforce the right build order
setup(ext_modules = cythonize("foo_extension.pyx", language_level=3))
setup(ext_modules = cythonize("getter[0-9].pyx", language_level=3))
for name in ("getter_fail0.pyx", "getter_fail1.pyx"):
######## run_failure_tests.py ########
import glob
import sys
from Cython.Build.Dependencies import cythonize
from Cython.Compiler.Errors import CompileError
# Run the failure tests
failed_tests = []
passed_tests = []
def run_test(name):
title = name
with open(name, 'r') as f:
for line in f:
if 'TEST' in line:
title = line.partition('TEST:')[2].strip()
break
sys.stderr.write("\n### TESTING: %s\n" % title)
try:
cythonize(name, language_level=3)
assert False
except CompileError as e:
print("\nGot expected exception, continuing\n")
sys.stderr.write("\nOK: got expected exception\n")
passed_tests.append(name)
else:
sys.stderr.write("\nFAIL: compilation did not detect the error\n")
failed_tests.append(name)
for name in sorted(glob.glob("getter_fail*.pyx")):
run_test(name)
assert not failed_tests, "Failed tests: %s" % failed_tests
assert passed_tests # check that tests were found at all
######## foo.h ########
......@@ -39,9 +75,9 @@ typedef struct {
} FooStructOpaque;
#define PyFoo_GET0M(a) ((FooStructNominal*)a)->f0
#define PyFoo_GET1M(a) ((FooStructNominal*)a)->f1
#define PyFoo_GET2M(a) ((FooStructNominal*)a)->f2
#define PyFoo_GET0M(a) (((FooStructNominal*)a)->f0)
#define PyFoo_GET1M(a) (((FooStructNominal*)a)->f1)
#define PyFoo_GET2M(a) (((FooStructNominal*)a)->f2)
int PyFoo_Get0F(FooStructOpaque *f)
{
......@@ -67,6 +103,7 @@ int *PyFoo_GetV(FooStructOpaque *f)
}
#endif
######## foo_extension.pyx ########
cdef class Foo:
......@@ -117,6 +154,7 @@ class OpaqueFoo(Foo):
def field2(self):
raise AttributeError('no direct access to field2')
######## getter0.pyx ########
# Access base Foo fields from C via aliased field names
......@@ -138,6 +176,7 @@ def check_pyobj(Foo f):
# compare the c code to the check_pyobj in getter2.pyx
return bool(f.field1)
######## getter.pxd ########
# Access base Foo fields from C via getter functions
......@@ -146,26 +185,33 @@ def check_pyobj(Foo f):
cdef extern from "foo.h":
ctypedef class foo_extension.Foo [object FooStructOpaque, check_size ignore]:
@property
cdef int fieldM0(self):
cdef inline int fieldM0(self):
return PyFoo_GET0M(self)
@property
cdef int fieldF1(self):
cdef inline int fieldF1(self) except -123:
return PyFoo_Get1F(self)
@property
cdef int fieldM2(self):
cdef inline int fieldM2(self):
return PyFoo_GET2M(self)
@property
cdef int *vector(self):
cdef inline int *vector(self):
return PyFoo_GetV(self)
@property
cdef inline int meaning_of_life(self) except -99:
cdef int ret = 21
ret *= 2
return ret
int PyFoo_GET0M(Foo); # this is actually a macro !
int PyFoo_Get1F(Foo);
int PyFoo_GET2M(Foo); # this is actually a macro !
int *PyFoo_GetV(Foo);
######## getter1.pyx ########
cimport getter
......@@ -184,6 +230,7 @@ def vec0(getter.Foo f):
def check_binop(getter.Foo f):
return f.fieldF1 / 10
######## getter2.pyx ########
cimport getter
......@@ -194,27 +241,81 @@ def check_pyobj(getter.Foo f):
def check_unary(getter.Foo f):
return -f.fieldF1
######## getter_fail0.pyx ########
def check_meaning_of_life(getter.Foo f):
return f.meaning_of_life
# Make sure not all decorators are accepted
######## getter_fail_classmethod.pyx ########
# TEST: Make sure not all decorators are accepted.
cdef extern from "foo.h":
ctypedef class foo_extension.Foo [object FooStructOpaque]:
@property
@classmethod
cdef void field0():
print('in staticmethod of Foo')
cdef inline int field0(cls):
print('in classmethod of Foo')
######## getter_fail1.pyx ########
# Make sure not all decorators are accepted
cimport cython
######## getter_fail_dot_getter.pyx ########
# TEST: Make sure not all decorators are accepted.
cdef extern from "foo.h":
ctypedef class foo_extension.Foo [object FooStructOpaque]:
@prop.getter
@property
cdef inline int field0(self):
pass
@field0.getter
cdef inline void field1(self):
pass
######## getter_fail_no_inline.pyx ########
# TEST: Properties must be declared "inline".
cdef extern from "foo.h":
ctypedef class foo_extension.Foo [object FooStructOpaque]:
@property
cdef int field0(self):
pass
######## getter_fail_void.pyx ########
# TEST: Properties must have a non-void return type.
cdef extern from "foo.h":
ctypedef class foo_extension.Foo [object FooStructOpaque]:
@property
cdef void field0(self):
pass
######## getter_fail_no_args.pyx ########
# TEST: Properties must have the right signature.
cdef extern from "foo.h":
ctypedef class foo_extension.Foo [object FooStructOpaque]:
@property
cdef int field0():
pass
######## getter_fail_too_many_args.pyx ########
# TEST: Properties must have the right signature.
cdef extern from "foo.h":
ctypedef class foo_extension.Foo [object FooStructOpaque]:
@property
cdef int field0(x, y):
pass
######## runner.py ########
import warnings
......@@ -247,6 +348,8 @@ val = getter2.check_pyobj(opaque_foo)
assert val is True
val = getter2.check_unary(opaque_foo)
assert val == -123
val = getter2.check_meaning_of_life(opaque_foo)
assert val == 42
try:
f0 = opaque_ret.field0
......
......@@ -21,7 +21,7 @@ def test_parallel_numpy_arrays():
3
4
"""
cdef Py_ssize_t i
cdef Py_ssize_t i, length
cdef np.ndarray[np.int_t] x
try:
......@@ -32,10 +32,10 @@ def test_parallel_numpy_arrays():
return
x = numpy.zeros(10, dtype=numpy.int)
length = x.shape[0]
for i in prange(x.shape[0], nogil=True):
for i in prange(length, nogil=True):
x[i] = i - 5
for i in x:
print i
print(i)
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