Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
P
pybind11
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
open
pybind11
Commits
18e34cb2
Commit
18e34cb2
authored
Feb 05, 2017
by
Wenzel Jakob
Committed by
GitHub
Feb 05, 2017
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #634 from jagerman/noconvert-arguments
Add support for non-converting arguments
parents
40db2c75
abc29cad
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
274 additions
and
59 deletions
+274
-59
docs/advanced/classes.rst
+2
-0
docs/advanced/functions.rst
+54
-0
include/pybind11/attr.h
+12
-22
include/pybind11/cast.h
+79
-21
include/pybind11/complex.h
+3
-1
include/pybind11/pybind11.h
+23
-15
tests/test_methods_and_attributes.cpp
+57
-0
tests/test_methods_and_attributes.py
+44
-0
No files found.
docs/advanced/classes.rst
View file @
18e34cb2
...
@@ -388,6 +388,8 @@ crucial that instances are deallocated on the C++ side to avoid memory leaks.
...
@@ -388,6 +388,8 @@ crucial that instances are deallocated on the C++ side to avoid memory leaks.
py::class_<MyClass, std::unique_ptr<MyClass, py::nodelete>>(m, "MyClass")
py::class_<MyClass, std::unique_ptr<MyClass, py::nodelete>>(m, "MyClass")
.def(py::init<>())
.def(py::init<>())
.. _implicit_conversions:
Implicit conversions
Implicit conversions
====================
====================
...
...
docs/advanced/functions.rst
View file @
18e34cb2
...
@@ -318,3 +318,57 @@ like so:
...
@@ -318,3 +318,57 @@ like so:
py::class_<MyClass>("MyClass")
py::class_<MyClass>("MyClass")
.def("myFunction", py::arg("arg") = (SomeType *) nullptr);
.def("myFunction", py::arg("arg") = (SomeType *) nullptr);
Non-converting arguments
========================
Certain argument types may support conversion from one type to another. Some
examples of conversions are:
* :ref:`implicit_conversions` declared using ``py::implicitly_convertible<A,B>()``
* Calling a method accepting a double with an integer argument
* Calling a ``std::complex<float>`` argument with a non-complex python type
(for example, with a float). (Requires the optional ``pybind11/complex.h``
header).
* Calling a function taking an Eigen matrix reference with a numpy array of the
wrong type or of an incompatible data layout. (Requires the optional
``pybind11/eigen.h`` header).
This behaviour is sometimes undesirable: the binding code may prefer to raise
an error rather than convert the argument. This behaviour can be obtained
through ``py::arg`` by calling the ``.noconvert()`` method of the ``py::arg``
object, such as:
.. code-block:: cpp
m.def("floats_only", [](double f) { return 0.5 * f; }, py::arg("f").noconvert());
m.def("floats_preferred", [](double f) { return 0.5 * f; }, py::arg("f"));
Attempting the call the second function (the one without ``.noconvert()``) with
an integer will succeed, but attempting to call the ``.noconvert()`` version
will fail with a ``TypeError``:
.. code-block:: pycon
>>> floats_preferred(4)
2.0
>>> floats_only(4)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: floats_only(): incompatible function arguments. The following argument types are supported:
1. (f: float) -> float
Invoked with: 4
You may, of course, combine this with the :var:`_a` shorthand notation (see
:ref:`keyword_args`) and/or :ref:`default_args`. It is also permitted to omit
the argument name by using the ``py::arg()`` constructor without an argument
name, i.e. by specifying ``py::arg().noconvert()``.
.. note::
When specifying ``py::arg`` options it is necessary to provide the same
number of options as the bound function has arguments. Thus if you want to
enable no-convert behaviour for just one of several arguments, you will
need to specify a ``py::arg()`` annotation for each argument with the
no-convert argument modified to ``py::arg().noconvert()``.
include/pybind11/attr.h
View file @
18e34cb2
...
@@ -69,7 +69,6 @@ struct undefined_t;
...
@@ -69,7 +69,6 @@ struct undefined_t;
template
<
op_id
id
,
op_type
ot
,
typename
L
=
undefined_t
,
typename
R
=
undefined_t
>
struct
op_
;
template
<
op_id
id
,
op_type
ot
,
typename
L
=
undefined_t
,
typename
R
=
undefined_t
>
struct
op_
;
template
<
typename
...
Args
>
struct
init
;
template
<
typename
...
Args
>
struct
init
;
template
<
typename
...
Args
>
struct
init_alias
;
template
<
typename
...
Args
>
struct
init_alias
;
struct
function_call
;
inline
void
keep_alive_impl
(
size_t
Nurse
,
size_t
Patient
,
function_call
&
call
,
handle
ret
);
inline
void
keep_alive_impl
(
size_t
Nurse
,
size_t
Patient
,
function_call
&
call
,
handle
ret
);
/// Internal data structure which holds metadata about a keyword argument
/// Internal data structure which holds metadata about a keyword argument
...
@@ -77,9 +76,10 @@ struct argument_record {
...
@@ -77,9 +76,10 @@ struct argument_record {
const
char
*
name
;
///< Argument name
const
char
*
name
;
///< Argument name
const
char
*
descr
;
///< Human-readable version of the argument value
const
char
*
descr
;
///< Human-readable version of the argument value
handle
value
;
///< Associated Python object
handle
value
;
///< Associated Python object
bool
convert
:
1
;
///< True if the argument is allowed to convert when loading
argument_record
(
const
char
*
name
,
const
char
*
descr
,
handle
value
)
argument_record
(
const
char
*
name
,
const
char
*
descr
,
handle
value
,
bool
convert
)
:
name
(
name
),
descr
(
descr
),
value
(
value
)
{
}
:
name
(
name
),
descr
(
descr
),
value
(
value
)
,
convert
(
convert
)
{
}
};
};
/// Internal data structure which holds metadata about a bound function (signature, overloads, etc.)
/// Internal data structure which holds metadata about a bound function (signature, overloads, etc.)
...
@@ -131,7 +131,7 @@ struct function_record {
...
@@ -131,7 +131,7 @@ struct function_record {
bool
is_method
:
1
;
bool
is_method
:
1
;
/// Number of arguments (including py::args and/or py::kwargs, if present)
/// Number of arguments (including py::args and/or py::kwargs, if present)
uint16_t
nargs
;
std
::
uint16_t
nargs
;
/// Python method object
/// Python method object
PyMethodDef
*
def
=
nullptr
;
PyMethodDef
*
def
=
nullptr
;
...
@@ -222,21 +222,11 @@ struct type_record {
...
@@ -222,21 +222,11 @@ struct type_record {
}
}
};
};
/// Internal data associated with a single function call
inline
function_call
::
function_call
(
function_record
&
f
,
handle
p
)
:
struct
function_call
{
func
(
f
),
parent
(
p
)
{
function_call
(
function_record
&
f
,
handle
p
)
:
func
(
f
),
parent
(
p
)
{
args
.
reserve
(
f
.
nargs
);
args
.
reserve
(
f
.
nargs
);
}
args_convert
.
reserve
(
f
.
nargs
);
}
/// The function data:
const
function_record
&
func
;
/// Arguments passed to the function:
std
::
vector
<
handle
>
args
;
/// The parent, if any
handle
parent
;
};
/**
/**
* Partial template specializations to process custom attributes provided to
* Partial template specializations to process custom attributes provided to
...
@@ -300,8 +290,8 @@ template <> struct process_attribute<is_operator> : process_attribute_default<is
...
@@ -300,8 +290,8 @@ template <> struct process_attribute<is_operator> : process_attribute_default<is
template
<>
struct
process_attribute
<
arg
>
:
process_attribute_default
<
arg
>
{
template
<>
struct
process_attribute
<
arg
>
:
process_attribute_default
<
arg
>
{
static
void
init
(
const
arg
&
a
,
function_record
*
r
)
{
static
void
init
(
const
arg
&
a
,
function_record
*
r
)
{
if
(
r
->
is_method
&&
r
->
args
.
empty
())
if
(
r
->
is_method
&&
r
->
args
.
empty
())
r
->
args
.
emplace_back
(
"self"
,
nullptr
,
handle
());
r
->
args
.
emplace_back
(
"self"
,
nullptr
,
handle
()
,
true
/*convert*/
);
r
->
args
.
emplace_back
(
a
.
name
,
nullptr
,
handle
());
r
->
args
.
emplace_back
(
a
.
name
,
nullptr
,
handle
()
,
!
a
.
flag_noconvert
);
}
}
};
};
...
@@ -309,7 +299,7 @@ template <> struct process_attribute<arg> : process_attribute_default<arg> {
...
@@ -309,7 +299,7 @@ template <> struct process_attribute<arg> : process_attribute_default<arg> {
template
<>
struct
process_attribute
<
arg_v
>
:
process_attribute_default
<
arg_v
>
{
template
<>
struct
process_attribute
<
arg_v
>
:
process_attribute_default
<
arg_v
>
{
static
void
init
(
const
arg_v
&
a
,
function_record
*
r
)
{
static
void
init
(
const
arg_v
&
a
,
function_record
*
r
)
{
if
(
r
->
is_method
&&
r
->
args
.
empty
())
if
(
r
->
is_method
&&
r
->
args
.
empty
())
r
->
args
.
emplace_back
(
"self"
,
nullptr
,
handle
()
);
r
->
args
.
emplace_back
(
"self"
,
nullptr
/*descr*/
,
handle
()
/*parent*/
,
true
/*convert*/
);
if
(
!
a
.
value
)
{
if
(
!
a
.
value
)
{
#if !defined(NDEBUG)
#if !defined(NDEBUG)
...
@@ -330,7 +320,7 @@ template <> struct process_attribute<arg_v> : process_attribute_default<arg_v> {
...
@@ -330,7 +320,7 @@ template <> struct process_attribute<arg_v> : process_attribute_default<arg_v> {
"Compile in debug mode for more information."
);
"Compile in debug mode for more information."
);
#endif
#endif
}
}
r
->
args
.
emplace_back
(
a
.
name
,
a
.
descr
,
a
.
value
.
inc_ref
());
r
->
args
.
emplace_back
(
a
.
name
,
a
.
descr
,
a
.
value
.
inc_ref
()
,
!
a
.
flag_noconvert
);
}
}
};
};
...
...
include/pybind11/cast.h
View file @
18e34cb2
...
@@ -473,18 +473,22 @@ public:
...
@@ -473,18 +473,22 @@ public:
template
<
typename
T
>
template
<
typename
T
>
struct
type_caster
<
T
,
enable_if_t
<
std
::
is_arithmetic
<
T
>::
value
>>
{
struct
type_caster
<
T
,
enable_if_t
<
std
::
is_arithmetic
<
T
>::
value
>>
{
typedef
typename
std
::
conditional
<
sizeof
(
T
)
<=
sizeof
(
long
),
long
,
long
long
>::
type
_py_type_0
;
using
_py_type_0
=
conditional_t
<
sizeof
(
T
)
<=
sizeof
(
long
),
long
,
long
long
>
;
typedef
typename
std
::
conditional
<
std
::
is_signed
<
T
>::
value
,
_py_type_0
,
typename
std
::
make_unsigned
<
_py_type_0
>::
type
>::
type
_py_type_1
;
using
_py_type_1
=
conditional_t
<
std
::
is_signed
<
T
>::
value
,
_py_type_0
,
typename
std
::
make_unsigned
<
_py_type_0
>::
type
>
;
typedef
typename
std
::
conditional
<
std
::
is_floating_point
<
T
>::
value
,
double
,
_py_type_1
>::
type
py_type
;
using
py_type
=
conditional_t
<
std
::
is_floating_point
<
T
>::
value
,
double
,
_py_type_1
>
;
public
:
public
:
bool
load
(
handle
src
,
bool
)
{
bool
load
(
handle
src
,
bool
convert
)
{
py_type
py_value
;
py_type
py_value
;
if
(
!
src
)
{
if
(
!
src
)
return
false
;
return
false
;
}
if
(
std
::
is_floating_point
<
T
>::
value
)
{
if
(
std
::
is_floating_point
<
T
>::
value
)
{
if
(
convert
||
PyFloat_Check
(
src
.
ptr
()))
py_value
=
(
py_type
)
PyFloat_AsDouble
(
src
.
ptr
());
py_value
=
(
py_type
)
PyFloat_AsDouble
(
src
.
ptr
());
else
return
false
;
}
else
if
(
sizeof
(
T
)
<=
sizeof
(
long
))
{
}
else
if
(
sizeof
(
T
)
<=
sizeof
(
long
))
{
if
(
PyFloat_Check
(
src
.
ptr
()))
if
(
PyFloat_Check
(
src
.
ptr
()))
return
false
;
return
false
;
...
@@ -511,7 +515,7 @@ public:
...
@@ -511,7 +515,7 @@ public:
bool
type_error
=
PyErr_ExceptionMatches
(
PyExc_TypeError
);
bool
type_error
=
PyErr_ExceptionMatches
(
PyExc_TypeError
);
#endif
#endif
PyErr_Clear
();
PyErr_Clear
();
if
(
type_error
&&
PyNumber_Check
(
src
.
ptr
()))
{
if
(
type_error
&&
convert
&&
PyNumber_Check
(
src
.
ptr
()))
{
auto
tmp
=
reinterpret_borrow
<
object
>
(
std
::
is_floating_point
<
T
>::
value
auto
tmp
=
reinterpret_borrow
<
object
>
(
std
::
is_floating_point
<
T
>::
value
?
PyNumber_Float
(
src
.
ptr
())
?
PyNumber_Float
(
src
.
ptr
())
:
PyNumber_Long
(
src
.
ptr
()));
:
PyNumber_Long
(
src
.
ptr
()));
...
@@ -1198,22 +1202,26 @@ template <return_value_policy policy = return_value_policy::automatic_reference,
...
@@ -1198,22 +1202,26 @@ template <return_value_policy policy = return_value_policy::automatic_reference,
}
}
/// \ingroup annotations
/// \ingroup annotations
/// Annotation for
keyword
arguments
/// Annotation for arguments
struct
arg
{
struct
arg
{
///
Set the name of the argument
///
Constructs an argument with the name of the argument; if null or omitted, this is a positional argument.
constexpr
explicit
arg
(
const
char
*
name
)
:
name
(
nam
e
)
{
}
constexpr
explicit
arg
(
const
char
*
name
=
nullptr
)
:
name
(
name
),
flag_noconvert
(
fals
e
)
{
}
/// Assign a value to this argument
/// Assign a value to this argument
template
<
typename
T
>
arg_v
operator
=
(
T
&&
value
)
const
;
template
<
typename
T
>
arg_v
operator
=
(
T
&&
value
)
const
;
/// Indicate that the type should not be converted in the type caster
arg
&
noconvert
(
bool
flag
=
true
)
{
flag_noconvert
=
flag
;
return
*
this
;
}
const
char
*
name
;
const
char
*
name
;
///< If non-null, this is a named kwargs argument
bool
flag_noconvert
:
1
;
///< If set, do not allow conversion (requires a supporting type caster!)
};
};
/// \ingroup annotations
/// \ingroup annotations
/// Annotation for
keyword
arguments with values
/// Annotation for arguments with values
struct
arg_v
:
arg
{
struct
arg_v
:
arg
{
private
:
template
<
typename
T
>
template
<
typename
T
>
arg_v
(
const
char
*
nam
e
,
T
&&
x
,
const
char
*
descr
=
nullptr
)
arg_v
(
arg
&&
bas
e
,
T
&&
x
,
const
char
*
descr
=
nullptr
)
:
arg
(
nam
e
),
:
arg
(
bas
e
),
value
(
reinterpret_steal
<
object
>
(
value
(
reinterpret_steal
<
object
>
(
detail
::
make_caster
<
T
>::
cast
(
x
,
return_value_policy
::
automatic
,
{})
detail
::
make_caster
<
T
>::
cast
(
x
,
return_value_policy
::
automatic
,
{})
)),
)),
...
@@ -1223,15 +1231,32 @@ struct arg_v : arg {
...
@@ -1223,15 +1231,32 @@ struct arg_v : arg {
#endif
#endif
{
}
{
}
public
:
/// Direct construction with name, default, and description
template
<
typename
T
>
arg_v
(
const
char
*
name
,
T
&&
x
,
const
char
*
descr
=
nullptr
)
:
arg_v
(
arg
(
name
),
std
::
forward
<
T
>
(
x
),
descr
)
{
}
/// Called internally when invoking `py::arg("a") = value`
template
<
typename
T
>
arg_v
(
const
arg
&
base
,
T
&&
x
,
const
char
*
descr
=
nullptr
)
:
arg_v
(
arg
(
base
),
std
::
forward
<
T
>
(
x
),
descr
)
{
}
/// Same as `arg::noconvert()`, but returns *this as arg_v&, not arg&
arg_v
&
noconvert
(
bool
flag
=
true
)
{
arg
::
noconvert
(
flag
);
return
*
this
;
}
/// The default value
object
value
;
object
value
;
/// The (optional) description of the default value
const
char
*
descr
;
const
char
*
descr
;
#if !defined(NDEBUG)
#if !defined(NDEBUG)
/// The C++ type name of the default value (only available when compiled in debug mode)
std
::
string
type
;
std
::
string
type
;
#endif
#endif
};
};
template
<
typename
T
>
template
<
typename
T
>
arg_v
arg
::
operator
=
(
T
&&
value
)
const
{
return
{
name
,
std
::
forward
<
T
>
(
value
)};
}
arg_v
arg
::
operator
=
(
T
&&
value
)
const
{
return
{
std
::
move
(
*
this
)
,
std
::
forward
<
T
>
(
value
)};
}
/// Alias for backward compatibility -- to be removed in version 2.0
/// Alias for backward compatibility -- to be removed in version 2.0
template
<
typename
/*unused*/
>
using
arg_t
=
arg_v
;
template
<
typename
/*unused*/
>
using
arg_t
=
arg_v
;
...
@@ -1248,11 +1273,28 @@ NAMESPACE_BEGIN(detail)
...
@@ -1248,11 +1273,28 @@ NAMESPACE_BEGIN(detail)
// forward declaration
// forward declaration
struct
function_record
;
struct
function_record
;
/// Internal data associated with a single function call
struct
function_call
{
function_call
(
function_record
&
f
,
handle
p
);
// Implementation in attr.h
/// The function data:
const
function_record
&
func
;
/// Arguments passed to the function:
std
::
vector
<
handle
>
args
;
/// The `convert` value the arguments should be loaded with
std
::
vector
<
bool
>
args_convert
;
/// The parent, if any
handle
parent
;
};
/// Helper class which loads arguments for C++ functions called from Python
/// Helper class which loads arguments for C++ functions called from Python
template
<
typename
...
Args
>
template
<
typename
...
Args
>
class
argument_loader
{
class
argument_loader
{
using
indices
=
make_index_sequence
<
sizeof
...(
Args
)
>
;
using
indices
=
make_index_sequence
<
sizeof
...(
Args
)
>
;
using
function_arguments
=
const
std
::
vector
<
handle
>
&
;
template
<
typename
Arg
>
using
argument_is_args
=
std
::
is_same
<
intrinsic_t
<
Arg
>
,
args
>
;
template
<
typename
Arg
>
using
argument_is_args
=
std
::
is_same
<
intrinsic_t
<
Arg
>
,
args
>
;
template
<
typename
Arg
>
using
argument_is_kwargs
=
std
::
is_same
<
intrinsic_t
<
Arg
>
,
kwargs
>
;
template
<
typename
Arg
>
using
argument_is_kwargs
=
std
::
is_same
<
intrinsic_t
<
Arg
>
,
kwargs
>
;
...
@@ -1270,8 +1312,8 @@ public:
...
@@ -1270,8 +1312,8 @@ public:
static
PYBIND11_DESCR
arg_names
()
{
return
detail
::
concat
(
make_caster
<
Args
>::
name
()...);
}
static
PYBIND11_DESCR
arg_names
()
{
return
detail
::
concat
(
make_caster
<
Args
>::
name
()...);
}
bool
load_args
(
function_
arguments
args
)
{
bool
load_args
(
function_
call
&
call
)
{
return
load_impl_sequence
(
args
,
indices
{});
return
load_impl_sequence
(
call
,
indices
{});
}
}
template
<
typename
Return
,
typename
Func
>
template
<
typename
Return
,
typename
Func
>
...
@@ -1287,11 +1329,11 @@ public:
...
@@ -1287,11 +1329,11 @@ public:
private
:
private
:
static
bool
load_impl_sequence
(
function_
arguments
,
index_sequence
<>
)
{
return
true
;
}
static
bool
load_impl_sequence
(
function_
call
&
,
index_sequence
<>
)
{
return
true
;
}
template
<
size_t
...
Is
>
template
<
size_t
...
Is
>
bool
load_impl_sequence
(
function_
arguments
args
,
index_sequence
<
Is
...
>
)
{
bool
load_impl_sequence
(
function_
call
&
call
,
index_sequence
<
Is
...
>
)
{
for
(
bool
r
:
{
std
::
get
<
Is
>
(
value
).
load
(
args
[
Is
],
true
)...})
for
(
bool
r
:
{
std
::
get
<
Is
>
(
value
).
load
(
call
.
args
[
Is
],
call
.
args_convert
[
Is
]
)...})
if
(
!
r
)
if
(
!
r
)
return
false
;
return
false
;
return
true
;
return
true
;
...
@@ -1380,6 +1422,13 @@ private:
...
@@ -1380,6 +1422,13 @@ private:
}
}
void
process
(
list
&
/*args_list*/
,
arg_v
a
)
{
void
process
(
list
&
/*args_list*/
,
arg_v
a
)
{
if
(
!
a
.
name
)
#if defined(NDEBUG)
nameless_argument_error
();
#else
nameless_argument_error
(
a
.
type
);
#endif
if
(
m_kwargs
.
contains
(
a
.
name
))
{
if
(
m_kwargs
.
contains
(
a
.
name
))
{
#if defined(NDEBUG)
#if defined(NDEBUG)
multiple_values_error
();
multiple_values_error
();
...
@@ -1412,6 +1461,15 @@ private:
...
@@ -1412,6 +1461,15 @@ private:
}
}
}
}
[[
noreturn
]]
static
void
nameless_argument_error
()
{
throw
type_error
(
"Got kwargs without a name; only named arguments "
"may be passed via py::arg() to a python function call. "
"(compile in debug mode for details)"
);
}
[[
noreturn
]]
static
void
nameless_argument_error
(
std
::
string
type
)
{
throw
type_error
(
"Got kwargs without a name of type '"
+
type
+
"'; only named "
"arguments may be passed via py::arg() to a python function call. "
);
}
[[
noreturn
]]
static
void
multiple_values_error
()
{
[[
noreturn
]]
static
void
multiple_values_error
()
{
throw
type_error
(
"Got multiple values for keyword argument "
throw
type_error
(
"Got multiple values for keyword argument "
"(compile in debug mode for details)"
);
"(compile in debug mode for details)"
);
...
...
include/pybind11/complex.h
View file @
18e34cb2
...
@@ -28,9 +28,11 @@ template <typename T> struct is_fmt_numeric<std::complex<T>> {
...
@@ -28,9 +28,11 @@ template <typename T> struct is_fmt_numeric<std::complex<T>> {
template
<
typename
T
>
class
type_caster
<
std
::
complex
<
T
>>
{
template
<
typename
T
>
class
type_caster
<
std
::
complex
<
T
>>
{
public
:
public
:
bool
load
(
handle
src
,
bool
)
{
bool
load
(
handle
src
,
bool
convert
)
{
if
(
!
src
)
if
(
!
src
)
return
false
;
return
false
;
if
(
!
convert
&&
!
PyComplex_Check
(
src
.
ptr
()))
return
false
;
Py_complex
result
=
PyComplex_AsCComplex
(
src
.
ptr
());
Py_complex
result
=
PyComplex_AsCComplex
(
src
.
ptr
());
if
(
result
.
real
==
-
1
.
0
&&
PyErr_Occurred
())
{
if
(
result
.
real
==
-
1
.
0
&&
PyErr_Occurred
())
{
PyErr_Clear
();
PyErr_Clear
();
...
...
include/pybind11/pybind11.h
View file @
18e34cb2
...
@@ -122,7 +122,7 @@ protected:
...
@@ -122,7 +122,7 @@ protected:
cast_in
args_converter
;
cast_in
args_converter
;
/* Try to cast the function arguments into the C++ domain */
/* Try to cast the function arguments into the C++ domain */
if
(
!
args_converter
.
load_args
(
call
.
args
))
if
(
!
args_converter
.
load_args
(
call
))
return
PYBIND11_TRY_NEXT_OVERLOAD
;
return
PYBIND11_TRY_NEXT_OVERLOAD
;
/* Invoke call policy pre-call hook */
/* Invoke call policy pre-call hook */
...
@@ -198,7 +198,7 @@ protected:
...
@@ -198,7 +198,7 @@ protected:
if
(
c
==
'{'
)
{
if
(
c
==
'{'
)
{
// Write arg name for everything except *args, **kwargs and return type.
// Write arg name for everything except *args, **kwargs and return type.
if
(
type_depth
==
0
&&
text
[
char_index
]
!=
'*'
&&
arg_index
<
args
)
{
if
(
type_depth
==
0
&&
text
[
char_index
]
!=
'*'
&&
arg_index
<
args
)
{
if
(
!
rec
->
args
.
empty
())
{
if
(
!
rec
->
args
.
empty
()
&&
rec
->
args
[
arg_index
].
name
)
{
signature
+=
rec
->
args
[
arg_index
].
name
;
signature
+=
rec
->
args
[
arg_index
].
name
;
}
else
if
(
arg_index
==
0
&&
rec
->
is_method
)
{
}
else
if
(
arg_index
==
0
&&
rec
->
is_method
)
{
signature
+=
"self"
;
signature
+=
"self"
;
...
@@ -257,7 +257,7 @@ protected:
...
@@ -257,7 +257,7 @@ protected:
rec
->
signature
=
strdup
(
signature
.
c_str
());
rec
->
signature
=
strdup
(
signature
.
c_str
());
rec
->
args
.
shrink_to_fit
();
rec
->
args
.
shrink_to_fit
();
rec
->
is_constructor
=
!
strcmp
(
rec
->
name
,
"__init__"
)
||
!
strcmp
(
rec
->
name
,
"__setstate__"
);
rec
->
is_constructor
=
!
strcmp
(
rec
->
name
,
"__init__"
)
||
!
strcmp
(
rec
->
name
,
"__setstate__"
);
rec
->
nargs
=
(
uint16_t
)
args
;
rec
->
nargs
=
(
std
::
uint16_t
)
args
;
#if PY_MAJOR_VERSION < 3
#if PY_MAJOR_VERSION < 3
if
(
rec
->
sibling
&&
PyMethod_Check
(
rec
->
sibling
.
ptr
()))
if
(
rec
->
sibling
&&
PyMethod_Check
(
rec
->
sibling
.
ptr
()))
...
@@ -392,8 +392,10 @@ protected:
...
@@ -392,8 +392,10 @@ protected:
handle
parent
=
n_args_in
>
0
?
PyTuple_GET_ITEM
(
args_in
,
0
)
:
nullptr
,
handle
parent
=
n_args_in
>
0
?
PyTuple_GET_ITEM
(
args_in
,
0
)
:
nullptr
,
result
=
PYBIND11_TRY_NEXT_OVERLOAD
;
result
=
PYBIND11_TRY_NEXT_OVERLOAD
;
try
{
try
{
for
(;
it
!=
nullptr
;
it
=
it
->
next
)
{
for
(;
it
!=
nullptr
;
it
=
it
->
next
)
{
/* For each overload:
/* For each overload:
1. Copy all positional arguments we were given, also checking to make sure that
1. Copy all positional arguments we were given, also checking to make sure that
named positional arguments weren't *also* specified via kwarg.
named positional arguments weren't *also* specified via kwarg.
...
@@ -435,14 +437,15 @@ protected:
...
@@ -435,14 +437,15 @@ protected:
// raise a TypeError like Python does. (We could also continue with the next
// raise a TypeError like Python does. (We could also continue with the next
// overload, but this seems highly likely to be a caller mistake rather than a
// overload, but this seems highly likely to be a caller mistake rather than a
// legitimate overload).
// legitimate overload).
if
(
kwargs_in
&&
args_copied
<
it
->
args
.
size
()
)
{
if
(
kwargs_in
&&
args_copied
<
func
.
args
.
size
()
&&
func
.
args
[
args_copied
].
name
)
{
handle
value
=
PyDict_GetItemString
(
kwargs_in
,
it
->
args
[
args_copied
].
name
);
handle
value
=
PyDict_GetItemString
(
kwargs_in
,
func
.
args
[
args_copied
].
name
);
if
(
value
)
if
(
value
)
throw
type_error
(
std
::
string
(
it
->
name
)
+
"(): got multiple values for argument '"
+
throw
type_error
(
std
::
string
(
func
.
name
)
+
"(): got multiple values for argument '"
+
std
::
string
(
it
->
args
[
args_copied
].
name
)
+
"'"
);
std
::
string
(
func
.
args
[
args_copied
].
name
)
+
"'"
);
}
}
call
.
args
.
push_back
(
PyTuple_GET_ITEM
(
args_in
,
args_copied
));
call
.
args
.
push_back
(
PyTuple_GET_ITEM
(
args_in
,
args_copied
));
call
.
args_convert
.
push_back
(
args_copied
<
func
.
args
.
size
()
?
func
.
args
[
args_copied
].
convert
:
true
);
}
}
// We'll need to copy this if we steal some kwargs for defaults
// We'll need to copy this if we steal some kwargs for defaults
...
@@ -453,10 +456,10 @@ protected:
...
@@ -453,10 +456,10 @@ protected:
bool
copied_kwargs
=
false
;
bool
copied_kwargs
=
false
;
for
(;
args_copied
<
pos_args
;
++
args_copied
)
{
for
(;
args_copied
<
pos_args
;
++
args_copied
)
{
const
auto
&
arg
=
it
->
args
[
args_copied
];
const
auto
&
arg
=
func
.
args
[
args_copied
];
handle
value
;
handle
value
;
if
(
kwargs_in
)
if
(
kwargs_in
&&
arg
.
name
)
value
=
PyDict_GetItemString
(
kwargs
.
ptr
(),
arg
.
name
);
value
=
PyDict_GetItemString
(
kwargs
.
ptr
(),
arg
.
name
);
if
(
value
)
{
if
(
value
)
{
...
@@ -470,8 +473,10 @@ protected:
...
@@ -470,8 +473,10 @@ protected:
value
=
arg
.
value
;
value
=
arg
.
value
;
}
}
if
(
value
)
if
(
value
)
{
call
.
args
.
push_back
(
value
);
call
.
args
.
push_back
(
value
);
call
.
args_convert
.
push_back
(
arg
.
convert
);
}
else
else
break
;
break
;
}
}
...
@@ -481,12 +486,12 @@ protected:
...
@@ -481,12 +486,12 @@ protected:
}
}
// 3. Check everything was consumed (unless we have a kwargs arg)
// 3. Check everything was consumed (unless we have a kwargs arg)
if
(
kwargs
&&
kwargs
.
size
()
>
0
&&
!
it
->
has_kwargs
)
if
(
kwargs
&&
kwargs
.
size
()
>
0
&&
!
func
.
has_kwargs
)
continue
;
// Unconsumed kwargs, but no py::kwargs argument to accept them
continue
;
// Unconsumed kwargs, but no py::kwargs argument to accept them
// 4a. If we have a py::args argument, create a new tuple with leftovers
// 4a. If we have a py::args argument, create a new tuple with leftovers
tuple
extra_args
;
tuple
extra_args
;
if
(
it
->
has_args
)
{
if
(
func
.
has_args
)
{
if
(
args_to_copy
==
0
)
{
if
(
args_to_copy
==
0
)
{
// We didn't copy out any position arguments from the args_in tuple, so we
// We didn't copy out any position arguments from the args_in tuple, so we
// can reuse it directly without copying:
// can reuse it directly without copying:
...
@@ -502,31 +507,34 @@ protected:
...
@@ -502,31 +507,34 @@ protected:
}
}
}
}
call
.
args
.
push_back
(
extra_args
);
call
.
args
.
push_back
(
extra_args
);
call
.
args_convert
.
push_back
(
false
);
}
}
// 4b. If we have a py::kwargs, pass on any remaining kwargs
// 4b. If we have a py::kwargs, pass on any remaining kwargs
if
(
it
->
has_kwargs
)
{
if
(
func
.
has_kwargs
)
{
if
(
!
kwargs
.
ptr
())
if
(
!
kwargs
.
ptr
())
kwargs
=
dict
();
// If we didn't get one, send an empty one
kwargs
=
dict
();
// If we didn't get one, send an empty one
call
.
args
.
push_back
(
kwargs
);
call
.
args
.
push_back
(
kwargs
);
call
.
args_convert
.
push_back
(
false
);
}
}
// 5. Put everything in a vector. Not technically step 5, we've been building it
// 5. Put everything in a vector. Not technically step 5, we've been building it
// in `call.args` all along.
// in `call.args` all along.
#if !defined(NDEBUG)
#if !defined(NDEBUG)
if
(
call
.
args
.
size
()
!=
call
.
func
.
nargs
)
if
(
call
.
args
.
size
()
!=
func
.
nargs
||
call
.
args_convert
.
size
()
!=
func
.
nargs
)
pybind11_fail
(
"Internal error: function call dispatcher inserted wrong number of arguments!"
);
pybind11_fail
(
"Internal error: function call dispatcher inserted wrong number of arguments!"
);
#endif
#endif
// 6. Call the function.
// 6. Call the function.
try
{
try
{
result
=
it
->
impl
(
call
);
result
=
func
.
impl
(
call
);
}
catch
(
reference_cast_error
&
)
{
}
catch
(
reference_cast_error
&
)
{
result
=
PYBIND11_TRY_NEXT_OVERLOAD
;
result
=
PYBIND11_TRY_NEXT_OVERLOAD
;
}
}
if
(
result
.
ptr
()
!=
PYBIND11_TRY_NEXT_OVERLOAD
)
if
(
result
.
ptr
()
!=
PYBIND11_TRY_NEXT_OVERLOAD
)
break
;
break
;
}
}
}
catch
(
error_already_set
&
e
)
{
}
catch
(
error_already_set
&
e
)
{
e
.
restore
();
e
.
restore
();
...
...
tests/test_methods_and_attributes.cpp
View file @
18e34cb2
...
@@ -97,6 +97,42 @@ public:
...
@@ -97,6 +97,42 @@ public:
class
CppDerivedDynamicClass
:
public
DynamicClass
{
};
class
CppDerivedDynamicClass
:
public
DynamicClass
{
};
// py::arg/py::arg_v testing: these arguments just record their argument when invoked
class
ArgInspector1
{
public
:
std
::
string
arg
=
"(default arg inspector 1)"
;
};
class
ArgInspector2
{
public
:
std
::
string
arg
=
"(default arg inspector 2)"
;
};
namespace
pybind11
{
namespace
detail
{
template
<>
struct
type_caster
<
ArgInspector1
>
{
public
:
PYBIND11_TYPE_CASTER
(
ArgInspector1
,
_
(
"ArgInspector1"
));
bool
load
(
handle
src
,
bool
convert
)
{
value
.
arg
=
"loading ArgInspector1 argument "
+
std
::
string
(
convert
?
"WITH"
:
"WITHOUT"
)
+
" conversion allowed. "
"Argument value = "
+
(
std
::
string
)
str
(
src
);
return
true
;
}
static
handle
cast
(
const
ArgInspector1
&
src
,
return_value_policy
,
handle
)
{
return
str
(
src
.
arg
).
release
();
}
};
template
<>
struct
type_caster
<
ArgInspector2
>
{
public
:
PYBIND11_TYPE_CASTER
(
ArgInspector2
,
_
(
"ArgInspector2"
));
bool
load
(
handle
src
,
bool
convert
)
{
value
.
arg
=
"loading ArgInspector2 argument "
+
std
::
string
(
convert
?
"WITH"
:
"WITHOUT"
)
+
" conversion allowed. "
"Argument value = "
+
(
std
::
string
)
str
(
src
);
return
true
;
}
static
handle
cast
(
const
ArgInspector2
&
src
,
return_value_policy
,
handle
)
{
return
str
(
src
.
arg
).
release
();
}
};
}}
test_initializer
methods_and_attributes
([](
py
::
module
&
m
)
{
test_initializer
methods_and_attributes
([](
py
::
module
&
m
)
{
py
::
class_
<
ExampleMandA
>
(
m
,
"ExampleMandA"
)
py
::
class_
<
ExampleMandA
>
(
m
,
"ExampleMandA"
)
.
def
(
py
::
init
<>
())
.
def
(
py
::
init
<>
())
...
@@ -183,4 +219,25 @@ test_initializer methods_and_attributes([](py::module &m) {
...
@@ -183,4 +219,25 @@ test_initializer methods_and_attributes([](py::module &m) {
py
::
class_
<
CppDerivedDynamicClass
,
DynamicClass
>
(
m
,
"CppDerivedDynamicClass"
)
py
::
class_
<
CppDerivedDynamicClass
,
DynamicClass
>
(
m
,
"CppDerivedDynamicClass"
)
.
def
(
py
::
init
());
.
def
(
py
::
init
());
#endif
#endif
class
ArgInspector
{
public
:
ArgInspector1
f
(
ArgInspector1
a
)
{
return
a
;
}
std
::
string
g
(
ArgInspector1
a
,
const
ArgInspector1
&
b
,
int
c
,
ArgInspector2
*
d
)
{
return
a
.
arg
+
"
\n
"
+
b
.
arg
+
"
\n
"
+
std
::
to_string
(
c
)
+
"
\n
"
+
d
->
arg
;
}
static
ArgInspector2
h
(
ArgInspector2
a
)
{
return
a
;
}
};
py
::
class_
<
ArgInspector
>
(
m
,
"ArgInspector"
)
.
def
(
py
::
init
<>
())
.
def
(
"f"
,
&
ArgInspector
::
f
)
.
def
(
"g"
,
&
ArgInspector
::
g
,
"a"
_a
.
noconvert
(),
"b"
_a
,
"c"
_a
.
noconvert
()
=
13
,
"d"
_a
=
ArgInspector2
())
.
def_static
(
"h"
,
&
ArgInspector
::
h
,
py
::
arg
().
noconvert
())
;
m
.
def
(
"arg_inspect_func"
,
[](
ArgInspector2
a
,
ArgInspector1
b
)
{
return
a
.
arg
+
"
\n
"
+
b
.
arg
;
},
py
::
arg
().
noconvert
(
false
),
py
::
arg_v
(
nullptr
,
ArgInspector1
()).
noconvert
(
true
));
m
.
def
(
"floats_preferred"
,
[](
double
f
)
{
return
0.5
*
f
;
},
py
::
arg
(
"f"
));
m
.
def
(
"floats_only"
,
[](
double
f
)
{
return
0.5
*
f
;
},
py
::
arg
(
"f"
).
noconvert
());
});
});
tests/test_methods_and_attributes.py
View file @
18e34cb2
...
@@ -203,3 +203,47 @@ def test_cyclic_gc():
...
@@ -203,3 +203,47 @@ def test_cyclic_gc():
assert
cstats
.
alive
()
==
2
assert
cstats
.
alive
()
==
2
del
i1
,
i2
del
i1
,
i2
assert
cstats
.
alive
()
==
0
assert
cstats
.
alive
()
==
0
def
test_noconvert_args
(
msg
):
from
pybind11_tests
import
ArgInspector
,
arg_inspect_func
,
floats_only
,
floats_preferred
a
=
ArgInspector
()
assert
msg
(
a
.
f
(
"hi"
))
==
"""
loading ArgInspector1 argument WITH conversion allowed. Argument value = hi
"""
assert
msg
(
a
.
g
(
"this is a"
,
"this is b"
))
==
"""
loading ArgInspector1 argument WITHOUT conversion allowed. Argument value = this is a
loading ArgInspector1 argument WITH conversion allowed. Argument value = this is b
13
loading ArgInspector2 argument WITH conversion allowed. Argument value = (default arg inspector 2)
"""
# noqa: E501 line too long
assert
msg
(
a
.
g
(
"this is a"
,
"this is b"
,
42
))
==
"""
loading ArgInspector1 argument WITHOUT conversion allowed. Argument value = this is a
loading ArgInspector1 argument WITH conversion allowed. Argument value = this is b
42
loading ArgInspector2 argument WITH conversion allowed. Argument value = (default arg inspector 2)
"""
# noqa: E501 line too long
assert
msg
(
a
.
g
(
"this is a"
,
"this is b"
,
42
,
"this is d"
))
==
"""
loading ArgInspector1 argument WITHOUT conversion allowed. Argument value = this is a
loading ArgInspector1 argument WITH conversion allowed. Argument value = this is b
42
loading ArgInspector2 argument WITH conversion allowed. Argument value = this is d
"""
assert
(
a
.
h
(
"arg 1"
)
==
"loading ArgInspector2 argument WITHOUT conversion allowed. Argument value = arg 1"
)
assert
msg
(
arg_inspect_func
(
"A1"
,
"A2"
))
==
"""
loading ArgInspector2 argument WITH conversion allowed. Argument value = A1
loading ArgInspector1 argument WITHOUT conversion allowed. Argument value = A2
"""
assert
floats_preferred
(
4
)
==
2.0
assert
floats_only
(
4.0
)
==
2.0
with
pytest
.
raises
(
TypeError
)
as
excinfo
:
floats_only
(
4
)
assert
msg
(
excinfo
.
value
)
==
"""
floats_only(): incompatible function arguments. The following argument types are supported:
1. (f: float) -> float
Invoked with: 4
"""
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