[Cython] [PATCH] Add a pass transforming Python-style properties in cdef class into Cython-style properties.
Emmanuel Gil Peyrot
linkmauve at linkmauve.fr
Tue Nov 3 01:37:50 EST 2015
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
More information about the cython-devel
mailing list