This makes properties work at all, in cdef classes, and gives them almost the same features as the “property something:” blocks. The only missing feature being the ability to assignate a docstring to the property itself, not only to the getter, setter and deleter.
Fixes T264. --- Cython/Compiler/ParseTreeTransforms.py | 72 ++++++++++++++++++++++++ Cython/Compiler/Pipeline.py | 3 +- tests/run/cdef_class_property_decorator_T264.pyx | 51 +++++++++++++++++ 3 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 tests/run/cdef_class_property_decorator_T264.pyx diff --git a/Cython/Compiler/ParseTreeTransforms.py b/Cython/Compiler/ParseTreeTransforms.py index b34f44b..67507ce 100644 --- a/Cython/Compiler/ParseTreeTransforms.py +++ b/Cython/Compiler/ParseTreeTransforms.py @@ -1270,6 +1270,78 @@ class WithTransform(CythonTransform, SkipDeclarations): return node +class PropertyTransform(ScopeTrackingTransform): + """Originally, this was the only place where decorators were + transformed into the corresponding calling code. Now, this is + done directly in DefNode and PyClassDefNode to avoid reassignments + to the function/class name - except for cdef class methods. For + those, the reassignment is required as methods are originally + defined in the PyMethodDef struct. + + The IndirectionNode allows DefNode to override the decorator + """ + + properties = {} + + def visit_DefNode(self, func_node): + func_node = self.visit_FuncDefNode(func_node) + if self.scope_type != 'cclass' or not func_node.decorators: + return func_node + return self.handle_decorators(func_node, func_node.decorators, + func_node.name) + + def handle_decorators(self, node, decorators, name): + properties = self.properties.setdefault(self.scope_node, {}) + for decorator in decorators[::-1]: + decorator_node = decorator.decorator + if (isinstance(decorator_node, ExprNodes.NameNode) and + decorator_node.name == 'property'): + node.name = '__get__' + node.decorators.remove(decorator) + stat_list = [node] + if name in properties: + property = properties[name] + property.body.stats = stat_list + return [] + else: + property = Nodes.PropertyNode(node.pos, name=name) + property.doc = EncodedString(u'') + property.body = Nodes.StatListNode(node.pos, stats=stat_list) + properties[name] = property + return [property] + elif (isinstance(decorator_node, ExprNodes.AttributeNode) and + decorator_node.attribute == 'setter' and + decorator_node.obj.name in properties): + assert decorator_node.obj.name == name + node.name = '__set__' + node.decorators.remove(decorator) + property = properties[name] + stats = property.body.stats + for i, stat in enumerate(stats): + if stat.name == '__set__': + stats[i] = node + break + else: + stats.append(node) + return [] + elif (isinstance(decorator_node, ExprNodes.AttributeNode) and + decorator_node.attribute == 'deleter' and + decorator_node.obj.name in properties): + assert decorator_node.obj.name == name + node.name = '__del__' + node.decorators.remove(decorator) + property = properties[name] + stats = property.body.stats + for i, stat in enumerate(stats): + if stat.name == '__del__': + stats[i] = node + break + else: + stats.append(node) + return [] + return node + + class DecoratorTransform(ScopeTrackingTransform, SkipDeclarations): """Originally, this was the only place where decorators were transformed into the corresponding calling code. Now, this is diff --git a/Cython/Compiler/Pipeline.py b/Cython/Compiler/Pipeline.py index 833e22c..0506aff 100644 --- a/Cython/Compiler/Pipeline.py +++ b/Cython/Compiler/Pipeline.py @@ -171,7 +171,7 @@ def create_pipeline(context, mode, exclude_classes=()): from .ParseTreeTransforms import CreateClosureClasses, MarkClosureVisitor, DecoratorTransform from .ParseTreeTransforms import InterpretCompilerDirectives, TransformBuiltinMethods from .ParseTreeTransforms import ExpandInplaceOperators, ParallelRangeTransform - from .ParseTreeTransforms import CalculateQualifiedNamesTransform + from .ParseTreeTransforms import CalculateQualifiedNamesTransform, PropertyTransform from .TypeInference import MarkParallelAssignments, MarkOverflowingArithmetic from .ParseTreeTransforms import AdjustDefByDirectives, AlignFunctionDefinitions from .ParseTreeTransforms import RemoveUnreachableCode, GilCheck @@ -216,6 +216,7 @@ def create_pipeline(context, mode, exclude_classes=()): RemoveUnreachableCode(context), ConstantFolding(), FlattenInListTransform(), + PropertyTransform(context), DecoratorTransform(context), ForwardDeclareTypes(context), AnalyseDeclarationsTransform(context), diff --git a/tests/run/cdef_class_property_decorator_T264.pyx b/tests/run/cdef_class_property_decorator_T264.pyx new file mode 100644 index 0000000..94b862c --- /dev/null +++ b/tests/run/cdef_class_property_decorator_T264.pyx @@ -0,0 +1,51 @@ +# mode: run +# ticket: 264 +# tag: property, decorator + +cdef class Prop(object): + """ + >>> p = Prop() + >>> p.prop + GETTING 'None' + >>> p.prop = 1 + SETTING '1' (previously: 'None') + >>> p.prop + GETTING '1' + 1 + >>> p.prop = 2 + SETTING '2' (previously: '1') + >>> p.prop + GETTING '2' + 2 + >>> del p.prop + DELETING '2' + >>> p.prop + GETTING 'None' + """ + cdef _value + def __init__(self): + self._value = None + + @property + def prop(self): + print("FAIL") + return 0 + + @prop.deleter + def prop(self): + print("FAIL") + + @property + def prop(self): + print("GETTING '%s'" % self._value) + return self._value + + @prop.setter + def prop(self, value): + print("SETTING '%s' (previously: '%s')" % (value, self._value)) + self._value = value + + @prop.deleter + def prop(self): + print("DELETING '%s'" % self._value) + self._value = None -- 2.6.2 _______________________________________________ cython-devel mailing list cython-devel@python.org https://mail.python.org/mailman/listinfo/cython-devel