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
e315e1fe
Unverified
Commit
e315e1fe
authored
Oct 04, 2021
by
Henry Schreiner
Browse files
Merge branch 'master' into stable
parents
71fd5241
97976c16
Changes
143
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
694 additions
and
289 deletions
+694
-289
include/pybind11/eigen.h
include/pybind11/eigen.h
+17
-31
include/pybind11/embed.h
include/pybind11/embed.h
+110
-27
include/pybind11/eval.h
include/pybind11/eval.h
+9
-0
include/pybind11/functional.h
include/pybind11/functional.h
+5
-1
include/pybind11/gil.h
include/pybind11/gil.h
+2
-2
include/pybind11/iostream.h
include/pybind11/iostream.h
+8
-6
include/pybind11/numpy.h
include/pybind11/numpy.h
+58
-25
include/pybind11/operators.h
include/pybind11/operators.h
+2
-12
include/pybind11/pybind11.h
include/pybind11/pybind11.h
+223
-88
include/pybind11/pytypes.h
include/pybind11/pytypes.h
+158
-44
include/pybind11/stl.h
include/pybind11/stl.h
+11
-32
include/pybind11/stl_bind.h
include/pybind11/stl_bind.h
+75
-3
noxfile.py
noxfile.py
+3
-2
pybind11/__init__.py
pybind11/__init__.py
+2
-3
pybind11/__main__.py
pybind11/__main__.py
+1
-1
pybind11/_version.py
pybind11/_version.py
+1
-1
pybind11/_version.pyi
pybind11/_version.pyi
+1
-1
pybind11/commands.py
pybind11/commands.py
+0
-1
pybind11/setup_helpers.py
pybind11/setup_helpers.py
+4
-4
pybind11/setup_helpers.pyi
pybind11/setup_helpers.pyi
+4
-5
No files found.
include/pybind11/eigen.h
View file @
e315e1fe
...
...
@@ -9,29 +9,13 @@
#pragma once
#include "numpy.h"
#if defined(__INTEL_COMPILER)
# pragma warning(disable: 1682) // implicit conversion of a 64-bit integral type to a smaller integral type (potential portability problem)
#elif defined(__GNUG__) || defined(__clang__)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wconversion"
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
# ifdef __clang__
// Eigen generates a bunch of implicit-copy-constructor-is-deprecated warnings with -Wdeprecated
// under Clang, so disable that warning here:
# pragma GCC diagnostic ignored "-Wdeprecated"
# endif
# if __GNUC__ >= 7
# pragma GCC diagnostic ignored "-Wint-in-bool-context"
# endif
#endif
/* HINT: To suppress warnings originating from the Eigen headers, use -isystem.
See also:
https://stackoverflow.com/questions/2579576/i-dir-vs-isystem-dir
https://stackoverflow.com/questions/1741816/isystem-for-ms-visual-studio-c-compiler
*/
#if defined(_MSC_VER)
# pragma warning(push)
# pragma warning(disable: 4127) // warning C4127: Conditional expression is constant
# pragma warning(disable: 4996) // warning C4996: std::unary_negate is deprecated in C++17
#endif
#include "numpy.h"
#include <Eigen/Core>
#include <Eigen/SparseCore>
...
...
@@ -77,6 +61,7 @@ template <bool EigenRowMajor> struct EigenConformable {
EigenDStride
stride
{
0
,
0
};
// Only valid if negativestrides is false!
bool
negativestrides
=
false
;
// If true, do not use stride!
// NOLINTNEXTLINE(google-explicit-constructor)
EigenConformable
(
bool
fits
=
false
)
:
conformable
{
fits
}
{}
// Matrix type:
EigenConformable
(
EigenIndex
r
,
EigenIndex
c
,
...
...
@@ -104,6 +89,7 @@ template <bool EigenRowMajor> struct EigenConformable {
(
props
::
outer_stride
==
Eigen
::
Dynamic
||
props
::
outer_stride
==
stride
.
outer
()
||
(
EigenRowMajor
?
rows
:
cols
)
==
1
);
}
// NOLINTNEXTLINE(google-explicit-constructor)
operator
bool
()
const
{
return
conformable
;
}
};
...
...
@@ -153,7 +139,8 @@ template <typename Type_> struct EigenProps {
np_cols
=
a
.
shape
(
1
),
np_rstride
=
a
.
strides
(
0
)
/
static_cast
<
ssize_t
>
(
sizeof
(
Scalar
)),
np_cstride
=
a
.
strides
(
1
)
/
static_cast
<
ssize_t
>
(
sizeof
(
Scalar
));
if
((
fixed_rows
&&
np_rows
!=
rows
)
||
(
fixed_cols
&&
np_cols
!=
cols
))
if
((
PYBIND11_SILENCE_MSVC_C4127
(
fixed_rows
)
&&
np_rows
!=
rows
)
||
(
PYBIND11_SILENCE_MSVC_C4127
(
fixed_cols
)
&&
np_cols
!=
cols
))
return
false
;
return
{
np_rows
,
np_cols
,
np_rstride
,
np_cstride
};
...
...
@@ -165,7 +152,7 @@ template <typename Type_> struct EigenProps {
stride
=
a
.
strides
(
0
)
/
static_cast
<
ssize_t
>
(
sizeof
(
Scalar
));
if
(
vector
)
{
// Eigen type is a compile-time vector
if
(
fixed
&&
size
!=
n
)
if
(
PYBIND11_SILENCE_MSVC_C4127
(
fixed
)
&&
size
!=
n
)
return
false
;
// Vector size mismatch
return
{
rows
==
1
?
1
:
n
,
cols
==
1
?
1
:
n
,
stride
};
}
...
...
@@ -179,7 +166,7 @@ template <typename Type_> struct EigenProps {
if
(
cols
!=
n
)
return
false
;
return
{
1
,
n
,
stride
};
}
// Otherwise it's either fully dynamic, or column dynamic; both become a column vector
if
(
fixed_rows
&&
rows
!=
n
)
return
false
;
if
(
PYBIND11_SILENCE_MSVC_C4127
(
fixed_rows
)
&&
rows
!=
n
)
return
false
;
return
{
n
,
1
,
stride
};
}
...
...
@@ -341,8 +328,11 @@ public:
static
constexpr
auto
name
=
props
::
descriptor
;
// NOLINTNEXTLINE(google-explicit-constructor)
operator
Type
*
()
{
return
&
value
;
}
// NOLINTNEXTLINE(google-explicit-constructor)
operator
Type
&
()
{
return
value
;
}
// NOLINTNEXTLINE(google-explicit-constructor)
operator
Type
&&
()
&&
{
return
std
::
move
(
value
);
}
template
<
typename
T
>
using
cast_op_type
=
movable_cast_op_type
<
T
>
;
...
...
@@ -466,7 +456,9 @@ public:
return
true
;
}
// NOLINTNEXTLINE(google-explicit-constructor)
operator
Type
*
()
{
return
ref
.
get
();
}
// NOLINTNEXTLINE(google-explicit-constructor)
operator
Type
&
()
{
return
*
ref
;
}
template
<
typename
_T
>
using
cast_op_type
=
pybind11
::
detail
::
cast_op_type
<
_T
>
;
...
...
@@ -596,9 +588,3 @@ struct type_caster<Type, enable_if_t<is_eigen_sparse<Type>::value>> {
PYBIND11_NAMESPACE_END
(
detail
)
PYBIND11_NAMESPACE_END
(
PYBIND11_NAMESPACE
)
#if defined(__GNUG__) || defined(__clang__)
# pragma GCC diagnostic pop
#elif defined(_MSC_VER)
# pragma warning(pop)
#endif
include/pybind11/embed.h
View file @
e315e1fe
...
...
@@ -12,6 +12,9 @@
#include "pybind11.h"
#include "eval.h"
#include <memory>
#include <vector>
#if defined(PYPY_VERSION)
# error Embedding the interpreter is not supported with PyPy
#endif
...
...
@@ -45,25 +48,23 @@
});
}
\endrst */
#define PYBIND11_EMBEDDED_MODULE(name, variable) \
static ::pybind11::module_::module_def \
PYBIND11_CONCAT(pybind11_module_def_, name); \
static void PYBIND11_CONCAT(pybind11_init_, name)(::pybind11::module_ &); \
static PyObject PYBIND11_CONCAT(*pybind11_init_wrapper_, name)() { \
auto m = ::pybind11::module_::create_extension_module( \
PYBIND11_TOSTRING(name), nullptr, \
&PYBIND11_CONCAT(pybind11_module_def_, name)); \
try { \
PYBIND11_CONCAT(pybind11_init_, name)(m); \
return m.ptr(); \
} PYBIND11_CATCH_INIT_EXCEPTIONS \
} \
PYBIND11_EMBEDDED_MODULE_IMPL(name) \
::pybind11::detail::embedded_module PYBIND11_CONCAT(pybind11_module_, name) \
(PYBIND11_TOSTRING(name), \
PYBIND11_CONCAT(pybind11_init_impl_, name)); \
void PYBIND11_CONCAT(pybind11_init_, name)(::pybind11::module_ &variable)
#define PYBIND11_EMBEDDED_MODULE(name, variable) \
static ::pybind11::module_::module_def PYBIND11_CONCAT(pybind11_module_def_, name); \
static void PYBIND11_CONCAT(pybind11_init_, name)(::pybind11::module_ &); \
static PyObject PYBIND11_CONCAT(*pybind11_init_wrapper_, name)() { \
auto m = ::pybind11::module_::create_extension_module( \
PYBIND11_TOSTRING(name), nullptr, &PYBIND11_CONCAT(pybind11_module_def_, name)); \
try { \
PYBIND11_CONCAT(pybind11_init_, name)(m); \
return m.ptr(); \
} \
PYBIND11_CATCH_INIT_EXCEPTIONS \
} \
PYBIND11_EMBEDDED_MODULE_IMPL(name) \
::pybind11::detail::embedded_module PYBIND11_CONCAT(pybind11_module_, name)( \
PYBIND11_TOSTRING(name), PYBIND11_CONCAT(pybind11_init_impl_, name)); \
void PYBIND11_CONCAT(pybind11_init_, name)(::pybind11::module_ \
& variable) // NOLINT(bugprone-macro-parentheses)
PYBIND11_NAMESPACE_BEGIN
(
PYBIND11_NAMESPACE
)
PYBIND11_NAMESPACE_BEGIN
(
detail
)
...
...
@@ -85,29 +86,106 @@ struct embedded_module {
}
};
struct
wide_char_arg_deleter
{
void
operator
()(
wchar_t
*
ptr
)
const
{
#if PY_VERSION_HEX >= 0x030500f0
// API docs: https://docs.python.org/3/c-api/sys.html#c.Py_DecodeLocale
PyMem_RawFree
(
ptr
);
#else
delete
[]
ptr
;
#endif
}
};
inline
wchar_t
*
widen_chars
(
const
char
*
safe_arg
)
{
#if PY_VERSION_HEX >= 0x030500f0
wchar_t
*
widened_arg
=
Py_DecodeLocale
(
safe_arg
,
nullptr
);
#else
wchar_t
*
widened_arg
=
nullptr
;
# if defined(HAVE_BROKEN_MBSTOWCS) && HAVE_BROKEN_MBSTOWCS
size_t
count
=
strlen
(
safe_arg
);
# else
size_t
count
=
mbstowcs
(
nullptr
,
safe_arg
,
0
);
# endif
if
(
count
!=
static_cast
<
size_t
>
(
-
1
))
{
widened_arg
=
new
wchar_t
[
count
+
1
];
mbstowcs
(
widened_arg
,
safe_arg
,
count
+
1
);
}
#endif
return
widened_arg
;
}
/// Python 2.x/3.x-compatible version of `PySys_SetArgv`
inline
void
set_interpreter_argv
(
int
argc
,
const
char
*
const
*
argv
,
bool
add_program_dir_to_path
)
{
// Before it was special-cased in python 3.8, passing an empty or null argv
// caused a segfault, so we have to reimplement the special case ourselves.
bool
special_case
=
(
argv
==
nullptr
||
argc
<=
0
);
const
char
*
const
empty_argv
[]{
"
\0
"
};
const
char
*
const
*
safe_argv
=
special_case
?
empty_argv
:
argv
;
if
(
special_case
)
argc
=
1
;
auto
argv_size
=
static_cast
<
size_t
>
(
argc
);
#if PY_MAJOR_VERSION >= 3
// SetArgv* on python 3 takes wchar_t, so we have to convert.
std
::
unique_ptr
<
wchar_t
*
[]
>
widened_argv
(
new
wchar_t
*
[
argv_size
]);
std
::
vector
<
std
::
unique_ptr
<
wchar_t
[],
wide_char_arg_deleter
>>
widened_argv_entries
;
widened_argv_entries
.
reserve
(
argv_size
);
for
(
size_t
ii
=
0
;
ii
<
argv_size
;
++
ii
)
{
widened_argv_entries
.
emplace_back
(
widen_chars
(
safe_argv
[
ii
]));
if
(
!
widened_argv_entries
.
back
())
{
// A null here indicates a character-encoding failure or the python
// interpreter out of memory. Give up.
return
;
}
widened_argv
[
ii
]
=
widened_argv_entries
.
back
().
get
();
}
auto
pysys_argv
=
widened_argv
.
get
();
#else
// python 2.x
std
::
vector
<
std
::
string
>
strings
{
safe_argv
,
safe_argv
+
argv_size
};
std
::
vector
<
char
*>
char_strings
{
argv_size
};
for
(
std
::
size_t
i
=
0
;
i
<
argv_size
;
++
i
)
char_strings
[
i
]
=
&
strings
[
i
][
0
];
char
**
pysys_argv
=
char_strings
.
data
();
#endif
PySys_SetArgvEx
(
argc
,
pysys_argv
,
static_cast
<
int
>
(
add_program_dir_to_path
));
}
PYBIND11_NAMESPACE_END
(
detail
)
/** \rst
Initialize the Python interpreter. No other pybind11 or CPython API functions can be
called before this is done; with the exception of `PYBIND11_EMBEDDED_MODULE`. The
optional parameter can be used to skip the registration of
signal handlers (see the
`Python documentation`_ for details). Calling this function
again after the interpreter
has already been initialized is a fatal error.
optional
`init_signal_handlers`
parameter can be used to skip the registration of
signal handlers (see the
`Python documentation`_ for details). Calling this function
again after the interpreter
has already been initialized is a fatal error.
If initializing the Python interpreter fails, then the program is terminated. (This
is controlled by the CPython runtime and is an exception to pybind11's normal behavior
of throwing exceptions on errors.)
The remaining optional parameters, `argc`, `argv`, and `add_program_dir_to_path` are
used to populate ``sys.argv`` and ``sys.path``.
See the |PySys_SetArgvEx documentation|_ for details.
.. _Python documentation: https://docs.python.org/3/c-api/init.html#c.Py_InitializeEx
.. |PySys_SetArgvEx documentation| replace:: ``PySys_SetArgvEx`` documentation
.. _PySys_SetArgvEx documentation: https://docs.python.org/3/c-api/init.html#c.PySys_SetArgvEx
\endrst */
inline
void
initialize_interpreter
(
bool
init_signal_handlers
=
true
)
{
inline
void
initialize_interpreter
(
bool
init_signal_handlers
=
true
,
int
argc
=
0
,
const
char
*
const
*
argv
=
nullptr
,
bool
add_program_dir_to_path
=
true
)
{
if
(
Py_IsInitialized
()
!=
0
)
pybind11_fail
(
"The interpreter is already running"
);
Py_InitializeEx
(
init_signal_handlers
?
1
:
0
);
// Make .py files in the working directory available by default
module_
::
import
(
"sys"
).
attr
(
"path"
).
cast
<
list
>
().
append
(
"."
);
detail
::
set_interpreter_argv
(
argc
,
argv
,
add_program_dir_to_path
);
}
/** \rst
...
...
@@ -169,6 +247,8 @@ inline void finalize_interpreter() {
Scope guard version of `initialize_interpreter` and `finalize_interpreter`.
This a move-only guard and only a single instance can exist.
See `initialize_interpreter` for a discussion of its constructor arguments.
.. code-block:: cpp
#include <pybind11/embed.h>
...
...
@@ -180,8 +260,11 @@ inline void finalize_interpreter() {
\endrst */
class
scoped_interpreter
{
public:
scoped_interpreter
(
bool
init_signal_handlers
=
true
)
{
initialize_interpreter
(
init_signal_handlers
);
explicit
scoped_interpreter
(
bool
init_signal_handlers
=
true
,
int
argc
=
0
,
const
char
*
const
*
argv
=
nullptr
,
bool
add_program_dir_to_path
=
true
)
{
initialize_interpreter
(
init_signal_handlers
,
argc
,
argv
,
add_program_dir_to_path
);
}
scoped_interpreter
(
const
scoped_interpreter
&
)
=
delete
;
...
...
include/pybind11/eval.h
View file @
e315e1fe
...
...
@@ -136,6 +136,15 @@ object eval_file(str fname, object global = globals(), object local = object())
pybind11_fail
(
"File
\"
"
+
fname_str
+
"
\"
could not be opened!"
);
}
// In Python2, this should be encoded by getfilesystemencoding.
// We don't boher setting it since Python2 is past EOL anyway.
// See PR#3233
#if PY_VERSION_HEX >= 0x03000000
if
(
!
global
.
contains
(
"__file__"
))
{
global
[
"__file__"
]
=
std
::
move
(
fname
);
}
#endif
#if PY_VERSION_HEX < 0x03000000 && defined(PYPY_VERSION)
PyObject
*
result
=
PyRun_File
(
f
,
fname_str
.
c_str
(),
start
,
global
.
ptr
(),
local
.
ptr
());
...
...
include/pybind11/functional.h
View file @
e315e1fe
...
...
@@ -69,6 +69,10 @@ public:
// ensure GIL is held during functor destruction
struct
func_handle
{
function
f
;
#if !(defined(_MSC_VER) && _MSC_VER == 1916 && defined(PYBIND11_CPP17) && PY_MAJOR_VERSION < 3)
// This triggers a syntax error under very special conditions (very weird indeed).
explicit
#endif
func_handle
(
function
&&
f_
)
noexcept
:
f
(
std
::
move
(
f_
))
{}
func_handle
(
const
func_handle
&
f_
)
{
operator
=
(
f_
);
}
func_handle
&
operator
=
(
const
func_handle
&
f_
)
{
...
...
@@ -85,7 +89,7 @@ public:
// to emulate 'move initialization capture' in C++11
struct
func_wrapper
{
func_handle
hfunc
;
func_wrapper
(
func_handle
&&
hf
)
noexcept
:
hfunc
(
std
::
move
(
hf
))
{}
explicit
func_wrapper
(
func_handle
&&
hf
)
noexcept
:
hfunc
(
std
::
move
(
hf
))
{}
Return
operator
()(
Args
...
args
)
const
{
gil_scoped_acquire
acq
;
object
retval
(
hfunc
.
f
(
std
::
forward
<
Args
>
(
args
)...));
...
...
include/pybind11/gil.h
View file @
e315e1fe
...
...
@@ -50,7 +50,7 @@ PYBIND11_NAMESPACE_END(detail)
class
gil_scoped_acquire
{
public:
PYBIND11_NOINLINE
gil_scoped_acquire
()
{
auto
const
&
internals
=
detail
::
get_internals
();
auto
&
internals
=
detail
::
get_internals
();
tstate
=
(
PyThreadState
*
)
PYBIND11_TLS_GET_VALUE
(
internals
.
tstate
);
if
(
!
tstate
)
{
...
...
@@ -132,7 +132,7 @@ public:
// `get_internals()` must be called here unconditionally in order to initialize
// `internals.tstate` for subsequent `gil_scoped_acquire` calls. Otherwise, an
// initialization race could occur as multiple threads try `gil_scoped_acquire`.
const
auto
&
internals
=
detail
::
get_internals
();
auto
&
internals
=
detail
::
get_internals
();
tstate
=
PyEval_SaveThread
();
if
(
disassoc
)
{
auto
key
=
internals
.
tstate
;
...
...
include/pybind11/iostream.h
View file @
e315e1fe
...
...
@@ -123,7 +123,7 @@ private:
}
public:
pythonbuf
(
const
object
&
pyostream
,
size_t
buffer_size
=
1024
)
explicit
pythonbuf
(
const
object
&
pyostream
,
size_t
buffer_size
=
1024
)
:
buf_size
(
buffer_size
),
d_buffer
(
new
char
[
buf_size
]),
pywrite
(
pyostream
.
attr
(
"write"
)),
pyflush
(
pyostream
.
attr
(
"flush"
))
{
setp
(
d_buffer
.
get
(),
d_buffer
.
get
()
+
buf_size
-
1
);
...
...
@@ -171,8 +171,9 @@ protected:
detail
::
pythonbuf
buffer
;
public:
scoped_ostream_redirect
(
std
::
ostream
&
costream
=
std
::
cout
,
const
object
&
pyostream
=
module_
::
import
(
"sys"
).
attr
(
"stdout"
))
explicit
scoped_ostream_redirect
(
std
::
ostream
&
costream
=
std
::
cout
,
const
object
&
pyostream
=
module_
::
import
(
"sys"
).
attr
(
"stdout"
))
:
costream
(
costream
),
buffer
(
pyostream
)
{
old
=
costream
.
rdbuf
(
&
buffer
);
}
...
...
@@ -201,8 +202,9 @@ public:
\endrst */
class
scoped_estream_redirect
:
public
scoped_ostream_redirect
{
public:
scoped_estream_redirect
(
std
::
ostream
&
costream
=
std
::
cerr
,
const
object
&
pyostream
=
module_
::
import
(
"sys"
).
attr
(
"stderr"
))
explicit
scoped_estream_redirect
(
std
::
ostream
&
costream
=
std
::
cerr
,
const
object
&
pyostream
=
module_
::
import
(
"sys"
).
attr
(
"stderr"
))
:
scoped_ostream_redirect
(
costream
,
pyostream
)
{}
};
...
...
@@ -217,7 +219,7 @@ class OstreamRedirect {
std
::
unique_ptr
<
scoped_estream_redirect
>
redirect_stderr
;
public:
OstreamRedirect
(
bool
do_stdout
=
true
,
bool
do_stderr
=
true
)
explicit
OstreamRedirect
(
bool
do_stdout
=
true
,
bool
do_stderr
=
true
)
:
do_stdout_
(
do_stdout
),
do_stderr_
(
do_stderr
)
{}
void
enter
()
{
...
...
include/pybind11/numpy.h
View file @
e315e1fe
...
...
@@ -25,11 +25,6 @@
#include <vector>
#include <typeindex>
#if defined(_MSC_VER)
# pragma warning(push)
# pragma warning(disable: 4127) // warning C4127: Conditional expression is constant
#endif
/* This will be true on all flat address space platforms and allows us to reduce the
whole npy_intp / ssize_t / Py_intptr_t business down to just ssize_t for all size
and dimension types (e.g. shape, strides, indexing), instead of inflicting this
...
...
@@ -104,7 +99,7 @@ struct numpy_internals {
}
};
inline
PYBIND11_NOINLINE
void
load_numpy_internals
(
numpy_internals
*
&
ptr
)
{
PYBIND11_NOINLINE
void
load_numpy_internals
(
numpy_internals
*
&
ptr
)
{
ptr
=
&
get_or_create_shared_data
<
numpy_internals
>
(
"_numpy_internals"
);
}
...
...
@@ -203,6 +198,9 @@ struct npy_api {
// Unused. Not removed because that affects ABI of the class.
int
(
*
PyArray_SetBaseObject_
)(
PyObject
*
,
PyObject
*
);
PyObject
*
(
*
PyArray_Resize_
)(
PyObject
*
,
PyArray_Dims
*
,
int
,
int
);
PyObject
*
(
*
PyArray_Newshape_
)(
PyObject
*
,
PyArray_Dims
*
,
int
);
PyObject
*
(
*
PyArray_View_
)(
PyObject
*
,
PyObject
*
,
PyObject
*
);
private:
enum
functions
{
API_PyArray_GetNDArrayCFeatureVersion
=
211
,
...
...
@@ -217,10 +215,12 @@ private:
API_PyArray_NewCopy
=
85
,
API_PyArray_NewFromDescr
=
94
,
API_PyArray_DescrNewFromType
=
96
,
API_PyArray_Newshape
=
135
,
API_PyArray_Squeeze
=
136
,
API_PyArray_View
=
137
,
API_PyArray_DescrConverter
=
174
,
API_PyArray_EquivTypes
=
182
,
API_PyArray_GetArrayParamsFromObject
=
278
,
API_PyArray_Squeeze
=
136
,
API_PyArray_SetBaseObject
=
282
};
...
...
@@ -248,11 +248,14 @@ private:
DECL_NPY_API
(
PyArray_NewCopy
);
DECL_NPY_API
(
PyArray_NewFromDescr
);
DECL_NPY_API
(
PyArray_DescrNewFromType
);
DECL_NPY_API
(
PyArray_Newshape
);
DECL_NPY_API
(
PyArray_Squeeze
);
DECL_NPY_API
(
PyArray_View
);
DECL_NPY_API
(
PyArray_DescrConverter
);
DECL_NPY_API
(
PyArray_EquivTypes
);
DECL_NPY_API
(
PyArray_GetArrayParamsFromObject
);
DECL_NPY_API
(
PyArray_Squeeze
);
DECL_NPY_API
(
PyArray_SetBaseObject
);
#undef DECL_NPY_API
return
api
;
}
...
...
@@ -319,7 +322,7 @@ template <typename T> using remove_all_extents_t = typename array_info<T>::type;
template
<
typename
T
>
using
is_pod_struct
=
all_of
<
std
::
is_standard_layout
<
T
>
,
// since we're accessing directly in memory we need a standard layout type
#if defined(__GLIBCXX__) && (__GLIBCXX__ < 20150422 || __GLIBCXX__ == 20150623 || __GLIBCXX__ == 20150626 || __GLIBCXX__ == 20160803)
#if defined(__GLIBCXX__) && (__GLIBCXX__ < 20150422 || __GLIBCXX__ ==
20150426 || __GLIBCXX__ ==
20150623 || __GLIBCXX__ == 20150626 || __GLIBCXX__ == 20160803)
// libstdc++ < 5 (including versions 4.8.5, 4.9.3 and 4.9.4 which were released after 5)
// don't implement is_trivially_copyable, so approximate it
std
::
is_trivially_destructible
<
T
>
,
...
...
@@ -474,15 +477,15 @@ public:
m_ptr
=
from_args
(
pybind11
::
str
(
format
)).
release
().
ptr
();
}
dtype
(
const
char
*
format
)
:
dtype
(
std
::
string
(
format
))
{
}
explicit
dtype
(
const
char
*
format
)
:
dtype
(
std
::
string
(
format
))
{}
dtype
(
list
names
,
list
formats
,
list
offsets
,
ssize_t
itemsize
)
{
dict
args
;
args
[
"names"
]
=
names
;
args
[
"formats"
]
=
formats
;
args
[
"offsets"
]
=
offsets
;
args
[
"names"
]
=
std
::
move
(
names
)
;
args
[
"formats"
]
=
std
::
move
(
formats
)
;
args
[
"offsets"
]
=
std
::
move
(
offsets
)
;
args
[
"itemsize"
]
=
pybind11
::
int_
(
itemsize
);
m_ptr
=
from_args
(
args
).
release
().
ptr
();
m_ptr
=
from_args
(
std
::
move
(
args
)
)
.
release
().
ptr
();
}
/// This is essentially the same as calling numpy.dtype(args) in Python.
...
...
@@ -560,7 +563,7 @@ private:
formats
.
append
(
descr
.
format
);
offsets
.
append
(
descr
.
offset
);
}
return
dtype
(
names
,
formats
,
offsets
,
itemsize
);
return
dtype
(
std
::
move
(
names
)
,
std
::
move
(
formats
)
,
std
::
move
(
offsets
)
,
itemsize
);
}
};
...
...
@@ -747,7 +750,7 @@ public:
* and the caller must take care not to access invalid dimensions or dimension indices.
*/
template
<
typename
T
,
ssize_t
Dims
=
-
1
>
detail
::
unchecked_mutable_reference
<
T
,
Dims
>
mutable_unchecked
()
&
{
if
(
Dims
>=
0
&&
ndim
()
!=
Dims
)
if
(
PYBIND11_SILENCE_MSVC_C4127
(
Dims
>=
0
)
&&
ndim
()
!=
Dims
)
throw
std
::
domain_error
(
"array has incorrect number of dimensions: "
+
std
::
to_string
(
ndim
())
+
"; expected "
+
std
::
to_string
(
Dims
));
return
detail
::
unchecked_mutable_reference
<
T
,
Dims
>
(
mutable_data
(),
shape
(),
strides
(),
ndim
());
...
...
@@ -761,7 +764,7 @@ public:
* invalid dimensions or dimension indices.
*/
template
<
typename
T
,
ssize_t
Dims
=
-
1
>
detail
::
unchecked_reference
<
T
,
Dims
>
unchecked
()
const
&
{
if
(
Dims
>=
0
&&
ndim
()
!=
Dims
)
if
(
PYBIND11_SILENCE_MSVC_C4127
(
Dims
>=
0
)
&&
ndim
()
!=
Dims
)
throw
std
::
domain_error
(
"array has incorrect number of dimensions: "
+
std
::
to_string
(
ndim
())
+
"; expected "
+
std
::
to_string
(
Dims
));
return
detail
::
unchecked_reference
<
T
,
Dims
>
(
data
(),
shape
(),
strides
(),
ndim
());
...
...
@@ -790,6 +793,33 @@ public:
if
(
isinstance
<
array
>
(
new_array
))
{
*
this
=
std
::
move
(
new_array
);
}
}
/// Optional `order` parameter omitted, to be added as needed.
array
reshape
(
ShapeContainer
new_shape
)
{
detail
::
npy_api
::
PyArray_Dims
d
=
{
reinterpret_cast
<
Py_intptr_t
*>
(
new_shape
->
data
()),
int
(
new_shape
->
size
())};
auto
new_array
=
reinterpret_steal
<
array
>
(
detail
::
npy_api
::
get
().
PyArray_Newshape_
(
m_ptr
,
&
d
,
0
));
if
(
!
new_array
)
{
throw
error_already_set
();
}
return
new_array
;
}
/// Create a view of an array in a different data type.
/// This function may fundamentally reinterpret the data in the array.
/// It is the responsibility of the caller to ensure that this is safe.
/// Only supports the `dtype` argument, the `type` argument is omitted,
/// to be added as needed.
array
view
(
const
std
::
string
&
dtype
)
{
auto
&
api
=
detail
::
npy_api
::
get
();
auto
new_view
=
reinterpret_steal
<
array
>
(
api
.
PyArray_View_
(
m_ptr
,
dtype
::
from_args
(
pybind11
::
str
(
dtype
)).
release
().
ptr
(),
nullptr
));
if
(
!
new_view
)
{
throw
error_already_set
();
}
return
new_view
;
}
/// Ensure that the argument is a NumPy array
/// In case of an error, nullptr is returned and the Python error is cleared.
static
array
ensure
(
handle
h
,
int
ExtraFlags
=
0
)
{
...
...
@@ -864,6 +894,7 @@ public:
if
(
!
is_borrowed
)
Py_XDECREF
(
h
.
ptr
());
}
// NOLINTNEXTLINE(google-explicit-constructor)
array_t
(
const
object
&
o
)
:
array
(
raw_array_t
(
o
.
ptr
()),
stolen_t
{})
{
if
(
!
m_ptr
)
throw
error_already_set
();
}
...
...
@@ -1110,7 +1141,7 @@ struct field_descriptor {
dtype
descr
;
};
inline
PYBIND11_NOINLINE
void
register_structured_dtype
(
PYBIND11_NOINLINE
void
register_structured_dtype
(
any_container
<
field_descriptor
>
fields
,
const
std
::
type_info
&
tinfo
,
ssize_t
itemsize
,
bool
(
*
direct_converter
)(
PyObject
*
,
void
*&
))
{
...
...
@@ -1134,7 +1165,10 @@ inline PYBIND11_NOINLINE void register_structured_dtype(
formats
.
append
(
field
.
descr
);
offsets
.
append
(
pybind11
::
int_
(
field
.
offset
));
}
auto
dtype_ptr
=
pybind11
::
dtype
(
names
,
formats
,
offsets
,
itemsize
).
release
().
ptr
();
auto
dtype_ptr
=
pybind11
::
dtype
(
std
::
move
(
names
),
std
::
move
(
formats
),
std
::
move
(
offsets
),
itemsize
)
.
release
()
.
ptr
();
// There is an existing bug in NumPy (as of v1.11): trailing bytes are
// not encoded explicitly into the format string. This will supposedly
...
...
@@ -1551,8 +1585,11 @@ private:
"pybind11::vectorize(...) requires a function with at least one vectorizable argument"
);
public:
template
<
typename
T
>
explicit
vectorize_helper
(
T
&&
f
)
:
f
(
std
::
forward
<
T
>
(
f
))
{
}
template
<
typename
T
,
// SFINAE to prevent shadowing the copy constructor.
typename
=
detail
::
enable_if_t
<
!
std
::
is_same
<
vectorize_helper
,
typename
std
::
decay
<
T
>
::
type
>::
value
>>
explicit
vectorize_helper
(
T
&&
f
)
:
f
(
std
::
forward
<
T
>
(
f
))
{}
object
operator
()(
typename
vectorize_arg
<
Args
>::
type
...
args
)
{
return
run
(
args
...,
...
...
@@ -1702,7 +1739,3 @@ Helper vectorize(Return (Class::*f)(Args...) const) {
}
PYBIND11_NAMESPACE_END
(
PYBIND11_NAMESPACE
)
#if defined(_MSC_VER)
#pragma warning(pop)
#endif
include/pybind11/operators.h
View file @
e315e1fe
...
...
@@ -11,13 +11,6 @@
#include "pybind11.h"
#if defined(__clang__) && !defined(__INTEL_COMPILER)
# pragma clang diagnostic ignored "-Wunsequenced" // multiple unsequenced modifications to 'self' (when using def(py::self OP Type()))
#elif defined(_MSC_VER)
# pragma warning(push)
# pragma warning(disable: 4127) // warning C4127: Conditional expression is constant
#endif
PYBIND11_NAMESPACE_BEGIN
(
PYBIND11_NAMESPACE
)
PYBIND11_NAMESPACE_BEGIN
(
detail
)
...
...
@@ -58,7 +51,8 @@ template <op_id id, op_type ot, typename L, typename R> struct op_ {
using
op
=
op_impl
<
id
,
ot
,
Base
,
L_type
,
R_type
>
;
cl
.
def
(
op
::
name
(),
&
op
::
execute
,
is_operator
(),
extra
...);
#if PY_MAJOR_VERSION < 3
if
(
id
==
op_truediv
||
id
==
op_itruediv
)
if
(
PYBIND11_SILENCE_MSVC_C4127
(
id
==
op_truediv
)
||
PYBIND11_SILENCE_MSVC_C4127
(
id
==
op_itruediv
))
cl
.
def
(
id
==
op_itruediv
?
"__idiv__"
:
ot
==
op_l
?
"__div__"
:
"__rdiv__"
,
&
op
::
execute
,
is_operator
(),
extra
...);
#endif
...
...
@@ -167,7 +161,3 @@ using detail::self;
using
detail
::
hash
;
PYBIND11_NAMESPACE_END
(
PYBIND11_NAMESPACE
)
#if defined(_MSC_VER)
# pragma warning(pop)
#endif
include/pybind11/pybind11.h
View file @
e315e1fe
This diff is collapsed.
Click to expand it.
include/pybind11/pytypes.h
View file @
e315e1fe
This diff is collapsed.
Click to expand it.
include/pybind11/stl.h
View file @
e315e1fe
...
...
@@ -9,6 +9,7 @@
#pragma once
#include "detail/common.h"
#include "pybind11.h"
#include <set>
#include <unordered_set>
...
...
@@ -19,33 +20,15 @@
#include <deque>
#include <valarray>
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable: 4127) // warning C4127: Conditional expression is constant
// See `detail/common.h` for implementation of these guards.
#if defined(PYBIND11_HAS_OPTIONAL)
# include <optional>
#elif defined(PYBIND11_HAS_EXP_OPTIONAL)
# include <experimental/optional>
#endif
#ifdef __has_include
// std::optional (but including it in c++14 mode isn't allowed)
# if defined(PYBIND11_CPP17) && __has_include(<optional>)
# include <optional>
# define PYBIND11_HAS_OPTIONAL 1
# endif
// std::experimental::optional (but not allowed in c++11 mode)
# if defined(PYBIND11_CPP14) && (__has_include(<experimental/optional>) && \
!__has_include(<optional>))
# include <experimental/optional>
# define PYBIND11_HAS_EXP_OPTIONAL 1
# endif
// std::variant
# if defined(PYBIND11_CPP17) && __has_include(<variant>)
# include <variant>
# define PYBIND11_HAS_VARIANT 1
# endif
#elif defined(_MSC_VER) && defined(PYBIND11_CPP17)
# include <optional>
#if defined(PYBIND11_HAS_VARIANT)
# include <variant>
# define PYBIND11_HAS_OPTIONAL 1
# define PYBIND11_HAS_VARIANT 1
#endif
PYBIND11_NAMESPACE_BEGIN
(
PYBIND11_NAMESPACE
)
...
...
@@ -173,12 +156,12 @@ public:
if
(
!
std
::
is_lvalue_reference
<
T
>::
value
)
policy
=
return_value_policy_override
<
Value
>::
policy
(
policy
);
list
l
(
src
.
size
());
size_t
index
=
0
;
s
size_t
index
=
0
;
for
(
auto
&&
value
:
src
)
{
auto
value_
=
reinterpret_steal
<
object
>
(
value_conv
::
cast
(
forward_like
<
T
>
(
value
),
policy
,
parent
));
if
(
!
value_
)
return
handle
();
PyList_SET_ITEM
(
l
.
ptr
(),
(
ssize_t
)
index
++
,
value_
.
release
().
ptr
());
// steals a reference
PyList_SET_ITEM
(
l
.
ptr
(),
index
++
,
value_
.
release
().
ptr
());
// steals a reference
}
return
l
.
release
();
}
...
...
@@ -230,12 +213,12 @@ public:
template
<
typename
T
>
static
handle
cast
(
T
&&
src
,
return_value_policy
policy
,
handle
parent
)
{
list
l
(
src
.
size
());
size_t
index
=
0
;
s
size_t
index
=
0
;
for
(
auto
&&
value
:
src
)
{
auto
value_
=
reinterpret_steal
<
object
>
(
value_conv
::
cast
(
forward_like
<
T
>
(
value
),
policy
,
parent
));
if
(
!
value_
)
return
handle
();
PyList_SET_ITEM
(
l
.
ptr
(),
(
ssize_t
)
index
++
,
value_
.
release
().
ptr
());
// steals a reference
PyList_SET_ITEM
(
l
.
ptr
(),
index
++
,
value_
.
release
().
ptr
());
// steals a reference
}
return
l
.
release
();
}
...
...
@@ -390,7 +373,3 @@ inline std::ostream &operator<<(std::ostream &os, const handle &obj) {
}
PYBIND11_NAMESPACE_END
(
PYBIND11_NAMESPACE
)
#if defined(_MSC_VER)
#pragma warning(pop)
#endif
include/pybind11/stl_bind.h
View file @
e315e1fe
...
...
@@ -595,6 +595,23 @@ template <typename Map, typename Class_> auto map_if_insertion_operator(Class_ &
);
}
template
<
typename
Map
>
struct
keys_view
{
Map
&
map
;
};
template
<
typename
Map
>
struct
values_view
{
Map
&
map
;
};
template
<
typename
Map
>
struct
items_view
{
Map
&
map
;
};
PYBIND11_NAMESPACE_END
(
detail
)
...
...
@@ -602,6 +619,9 @@ template <typename Map, typename holder_type = std::unique_ptr<Map>, typename...
class_
<
Map
,
holder_type
>
bind_map
(
handle
scope
,
const
std
::
string
&
name
,
Args
&&
...
args
)
{
using
KeyType
=
typename
Map
::
key_type
;
using
MappedType
=
typename
Map
::
mapped_type
;
using
KeysView
=
detail
::
keys_view
<
Map
>
;
using
ValuesView
=
detail
::
values_view
<
Map
>
;
using
ItemsView
=
detail
::
items_view
<
Map
>
;
using
Class_
=
class_
<
Map
,
holder_type
>
;
// If either type is a non-module-local bound type then make the map binding non-local as well;
...
...
@@ -615,6 +635,12 @@ class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args&&.
}
Class_
cl
(
scope
,
name
.
c_str
(),
pybind11
::
module_local
(
local
),
std
::
forward
<
Args
>
(
args
)...);
class_
<
KeysView
>
keys_view
(
scope
,
(
"KeysView["
+
name
+
"]"
).
c_str
(),
pybind11
::
module_local
(
local
));
class_
<
ValuesView
>
values_view
(
scope
,
(
"ValuesView["
+
name
+
"]"
).
c_str
(),
pybind11
::
module_local
(
local
));
class_
<
ItemsView
>
items_view
(
scope
,
(
"ItemsView["
+
name
+
"]"
).
c_str
(),
pybind11
::
module_local
(
local
));
cl
.
def
(
init
<>
());
...
...
@@ -628,12 +654,22 @@ class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args&&.
cl
.
def
(
"__iter__"
,
[](
Map
&
m
)
{
return
make_key_iterator
(
m
.
begin
(),
m
.
end
());
},
keep_alive
<
0
,
1
>
()
/* Essential: keep list alive while iterator exists */
keep_alive
<
0
,
1
>
()
/* Essential: keep map alive while iterator exists */
);
cl
.
def
(
"keys"
,
[](
Map
&
m
)
{
return
KeysView
{
m
};
},
keep_alive
<
0
,
1
>
()
/* Essential: keep map alive while view exists */
);
cl
.
def
(
"values"
,
[](
Map
&
m
)
{
return
ValuesView
{
m
};
},
keep_alive
<
0
,
1
>
()
/* Essential: keep map alive while view exists */
);
cl
.
def
(
"items"
,
[](
Map
&
m
)
{
return
make_iterator
(
m
.
begin
(),
m
.
end
())
;
},
keep_alive
<
0
,
1
>
()
/* Essential: keep
list
alive while
iterator
exists */
[](
Map
&
m
)
{
return
ItemsView
{
m
}
;
},
keep_alive
<
0
,
1
>
()
/* Essential: keep
map
alive while
view
exists */
);
cl
.
def
(
"__getitem__"
,
...
...
@@ -654,6 +690,8 @@ class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args&&.
return
true
;
}
);
// Fallback for when the object is not of the key type
cl
.
def
(
"__contains__"
,
[](
Map
&
,
const
object
&
)
->
bool
{
return
false
;
});
// Assignment provided only if the type is copyable
detail
::
map_assignment
<
Map
,
Class_
>
(
cl
);
...
...
@@ -669,6 +707,40 @@ class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args&&.
cl
.
def
(
"__len__"
,
&
Map
::
size
);
keys_view
.
def
(
"__len__"
,
[](
KeysView
&
view
)
{
return
view
.
map
.
size
();
});
keys_view
.
def
(
"__iter__"
,
[](
KeysView
&
view
)
{
return
make_key_iterator
(
view
.
map
.
begin
(),
view
.
map
.
end
());
},
keep_alive
<
0
,
1
>
()
/* Essential: keep view alive while iterator exists */
);
keys_view
.
def
(
"__contains__"
,
[](
KeysView
&
view
,
const
KeyType
&
k
)
->
bool
{
auto
it
=
view
.
map
.
find
(
k
);
if
(
it
==
view
.
map
.
end
())
return
false
;
return
true
;
}
);
// Fallback for when the object is not of the key type
keys_view
.
def
(
"__contains__"
,
[](
KeysView
&
,
const
object
&
)
->
bool
{
return
false
;
});
values_view
.
def
(
"__len__"
,
[](
ValuesView
&
view
)
{
return
view
.
map
.
size
();
});
values_view
.
def
(
"__iter__"
,
[](
ValuesView
&
view
)
{
return
make_value_iterator
(
view
.
map
.
begin
(),
view
.
map
.
end
());
},
keep_alive
<
0
,
1
>
()
/* Essential: keep view alive while iterator exists */
);
items_view
.
def
(
"__len__"
,
[](
ItemsView
&
view
)
{
return
view
.
map
.
size
();
});
items_view
.
def
(
"__iter__"
,
[](
ItemsView
&
view
)
{
return
make_iterator
(
view
.
map
.
begin
(),
view
.
map
.
end
());
},
keep_alive
<
0
,
1
>
()
/* Essential: keep view alive while iterator exists */
);
return
cl
;
}
...
...
noxfile.py
View file @
e315e1fe
import
nox
nox
.
options
.
sessions
=
[
"lint"
,
"tests"
,
"tests_packaging"
]
PYTHON_VERISONS
=
[
"2.7"
,
"3.5"
,
"3.6"
,
"3.7"
,
"3.8"
,
"3.9"
,
"3.10"
]
@
nox
.
session
(
reuse_venv
=
True
)
def
lint
(
session
:
nox
.
Session
)
->
None
:
...
...
@@ -13,7 +14,7 @@ def lint(session: nox.Session) -> None:
session
.
run
(
"pre-commit"
,
"run"
,
"-a"
)
@
nox
.
session
@
nox
.
session
(
python
=
PYTHON_VERISONS
)
def
tests
(
session
:
nox
.
Session
)
->
None
:
"""
Run the tests (requires a compiler).
...
...
pybind11/__init__.py
View file @
e315e1fe
# -*- coding: utf-8 -*-
from
._version
import
version_info
,
__version__
from
.commands
import
get_include
,
get_cmake_dir
from
._version
import
__version__
,
version_info
from
.commands
import
get_cmake_dir
,
get_include
__all__
=
(
"version_info"
,
...
...
pybind11/__main__.py
View file @
e315e1fe
...
...
@@ -5,7 +5,7 @@ import argparse
import
sys
import
sysconfig
from
.commands
import
get_
include
,
get_cmake_dir
from
.commands
import
get_
cmake_dir
,
get_include
def
print_includes
():
...
...
pybind11/_version.py
View file @
e315e1fe
...
...
@@ -8,5 +8,5 @@ def _to_int(s):
return
s
__version__
=
"2.
7.1
"
__version__
=
"2.
8.0
"
version_info
=
tuple
(
_to_int
(
s
)
for
s
in
__version__
.
split
(
"."
))
pybind11/_version.pyi
View file @
e315e1fe
from typing import
Union, Tuple
from typing import
Tuple, Union
def _to_int(s: str) -> Union[int, str]: ...
...
...
pybind11/commands.py
View file @
e315e1fe
# -*- coding: utf-8 -*-
import
os
DIR
=
os
.
path
.
abspath
(
os
.
path
.
dirname
(
__file__
))
...
...
pybind11/setup_helpers.py
View file @
e315e1fe
...
...
@@ -41,23 +41,23 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import
contextlib
import
os
import
platform
import
shutil
import
sys
import
sysconfig
import
tempfile
import
threading
import
platform
import
warnings
import
sysconfig
try
:
from
setuptools.command.build_ext
import
build_ext
as
_build_ext
from
setuptools
import
Extension
as
_Extension
from
setuptools.command.build_ext
import
build_ext
as
_build_ext
except
ImportError
:
from
distutils.command.build_ext
import
build_ext
as
_build_ext
from
distutils.extension
import
Extension
as
_Extension
import
distutils.errors
import
distutils.ccompiler
import
distutils.errors
WIN
=
sys
.
platform
.
startswith
(
"win32"
)
and
"mingw"
not
in
sysconfig
.
get_platform
()
PY2
=
sys
.
version_info
[
0
]
<
3
...
...
pybind11/setup_helpers.pyi
View file @
e315e1fe
# IMPORTANT: Should stay in sync with setup_helpers.py (mostly checked by CI /
# pre-commit).
from typing import Any, Callable, Dict, Iterator, List, Optional, Type, TypeVar, Union
from types import TracebackType
import contextlib
import distutils.ccompiler
from distutils.command.build_ext import build_ext as _build_ext # type: ignore
from distutils.extension import Extension as _Extension
import distutils.ccompiler
import contextlib
from types import TracebackType
from typing import Any, Callable, Dict, Iterator, List, Optional, Type, TypeVar, Union
WIN: bool
PY2: bool
...
...
Prev
1
2
3
4
5
6
7
8
Next
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