Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
cython
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Gwenaël Samain
cython
Commits
51ac02b7
Commit
51ac02b7
authored
6 years ago
by
Stefan Behnel
Committed by
GitHub
6 years ago
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #2640 from mattip/ctypedef-class-getter2
ENH: allow @property decorator on external ctypedef classes
parents
3a5a2e3b
499fd678
Changes
6
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
204 additions
and
43 deletions
+204
-43
Cython/Compiler/ExprNodes.py
Cython/Compiler/ExprNodes.py
+6
-0
Cython/Compiler/Nodes.py
Cython/Compiler/Nodes.py
+34
-13
Cython/Compiler/ParseTreeTransforms.py
Cython/Compiler/ParseTreeTransforms.py
+24
-1
Cython/Compiler/Pipeline.py
Cython/Compiler/Pipeline.py
+2
-1
Cython/Compiler/Symtab.py
Cython/Compiler/Symtab.py
+16
-7
tests/run/ext_attr_getter.srctree
tests/run/ext_attr_getter.srctree
+122
-21
No files found.
Cython/Compiler/ExprNodes.py
View file @
51ac02b7
...
...
@@ -7154,6 +7154,8 @@ 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
...
...
@@ -11242,6 +11244,10 @@ class NumBinopNode(BinopNode):
self
.
operand2
=
self
.
operand2
.
coerce_to
(
self
.
type
,
env
)
def
compute_c_result_type
(
self
,
type1
,
type2
):
if
type1
.
is_cfunction
and
type1
.
entry
.
is_cgetter
:
type1
=
type1
.
return_type
if
type2
.
is_cfunction
and
type2
.
entry
.
is_cgetter
:
type2
=
type2
.
return_type
if
self
.
c_types_okay
(
type1
,
type2
):
widest_type
=
PyrexTypes
.
widest_numeric_type
(
type1
,
type2
)
if
widest_type
is
PyrexTypes
.
c_bint_type
:
...
...
This diff is collapsed.
Click to expand it.
Cython/Compiler/Nodes.py
View file @
51ac02b7
...
...
@@ -2321,7 +2321,8 @@ 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"
]
child_attrs
=
[
"base_type"
,
"declarator"
,
"body"
,
"py_func_stat"
,
"decorators"
]
outer_attrs
=
[
"decorators"
]
inline_in_pxd
=
False
decorators
=
None
...
...
@@ -2341,6 +2342,21 @@ class CFuncDefNode(FuncDefNode):
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
=
{}
...
...
@@ -2355,20 +2371,20 @@ class CFuncDefNode(FuncDefNode):
self
.
is_static_method
=
'staticmethod'
in
env
.
directives
and
not
env
.
lookup_here
(
'staticmethod'
)
# The 2 here is because we need both function and argument names.
if
isinstance
(
self
.
declarator
,
CFuncDeclaratorNode
):
name_declarator
,
typ
e
=
self
.
declarator
.
analyse
(
name_declarator
,
typ
=
self
.
declarator
.
analyse
(
base_type
,
env
,
nonempty
=
2
*
(
self
.
body
is
not
None
),
directive_locals
=
self
.
directive_locals
,
visibility
=
self
.
visibility
)
else
:
name_declarator
,
typ
e
=
self
.
declarator
.
analyse
(
name_declarator
,
typ
=
self
.
declarator
.
analyse
(
base_type
,
env
,
nonempty
=
2
*
(
self
.
body
is
not
None
),
visibility
=
self
.
visibility
)
if
not
typ
e
.
is_cfunction
:
if
not
typ
.
is_cfunction
:
error
(
self
.
pos
,
"Suite attached to non-function declaration"
)
# Remember the actual type according to the function header
# written here, because the type in the symbol table entry
# may be different if we're overriding a C method inherited
# from the base type of an extension type.
self
.
type
=
typ
e
typ
e
.
is_overridable
=
self
.
overridable
self
.
type
=
typ
typ
.
is_overridable
=
self
.
overridable
declarator
=
self
.
declarator
while
not
hasattr
(
declarator
,
'args'
):
declarator
=
declarator
.
base
...
...
@@ -2381,11 +2397,11 @@ class CFuncDefNode(FuncDefNode):
error
(
self
.
cfunc_declarator
.
pos
,
"Function with optional arguments may not be declared public or api"
)
if
typ
e
.
exception_check
==
'+'
and
self
.
visibility
!=
'extern'
:
if
typ
.
exception_check
==
'+'
and
self
.
visibility
!=
'extern'
:
warning
(
self
.
cfunc_declarator
.
pos
,
"Only extern functions can throw C++ exceptions."
)
for
formal_arg
,
type_arg
in
zip
(
self
.
args
,
typ
e
.
args
):
for
formal_arg
,
type_arg
in
zip
(
self
.
args
,
typ
.
args
):
self
.
align_argument_type
(
env
,
type_arg
)
formal_arg
.
type
=
type_arg
.
type
formal_arg
.
name
=
type_arg
.
name
...
...
@@ -2406,20 +2422,25 @@ class CFuncDefNode(FuncDefNode):
elif
'inline'
in
self
.
modifiers
:
warning
(
formal_arg
.
pos
,
"Buffer unpacking not optimized away."
,
1
)
self
.
_validate_type_visibility
(
typ
e
.
return_type
,
self
.
pos
,
env
)
self
.
_validate_type_visibility
(
typ
.
return_type
,
self
.
pos
,
env
)
name
=
name_declarator
.
name
cname
=
name_declarator
.
cname
type
.
is_const_method
=
self
.
is_const_method
type
.
is_static_method
=
self
.
is_static_method
typ
.
is_const_method
=
self
.
is_const_method
typ
.
is_static_method
=
self
.
is_static_method
self
.
entry
=
env
.
declare_cfunction
(
name
,
typ
e
,
self
.
pos
,
name
,
typ
,
self
.
pos
,
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
e
.
return_type
self
.
return_type
=
typ
.
return_type
if
self
.
return_type
.
is_array
and
self
.
visibility
!=
'extern'
:
error
(
self
.
pos
,
"Function cannot return an array"
)
if
self
.
return_type
.
is_cpp_class
:
...
...
This diff is collapsed.
Click to expand it.
Cython/Compiler/ParseTreeTransforms.py
View file @
51ac02b7
...
...
@@ -1031,7 +1031,7 @@ class InterpretCompilerDirectives(CythonTransform):
else
:
realdecs
.
append
(
dec
)
if
realdecs
and
(
scope_name
==
'cclass'
or
isinstance
(
node
,
(
Nodes
.
C
FuncDefNode
,
Nodes
.
C
ClassDefNode
,
Nodes
.
CVarDefNode
))):
isinstance
(
node
,
(
Nodes
.
CClassDefNode
,
Nodes
.
CVarDefNode
))):
raise
PostParseError
(
realdecs
[
0
].
pos
,
"Cdef functions/classes cannot take arbitrary decorators."
)
node
.
decorators
=
realdecs
[::
-
1
]
+
both
[::
-
1
]
# merge or override repeated directives
...
...
@@ -2239,6 +2239,29 @@ 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
):
...
...
This diff is collapsed.
Click to expand it.
Cython/Compiler/Pipeline.py
View file @
51ac02b7
...
...
@@ -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
from
.ParseTreeTransforms
import
CalculateQualifiedNamesTransform
,
ReplacePropertyNode
from
.TypeInference
import
MarkParallelAssignments
,
MarkOverflowingArithmetic
from
.ParseTreeTransforms
import
AdjustDefByDirectives
,
AlignFunctionDefinitions
from
.ParseTreeTransforms
import
RemoveUnreachableCode
,
GilCheck
...
...
@@ -198,6 +198,7 @@ def create_pipeline(context, mode, exclude_classes=()):
AnalyseDeclarationsTransform
(
context
),
AutoTestDictTransform
(
context
),
EmbedSignature
(
context
),
ReplacePropertyNode
(
context
),
EarlyReplaceBuiltinCalls
(
context
),
## Necessary?
TransformBuiltinMethods
(
context
),
MarkParallelAssignments
(
context
),
...
...
This diff is collapsed.
Click to expand it.
Cython/Compiler/Symtab.py
View file @
51ac02b7
...
...
@@ -134,6 +134,7 @@ class Entry(object):
# cf_used boolean Entry is used
# is_fused_specialized boolean Whether this entry of a cdef or def function
# is a specialization
# is_cgetter boolean Is a c-level getter function
# TODO: utility_code and utility_code_definition serves the same purpose...
...
...
@@ -203,6 +204,7 @@ class Entry(object):
error_on_uninitialized
=
False
cf_used
=
True
outer_entry
=
None
is_cgetter
=
False
def
__init__
(
self
,
name
,
cname
,
type
,
pos
=
None
,
init
=
None
):
self
.
name
=
name
...
...
@@ -829,7 +831,8 @@ class Scope(object):
type
.
entry
=
entry
return
entry
def
add_cfunction
(
self
,
name
,
type
,
pos
,
cname
,
visibility
,
modifiers
,
inherited
=
False
):
def
add_cfunction
(
self
,
name
,
type
,
pos
,
cname
,
visibility
,
modifiers
,
inherited
=
False
):
# Add a C function entry without giving it a func_cname.
entry
=
self
.
declare
(
name
,
cname
,
type
,
pos
,
visibility
)
entry
.
is_cfunction
=
1
...
...
@@ -1435,7 +1438,8 @@ class ModuleScope(Scope):
def
declare_cfunction
(
self
,
name
,
type
,
pos
,
cname
=
None
,
visibility
=
'private'
,
api
=
0
,
in_pxd
=
0
,
defining
=
0
,
modifiers
=
(),
utility_code
=
None
,
overridable
=
False
):
defining
=
0
,
modifiers
=
(),
utility_code
=
None
,
overridable
=
False
):
if
not
defining
and
'inline'
in
modifiers
:
# TODO(github/1736): Make this an error.
warning
(
pos
,
"Declarations should not be declared inline."
,
1
)
...
...
@@ -1933,7 +1937,8 @@ class StructOrUnionScope(Scope):
def
declare_cfunction
(
self
,
name
,
type
,
pos
,
cname
=
None
,
visibility
=
'private'
,
api
=
0
,
in_pxd
=
0
,
defining
=
0
,
modifiers
=
(),
overridable
=
False
):
# currently no utility code ...
defining
=
0
,
modifiers
=
(),
overridable
=
False
):
# currently no utility code ...
if
overridable
:
error
(
pos
,
"C struct/union member cannot be declared 'cpdef'"
)
return
self
.
declare_var
(
name
,
type
,
pos
,
...
...
@@ -2214,7 +2219,8 @@ class CClassScope(ClassScope):
def
declare_cfunction
(
self
,
name
,
type
,
pos
,
cname
=
None
,
visibility
=
'private'
,
api
=
0
,
in_pxd
=
0
,
defining
=
0
,
modifiers
=
(),
utility_code
=
None
,
overridable
=
False
):
defining
=
0
,
modifiers
=
(),
utility_code
=
None
,
overridable
=
False
):
if
get_special_method_signature
(
name
)
and
not
self
.
parent_type
.
is_builtin_type
:
error
(
pos
,
"Special methods must be declared with 'def', not 'cdef'"
)
args
=
type
.
args
...
...
@@ -2258,7 +2264,8 @@ class CClassScope(ClassScope):
error
(
pos
,
"C method '%s' not previously declared in definition part of"
" extension type '%s'"
%
(
name
,
self
.
class_name
))
entry
=
self
.
add_cfunction
(
name
,
type
,
pos
,
cname
,
visibility
,
modifiers
)
entry
=
self
.
add_cfunction
(
name
,
type
,
pos
,
cname
,
visibility
,
modifiers
)
if
defining
:
entry
.
func_cname
=
self
.
mangle
(
Naming
.
func_prefix
,
name
)
entry
.
utility_code
=
utility_code
...
...
@@ -2274,11 +2281,13 @@ class CClassScope(ClassScope):
return
entry
def
add_cfunction
(
self
,
name
,
type
,
pos
,
cname
,
visibility
,
modifiers
,
inherited
=
False
):
def
add_cfunction
(
self
,
name
,
type
,
pos
,
cname
,
visibility
,
modifiers
,
inherited
=
False
):
# Add a cfunction entry without giving it a func_cname.
prev_entry
=
self
.
lookup_here
(
name
)
entry
=
ClassScope
.
add_cfunction
(
self
,
name
,
type
,
pos
,
cname
,
visibility
,
modifiers
,
inherited
=
inherited
)
visibility
,
modifiers
,
inherited
=
inherited
)
entry
.
is_cmethod
=
1
entry
.
prev_entry
=
prev_entry
return
entry
...
...
This diff is collapsed.
Click to expand it.
tests/run/ext_attr_getter.srctree
View file @
51ac02b7
...
...
@@ -4,14 +4,22 @@ PYTHON -c "import runner"
######## setup.py ########
from Cython.Build.Dependencies import cythonize
from Cython.Compiler.Errors import CompileError
from distutils.core import setup
# force the build order
setup(ext_modules= cythonize("foo_extension.pyx"))
setup(ext_modules= cythonize("foo_extension.pyx"
, language_level=3
))
setup(ext_modules = cythonize("getter
*.pyx"
))
setup(ext_modules = cythonize("getter
[0-9].pyx", language_level=3
))
######## foo_nominal.h ########
for name in ("getter_fail0.pyx", "getter_fail1.pyx"):
try:
cythonize(name, language_level=3)
assert False
except CompileError as e:
print("\nGot expected exception, continuing\n")
######## foo.h ########
#include <Python.h>
...
...
@@ -26,6 +34,30 @@ typedef struct {
int f2;
} FooStructNominal;
typedef struct {
PyObject_HEAD
} FooStructOpaque;
#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)
{
return PyFoo_GET0M(f);
}
int PyFoo_Get1F(FooStructOpaque *f)
{
return PyFoo_GET1M(f);
}
int PyFoo_Get2F(FooStructOpaque *f)
{
return PyFoo_GET2M(f);
}
#ifdef __cplusplus
}
#endif
...
...
@@ -33,21 +65,24 @@ typedef struct {
######## foo_extension.pyx ########
cdef class Foo:
cdef public int
field0, field1,
field2;
cdef public int
_field0, _field1, _
field2;
def __init__(self, f0, f1, f2):
self.field0 = f0
self.field1 = f1
self.field2 = f2
@property
def field0(self):
return self._field0
cdef get_field0(Foo f):
return f.field0
@property
def field1(self):
return self._field1
cdef get_field1(Foo f):
return f.field1
@property
def field2(self):
return self._field2
cdef get_field2(Foo f):
return f.field2
def __init__(self, f0, f1, f2):
self._field0 = f0
self._field1 = f1
self._field2 = f2
# A pure-python class that disallows direct access to fields
class OpaqueFoo(Foo):
...
...
@@ -64,12 +99,11 @@ 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
cdef extern from "foo
_nominal
.h":
cdef extern from "foo.h":
ctypedef class foo_extension.Foo [object FooStructNominal]:
cdef:
...
...
@@ -78,13 +112,70 @@ cdef extern from "foo_nominal.h":
int field2 "f2"
def sum(Foo f):
# the f.__getattr__('field0') is replaced in c by f->f0
# Note - not a cdef function but compiling the f.__getattr__('field0')
# notices the alias and replaces the __getattr__ in c by f->f0 anyway
return f.field0 + f.field1 + f.field2
######## getter1.pyx ########
# Access base Foo fields from C via getter functions
cdef extern from "foo.h":
ctypedef class foo_extension.Foo [object FooStructOpaque, check_size ignore]:
@property
cdef int fieldM0(self):
return PyFoo_GET0M(self)
@property
cdef int fieldF1(self):
return PyFoo_Get1F(self)
@property
cdef int fieldM2(self):
return PyFoo_GET2M(self)
int PyFoo_GET0M(Foo); # this is actually a macro !
int PyFoo_Get1F(Foo);
int PyFoo_GET2M(Foo); # this is actually a macro !
def sum(Foo f):
# Note - not a cdef function but compiling the f.__getattr__('field0')
# notices the getter and replaces the __getattr__ in c by PyFoo_GET anyway
return f.fieldM0 + f.fieldF1 + f.fieldM2
######## getter_fail0.pyx ########
# Make sure not all decorators are accepted
cdef extern from "foo.h":
ctypedef class foo_extension.Foo [object FooStructOpaque]:
@classmethod
cdef void field0():
print('in staticmethod of Foo')
######## getter_fail1.pyx ########
# Make sure not all decorators are accepted
cimport cython
cdef extern from "foo.h":
ctypedef class foo_extension.Foo [object FooStructOpaque]:
@prop.getter
cdef void field0(self):
pass
######## runner.py ########
import foo_extension, getter0
import warnings
import foo_extension, getter0, getter1
def sum(f):
# pure python field access, but code is identical to cython cdef sum
return f.field0 + f.field1 + f.field2
# Baseline test: if this fails something else is wrong
foo = foo_extension.Foo(23, 123, 1023)
assert foo.field0 == 23
...
...
@@ -92,18 +183,28 @@ assert foo.field1 == 123
assert foo.field2 == 1023
ret = getter0.sum(foo)
assert ret == foo.field0 + foo.field1 + foo.field2
assert ret == sum(foo)
# Aliasing test. Check 'cdef int field0 "f0" works as advertised:
# - C can access the fields through the aliases
# - Python cannot access the fields at all
opaque_foo = foo_extension.OpaqueFoo(23, 123, 1023)
# C can access the fields through the aliases
opaque_ret = getter0.sum(opaque_foo)
assert opaque_ret == ret
try:
# Python cannot access the fields
f0 = opaque_ret.field0
assert False
except AttributeError as e:
pass
# Getter test. Check C-level getter works as advertised:
# - C accesses the fields through getter calls (maybe macros)
# - Python accesses the fields through attribute lookup
opaque_foo = foo_extension.OpaqueFoo(23, 123, 1023)
opaque_ret = getter1.sum(opaque_foo)
assert opaque_ret == ret
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment