Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
gaoqiong
pybind11
Commits
a3dbdc67
Commit
a3dbdc67
authored
Sep 07, 2016
by
Wenzel Jakob
Committed by
GitHub
Sep 07, 2016
Browse files
Merge pull request #372 from dean0x7d/keywords
Keyword arguments and generalized unpacking for C++ API
parents
6f017cf6
60b26802
Changes
16
Hide whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
575 additions
and
130 deletions
+575
-130
docs/advanced.rst
docs/advanced.rst
+61
-9
docs/changelog.rst
docs/changelog.rst
+7
-0
include/pybind11/attr.h
include/pybind11/attr.h
+5
-49
include/pybind11/cast.h
include/pybind11/cast.h
+234
-39
include/pybind11/common.h
include/pybind11/common.h
+36
-0
include/pybind11/pybind11.h
include/pybind11/pybind11.h
+27
-0
include/pybind11/pytypes.h
include/pybind11/pytypes.h
+35
-3
include/pybind11/stl.h
include/pybind11/stl.h
+9
-9
tests/conftest.py
tests/conftest.py
+13
-5
tests/pybind11_tests.h
tests/pybind11_tests.h
+1
-0
tests/test_callbacks.cpp
tests/test_callbacks.cpp
+52
-1
tests/test_callbacks.py
tests/test_callbacks.py
+35
-0
tests/test_kwargs_and_defaults.cpp
tests/test_kwargs_and_defaults.cpp
+0
-10
tests/test_kwargs_and_defaults.py
tests/test_kwargs_and_defaults.py
+2
-5
tests/test_python_types.cpp
tests/test_python_types.cpp
+28
-0
tests/test_python_types.py
tests/test_python_types.py
+30
-0
No files found.
docs/advanced.rst
View file @
a3dbdc67
...
@@ -1622,24 +1622,76 @@ It is also possible to call python functions via ``operator()``.
...
@@ -1622,24 +1622,76 @@ It is also possible to call python functions via ``operator()``.
py::object result_py = f(1234, "hello", some_instance);
py::object result_py = f(1234, "hello", some_instance);
MyClass &result = result_py.cast<MyClass>();
MyClass &result = result_py.cast<MyClass>();
The special ``f(*args)`` and ``f(*args, **kwargs)`` syntax is also supported to
Keyword arguments are also supported. In Python, there is the usual call syntax:
supply arbitrary argument and keyword lists, although these cannot be mixed
with other parameters.
.. code-block:: python
def f(number, say, to):
... # function code
f(1234, say="hello", to=some_instance) # keyword call in Python
In C++, the same call can be made using:
.. code-block:: cpp
.. code-block:: cpp
py::function f = <...>;
using pybind11::literals; // to bring in the `_a` literal
f(1234, "say"_a="hello", "to"_a=some_instance); // keyword call in C++
Unpacking of ``*args`` and ``**kwargs`` is also possible and can be mixed with
other arguments:
.. code-block:: cpp
// * unpacking
py::tuple args = py::make_tuple(1234, "hello", some_instance);
f(*args);
// ** unpacking
py::dict kwargs = py::dict("number"_a=1234, "say"_a="hello", "to"_a=some_instance);
f(**kwargs);
// mixed keywords, * and ** unpacking
py::tuple args = py::make_tuple(1234);
py::tuple args = py::make_tuple(1234);
py::dict kwargs;
py::dict kwargs = py::dict("to"_a=some_instance);
kwargs["y"] = py::cast(5678);
f(*args, "say"_a="hello", **kwargs);
py::object result = f(*args, **kwargs);
Generalized unpacking according to PEP448_ is also supported:
.. code-block:: cpp
py::dict kwargs1 = py::dict("number"_a=1234);
py::dict kwargs2 = py::dict("to"_a=some_instance);
f(**kwargs1, "say"_a="hello", **kwargs2);
.. seealso::
.. seealso::
The file :file:`tests/test_python_types.cpp` contains a complete
The file :file:`tests/test_python_types.cpp` contains a complete
example that demonstrates passing native Python types in more detail. The
example that demonstrates passing native Python types in more detail. The
file :file:`tests/test_kwargs_and_defaults.cpp` discusses usage
file :file:`tests/test_callbacks.cpp` presents a few examples of calling
of ``args`` and ``kwargs``.
Python functions from C++, including keywords arguments and unpacking.
.. _PEP448: https://www.python.org/dev/peps/pep-0448/
Using Python's print function in C++
====================================
The usual way to write output in C++ is using ``std::cout`` while in Python one
would use ``print``. Since these methods use different buffers, mixing them can
lead to output order issues. To resolve this, pybind11 modules can use the
:func:`py::print` function which writes to Python's ``sys.stdout`` for consistency.
Python's ``print`` function is replicated in the C++ API including optional
keyword arguments ``sep``, ``end``, ``file``, ``flush``. Everything works as
expected in Python:
.. code-block:: cpp
py::print(1, 2.0, "three"); // 1 2.0 three
py::print(1, 2.0, "three", "sep"_a="-"); // 1-2.0-three
auto args = py::make_tuple("unpacked", true);
py::print("->", *args, "end"_a="<-"); // -> unpacked True <-
Default arguments revisited
Default arguments revisited
===========================
===========================
...
...
docs/changelog.rst
View file @
a3dbdc67
...
@@ -46,6 +46,13 @@ Breaking changes queued for v2.0.0 (Not yet released)
...
@@ -46,6 +46,13 @@ Breaking changes queued for v2.0.0 (Not yet released)
* Added constructors for ``str`` and ``bytes`` from zero-terminated char pointers,
* Added constructors for ``str`` and ``bytes`` from zero-terminated char pointers,
and from char pointers and length.
and from char pointers and length.
* Added ``memoryview`` wrapper type which is constructible from ``buffer_info``.
* Added ``memoryview`` wrapper type which is constructible from ``buffer_info``.
* New syntax to call a Python function from C++ using keyword arguments and unpacking,
e.g. ``foo(1, 2, "z"_a=3)`` or ``bar(1, *args, "z"_a=3, **kwargs)``.
* Added ``py::print()`` function which replicates Python's API and writes to Python's
``sys.stdout`` by default (as opposed to C's ``stdout`` like ``std::cout``).
* Added ``py::dict`` keyword constructor:``auto d = dict("number"_a=42, "name"_a="World");``
* Added ``py::str::format()`` method and ``_s`` literal:
``py::str s = "1 + 2 = {}"_s.format(3);``
* Various minor improvements of library internals (no user-visible changes)
* Various minor improvements of library internals (no user-visible changes)
1.8.1 (July 12, 2016)
1.8.1 (July 12, 2016)
...
...
include/pybind11/attr.h
View file @
a3dbdc67
...
@@ -14,35 +14,6 @@
...
@@ -14,35 +14,6 @@
NAMESPACE_BEGIN
(
pybind11
)
NAMESPACE_BEGIN
(
pybind11
)
template
<
typename
T
>
struct
arg_t
;
/// Annotation for keyword arguments
struct
arg
{
constexpr
explicit
arg
(
const
char
*
name
)
:
name
(
name
)
{
}
template
<
typename
T
>
constexpr
arg_t
<
T
>
operator
=
(
const
T
&
value
)
const
{
return
{
name
,
value
};
}
template
<
typename
T
,
size_t
N
>
constexpr
arg_t
<
const
T
*>
operator
=
(
T
const
(
&
value
)[
N
])
const
{
return
operator
=
((
const
T
*
)
value
);
}
const
char
*
name
;
};
/// Annotation for keyword arguments with default values
template
<
typename
T
>
struct
arg_t
:
public
arg
{
constexpr
arg_t
(
const
char
*
name
,
const
T
&
value
,
const
char
*
descr
=
nullptr
)
:
arg
(
name
),
value
(
value
),
descr
(
descr
)
{
}
T
value
;
const
char
*
descr
;
};
inline
namespace
literals
{
/// String literal version of arg
constexpr
arg
operator
""
_a
(
const
char
*
name
,
size_t
)
{
return
arg
(
name
);
}
}
/// Annotation for methods
/// Annotation for methods
struct
is_method
{
handle
class_
;
is_method
(
const
handle
&
c
)
:
class_
(
c
)
{
}
};
struct
is_method
{
handle
class_
;
is_method
(
const
handle
&
c
)
:
class_
(
c
)
{
}
};
...
@@ -238,21 +209,14 @@ template <> struct process_attribute<arg> : process_attribute_default<arg> {
...
@@ -238,21 +209,14 @@ template <> struct process_attribute<arg> : process_attribute_default<arg> {
};
};
/// Process a keyword argument attribute (*with* a default value)
/// Process a keyword argument attribute (*with* a default value)
template
<
typename
T
>
template
<
>
struct
process_attribute
<
arg_v
>
:
process_attribute_default
<
arg_v
>
{
struct
process_attribute
<
arg_t
<
T
>>
:
process_attribute_default
<
arg_t
<
T
>>
{
static
void
init
(
const
arg_v
&
a
,
function_record
*
r
)
{
static
void
init
(
const
arg_t
<
T
>
&
a
,
function_record
*
r
)
{
if
(
r
->
class_
&&
r
->
args
.
empty
())
if
(
r
->
class_
&&
r
->
args
.
empty
())
r
->
args
.
emplace_back
(
"self"
,
nullptr
,
handle
());
r
->
args
.
emplace_back
(
"self"
,
nullptr
,
handle
());
/* Convert keyword value into a Python object */
if
(
!
a
.
value
)
{
object
o
=
object
(
detail
::
type_caster
<
typename
detail
::
intrinsic_type
<
T
>::
type
>::
cast
(
a
.
value
,
return_value_policy
::
automatic
,
handle
()),
false
);
if
(
!
o
)
{
#if !defined(NDEBUG)
#if !defined(NDEBUG)
std
::
string
descr
(
typeid
(
T
).
name
());
auto
descr
=
"'"
+
std
::
string
(
a
.
name
)
+
": "
+
a
.
type
+
"'"
;
detail
::
clean_type_id
(
descr
);
descr
=
"'"
+
std
::
string
(
a
.
name
)
+
": "
+
descr
+
"'"
;
if
(
r
->
class_
)
{
if
(
r
->
class_
)
{
if
(
r
->
name
)
if
(
r
->
name
)
descr
+=
" in method '"
+
(
std
::
string
)
r
->
class_
.
str
()
+
"."
+
(
std
::
string
)
r
->
name
+
"'"
;
descr
+=
" in method '"
+
(
std
::
string
)
r
->
class_
.
str
()
+
"."
+
(
std
::
string
)
r
->
name
+
"'"
;
...
@@ -269,7 +233,7 @@ struct process_attribute<arg_t<T>> : process_attribute_default<arg_t<T>> {
...
@@ -269,7 +233,7 @@ struct process_attribute<arg_t<T>> : process_attribute_default<arg_t<T>> {
"Compile in debug mode for more information."
);
"Compile in debug mode for more information."
);
#endif
#endif
}
}
r
->
args
.
emplace_back
(
a
.
name
,
a
.
descr
,
o
.
release
());
r
->
args
.
emplace_back
(
a
.
name
,
a
.
descr
,
a
.
value
.
inc_ref
());
}
}
};
};
...
@@ -301,9 +265,6 @@ template <int Nurse, int Patient> struct process_attribute<keep_alive<Nurse, Pat
...
@@ -301,9 +265,6 @@ template <int Nurse, int Patient> struct process_attribute<keep_alive<Nurse, Pat
static
void
postcall
(
handle
args
,
handle
ret
)
{
keep_alive_impl
(
Nurse
,
Patient
,
args
,
ret
);
}
static
void
postcall
(
handle
args
,
handle
ret
)
{
keep_alive_impl
(
Nurse
,
Patient
,
args
,
ret
);
}
};
};
/// Ignore that a variable is unused in compiler warnings
inline
void
ignore_unused
(
const
int
*
)
{
}
/// Recursively iterate over variadic template arguments
/// Recursively iterate over variadic template arguments
template
<
typename
...
Args
>
struct
process_attributes
{
template
<
typename
...
Args
>
struct
process_attributes
{
static
void
init
(
const
Args
&
...
args
,
function_record
*
r
)
{
static
void
init
(
const
Args
&
...
args
,
function_record
*
r
)
{
...
@@ -324,11 +285,6 @@ template <typename... Args> struct process_attributes {
...
@@ -324,11 +285,6 @@ template <typename... Args> struct process_attributes {
}
}
};
};
/// Compile-time integer sum
constexpr
size_t
constexpr_sum
()
{
return
0
;
}
template
<
typename
T
,
typename
...
Ts
>
constexpr
size_t
constexpr_sum
(
T
n
,
Ts
...
ns
)
{
return
n
+
constexpr_sum
(
ns
...);
}
/// Check the number of named arguments at compile time
/// Check the number of named arguments at compile time
template
<
typename
...
Extra
,
template
<
typename
...
Extra
,
size_t
named
=
constexpr_sum
(
std
::
is_base_of
<
arg
,
Extra
>
::
value
...),
size_t
named
=
constexpr_sum
(
std
::
is_base_of
<
arg
,
Extra
>
::
value
...),
...
...
include/pybind11/cast.h
View file @
a3dbdc67
...
@@ -57,6 +57,7 @@ PYBIND11_NOINLINE inline internals &get_internals() {
...
@@ -57,6 +57,7 @@ PYBIND11_NOINLINE inline internals &get_internals() {
}
catch
(
const
index_error
&
e
)
{
PyErr_SetString
(
PyExc_IndexError
,
e
.
what
());
return
;
}
catch
(
const
index_error
&
e
)
{
PyErr_SetString
(
PyExc_IndexError
,
e
.
what
());
return
;
}
catch
(
const
key_error
&
e
)
{
PyErr_SetString
(
PyExc_KeyError
,
e
.
what
());
return
;
}
catch
(
const
key_error
&
e
)
{
PyErr_SetString
(
PyExc_KeyError
,
e
.
what
());
return
;
}
catch
(
const
value_error
&
e
)
{
PyErr_SetString
(
PyExc_ValueError
,
e
.
what
());
return
;
}
catch
(
const
value_error
&
e
)
{
PyErr_SetString
(
PyExc_ValueError
,
e
.
what
());
return
;
}
catch
(
const
type_error
&
e
)
{
PyErr_SetString
(
PyExc_TypeError
,
e
.
what
());
return
;
}
catch
(
const
stop_iteration
&
e
)
{
PyErr_SetString
(
PyExc_StopIteration
,
e
.
what
());
return
;
}
catch
(
const
stop_iteration
&
e
)
{
PyErr_SetString
(
PyExc_StopIteration
,
e
.
what
());
return
;
}
catch
(
const
std
::
bad_alloc
&
e
)
{
PyErr_SetString
(
PyExc_MemoryError
,
e
.
what
());
return
;
}
catch
(
const
std
::
bad_alloc
&
e
)
{
PyErr_SetString
(
PyExc_MemoryError
,
e
.
what
());
return
;
}
catch
(
const
std
::
domain_error
&
e
)
{
PyErr_SetString
(
PyExc_ValueError
,
e
.
what
());
return
;
}
catch
(
const
std
::
domain_error
&
e
)
{
PyErr_SetString
(
PyExc_ValueError
,
e
.
what
());
return
;
...
@@ -251,8 +252,8 @@ protected:
...
@@ -251,8 +252,8 @@ protected:
/* Determine suitable casting operator */
/* Determine suitable casting operator */
template
<
typename
T
>
template
<
typename
T
>
using
cast_op_type
=
typename
std
::
conditional
<
std
::
is_pointer
<
typename
std
::
remove_reference
<
T
>::
type
>::
value
,
using
cast_op_type
=
typename
std
::
conditional
<
std
::
is_pointer
<
typename
std
::
remove_reference
<
T
>::
type
>::
value
,
typename
std
::
add_pointer
<
typename
intrinsic_t
ype
<
T
>::
type
>::
type
,
typename
std
::
add_pointer
<
intrinsic_t
<
T
>
>::
type
,
typename
std
::
add_lvalue_reference
<
typename
intrinsic_t
ype
<
T
>::
type
>::
type
>::
type
;
typename
std
::
add_lvalue_reference
<
intrinsic_t
<
T
>
>::
type
>::
type
;
/// Generic type caster for objects stored on the heap
/// Generic type caster for objects stored on the heap
template
<
typename
type
>
class
type_caster_base
:
public
type_caster_generic
{
template
<
typename
type
>
class
type_caster_base
:
public
type_caster_generic
{
...
@@ -308,6 +309,7 @@ protected:
...
@@ -308,6 +309,7 @@ protected:
};
};
template
<
typename
type
,
typename
SFINAE
=
void
>
class
type_caster
:
public
type_caster_base
<
type
>
{
};
template
<
typename
type
,
typename
SFINAE
=
void
>
class
type_caster
:
public
type_caster_base
<
type
>
{
};
template
<
typename
type
>
using
make_caster
=
type_caster
<
intrinsic_t
<
type
>>
;
template
<
typename
type
>
class
type_caster
<
std
::
reference_wrapper
<
type
>>
:
public
type_caster_base
<
type
>
{
template
<
typename
type
>
class
type_caster
<
std
::
reference_wrapper
<
type
>>
:
public
type_caster_base
<
type
>
{
public:
public:
...
@@ -610,8 +612,8 @@ public:
...
@@ -610,8 +612,8 @@ public:
}
}
static
handle
cast
(
const
type
&
src
,
return_value_policy
policy
,
handle
parent
)
{
static
handle
cast
(
const
type
&
src
,
return_value_policy
policy
,
handle
parent
)
{
object
o1
=
object
(
typ
e_caster
<
typename
intrinsic_type
<
T1
>::
type
>::
cast
(
src
.
first
,
policy
,
parent
),
false
);
object
o1
=
object
(
mak
e_caster
<
T1
>::
cast
(
src
.
first
,
policy
,
parent
),
false
);
object
o2
=
object
(
typ
e_caster
<
typename
intrinsic_type
<
T2
>::
type
>::
cast
(
src
.
second
,
policy
,
parent
),
false
);
object
o2
=
object
(
mak
e_caster
<
T2
>::
cast
(
src
.
second
,
policy
,
parent
),
false
);
if
(
!
o1
||
!
o2
)
if
(
!
o1
||
!
o2
)
return
handle
();
return
handle
();
tuple
result
(
2
);
tuple
result
(
2
);
...
@@ -622,24 +624,24 @@ public:
...
@@ -622,24 +624,24 @@ public:
static
PYBIND11_DESCR
name
()
{
static
PYBIND11_DESCR
name
()
{
return
type_descr
(
return
type_descr
(
_
(
"Tuple["
)
+
typ
e_caster
<
typename
intrinsic_type
<
T1
>::
type
>::
name
()
+
_
(
"Tuple["
)
+
mak
e_caster
<
T1
>::
name
()
+
_
(
", "
)
+
make_caster
<
T2
>::
name
()
+
_
(
"]"
)
_
(
", "
)
+
type_caster
<
typename
intrinsic_type
<
T2
>::
type
>::
name
()
+
_
(
"]"
)
);
);
}
}
template
<
typename
T
>
using
cast_op_type
=
type
;
template
<
typename
T
>
using
cast_op_type
=
type
;
operator
type
()
{
operator
type
()
{
return
type
(
first
.
operator
typename
typ
e_caster
<
typename
intrinsic_type
<
T1
>::
type
>::
template
cast_op_type
<
T1
>(),
return
type
(
first
.
operator
typename
mak
e_caster
<
T1
>::
template
cast_op_type
<
T1
>(),
second
.
operator
typename
typ
e_caster
<
typename
intrinsic_type
<
T2
>::
type
>::
template
cast_op_type
<
T2
>());
second
.
operator
typename
mak
e_caster
<
T2
>::
template
cast_op_type
<
T2
>());
}
}
protected:
protected:
typ
e_caster
<
typename
intrinsic_type
<
T1
>::
type
>
first
;
mak
e_caster
<
T1
>
first
;
typ
e_caster
<
typename
intrinsic_type
<
T2
>::
type
>
second
;
mak
e_caster
<
T2
>
second
;
};
};
template
<
typename
...
Tuple
>
class
type_caster
<
std
::
tuple
<
Tuple
...
>>
{
template
<
typename
...
Tuple
>
class
type_caster
<
std
::
tuple
<
Tuple
...
>>
{
typedef
std
::
tuple
<
Tuple
...
>
type
;
typedef
std
::
tuple
<
Tuple
...
>
type
;
typedef
std
::
tuple
<
typename
intrinsic_t
ype
<
Tuple
>
::
type
...
>
itype
;
typedef
std
::
tuple
<
intrinsic_t
<
Tuple
>
...
>
itype
;
typedef
std
::
tuple
<
args
>
args_type
;
typedef
std
::
tuple
<
args
>
args_type
;
typedef
std
::
tuple
<
args
,
kwargs
>
args_kwargs_type
;
typedef
std
::
tuple
<
args
,
kwargs
>
args_kwargs_type
;
public:
public:
...
@@ -679,7 +681,7 @@ public:
...
@@ -679,7 +681,7 @@ public:
}
}
static
PYBIND11_DESCR
element_names
()
{
static
PYBIND11_DESCR
element_names
()
{
return
detail
::
concat
(
typ
e_caster
<
typename
intrinsic_type
<
Tuple
>::
typ
e
>::
name
()...);
return
detail
::
concat
(
mak
e_caster
<
Tupl
e
>::
name
()...);
}
}
static
PYBIND11_DESCR
name
()
{
static
PYBIND11_DESCR
name
()
{
...
@@ -704,12 +706,12 @@ public:
...
@@ -704,12 +706,12 @@ public:
protected:
protected:
template
<
typename
ReturnValue
,
typename
Func
,
size_t
...
Index
>
ReturnValue
call
(
Func
&&
f
,
index_sequence
<
Index
...
>
)
{
template
<
typename
ReturnValue
,
typename
Func
,
size_t
...
Index
>
ReturnValue
call
(
Func
&&
f
,
index_sequence
<
Index
...
>
)
{
return
f
(
std
::
get
<
Index
>
(
value
)
return
f
(
std
::
get
<
Index
>
(
value
)
.
operator
typename
typ
e_caster
<
typename
intrinsic_type
<
Tuple
>::
typ
e
>::
template
cast_op_type
<
Tuple
>()...);
.
operator
typename
mak
e_caster
<
Tupl
e
>::
template
cast_op_type
<
Tuple
>()...);
}
}
template
<
size_t
...
Index
>
type
cast
(
index_sequence
<
Index
...
>
)
{
template
<
size_t
...
Index
>
type
cast
(
index_sequence
<
Index
...
>
)
{
return
type
(
std
::
get
<
Index
>
(
value
)
return
type
(
std
::
get
<
Index
>
(
value
)
.
operator
typename
typ
e_caster
<
typename
intrinsic_type
<
Tuple
>::
typ
e
>::
template
cast_op_type
<
Tuple
>()...);
.
operator
typename
mak
e_caster
<
Tupl
e
>::
template
cast_op_type
<
Tuple
>()...);
}
}
template
<
size_t
...
Indices
>
bool
load
(
handle
src
,
bool
convert
,
index_sequence
<
Indices
...
>
)
{
template
<
size_t
...
Indices
>
bool
load
(
handle
src
,
bool
convert
,
index_sequence
<
Indices
...
>
)
{
...
@@ -726,7 +728,7 @@ protected:
...
@@ -726,7 +728,7 @@ protected:
/* Implementation: Convert a C++ tuple into a Python tuple */
/* Implementation: Convert a C++ tuple into a Python tuple */
template
<
size_t
...
Indices
>
static
handle
cast
(
const
type
&
src
,
return_value_policy
policy
,
handle
parent
,
index_sequence
<
Indices
...
>
)
{
template
<
size_t
...
Indices
>
static
handle
cast
(
const
type
&
src
,
return_value_policy
policy
,
handle
parent
,
index_sequence
<
Indices
...
>
)
{
std
::
array
<
object
,
size
>
entries
{{
std
::
array
<
object
,
size
>
entries
{{
object
(
typ
e_caster
<
typename
intrinsic_type
<
Tuple
>::
typ
e
>::
cast
(
std
::
get
<
Indices
>
(
src
),
policy
,
parent
),
false
)...
object
(
mak
e_caster
<
Tupl
e
>::
cast
(
std
::
get
<
Indices
>
(
src
),
policy
,
parent
),
false
)...
}};
}};
for
(
const
auto
&
entry
:
entries
)
for
(
const
auto
&
entry
:
entries
)
if
(
!
entry
)
if
(
!
entry
)
...
@@ -739,7 +741,7 @@ protected:
...
@@ -739,7 +741,7 @@ protected:
}
}
protected:
protected:
std
::
tuple
<
typ
e_caster
<
typename
intrinsic_type
<
Tuple
>::
typ
e
>
...
>
value
;
std
::
tuple
<
mak
e_caster
<
Tupl
e
>
...
>
value
;
};
};
/// Type caster for holder types like std::shared_ptr, etc.
/// Type caster for holder types like std::shared_ptr, etc.
...
@@ -846,7 +848,7 @@ template <typename T> using move_never = std::integral_constant<bool, !move_alwa
...
@@ -846,7 +848,7 @@ template <typename T> using move_never = std::integral_constant<bool, !move_alwa
NAMESPACE_END
(
detail
)
NAMESPACE_END
(
detail
)
template
<
typename
T
>
T
cast
(
const
handle
&
handle
)
{
template
<
typename
T
>
T
cast
(
const
handle
&
handle
)
{
typedef
detail
::
type_caster
<
typename
detail
::
intrinsic_type
<
T
>::
type
>
typ
e_caster
;
using
type_caster
=
detail
::
mak
e_caster
<
T
>
;
type_caster
conv
;
type_caster
conv
;
if
(
!
conv
.
load
(
handle
,
true
))
{
if
(
!
conv
.
load
(
handle
,
true
))
{
#if defined(NDEBUG)
#if defined(NDEBUG)
...
@@ -866,7 +868,7 @@ template <typename T> object cast(const T &value,
...
@@ -866,7 +868,7 @@ template <typename T> object cast(const T &value,
policy
=
std
::
is_pointer
<
T
>::
value
?
return_value_policy
::
take_ownership
:
return_value_policy
::
copy
;
policy
=
std
::
is_pointer
<
T
>::
value
?
return_value_policy
::
take_ownership
:
return_value_policy
::
copy
;
else
if
(
policy
==
return_value_policy
::
automatic_reference
)
else
if
(
policy
==
return_value_policy
::
automatic_reference
)
policy
=
std
::
is_pointer
<
T
>::
value
?
return_value_policy
::
reference
:
return_value_policy
::
copy
;
policy
=
std
::
is_pointer
<
T
>::
value
?
return_value_policy
::
reference
:
return_value_policy
::
copy
;
return
object
(
detail
::
typ
e_caster
<
typename
detail
::
intrinsic_type
<
T
>::
type
>::
cast
(
value
,
policy
,
parent
),
false
);
return
object
(
detail
::
mak
e_caster
<
T
>::
cast
(
value
,
policy
,
parent
),
false
);
}
}
template
<
typename
T
>
T
handle
::
cast
()
const
{
return
pybind11
::
cast
<
T
>
(
*
this
);
}
template
<
typename
T
>
T
handle
::
cast
()
const
{
return
pybind11
::
cast
<
T
>
(
*
this
);
}
...
@@ -927,7 +929,7 @@ template <return_value_policy policy = return_value_policy::automatic_reference,
...
@@ -927,7 +929,7 @@ template <return_value_policy policy = return_value_policy::automatic_reference,
typename
...
Args
>
tuple
make_tuple
(
Args
&&
...
args_
)
{
typename
...
Args
>
tuple
make_tuple
(
Args
&&
...
args_
)
{
const
size_t
size
=
sizeof
...(
Args
);
const
size_t
size
=
sizeof
...(
Args
);
std
::
array
<
object
,
size
>
args
{
std
::
array
<
object
,
size
>
args
{
{
object
(
detail
::
typ
e_caster
<
typename
detail
::
intrinsic_type
<
Args
>::
type
>::
cast
(
{
object
(
detail
::
mak
e_caster
<
Args
>::
cast
(
std
::
forward
<
Args
>
(
args_
),
policy
,
nullptr
),
false
)...
}
std
::
forward
<
Args
>
(
args_
),
policy
,
nullptr
),
false
)...
}
};
};
for
(
auto
&
arg_value
:
args
)
{
for
(
auto
&
arg_value
:
args
)
{
...
@@ -947,32 +949,225 @@ template <return_value_policy policy = return_value_policy::automatic_reference,
...
@@ -947,32 +949,225 @@ template <return_value_policy policy = return_value_policy::automatic_reference,
return
result
;
return
result
;
}
}
template
<
return_value_policy
policy
,
/// Annotation for keyword arguments
typename
...
Args
>
object
handle
::
operator
()(
Args
&&
...
args
)
const
{
struct
arg
{
tuple
args_tuple
=
pybind11
::
make_tuple
<
policy
>
(
std
::
forward
<
Args
>
(
args
)...);
constexpr
explicit
arg
(
const
char
*
name
)
:
name
(
name
)
{
}
object
result
(
PyObject_CallObject
(
m_ptr
,
args_tuple
.
ptr
()),
false
);
template
<
typename
T
>
arg_v
operator
=
(
T
&&
value
)
const
;
if
(
!
result
)
throw
error_already_set
();
const
char
*
name
;
return
result
;
};
/// Annotation for keyword arguments with values
struct
arg_v
:
arg
{
template
<
typename
T
>
arg_v
(
const
char
*
name
,
T
&&
x
,
const
char
*
descr
=
nullptr
)
:
arg
(
name
),
value
(
detail
::
make_caster
<
T
>::
cast
(
x
,
return_value_policy
::
automatic
,
handle
()),
false
),
descr
(
descr
)
#if !defined(NDEBUG)
,
type
(
type_id
<
T
>
())
#endif
{
}
object
value
;
const
char
*
descr
;
#if !defined(NDEBUG)
std
::
string
type
;
#endif
};
template
<
typename
T
>
arg_v
arg
::
operator
=
(
T
&&
value
)
const
{
return
{
name
,
std
::
forward
<
T
>
(
value
)};
}
/// Alias for backward compatibility -- to be remove in version 2.0
template
<
typename
/*unused*/
>
using
arg_t
=
arg_v
;
inline
namespace
literals
{
/// String literal version of arg
constexpr
arg
operator
""
_a
(
const
char
*
name
,
size_t
)
{
return
arg
(
name
);
}
}
}
template
<
return_value_policy
policy
,
NAMESPACE_BEGIN
(
detail
)
typename
...
Args
>
object
handle
::
call
(
Args
&&
...
args
)
const
{
NAMESPACE_BEGIN
(
constexpr_impl
)
return
operator
()
<
policy
>
(
std
::
forward
<
Args
>
(
args
)...);
/// Implementation details for constexpr functions
constexpr
int
first
(
int
i
)
{
return
i
;
}
template
<
typename
T
,
typename
...
Ts
>
constexpr
int
first
(
int
i
,
T
v
,
Ts
...
vs
)
{
return
v
?
i
:
first
(
i
+
1
,
vs
...);
}
constexpr
int
last
(
int
/*i*/
,
int
result
)
{
return
result
;
}
template
<
typename
T
,
typename
...
Ts
>
constexpr
int
last
(
int
i
,
int
result
,
T
v
,
Ts
...
vs
)
{
return
last
(
i
+
1
,
v
?
i
:
result
,
vs
...);
}
NAMESPACE_END
(
constexpr_impl
)
/// Return the index of the first type in Ts which satisfies Predicate<T>
template
<
template
<
typename
>
class
Predicate
,
typename
...
Ts
>
constexpr
int
constexpr_first
()
{
return
constexpr_impl
::
first
(
0
,
Predicate
<
Ts
>::
value
...);
}
/// Return the index of the last type in Ts which satisfies Predicate<T>
template
<
template
<
typename
>
class
Predicate
,
typename
...
Ts
>
constexpr
int
constexpr_last
()
{
return
constexpr_impl
::
last
(
0
,
-
1
,
Predicate
<
Ts
>::
value
...);
}
/// Helper class which collects only positional arguments for a Python function call.
/// A fancier version below can collect any argument, but this one is optimal for simple calls.
template
<
return_value_policy
policy
>
class
simple_collector
{
public:
template
<
typename
...
Ts
>
simple_collector
(
Ts
&&
...
values
)
:
m_args
(
pybind11
::
make_tuple
<
policy
>
(
std
::
forward
<
Ts
>
(
values
)...))
{
}
const
tuple
&
args
()
const
&
{
return
m_args
;
}
dict
kwargs
()
const
{
return
{};
}
tuple
args
()
&&
{
return
std
::
move
(
m_args
);
}
/// Call a Python function and pass the collected arguments
object
call
(
PyObject
*
ptr
)
const
{
auto
result
=
object
(
PyObject_CallObject
(
ptr
,
m_args
.
ptr
()),
false
);
if
(
!
result
)
throw
error_already_set
();
return
result
;
}
private:
tuple
m_args
;
};
/// Helper class which collects positional, keyword, * and ** arguments for a Python function call
template
<
return_value_policy
policy
>
class
unpacking_collector
{
public:
template
<
typename
...
Ts
>
unpacking_collector
(
Ts
&&
...
values
)
{
// Tuples aren't (easily) resizable so a list is needed for collection,
// but the actual function call strictly requires a tuple.
auto
args_list
=
list
();
int
_
[]
=
{
0
,
(
process
(
args_list
,
std
::
forward
<
Ts
>
(
values
)),
0
)...
};
ignore_unused
(
_
);
m_args
=
object
(
PyList_AsTuple
(
args_list
.
ptr
()),
false
);
}
const
tuple
&
args
()
const
&
{
return
m_args
;
}
const
dict
&
kwargs
()
const
&
{
return
m_kwargs
;
}
tuple
args
()
&&
{
return
std
::
move
(
m_args
);
}
dict
kwargs
()
&&
{
return
std
::
move
(
m_kwargs
);
}
/// Call a Python function and pass the collected arguments
object
call
(
PyObject
*
ptr
)
const
{
auto
result
=
object
(
PyObject_Call
(
ptr
,
m_args
.
ptr
(),
m_kwargs
.
ptr
()),
false
);
if
(
!
result
)
throw
error_already_set
();
return
result
;
}
private:
template
<
typename
T
>
void
process
(
list
&
args_list
,
T
&&
x
)
{
auto
o
=
object
(
detail
::
make_caster
<
T
>::
cast
(
std
::
forward
<
T
>
(
x
),
policy
,
nullptr
),
false
);
if
(
!
o
)
{
#if defined(NDEBUG)
argument_cast_error
();
#else
argument_cast_error
(
std
::
to_string
(
args_list
.
size
()),
type_id
<
T
>
());
#endif
}
args_list
.
append
(
o
);
}
void
process
(
list
&
args_list
,
detail
::
args_proxy
ap
)
{
for
(
const
auto
&
a
:
ap
)
{
args_list
.
append
(
a
.
cast
<
object
>
());
}
}
void
process
(
list
&
/*args_list*/
,
arg_v
a
)
{
if
(
m_kwargs
[
a
.
name
])
{
#if defined(NDEBUG)
multiple_values_error
();
#else
multiple_values_error
(
a
.
name
);
#endif
}
if
(
!
a
.
value
)
{
#if defined(NDEBUG)
argument_cast_error
();
#else
argument_cast_error
(
a
.
name
,
a
.
type
);
#endif
}
m_kwargs
[
a
.
name
]
=
a
.
value
;
}
void
process
(
list
&
/*args_list*/
,
detail
::
kwargs_proxy
kp
)
{
for
(
const
auto
&
k
:
dict
(
kp
,
true
))
{
if
(
m_kwargs
[
k
.
first
])
{
#if defined(NDEBUG)
multiple_values_error
();
#else
multiple_values_error
(
k
.
first
.
str
());
#endif
}
m_kwargs
[
k
.
first
]
=
k
.
second
;
}
}
[[
noreturn
]]
static
void
multiple_values_error
()
{
throw
type_error
(
"Got multiple values for keyword argument "
"(compile in debug mode for details)"
);
}
[[
noreturn
]]
static
void
multiple_values_error
(
std
::
string
name
)
{
throw
type_error
(
"Got multiple values for keyword argument '"
+
name
+
"'"
);
}
[[
noreturn
]]
static
void
argument_cast_error
()
{
throw
cast_error
(
"Unable to convert call argument to Python object "
"(compile in debug mode for details)"
);
}
[[
noreturn
]]
static
void
argument_cast_error
(
std
::
string
name
,
std
::
string
type
)
{
throw
cast_error
(
"Unable to convert call argument '"
+
name
+
"' of type '"
+
type
+
"' to Python object"
);
}
private:
tuple
m_args
;
dict
m_kwargs
;
};
/// Collect only positional arguments for a Python function call
template
<
return_value_policy
policy
,
typename
...
Args
,
typename
=
enable_if_t
<
all_of_t
<
is_positional
,
Args
...>
::
value
>>
simple_collector
<
policy
>
collect_arguments
(
Args
&&
...
args
)
{
return
{
std
::
forward
<
Args
>
(
args
)...};
}
}
inline
object
handle
::
operator
()(
detail
::
args_proxy
args
)
const
{
/// Collect all arguments, including keywords and unpacking (only instantiated when needed)
object
result
(
PyObject_CallObject
(
m_ptr
,
args
.
ptr
()),
false
);
template
<
return_value_policy
policy
,
typename
...
Args
,
if
(
!
result
)
typename
=
enable_if_t
<!
all_of_t
<
is_positional
,
Args
...>
::
value
>>
throw
error_already_set
();
unpacking_collector
<
policy
>
collect_arguments
(
Args
&&
...
args
)
{
return
result
;
// Following argument order rules for generalized unpacking according to PEP 448
static_assert
(
constexpr_last
<
is_positional
,
Args
...
>
()
<
constexpr_first
<
is_keyword_or_ds
,
Args
...
>
()
&&
constexpr_last
<
is_s_unpacking
,
Args
...
>
()
<
constexpr_first
<
is_ds_unpacking
,
Args
...
>
(),
"Invalid function call: positional args must precede keywords and ** unpacking; "
"* unpacking must precede ** unpacking"
);
return
{
std
::
forward
<
Args
>
(
args
)...};
}
}
inline
object
handle
::
operator
()(
detail
::
args_proxy
args
,
detail
::
kwargs_proxy
kwargs
)
const
{
NAMESPACE_END
(
detail
)
object
result
(
PyObject_Call
(
m_ptr
,
args
.
ptr
(),
kwargs
.
ptr
()),
false
);
if
(
!
result
)
template
<
return_value_policy
policy
,
typename
...
Args
>
throw
error_already_set
();
object
handle
::
operator
()(
Args
&&
...
args
)
const
{
return
result
;
return
detail
::
collect_arguments
<
policy
>
(
std
::
forward
<
Args
>
(
args
)...).
call
(
m_ptr
);
}
template
<
return_value_policy
policy
,
typename
...
Args
>
object
handle
::
call
(
Args
&&
...
args
)
const
{
return
operator
()
<
policy
>
(
std
::
forward
<
Args
>
(
args
)...);
}
}
#define PYBIND11_MAKE_OPAQUE(Type) \
#define PYBIND11_MAKE_OPAQUE(Type) \
...
...
include/pybind11/common.h
View file @
a3dbdc67
...
@@ -326,10 +326,45 @@ template <typename T> struct intrinsic_type<T&> { typedef type
...
@@ -326,10 +326,45 @@ template <typename T> struct intrinsic_type<T&> { typedef type
template
<
typename
T
>
struct
intrinsic_type
<
T
&&>
{
typedef
typename
intrinsic_type
<
T
>::
type
type
;
};
template
<
typename
T
>
struct
intrinsic_type
<
T
&&>
{
typedef
typename
intrinsic_type
<
T
>::
type
type
;
};
template
<
typename
T
,
size_t
N
>
struct
intrinsic_type
<
const
T
[
N
]
>
{
typedef
typename
intrinsic_type
<
T
>::
type
type
;
};
template
<
typename
T
,
size_t
N
>
struct
intrinsic_type
<
const
T
[
N
]
>
{
typedef
typename
intrinsic_type
<
T
>::
type
type
;
};
template
<
typename
T
,
size_t
N
>
struct
intrinsic_type
<
T
[
N
]
>
{
typedef
typename
intrinsic_type
<
T
>::
type
type
;
};
template
<
typename
T
,
size_t
N
>
struct
intrinsic_type
<
T
[
N
]
>
{
typedef
typename
intrinsic_type
<
T
>::
type
type
;
};
template
<
typename
T
>
using
intrinsic_t
=
typename
intrinsic_type
<
T
>::
type
;
/// Helper type to replace 'void' in some expressions
/// Helper type to replace 'void' in some expressions
struct
void_type
{
};
struct
void_type
{
};
/// from __cpp_future__ import (convenient aliases from C++14/17)
template
<
bool
B
>
using
bool_constant
=
std
::
integral_constant
<
bool
,
B
>
;
template
<
class
T
>
using
negation
=
bool_constant
<!
T
::
value
>
;
template
<
bool
B
,
typename
T
=
void
>
using
enable_if_t
=
typename
std
::
enable_if
<
B
,
T
>::
type
;
template
<
bool
B
,
typename
T
,
typename
F
>
using
conditional_t
=
typename
std
::
conditional
<
B
,
T
,
F
>::
type
;
/// Compile-time integer sum
constexpr
size_t
constexpr_sum
()
{
return
0
;
}
template
<
typename
T
,
typename
...
Ts
>
constexpr
size_t
constexpr_sum
(
T
n
,
Ts
...
ns
)
{
return
size_t
{
n
}
+
constexpr_sum
(
ns
...);
}
/// Return true if all/any Ts satify Predicate<T>
#if !defined(_MSC_VER)
template
<
template
<
typename
>
class
Predicate
,
typename
...
Ts
>
using
all_of_t
=
bool_constant
<
(
constexpr_sum
(
Predicate
<
Ts
>::
value
...)
==
sizeof
...(
Ts
))
>
;
template
<
template
<
typename
>
class
Predicate
,
typename
...
Ts
>
using
any_of_t
=
bool_constant
<
(
constexpr_sum
(
Predicate
<
Ts
>::
value
...)
>
0
)
>
;
#else
// MSVC workaround (2015 Update 3 has issues with some member type aliases and constexpr)
template
<
template
<
typename
>
class
P
,
typename
...
>
struct
all_of_t
:
std
::
true_type
{
};
template
<
template
<
typename
>
class
P
,
typename
T
,
typename
...
Ts
>
struct
all_of_t
<
P
,
T
,
Ts
...
>
:
conditional_t
<
P
<
T
>::
value
,
all_of_t
<
P
,
Ts
...
>
,
std
::
false_type
>
{
};
template
<
template
<
typename
>
class
P
,
typename
...
>
struct
any_of_t
:
std
::
false_type
{
};
template
<
template
<
typename
>
class
P
,
typename
T
,
typename
...
Ts
>
struct
any_of_t
<
P
,
T
,
Ts
...
>
:
conditional_t
<
P
<
T
>::
value
,
std
::
true_type
,
any_of_t
<
P
,
Ts
...
>>
{
};
#endif
/// Defer the evaluation of type T until types Us are instantiated
template
<
typename
T
,
typename
...
/*Us*/
>
struct
deferred_type
{
using
type
=
T
;
};
template
<
typename
T
,
typename
...
Us
>
using
deferred_t
=
typename
deferred_type
<
T
,
Us
...
>::
type
;
/// Ignore that a variable is unused in compiler warnings
inline
void
ignore_unused
(
const
int
*
)
{
}
NAMESPACE_END
(
detail
)
NAMESPACE_END
(
detail
)
#define PYBIND11_RUNTIME_EXCEPTION(name) \
#define PYBIND11_RUNTIME_EXCEPTION(name) \
...
@@ -345,6 +380,7 @@ PYBIND11_RUNTIME_EXCEPTION(stop_iteration)
...
@@ -345,6 +380,7 @@ PYBIND11_RUNTIME_EXCEPTION(stop_iteration)
PYBIND11_RUNTIME_EXCEPTION
(
index_error
)
PYBIND11_RUNTIME_EXCEPTION
(
index_error
)
PYBIND11_RUNTIME_EXCEPTION
(
key_error
)
PYBIND11_RUNTIME_EXCEPTION
(
key_error
)
PYBIND11_RUNTIME_EXCEPTION
(
value_error
)
PYBIND11_RUNTIME_EXCEPTION
(
value_error
)
PYBIND11_RUNTIME_EXCEPTION
(
type_error
)
PYBIND11_RUNTIME_EXCEPTION
(
cast_error
)
/// Thrown when pybind11::cast or handle::call fail due to a type casting error
PYBIND11_RUNTIME_EXCEPTION
(
cast_error
)
/// Thrown when pybind11::cast or handle::call fail due to a type casting error
PYBIND11_RUNTIME_EXCEPTION
(
reference_cast_error
)
/// Used internally
PYBIND11_RUNTIME_EXCEPTION
(
reference_cast_error
)
/// Used internally
...
...
include/pybind11/pybind11.h
View file @
a3dbdc67
...
@@ -1233,6 +1233,33 @@ public:
...
@@ -1233,6 +1233,33 @@ public:
}
}
};
};
NAMESPACE_BEGIN
(
detail
)
PYBIND11_NOINLINE
inline
void
print
(
tuple
args
,
dict
kwargs
)
{
auto
strings
=
tuple
(
args
.
size
());
for
(
size_t
i
=
0
;
i
<
args
.
size
();
++
i
)
{
strings
[
i
]
=
args
[
i
].
cast
<
object
>
().
str
();
}
auto
sep
=
kwargs
[
"sep"
]
?
kwargs
[
"sep"
]
:
cast
(
" "
);
auto
line
=
sep
.
attr
(
"join"
).
cast
<
object
>
()(
strings
);
auto
file
=
kwargs
[
"file"
]
?
kwargs
[
"file"
].
cast
<
object
>
()
:
module
::
import
(
"sys"
).
attr
(
"stdout"
);
auto
write
=
file
.
attr
(
"write"
).
cast
<
object
>
();
write
(
line
);
write
(
kwargs
[
"end"
]
?
kwargs
[
"end"
]
:
cast
(
"
\n
"
));
if
(
kwargs
[
"flush"
]
&&
kwargs
[
"flush"
].
cast
<
bool
>
())
{
file
.
attr
(
"flush"
).
cast
<
object
>
()();
}
}
NAMESPACE_END
(
detail
)
template
<
return_value_policy
policy
=
return_value_policy
::
automatic_reference
,
typename
...
Args
>
void
print
(
Args
&&
...
args
)
{
auto
c
=
detail
::
collect_arguments
<
policy
>
(
std
::
forward
<
Args
>
(
args
)...);
detail
::
print
(
c
.
args
(),
c
.
kwargs
());
}
#if defined(WITH_THREAD)
#if defined(WITH_THREAD)
/* The functions below essentially reproduce the PyGILState_* API using a RAII
/* The functions below essentially reproduce the PyGILState_* API using a RAII
...
...
include/pybind11/pytypes.h
View file @
a3dbdc67
...
@@ -16,7 +16,8 @@
...
@@ -16,7 +16,8 @@
NAMESPACE_BEGIN
(
pybind11
)
NAMESPACE_BEGIN
(
pybind11
)
/* A few forward declarations */
/* A few forward declarations */
class
object
;
class
str
;
class
object
;
class
dict
;
class
iterator
;
class
object
;
class
str
;
class
iterator
;
struct
arg
;
struct
arg_v
;
namespace
detail
{
class
accessor
;
class
args_proxy
;
class
kwargs_proxy
;
}
namespace
detail
{
class
accessor
;
class
args_proxy
;
class
kwargs_proxy
;
}
/// Holds a reference to a Python object (no reference counting)
/// Holds a reference to a Python object (no reference counting)
...
@@ -47,8 +48,6 @@ public:
...
@@ -47,8 +48,6 @@ public:
object
call
(
Args
&&
...
args
)
const
;
object
call
(
Args
&&
...
args
)
const
;
template
<
return_value_policy
policy
=
return_value_policy
::
automatic_reference
,
typename
...
Args
>
template
<
return_value_policy
policy
=
return_value_policy
::
automatic_reference
,
typename
...
Args
>
object
operator
()(
Args
&&
...
args
)
const
;
object
operator
()(
Args
&&
...
args
)
const
;
inline
object
operator
()(
detail
::
args_proxy
args
)
const
;
inline
object
operator
()(
detail
::
args_proxy
f_args
,
detail
::
kwargs_proxy
kwargs
)
const
;
operator
bool
()
const
{
return
m_ptr
!=
nullptr
;
}
operator
bool
()
const
{
return
m_ptr
!=
nullptr
;
}
bool
operator
==
(
const
handle
&
h
)
const
{
return
m_ptr
==
h
.
m_ptr
;
}
bool
operator
==
(
const
handle
&
h
)
const
{
return
m_ptr
==
h
.
m_ptr
;
}
bool
operator
!=
(
const
handle
&
h
)
const
{
return
m_ptr
!=
h
.
m_ptr
;
}
bool
operator
!=
(
const
handle
&
h
)
const
{
return
m_ptr
!=
h
.
m_ptr
;
}
...
@@ -249,6 +248,23 @@ public:
...
@@ -249,6 +248,23 @@ public:
kwargs_proxy
operator
*
()
const
{
return
kwargs_proxy
(
*
this
);
}
kwargs_proxy
operator
*
()
const
{
return
kwargs_proxy
(
*
this
);
}
};
};
/// Python argument categories (using PEP 448 terms)
template
<
typename
T
>
using
is_keyword
=
std
::
is_base_of
<
arg
,
T
>
;
template
<
typename
T
>
using
is_s_unpacking
=
std
::
is_same
<
args_proxy
,
T
>
;
// * unpacking
template
<
typename
T
>
using
is_ds_unpacking
=
std
::
is_same
<
kwargs_proxy
,
T
>
;
// ** unpacking
template
<
typename
T
>
using
is_positional
=
bool_constant
<
!
is_keyword
<
T
>::
value
&&
!
is_s_unpacking
<
T
>::
value
&&
!
is_ds_unpacking
<
T
>::
value
>
;
template
<
typename
T
>
using
is_keyword_or_ds
=
bool_constant
<
is_keyword
<
T
>::
value
||
is_ds_unpacking
<
T
>::
value
>
;
// Call argument collector forward declarations
template
<
return_value_policy
policy
=
return_value_policy
::
automatic_reference
>
class
simple_collector
;
template
<
return_value_policy
policy
=
return_value_policy
::
automatic_reference
>
class
unpacking_collector
;
NAMESPACE_END
(
detail
)
NAMESPACE_END
(
detail
)
#define PYBIND11_OBJECT_CVT(Name, Parent, CheckFun, CvtStmt) \
#define PYBIND11_OBJECT_CVT(Name, Parent, CheckFun, CvtStmt) \
...
@@ -373,8 +389,18 @@ public:
...
@@ -373,8 +389,18 @@ public:
pybind11_fail
(
"Unable to extract string contents! (invalid type)"
);
pybind11_fail
(
"Unable to extract string contents! (invalid type)"
);
return
std
::
string
(
buffer
,
(
size_t
)
length
);
return
std
::
string
(
buffer
,
(
size_t
)
length
);
}
}
template
<
typename
...
Args
>
str
format
(
Args
&&
...
args
)
const
{
return
attr
(
"format"
).
cast
<
object
>
()(
std
::
forward
<
Args
>
(
args
)...);
}
};
};
inline
namespace
literals
{
/// String literal version of str
inline
str
operator
""
_s
(
const
char
*
s
,
size_t
size
)
{
return
{
s
,
size
};
}
}
inline
pybind11
::
str
handle
::
str
()
const
{
inline
pybind11
::
str
handle
::
str
()
const
{
PyObject
*
strValue
=
PyObject_Str
(
m_ptr
);
PyObject
*
strValue
=
PyObject_Str
(
m_ptr
);
#if PY_MAJOR_VERSION < 3
#if PY_MAJOR_VERSION < 3
...
@@ -567,6 +593,12 @@ public:
...
@@ -567,6 +593,12 @@ public:
dict
()
:
object
(
PyDict_New
(),
false
)
{
dict
()
:
object
(
PyDict_New
(),
false
)
{
if
(
!
m_ptr
)
pybind11_fail
(
"Could not allocate dict object!"
);
if
(
!
m_ptr
)
pybind11_fail
(
"Could not allocate dict object!"
);
}
}
template
<
typename
...
Args
,
typename
=
detail
::
enable_if_t
<
detail
::
all_of_t
<
detail
::
is_keyword_or_ds
,
Args
...>
::
value
>
,
// MSVC workaround: it can't compile an out-of-line definition, so defer the collector
typename
collector
=
detail
::
deferred_t
<
detail
::
unpacking_collector
<>
,
Args
...
>>
dict
(
Args
&&
...
args
)
:
dict
(
collector
(
std
::
forward
<
Args
>
(
args
)...).
kwargs
())
{
}
size_t
size
()
const
{
return
(
size_t
)
PyDict_Size
(
m_ptr
);
}
size_t
size
()
const
{
return
(
size_t
)
PyDict_Size
(
m_ptr
);
}
detail
::
dict_iterator
begin
()
const
{
return
(
++
detail
::
dict_iterator
(
*
this
,
0
));
}
detail
::
dict_iterator
begin
()
const
{
return
(
++
detail
::
dict_iterator
(
*
this
,
0
));
}
detail
::
dict_iterator
end
()
const
{
return
detail
::
dict_iterator
();
}
detail
::
dict_iterator
end
()
const
{
return
detail
::
dict_iterator
();
}
...
...
include/pybind11/stl.h
View file @
a3dbdc67
...
@@ -26,8 +26,8 @@ NAMESPACE_BEGIN(pybind11)
...
@@ -26,8 +26,8 @@ NAMESPACE_BEGIN(pybind11)
NAMESPACE_BEGIN
(
detail
)
NAMESPACE_BEGIN
(
detail
)
template
<
typename
Type
,
typename
Key
>
struct
set_caster
{
template
<
typename
Type
,
typename
Key
>
struct
set_caster
{
typedef
Type
t
ype
;
using
type
=
T
ype
;
typedef
type_caster
<
typename
intrinsic_type
<
Key
>::
type
>
key_conv
;
using
key_conv
=
make_caster
<
Key
>
;
bool
load
(
handle
src
,
bool
convert
)
{
bool
load
(
handle
src
,
bool
convert
)
{
pybind11
::
set
s
(
src
,
true
);
pybind11
::
set
s
(
src
,
true
);
...
@@ -57,9 +57,9 @@ template <typename Type, typename Key> struct set_caster {
...
@@ -57,9 +57,9 @@ template <typename Type, typename Key> struct set_caster {
};
};
template
<
typename
Type
,
typename
Key
,
typename
Value
>
struct
map_caster
{
template
<
typename
Type
,
typename
Key
,
typename
Value
>
struct
map_caster
{
typedef
Type
t
ype
;
using
type
=
T
ype
;
typedef
type_caster
<
typename
intrinsic_type
<
Key
>::
type
>
key_conv
;
using
key_conv
=
make_caster
<
Key
>
;
typedef
type_caster
<
typename
intrinsic_type
<
Value
>::
type
>
value_conv
;
using
value_conv
=
make_caster
<
Value
>
;
bool
load
(
handle
src
,
bool
convert
)
{
bool
load
(
handle
src
,
bool
convert
)
{
dict
d
(
src
,
true
);
dict
d
(
src
,
true
);
...
@@ -93,8 +93,8 @@ template <typename Type, typename Key, typename Value> struct map_caster {
...
@@ -93,8 +93,8 @@ template <typename Type, typename Key, typename Value> struct map_caster {
};
};
template
<
typename
Type
,
typename
Value
>
struct
list_caster
{
template
<
typename
Type
,
typename
Value
>
struct
list_caster
{
typedef
Type
t
ype
;
using
type
=
T
ype
;
typedef
type_caster
<
typename
intrinsic_type
<
Value
>::
type
>
value_conv
;
using
value_conv
=
make_caster
<
Value
>
;
bool
load
(
handle
src
,
bool
convert
)
{
bool
load
(
handle
src
,
bool
convert
)
{
list
l
(
src
,
true
);
list
l
(
src
,
true
);
...
@@ -138,8 +138,8 @@ template <typename Type, typename Alloc> struct type_caster<std::list<Type, Allo
...
@@ -138,8 +138,8 @@ template <typename Type, typename Alloc> struct type_caster<std::list<Type, Allo
:
list_caster
<
std
::
list
<
Type
,
Alloc
>
,
Type
>
{
};
:
list_caster
<
std
::
list
<
Type
,
Alloc
>
,
Type
>
{
};
template
<
typename
Type
,
size_t
Size
>
struct
type_caster
<
std
::
array
<
Type
,
Size
>>
{
template
<
typename
Type
,
size_t
Size
>
struct
type_caster
<
std
::
array
<
Type
,
Size
>>
{
type
def
std
::
array
<
Type
,
Size
>
array_type
;
using
array_
type
=
std
::
array
<
Type
,
Size
>
;
typedef
type_caster
<
typename
intrinsic_type
<
Type
>::
type
>
value_conv
;
using
value_conv
=
make_caster
<
Type
>
;
bool
load
(
handle
src
,
bool
convert
)
{
bool
load
(
handle
src
,
bool
convert
)
{
list
l
(
src
,
true
);
list
l
(
src
,
true
);
...
...
tests/conftest.py
View file @
a3dbdc67
...
@@ -68,18 +68,22 @@ class Capture(object):
...
@@ -68,18 +68,22 @@ class Capture(object):
def
__init__
(
self
,
capfd
):
def
__init__
(
self
,
capfd
):
self
.
capfd
=
capfd
self
.
capfd
=
capfd
self
.
out
=
""
self
.
out
=
""
self
.
err
=
""
def
_flush_stdout
(
self
):
def
_flush
(
self
):
"""Workaround for issues on Windows: to be removed after tests get py::print"""
sys
.
stdout
.
flush
()
sys
.
stdout
.
flush
()
os
.
fsync
(
sys
.
stdout
.
fileno
())
# make sure C++ output is also read
os
.
fsync
(
sys
.
stdout
.
fileno
())
return
self
.
capfd
.
readouterr
()[
0
]
sys
.
stderr
.
flush
()
os
.
fsync
(
sys
.
stderr
.
fileno
())
return
self
.
capfd
.
readouterr
()
def
__enter__
(
self
):
def
__enter__
(
self
):
self
.
_flush
_stdout
()
self
.
_flush
()
return
self
return
self
def
__exit__
(
self
,
*
_
):
def
__exit__
(
self
,
*
_
):
self
.
out
=
self
.
_flush
_stdout
()
self
.
out
,
self
.
err
=
self
.
_flush
()
def
__eq__
(
self
,
other
):
def
__eq__
(
self
,
other
):
a
=
Output
(
self
.
out
)
a
=
Output
(
self
.
out
)
...
@@ -100,6 +104,10 @@ class Capture(object):
...
@@ -100,6 +104,10 @@ class Capture(object):
def
unordered
(
self
):
def
unordered
(
self
):
return
Unordered
(
self
.
out
)
return
Unordered
(
self
.
out
)
@
property
def
stderr
(
self
):
return
Output
(
self
.
err
)
@
pytest
.
fixture
@
pytest
.
fixture
def
capture
(
capfd
):
def
capture
(
capfd
):
...
...
tests/pybind11_tests.h
View file @
a3dbdc67
...
@@ -8,6 +8,7 @@ using std::cout;
...
@@ -8,6 +8,7 @@ using std::cout;
using
std
::
endl
;
using
std
::
endl
;
namespace
py
=
pybind11
;
namespace
py
=
pybind11
;
using
namespace
pybind11
::
literals
;
class
test_initializer
{
class
test_initializer
{
public:
public:
...
...
tests/test_callbacks.cpp
View file @
a3dbdc67
...
@@ -71,6 +71,9 @@ struct Payload {
...
@@ -71,6 +71,9 @@ struct Payload {
}
}
};
};
/// Something to trigger a conversion error
struct
Unregistered
{};
test_initializer
callbacks
([](
py
::
module
&
m
)
{
test_initializer
callbacks
([](
py
::
module
&
m
)
{
m
.
def
(
"test_callback1"
,
&
test_callback1
);
m
.
def
(
"test_callback1"
,
&
test_callback1
);
m
.
def
(
"test_callback2"
,
&
test_callback2
);
m
.
def
(
"test_callback2"
,
&
test_callback2
);
...
@@ -78,8 +81,56 @@ test_initializer callbacks([](py::module &m) {
...
@@ -78,8 +81,56 @@ test_initializer callbacks([](py::module &m) {
m
.
def
(
"test_callback4"
,
&
test_callback4
);
m
.
def
(
"test_callback4"
,
&
test_callback4
);
m
.
def
(
"test_callback5"
,
&
test_callback5
);
m
.
def
(
"test_callback5"
,
&
test_callback5
);
/* Test cleanup of lambda closure */
// Test keyword args and generalized unpacking
m
.
def
(
"test_tuple_unpacking"
,
[](
py
::
function
f
)
{
auto
t1
=
py
::
make_tuple
(
2
,
3
);
auto
t2
=
py
::
make_tuple
(
5
,
6
);
return
f
(
"positional"
,
1
,
*
t1
,
4
,
*
t2
);
});
m
.
def
(
"test_dict_unpacking"
,
[](
py
::
function
f
)
{
auto
d1
=
py
::
dict
(
"key"
_a
=
"value"
,
"a"
_a
=
1
);
auto
d2
=
py
::
dict
();
auto
d3
=
py
::
dict
(
"b"
_a
=
2
);
return
f
(
"positional"
,
1
,
**
d1
,
**
d2
,
**
d3
);
});
m
.
def
(
"test_keyword_args"
,
[](
py
::
function
f
)
{
return
f
(
"x"
_a
=
10
,
"y"
_a
=
20
);
});
m
.
def
(
"test_unpacking_and_keywords1"
,
[](
py
::
function
f
)
{
auto
args
=
py
::
make_tuple
(
2
);
auto
kwargs
=
py
::
dict
(
"d"
_a
=
4
);
return
f
(
1
,
*
args
,
"c"
_a
=
3
,
**
kwargs
);
});
m
.
def
(
"test_unpacking_and_keywords2"
,
[](
py
::
function
f
)
{
auto
kwargs1
=
py
::
dict
(
"a"
_a
=
1
);
auto
kwargs2
=
py
::
dict
(
"c"
_a
=
3
,
"d"
_a
=
4
);
return
f
(
"positional"
,
*
py
::
make_tuple
(
1
),
2
,
*
py
::
make_tuple
(
3
,
4
),
5
,
"key"
_a
=
"value"
,
**
kwargs1
,
"b"
_a
=
2
,
**
kwargs2
,
"e"
_a
=
5
);
});
m
.
def
(
"test_unpacking_error1"
,
[](
py
::
function
f
)
{
auto
kwargs
=
py
::
dict
(
"x"
_a
=
3
);
return
f
(
"x"
_a
=
1
,
"y"
_a
=
2
,
**
kwargs
);
// duplicate ** after keyword
});
m
.
def
(
"test_unpacking_error2"
,
[](
py
::
function
f
)
{
auto
kwargs
=
py
::
dict
(
"x"
_a
=
3
);
return
f
(
**
kwargs
,
"x"
_a
=
1
);
// duplicate keyword after **
});
m
.
def
(
"test_arg_conversion_error1"
,
[](
py
::
function
f
)
{
f
(
234
,
Unregistered
(),
"kw"
_a
=
567
);
});
m
.
def
(
"test_arg_conversion_error2"
,
[](
py
::
function
f
)
{
f
(
234
,
"expected_name"
_a
=
Unregistered
(),
"kw"
_a
=
567
);
});
/* Test cleanup of lambda closure */
m
.
def
(
"test_cleanup"
,
[]()
->
std
::
function
<
void
(
void
)
>
{
m
.
def
(
"test_cleanup"
,
[]()
->
std
::
function
<
void
(
void
)
>
{
Payload
p
;
Payload
p
;
...
...
tests/test_callbacks.py
View file @
a3dbdc67
...
@@ -27,6 +27,41 @@ def test_callbacks():
...
@@ -27,6 +27,41 @@ def test_callbacks():
assert
f
(
number
=
43
)
==
44
assert
f
(
number
=
43
)
==
44
def
test_keyword_args_and_generalized_unpacking
():
from
pybind11_tests
import
(
test_tuple_unpacking
,
test_dict_unpacking
,
test_keyword_args
,
test_unpacking_and_keywords1
,
test_unpacking_and_keywords2
,
test_unpacking_error1
,
test_unpacking_error2
,
test_arg_conversion_error1
,
test_arg_conversion_error2
)
def
f
(
*
args
,
**
kwargs
):
return
args
,
kwargs
assert
test_tuple_unpacking
(
f
)
==
((
"positional"
,
1
,
2
,
3
,
4
,
5
,
6
),
{})
assert
test_dict_unpacking
(
f
)
==
((
"positional"
,
1
),
{
"key"
:
"value"
,
"a"
:
1
,
"b"
:
2
})
assert
test_keyword_args
(
f
)
==
((),
{
"x"
:
10
,
"y"
:
20
})
assert
test_unpacking_and_keywords1
(
f
)
==
((
1
,
2
),
{
"c"
:
3
,
"d"
:
4
})
assert
test_unpacking_and_keywords2
(
f
)
==
(
(
"positional"
,
1
,
2
,
3
,
4
,
5
),
{
"key"
:
"value"
,
"a"
:
1
,
"b"
:
2
,
"c"
:
3
,
"d"
:
4
,
"e"
:
5
}
)
with
pytest
.
raises
(
TypeError
)
as
excinfo
:
test_unpacking_error1
(
f
)
assert
"Got multiple values for keyword argument"
in
str
(
excinfo
.
value
)
with
pytest
.
raises
(
TypeError
)
as
excinfo
:
test_unpacking_error2
(
f
)
assert
"Got multiple values for keyword argument"
in
str
(
excinfo
.
value
)
with
pytest
.
raises
(
RuntimeError
)
as
excinfo
:
test_arg_conversion_error1
(
f
)
assert
"Unable to convert call argument"
in
str
(
excinfo
.
value
)
with
pytest
.
raises
(
RuntimeError
)
as
excinfo
:
test_arg_conversion_error2
(
f
)
assert
"Unable to convert call argument"
in
str
(
excinfo
.
value
)
def
test_lambda_closure_cleanup
():
def
test_lambda_closure_cleanup
():
from
pybind11_tests
import
test_cleanup
,
payload_cstats
from
pybind11_tests
import
test_cleanup
,
payload_cstats
...
...
tests/test_kwargs_and_defaults.cpp
View file @
a3dbdc67
...
@@ -20,13 +20,6 @@ std::string kw_func4(const std::vector<int> &entries) {
...
@@ -20,13 +20,6 @@ std::string kw_func4(const std::vector<int> &entries) {
return
ret
;
return
ret
;
}
}
py
::
object
call_kw_func
(
py
::
function
f
)
{
py
::
tuple
args
=
py
::
make_tuple
(
1234
);
py
::
dict
kwargs
;
kwargs
[
"y"
]
=
py
::
cast
(
5678
);
return
f
(
*
args
,
**
kwargs
);
}
py
::
tuple
args_function
(
py
::
args
args
)
{
py
::
tuple
args_function
(
py
::
args
args
)
{
return
args
;
return
args
;
}
}
...
@@ -49,14 +42,11 @@ test_initializer arg_keywords_and_defaults([](py::module &m) {
...
@@ -49,14 +42,11 @@ test_initializer arg_keywords_and_defaults([](py::module &m) {
std
::
vector
<
int
>
list
;
std
::
vector
<
int
>
list
;
list
.
push_back
(
13
);
list
.
push_back
(
13
);
list
.
push_back
(
17
);
list
.
push_back
(
17
);
m
.
def
(
"kw_func4"
,
&
kw_func4
,
py
::
arg
(
"myList"
)
=
list
);
m
.
def
(
"kw_func4"
,
&
kw_func4
,
py
::
arg
(
"myList"
)
=
list
);
m
.
def
(
"call_kw_func"
,
&
call_kw_func
);
m
.
def
(
"args_function"
,
&
args_function
);
m
.
def
(
"args_function"
,
&
args_function
);
m
.
def
(
"args_kwargs_function"
,
&
args_kwargs_function
);
m
.
def
(
"args_kwargs_function"
,
&
args_kwargs_function
);
using
namespace
py
::
literals
;
m
.
def
(
"kw_func_udl"
,
&
kw_func
,
"x"
_a
,
"y"
_a
=
300
);
m
.
def
(
"kw_func_udl"
,
&
kw_func
,
"x"
_a
,
"y"
_a
=
300
);
m
.
def
(
"kw_func_udl_z"
,
&
kw_func
,
"x"
_a
,
"y"
_a
=
0
);
m
.
def
(
"kw_func_udl_z"
,
&
kw_func
,
"x"
_a
,
"y"
_a
=
0
);
...
...
tests/test_kwargs_and_defaults.py
View file @
a3dbdc67
import
pytest
import
pytest
from
pybind11_tests
import
(
kw_func0
,
kw_func1
,
kw_func2
,
kw_func3
,
kw_func4
,
call_kw_func
,
from
pybind11_tests
import
(
kw_func0
,
kw_func1
,
kw_func2
,
kw_func3
,
kw_func4
,
args_function
,
args_function
,
args_kwargs_function
,
kw_func_udl
,
kw_func_udl_z
,
args_kwargs_function
,
kw_func_udl
,
kw_func_udl_z
,
KWClass
)
KWClass
)
def
test_function_signatures
(
doc
):
def
test_function_signatures
(
doc
):
...
@@ -49,8 +48,6 @@ def test_named_arguments(msg):
...
@@ -49,8 +48,6 @@ def test_named_arguments(msg):
def
test_arg_and_kwargs
():
def
test_arg_and_kwargs
():
assert
call_kw_func
(
kw_func2
)
==
"x=1234, y=5678"
args
=
'arg1_value'
,
'arg2_value'
,
3
args
=
'arg1_value'
,
'arg2_value'
,
3
assert
args_function
(
*
args
)
==
args
assert
args_function
(
*
args
)
==
args
...
...
tests/test_python_types.cpp
View file @
a3dbdc67
...
@@ -197,4 +197,32 @@ test_initializer python_types([](py::module &m) {
...
@@ -197,4 +197,32 @@ test_initializer python_types([](py::module &m) {
.
def_readwrite_static
(
"value"
,
&
ExamplePythonTypes
::
value
,
"Static value member"
)
.
def_readwrite_static
(
"value"
,
&
ExamplePythonTypes
::
value
,
"Static value member"
)
.
def_readonly_static
(
"value2"
,
&
ExamplePythonTypes
::
value2
,
"Static value member (readonly)"
)
.
def_readonly_static
(
"value2"
,
&
ExamplePythonTypes
::
value2
,
"Static value member (readonly)"
)
;
;
m
.
def
(
"test_print_function"
,
[]()
{
py
::
print
(
"Hello, World!"
);
py
::
print
(
1
,
2.0
,
"three"
,
true
,
std
::
string
(
"-- multiple args"
));
auto
args
=
py
::
make_tuple
(
"and"
,
"a"
,
"custom"
,
"separator"
);
py
::
print
(
"*args"
,
*
args
,
"sep"
_a
=
"-"
);
py
::
print
(
"no new line here"
,
"end"
_a
=
" -- "
);
py
::
print
(
"next print"
);
auto
py_stderr
=
py
::
module
::
import
(
"sys"
).
attr
(
"stderr"
).
cast
<
py
::
object
>
();
py
::
print
(
"this goes to stderr"
,
"file"
_a
=
py_stderr
);
py
::
print
(
"flush"
,
"flush"
_a
=
true
);
py
::
print
(
"{a} + {b} = {c}"
_s
.
format
(
"a"
_a
=
"py::print"
,
"b"
_a
=
"str.format"
,
"c"
_a
=
"this"
));
});
m
.
def
(
"test_str_format"
,
[]()
{
auto
s1
=
"{} + {} = {}"
_s
.
format
(
1
,
2
,
3
);
auto
s2
=
"{a} + {b} = {c}"
_s
.
format
(
"a"
_a
=
1
,
"b"
_a
=
2
,
"c"
_a
=
3
);
return
py
::
make_tuple
(
s1
,
s2
);
});
m
.
def
(
"test_dict_keyword_constructor"
,
[]()
{
auto
d1
=
py
::
dict
(
"x"
_a
=
1
,
"y"
_a
=
2
);
auto
d2
=
py
::
dict
(
"z"
_a
=
3
,
**
d1
);
return
d2
;
});
});
});
tests/test_python_types.py
View file @
a3dbdc67
...
@@ -218,3 +218,33 @@ def test_module():
...
@@ -218,3 +218,33 @@ def test_module():
assert
ExamplePythonTypes
.
__module__
==
"pybind11_tests"
assert
ExamplePythonTypes
.
__module__
==
"pybind11_tests"
assert
ExamplePythonTypes
.
get_set
.
__name__
==
"get_set"
assert
ExamplePythonTypes
.
get_set
.
__name__
==
"get_set"
assert
ExamplePythonTypes
.
get_set
.
__module__
==
"pybind11_tests"
assert
ExamplePythonTypes
.
get_set
.
__module__
==
"pybind11_tests"
def
test_print
(
capture
):
from
pybind11_tests
import
test_print_function
with
capture
:
test_print_function
()
assert
capture
==
"""
Hello, World!
1 2.0 three True -- multiple args
*args-and-a-custom-separator
no new line here -- next print
flush
py::print + str.format = this
"""
assert
capture
.
stderr
==
"this goes to stderr"
def
test_str_api
():
from
pybind11_tests
import
test_str_format
s1
,
s2
=
test_str_format
()
assert
s1
==
"1 + 2 = 3"
assert
s1
==
s2
def
test_dict_api
():
from
pybind11_tests
import
test_dict_keyword_constructor
assert
test_dict_keyword_constructor
()
==
{
"x"
:
1
,
"y"
:
2
,
"z"
:
3
}
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