Squashed 'wrap/' changes from ca357ccdd..b37a1fec6

b37a1fec6 Merge pull request #154 from borglab/matlab/properties
93dec957a convert class properties to type parser.Variable so that the property collector functions are written correctly
d84e07f56 fix bug with proper closing of global functions
5a8505235 define setter and getter collector functions for cpp file
5067655e2 abstract out unwrap_argument and collector_return to their own functions
1ce29d71d add properties to properties block of .m file and add setters and getters
0c3e5573d write properties in class comment
9d85f7b6a disable superfluous pylint warnings
b45994d34 Merge pull request #152 from borglab/pybind-2.10
76ba199a2 upgrade pybind to 2.10
16e4e674b Merge pull request #150 from borglab/feature/parent_class_template
c7d1a466f Merge pull request #151 from borglab/fix/function_template_parameter_namespace
7278a309d retain namespace for template arguments
18ae5fb04 implement parent class allowed to have template parameters
fd4437899 unit tests for parent classes allowed to have template parameters

git-subtree-dir: wrap
git-subtree-split: b37a1fec689d6a42837a3bfb4dc947674e72be54
release/4.3a0
Varun Agrawal 2022-10-28 13:10:19 -04:00
parent aaeeccf8f5
commit 41d0de3e92
222 changed files with 11974 additions and 8265 deletions

View File

@ -299,7 +299,7 @@ class Class:
# If the base class is a TemplatedType, # If the base class is a TemplatedType,
# we want the instantiated Typename # we want the instantiated Typename
if isinstance(parent_class, TemplatedType): if isinstance(parent_class, TemplatedType):
parent_class = parent_class.typename # type: ignore pass # Note: this must get handled in InstantiatedClass
self.parent_class = parent_class self.parent_class = parent_class
else: else:

View File

@ -3,7 +3,7 @@ Code to use the parsed results and convert it to a format
that Matlab's MEX compiler can use. that Matlab's MEX compiler can use.
""" """
# pylint: disable=too-many-lines, no-self-use, too-many-arguments, too-many-branches, too-many-statements # pylint: disable=too-many-lines, no-self-use, too-many-arguments, too-many-branches, too-many-statements, consider-using-f-string, unspecified-encoding
import copy import copy
import os import os
@ -17,6 +17,7 @@ import gtwrap.template_instantiator as instantiator
from gtwrap.interface_parser.function import ArgumentList from gtwrap.interface_parser.function import ArgumentList
from gtwrap.matlab_wrapper.mixins import CheckMixin, FormatMixin from gtwrap.matlab_wrapper.mixins import CheckMixin, FormatMixin
from gtwrap.matlab_wrapper.templates import WrapperTemplate from gtwrap.matlab_wrapper.templates import WrapperTemplate
from gtwrap.template_instantiator.classes import InstantiatedClass
class MatlabWrapper(CheckMixin, FormatMixin): class MatlabWrapper(CheckMixin, FormatMixin):
@ -28,6 +29,7 @@ class MatlabWrapper(CheckMixin, FormatMixin):
top_module_namespace: C++ namespace for the top module (default '') top_module_namespace: C++ namespace for the top module (default '')
ignore_classes: A list of classes to ignore (default []) ignore_classes: A list of classes to ignore (default [])
""" """
def __init__(self, def __init__(self,
module_name, module_name,
top_module_namespace='', top_module_namespace='',
@ -94,16 +96,19 @@ class MatlabWrapper(CheckMixin, FormatMixin):
self.classes_elems[instantiated_class] = 0 self.classes_elems[instantiated_class] = 0
self.classes.append(instantiated_class) self.classes.append(instantiated_class)
def _update_wrapper_id(self, collector_function=None, id_diff=0): def _update_wrapper_id(self,
collector_function=None,
id_diff=0,
function_name: str = None):
""" """
Get and define wrapper ids. Get and define wrapper ids.
Generates the map of id -> collector function. Generates the map of id -> collector function.
Args: Args:
collector_function: tuple storing info about the wrapper function collector_function: tuple storing info about the wrapper function
(namespace, class instance, function type, function name, (namespace, class instance, function name, function object)
extra)
id_diff: constant to add to the id in the map id_diff: constant to add to the id in the map
function_name: Optional custom function_name.
Returns: Returns:
the current wrapper id the current wrapper id
@ -112,11 +117,12 @@ class MatlabWrapper(CheckMixin, FormatMixin):
is_instantiated_class = isinstance(collector_function[1], is_instantiated_class = isinstance(collector_function[1],
instantiator.InstantiatedClass) instantiator.InstantiatedClass)
if is_instantiated_class: if function_name is None:
function_name = collector_function[0] + \ if is_instantiated_class:
collector_function[1].name + '_' + collector_function[2] function_name = collector_function[0] + \
else: collector_function[1].name + '_' + collector_function[2]
function_name = collector_function[1].name else:
function_name = collector_function[1].name
self.wrapper_map[self.wrapper_id] = ( self.wrapper_map[self.wrapper_id] = (
collector_function[0], collector_function[1], collector_function[0], collector_function[1],
@ -145,6 +151,7 @@ class MatlabWrapper(CheckMixin, FormatMixin):
We create "overload" functions with fewer arguments, but since we have to "remember" what We create "overload" functions with fewer arguments, but since we have to "remember" what
the default arguments are for later, we make a backup. the default arguments are for later, we make a backup.
""" """
def args_copy(args): def args_copy(args):
return ArgumentList([copy.copy(arg) for arg in args.list()]) return ArgumentList([copy.copy(arg) for arg in args.list()])
@ -332,6 +339,38 @@ class MatlabWrapper(CheckMixin, FormatMixin):
return check_statement return check_statement
def _unwrap_argument(self, arg, arg_id=0, constructor=False):
ctype_camel = self._format_type_name(arg.ctype.typename, separator='')
ctype_sep = self._format_type_name(arg.ctype.typename)
if self.is_ref(arg.ctype): # and not constructor:
arg_type = "{ctype}&".format(ctype=ctype_sep)
unwrap = '*unwrap_shared_ptr< {ctype} >(in[{id}], "ptr_{ctype_camel}");'.format(
ctype=ctype_sep, ctype_camel=ctype_camel, id=arg_id)
elif self.is_ptr(arg.ctype) and \
arg.ctype.typename.name not in self.ignore_namespace:
arg_type = "{ctype_sep}*".format(ctype_sep=ctype_sep)
unwrap = 'unwrap_ptr< {ctype_sep} >(in[{id}], "ptr_{ctype}");'.format(
ctype_sep=ctype_sep, ctype=ctype_camel, id=arg_id)
elif (self.is_shared_ptr(arg.ctype) or self.can_be_pointer(arg.ctype)) and \
arg.ctype.typename.name not in self.ignore_namespace:
arg_type = "{std_boost}::shared_ptr<{ctype_sep}>".format(
std_boost='boost' if constructor else 'boost',
ctype_sep=ctype_sep)
unwrap = 'unwrap_shared_ptr< {ctype_sep} >(in[{id}], "ptr_{ctype}");'.format(
ctype_sep=ctype_sep, ctype=ctype_camel, id=arg_id)
else:
arg_type = "{ctype}".format(ctype=arg.ctype.typename.name)
unwrap = 'unwrap< {ctype} >(in[{id}]);'.format(
ctype=arg.ctype.typename.name, id=arg_id)
return arg_type, unwrap
def _wrapper_unwrap_arguments(self, args, arg_id=0, constructor=False): def _wrapper_unwrap_arguments(self, args, arg_id=0, constructor=False):
"""Format the interface_parser.Arguments. """Format the interface_parser.Arguments.
@ -343,36 +382,7 @@ class MatlabWrapper(CheckMixin, FormatMixin):
body_args = '' body_args = ''
for arg in args.list(): for arg in args.list():
ctype_camel = self._format_type_name(arg.ctype.typename, arg_type, unwrap = self._unwrap_argument(arg, arg_id, constructor)
separator='')
ctype_sep = self._format_type_name(arg.ctype.typename)
if self.is_ref(arg.ctype): # and not constructor:
arg_type = "{ctype}&".format(ctype=ctype_sep)
unwrap = '*unwrap_shared_ptr< {ctype} >(in[{id}], "ptr_{ctype_camel}");'.format(
ctype=ctype_sep, ctype_camel=ctype_camel, id=arg_id)
elif self.is_ptr(arg.ctype) and \
arg.ctype.typename.name not in self.ignore_namespace:
arg_type = "{ctype_sep}*".format(ctype_sep=ctype_sep)
unwrap = 'unwrap_ptr< {ctype_sep} >(in[{id}], "ptr_{ctype}");'.format(
ctype_sep=ctype_sep, ctype=ctype_camel, id=arg_id)
elif (self.is_shared_ptr(arg.ctype) or self.can_be_pointer(arg.ctype)) and \
arg.ctype.typename.name not in self.ignore_namespace:
call_type = arg.ctype.is_shared_ptr
arg_type = "{std_boost}::shared_ptr<{ctype_sep}>".format(
std_boost='boost' if constructor else 'boost',
ctype_sep=ctype_sep)
unwrap = 'unwrap_shared_ptr< {ctype_sep} >(in[{id}], "ptr_{ctype}");'.format(
ctype_sep=ctype_sep, ctype=ctype_camel, id=arg_id)
else:
arg_type = "{ctype}".format(ctype=arg.ctype.typename.name)
unwrap = 'unwrap< {ctype} >(in[{id}]);'.format(
ctype=arg.ctype.typename.name, id=arg_id)
body_args += textwrap.indent(textwrap.dedent('''\ body_args += textwrap.indent(textwrap.dedent('''\
{arg_type} {name} = {unwrap} {arg_type} {name} = {unwrap}
@ -452,6 +462,7 @@ class MatlabWrapper(CheckMixin, FormatMixin):
""" """
class_name = instantiated_class.name class_name = instantiated_class.name
ctors = instantiated_class.ctors ctors = instantiated_class.ctors
properties = instantiated_class.properties
methods = instantiated_class.methods methods = instantiated_class.methods
static_methods = instantiated_class.static_methods static_methods = instantiated_class.static_methods
@ -469,6 +480,12 @@ class MatlabWrapper(CheckMixin, FormatMixin):
args=self._wrap_args( args=self._wrap_args(
ctor.args)) ctor.args))
if len(properties) != 0:
comment += '%\n' \
'%-------Properties-------\n'
for property in properties:
comment += '%{}\n'.format(property.name)
if len(methods) != 0: if len(methods) != 0:
comment += '%\n' \ comment += '%\n' \
'%-------Methods-------\n' '%-------Methods-------\n'
@ -578,12 +595,13 @@ class MatlabWrapper(CheckMixin, FormatMixin):
param_wrap += textwrap.indent(textwrap.dedent('''\ param_wrap += textwrap.indent(textwrap.dedent('''\
else else
error('Arguments do not match any overload of function {func_name}'); error('Arguments do not match any overload of function {func_name}');
''').format(func_name=function_name), end''').format(func_name=function_name),
prefix=' ') prefix=' ')
global_function = textwrap.indent(textwrap.dedent('''\ global_function = textwrap.indent(textwrap.dedent('''\
function varargout = {m_method}(varargin) function varargout = {m_method}(varargin)
{statements} end {statements}
end
''').format(m_method=function_name, statements=param_wrap), ''').format(m_method=function_name, statements=param_wrap),
prefix='') prefix='')
@ -693,13 +711,82 @@ class MatlabWrapper(CheckMixin, FormatMixin):
return methods_wrap return methods_wrap
def wrap_class_properties(self, class_name): def wrap_properties_block(self, class_name, inst_class):
"""Generate properties of class.""" """Generate Matlab properties block of the class.
return textwrap.dedent('''\
E.g.
```
properties
ptr_gtsamISAM2Params = 0
relinearizeSkip
end
```
Args:
class_name: Class name with namespace to assign unique pointer.
inst_class: The instantiated class whose properties we want to wrap.
Returns:
str: The `properties` block in a Matlab `classdef`.
"""
# Get the property names and make into newline separated block
class_pointer = " ptr_{class_name} = 0".format(class_name=class_name)
if len(inst_class.properties) > 0:
properties = '\n' + "".join(
[" {}".format(p.name) for p in inst_class.properties])
else:
properties = ''
properties = class_pointer + properties
properties_block = textwrap.dedent('''\
properties properties
ptr_{} = 0 {properties}
end end
''').format(class_name) ''').format(properties=properties)
return properties_block
def wrap_class_properties(self, namespace_name: str,
inst_class: InstantiatedClass):
"""Generate wrappers for the setters & getters of class properties.
Args:
inst_class: The instantiated class whose properties we wish to wrap.
"""
properties = []
for property in inst_class.properties:
# These are the setters and getters in the .m file
function_name = namespace_name + inst_class.name + '_get_' + property.name
getter = """
function varargout = get.{name}(this)
{varargout} = {wrapper}({num}, this);
this.{name} = {varargout};
end
""".format(
name=property.name,
varargout='varargout{1}',
wrapper=self._wrapper_name(),
num=self._update_wrapper_id(
(namespace_name, inst_class, property.name, property),
function_name=function_name))
properties.append(getter)
# Setter doesn't need varargin since it needs just one input.
function_name = namespace_name + inst_class.name + '_set_' + property.name
setter = """
function set.{name}(this, value)
obj.{name} = value;
{wrapper}({num}, this, value);
end
""".format(
name=property.name,
wrapper=self._wrapper_name(),
num=self._update_wrapper_id(
(namespace_name, inst_class, property.name, property),
function_name=function_name))
properties.append(setter)
return properties
def wrap_class_deconstructor(self, namespace_name, inst_class): def wrap_class_deconstructor(self, namespace_name, inst_class):
"""Generate the delete function for the Matlab class.""" """Generate the delete function for the Matlab class."""
@ -921,7 +1008,9 @@ class MatlabWrapper(CheckMixin, FormatMixin):
return method_text return method_text
def wrap_instantiated_class(self, instantiated_class, namespace_name=''): def wrap_instantiated_class(self,
instantiated_class,
namespace_name: str = ''):
"""Generate comments and code for given class. """Generate comments and code for given class.
Args: Args:
@ -955,8 +1044,8 @@ class MatlabWrapper(CheckMixin, FormatMixin):
# Class properties # Class properties
content_text += ' ' + reduce( content_text += ' ' + reduce(
self._insert_spaces, self._insert_spaces,
self.wrap_class_properties( self.wrap_properties_block(namespace_file_name,
namespace_file_name).splitlines()) + '\n' instantiated_class).splitlines()) + '\n'
# Class constructor # Class constructor
content_text += ' ' + reduce( content_text += ' ' + reduce(
@ -996,14 +1085,25 @@ class MatlabWrapper(CheckMixin, FormatMixin):
lambda x, y: x + '\n' + ('' if y == '' else ' ') + y, lambda x, y: x + '\n' + ('' if y == '' else ' ') + y,
class_methods_wrapped) + '\n' class_methods_wrapped) + '\n'
# Class properties
if len(instantiated_class.properties) != 0:
property_accessors = self.wrap_class_properties(
namespace_name, instantiated_class)
content_text += textwrap.indent(textwrap.dedent(
"".join(property_accessors)),
prefix=' ')
content_text += ' end' # End the `methods` block
# Static class methods # Static class methods
content_text += ' end\n\n ' + reduce( content_text += '\n\n ' + reduce(
self._insert_spaces, self._insert_spaces,
self.wrap_static_methods(namespace_name, instantiated_class, self.wrap_static_methods(namespace_name, instantiated_class,
serialize[0]).splitlines()) + '\n' serialize[0]).splitlines()) + '\n' + \
' end\n'
# Close the classdef
content_text += textwrap.dedent('''\ content_text += textwrap.dedent('''\
end
end end
''') ''')
@ -1112,6 +1212,41 @@ class MatlabWrapper(CheckMixin, FormatMixin):
return return_type_text return return_type_text
def _collector_return(self, obj: str, ctype: parser.Type):
"""Helper method to get the final statement before the return in the collector function."""
expanded = ''
if self.is_shared_ptr(ctype) or self.is_ptr(ctype) or \
self.can_be_pointer(ctype):
sep_method_name = partial(self._format_type_name,
ctype.typename,
include_namespace=True)
if ctype.typename.name in self.ignore_namespace:
expanded += self.wrap_collector_function_shared_return(
ctype.typename, obj, 0, new_line=False)
if ctype.is_shared_ptr or ctype.is_ptr:
shared_obj = '{obj},"{method_name_sep}"'.format(
obj=obj, method_name_sep=sep_method_name('.'))
else:
method_name_sep_dot = sep_method_name('.')
shared_obj_template = 'boost::make_shared<{method_name_sep_col}>({obj}),' \
'"{method_name_sep_dot}"'
shared_obj = shared_obj_template \
.format(method_name_sep_col=sep_method_name(),
method_name_sep_dot=method_name_sep_dot,
obj=obj)
if ctype.typename.name not in self.ignore_namespace:
expanded += textwrap.indent(
'out[0] = wrap_shared_ptr({}, false);'.format(shared_obj),
prefix=' ')
else:
expanded += ' out[0] = wrap< {} >({});'.format(
ctype.typename.name, obj)
return expanded
def wrap_collector_function_return(self, method): def wrap_collector_function_return(self, method):
""" """
Wrap the complete return type of the function. Wrap the complete return type of the function.
@ -1154,36 +1289,8 @@ class MatlabWrapper(CheckMixin, FormatMixin):
if return_1_name != 'void': if return_1_name != 'void':
if return_count == 1: if return_count == 1:
if self.is_shared_ptr(return_1) or self.is_ptr(return_1) or \ expanded += self._collector_return(obj, return_1)
self.can_be_pointer(return_1):
sep_method_name = partial(self._format_type_name,
return_1.typename,
include_namespace=True)
if return_1.typename.name in self.ignore_namespace:
expanded += self.wrap_collector_function_shared_return(
return_1.typename, obj, 0, new_line=False)
if return_1.is_shared_ptr or return_1.is_ptr:
shared_obj = '{obj},"{method_name_sep}"'.format(
obj=obj, method_name_sep=sep_method_name('.'))
else:
method_name_sep_dot = sep_method_name('.')
shared_obj_template = 'boost::make_shared<{method_name_sep_col}>({obj}),' \
'"{method_name_sep_dot}"'
shared_obj = shared_obj_template \
.format(method_name_sep_col=sep_method_name(),
method_name_sep_dot=method_name_sep_dot,
obj=obj)
if return_1.typename.name not in self.ignore_namespace:
expanded += textwrap.indent(
'out[0] = wrap_shared_ptr({}, false);'.format(
shared_obj),
prefix=' ')
else:
expanded += ' out[0] = wrap< {} >({});'.format(
return_1.typename.name, obj)
elif return_count == 2: elif return_count == 2:
return_2 = method.return_type.type2 return_2 = method.return_type.type2
@ -1197,6 +1304,14 @@ class MatlabWrapper(CheckMixin, FormatMixin):
return expanded return expanded
def wrap_collector_property_return(self, class_property: parser.Variable):
"""Get the last collector function statement before return for a property."""
property_name = class_property.name
obj = 'obj->{}'.format(property_name)
property_type = class_property.ctype
return self._collector_return(obj, property_type)
def wrap_collector_function_upcast_from_void(self, class_name, func_id, def wrap_collector_function_upcast_from_void(self, class_name, func_id,
cpp_name): cpp_name):
""" """
@ -1207,7 +1322,10 @@ class MatlabWrapper(CheckMixin, FormatMixin):
def generate_collector_function(self, func_id): def generate_collector_function(self, func_id):
""" """
Generate the complete collector function. Generate the complete collector function that goes into the wrapper.cpp file.
A collector function is the Mex function used to interact between
the C++ object and the Matlab .m files.
""" """
collector_func = self.wrapper_map.get(func_id) collector_func = self.wrapper_map.get(func_id)
@ -1228,6 +1346,7 @@ class MatlabWrapper(CheckMixin, FormatMixin):
class_name_separated = collector_func[1].to_cpp() class_name_separated = collector_func[1].to_cpp()
is_method = isinstance(extra, parser.Method) is_method = isinstance(extra, parser.Method)
is_static_method = isinstance(extra, parser.StaticMethod) is_static_method = isinstance(extra, parser.StaticMethod)
is_property = isinstance(extra, parser.Variable)
if collector_func[2] == 'collectorInsertAndMakeBase': if collector_func[2] == 'collectorInsertAndMakeBase':
body += textwrap.indent(textwrap.dedent('''\ body += textwrap.indent(textwrap.dedent('''\
@ -1246,6 +1365,7 @@ class MatlabWrapper(CheckMixin, FormatMixin):
*reinterpret_cast<SharedBase**>(mxGetData(out[0])) = new SharedBase(*self); *reinterpret_cast<SharedBase**>(mxGetData(out[0])) = new SharedBase(*self);
''').format(collector_func[1].parent_class), ''').format(collector_func[1].parent_class),
prefix=' ') prefix=' ')
elif collector_func[2] == 'constructor': elif collector_func[2] == 'constructor':
base = '' base = ''
params, body_args = self._wrapper_unwrap_arguments( params, body_args = self._wrapper_unwrap_arguments(
@ -1271,6 +1391,7 @@ class MatlabWrapper(CheckMixin, FormatMixin):
params=params, params=params,
class_name=class_name, class_name=class_name,
base=base) base=base)
elif collector_func[2] == 'deconstructor': elif collector_func[2] == 'deconstructor':
body += textwrap.indent(textwrap.dedent('''\ body += textwrap.indent(textwrap.dedent('''\
typedef boost::shared_ptr<{class_name_sep}> Shared; typedef boost::shared_ptr<{class_name_sep}> Shared;
@ -1285,16 +1406,19 @@ class MatlabWrapper(CheckMixin, FormatMixin):
''').format(class_name_sep=class_name_separated, ''').format(class_name_sep=class_name_separated,
class_name=class_name), class_name=class_name),
prefix=' ') prefix=' ')
elif extra == 'serialize': elif extra == 'serialize':
body += self.wrap_collector_function_serialize( body += self.wrap_collector_function_serialize(
collector_func[1].name, collector_func[1].name,
full_name=collector_func[1].to_cpp(), full_name=collector_func[1].to_cpp(),
namespace=collector_func[0]) namespace=collector_func[0])
elif extra == 'deserialize': elif extra == 'deserialize':
body += self.wrap_collector_function_deserialize( body += self.wrap_collector_function_deserialize(
collector_func[1].name, collector_func[1].name,
full_name=collector_func[1].to_cpp(), full_name=collector_func[1].to_cpp(),
namespace=collector_func[0]) namespace=collector_func[0])
elif is_method or is_static_method: elif is_method or is_static_method:
method_name = '' method_name = ''
@ -1303,12 +1427,9 @@ class MatlabWrapper(CheckMixin, FormatMixin):
method_name += extra.name method_name += extra.name
# return_type = extra.return_type _, body_args = self._wrapper_unwrap_arguments(
# return_count = self._return_count(return_type)
return_body = self.wrap_collector_function_return(extra)
params, body_args = self._wrapper_unwrap_arguments(
extra.args, arg_id=1 if is_method else 0) extra.args, arg_id=1 if is_method else 0)
return_body = self.wrap_collector_function_return(extra)
shared_obj = '' shared_obj = ''
@ -1330,6 +1451,57 @@ class MatlabWrapper(CheckMixin, FormatMixin):
body_args=body_args, body_args=body_args,
return_body=return_body) return_body=return_body)
elif is_property:
shared_obj = ' auto obj = unwrap_shared_ptr<{class_name_sep}>' \
'(in[0], "ptr_{class_name}");\n'.format(
class_name_sep=class_name_separated,
class_name=class_name)
# Unpack the property from mxArray
property_type, unwrap = self._unwrap_argument(extra, arg_id=1)
unpack_property = textwrap.indent(textwrap.dedent('''\
{arg_type} {name} = {unwrap}
'''.format(arg_type=property_type,
name=extra.name,
unwrap=unwrap)),
prefix=' ')
# Getter
if "_get_" in method_name:
return_body = self.wrap_collector_property_return(extra)
getter = ' checkArguments("{property_name}",nargout,nargin{min1},' \
'{num_args});\n' \
'{shared_obj}' \
'{return_body}\n'.format(
property_name=extra.name,
min1='-1',
num_args=0,
shared_obj=shared_obj,
return_body=return_body)
body += getter
# Setter
if "_set_" in method_name:
is_ptr_type = self.can_be_pointer(extra.ctype)
return_body = ' obj->{0} = {1}{0};'.format(
extra.name, '*' if is_ptr_type else '')
setter = ' checkArguments("{property_name}",nargout,nargin{min1},' \
'{num_args});\n' \
'{shared_obj}' \
'{unpack_property}' \
'{return_body}\n'.format(
property_name=extra.name,
min1='-1',
num_args=1,
shared_obj=shared_obj,
unpack_property=unpack_property,
return_body=return_body)
body += setter
body += '}\n' body += '}\n'
if extra not in ['serialize', 'deserialize']: if extra not in ['serialize', 'deserialize']:

View File

@ -5,7 +5,8 @@ from gtwrap.template_instantiator.constructor import InstantiatedConstructor
from gtwrap.template_instantiator.helpers import (InstantiationHelper, from gtwrap.template_instantiator.helpers import (InstantiationHelper,
instantiate_args_list, instantiate_args_list,
instantiate_name, instantiate_name,
instantiate_return_type) instantiate_return_type,
instantiate_type)
from gtwrap.template_instantiator.method import (InstantiatedMethod, from gtwrap.template_instantiator.method import (InstantiatedMethod,
InstantiatedStaticMethod) InstantiatedStaticMethod)
@ -14,6 +15,7 @@ class InstantiatedClass(parser.Class):
""" """
Instantiate the class defined in the interface file. Instantiate the class defined in the interface file.
""" """
def __init__(self, original: parser.Class, instantiations=(), new_name=''): def __init__(self, original: parser.Class, instantiations=(), new_name=''):
""" """
Template <T, U> Template <T, U>
@ -24,7 +26,6 @@ class InstantiatedClass(parser.Class):
self.template = None self.template = None
self.is_virtual = original.is_virtual self.is_virtual = original.is_virtual
self.parent_class = original.parent_class
self.parent = original.parent self.parent = original.parent
# If the class is templated, check if the number of provided instantiations # If the class is templated, check if the number of provided instantiations
@ -42,7 +43,8 @@ class InstantiatedClass(parser.Class):
# This will allow the `This` keyword to be used in both templated and non-templated classes. # This will allow the `This` keyword to be used in both templated and non-templated classes.
typenames = self.original.template.typenames if self.original.template else [] typenames = self.original.template.typenames if self.original.template else []
# Instantiate the constructors, static methods, properties, respectively. # Instantiate the parent class, constructors, static methods, properties, respectively.
self.parent_class = self.instantiate_parent_class(typenames)
self.ctors = self.instantiate_ctors(typenames) self.ctors = self.instantiate_ctors(typenames)
self.static_methods = self.instantiate_static_methods(typenames) self.static_methods = self.instantiate_static_methods(typenames)
self.properties = self.instantiate_properties(typenames) self.properties = self.instantiate_properties(typenames)
@ -83,6 +85,23 @@ class InstantiatedClass(parser.Class):
operators="\n".join([repr(op) for op in self.operators]) operators="\n".join([repr(op) for op in self.operators])
) )
def instantiate_parent_class(self, typenames):
"""
Instantiate the inherited parent names.
Args:
typenames: List of template types to instantiate.
Return: List of constructors instantiated with provided template args.
"""
if isinstance(self.original.parent_class, parser.type.TemplatedType):
return instantiate_type(
self.original.parent_class, typenames, self.instantiations,
parser.Typename(self.namespaces())).typename
else:
return self.original.parent_class
def instantiate_ctors(self, typenames): def instantiate_ctors(self, typenames):
""" """
Instantiate the class constructors. Instantiate the class constructors.
@ -178,12 +197,18 @@ class InstantiatedClass(parser.Class):
Return: List of properties instantiated with provided template args. Return: List of properties instantiated with provided template args.
""" """
instantiated_properties = instantiate_args_list( instantiated_ = instantiate_args_list(
self.original.properties, self.original.properties,
typenames, typenames,
self.instantiations, self.instantiations,
self.cpp_typename(), self.cpp_typename(),
) )
# Convert to type Variable
instantiated_properties = [
parser.Variable(ctype=[arg.ctype],
name=arg.name,
default=arg.default) for arg in instantiated_
]
return instantiated_properties return instantiated_properties
def cpp_typename(self): def cpp_typename(self):

View File

@ -55,7 +55,8 @@ class InstantiatedGlobalFunction(parser.GlobalFunction):
"""Generate the C++ code for wrapping.""" """Generate the C++ code for wrapping."""
if self.original.template: if self.original.template:
instantiated_names = [ instantiated_names = [
inst.instantiated_name() for inst in self.instantiations "::".join(inst.namespaces + [inst.instantiated_name()])
for inst in self.instantiations
] ]
ret = "{}<{}>".format(self.original.name, ret = "{}<{}>".format(self.original.name,
",".join(instantiated_names)) ",".join(instantiated_names))

View File

@ -1,6 +1,6 @@
version: 1.0.{build} version: 1.0.{build}
image: image:
- Visual Studio 2015 - Visual Studio 2017
test: off test: off
skip_branch_with_pr: true skip_branch_with_pr: true
build: build:
@ -11,11 +11,9 @@ environment:
matrix: matrix:
- PYTHON: 36 - PYTHON: 36
CONFIG: Debug CONFIG: Debug
- PYTHON: 27
CONFIG: Debug
install: install:
- ps: | - ps: |
$env:CMAKE_GENERATOR = "Visual Studio 14 2015" $env:CMAKE_GENERATOR = "Visual Studio 15 2017"
if ($env:PLATFORM -eq "x64") { $env:PYTHON = "$env:PYTHON-x64" } if ($env:PLATFORM -eq "x64") { $env:PYTHON = "$env:PYTHON-x64" }
$env:PATH = "C:\Python$env:PYTHON\;C:\Python$env:PYTHON\Scripts\;$env:PATH" $env:PATH = "C:\Python$env:PYTHON\;C:\Python$env:PYTHON\Scripts\;$env:PATH"
python -W ignore -m pip install --upgrade pip wheel python -W ignore -m pip install --upgrade pip wheel

View File

@ -3,17 +3,36 @@
# clang-format --style=llvm --dump-config # clang-format --style=llvm --dump-config
BasedOnStyle: LLVM BasedOnStyle: LLVM
AccessModifierOffset: -4 AccessModifierOffset: -4
AllowShortLambdasOnASingleLine: true
AlwaysBreakTemplateDeclarations: Yes AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: false BinPackArguments: false
BinPackParameters: false BinPackParameters: false
BreakBeforeBinaryOperators: All BreakBeforeBinaryOperators: All
BreakConstructorInitializers: BeforeColon BreakConstructorInitializers: BeforeColon
ColumnLimit: 99 ColumnLimit: 99
CommentPragmas: 'NOLINT:.*|^ IWYU pragma:'
IncludeBlocks: Regroup
IndentCaseLabels: true IndentCaseLabels: true
IndentPPDirectives: AfterHash IndentPPDirectives: AfterHash
IndentWidth: 4 IndentWidth: 4
Language: Cpp Language: Cpp
SpaceAfterCStyleCast: true SpaceAfterCStyleCast: true
Standard: Cpp11 Standard: Cpp11
StatementMacros: ['PyObject_HEAD']
TabWidth: 4 TabWidth: 4
IncludeCategories:
- Regex: '<pybind11/.*'
Priority: -1
- Regex: 'pybind11.h"$'
Priority: 1
- Regex: '^".*/?detail/'
Priority: 1
SortPriority: 2
- Regex: '^"'
Priority: 1
SortPriority: 3
- Regex: '<[[:alnum:]._]+>'
Priority: 4
- Regex: '.*'
Priority: 5
... ...

View File

@ -1,66 +1,75 @@
FormatStyle: file FormatStyle: file
Checks: ' Checks: |
*bugprone*, *bugprone*,
cppcoreguidelines-init-variables, *performance*,
cppcoreguidelines-slicing, clang-analyzer-optin.cplusplus.VirtualCall,
clang-analyzer-optin.cplusplus.VirtualCall, clang-analyzer-optin.performance.Padding,
google-explicit-constructor, cppcoreguidelines-init-variables,
llvm-namespace-comment, cppcoreguidelines-prefer-member-initializer,
misc-misplaced-const, cppcoreguidelines-pro-type-static-cast-downcast,
misc-non-copyable-objects, cppcoreguidelines-slicing,
misc-static-assert, google-explicit-constructor,
misc-throw-by-value-catch-by-reference, llvm-namespace-comment,
misc-uniqueptr-reset-release, misc-definitions-in-headers,
misc-unused-parameters, misc-misplaced-const,
modernize-avoid-bind, misc-non-copyable-objects,
modernize-make-shared, misc-static-assert,
modernize-redundant-void-arg, misc-throw-by-value-catch-by-reference,
modernize-replace-auto-ptr, misc-uniqueptr-reset-release,
modernize-replace-disallow-copy-and-assign-macro, misc-unused-parameters,
modernize-replace-random-shuffle, modernize-avoid-bind,
modernize-shrink-to-fit, modernize-loop-convert,
modernize-use-auto, modernize-make-shared,
modernize-use-bool-literals, modernize-redundant-void-arg,
modernize-use-equals-default, modernize-replace-auto-ptr,
modernize-use-equals-delete, modernize-replace-disallow-copy-and-assign-macro,
modernize-use-default-member-init, modernize-replace-random-shuffle,
modernize-use-noexcept, modernize-shrink-to-fit,
modernize-use-emplace, modernize-use-auto,
modernize-use-override, modernize-use-bool-literals,
modernize-use-using, modernize-use-default-member-init,
*performance*, modernize-use-emplace,
readability-avoid-const-params-in-decls, modernize-use-equals-default,
readability-const-return-type, modernize-use-equals-delete,
readability-container-size-empty, modernize-use-noexcept,
readability-delete-null-pointer, modernize-use-nullptr,
readability-else-after-return, modernize-use-override,
readability-implicit-bool-conversion, modernize-use-using,
readability-make-member-function-const, readability-avoid-const-params-in-decls,
readability-misplaced-array-index, readability-braces-around-statements,
readability-non-const-parameter, readability-const-return-type,
readability-redundant-function-ptr-dereference, readability-container-size-empty,
readability-redundant-smartptr-get, readability-delete-null-pointer,
readability-redundant-string-cstr, readability-else-after-return,
readability-simplify-subscript-expr, readability-implicit-bool-conversion,
readability-static-accessed-through-instance, readability-inconsistent-declaration-parameter-name,
readability-static-definition-in-anonymous-namespace, readability-make-member-function-const,
readability-string-compare, readability-misplaced-array-index,
readability-suspicious-call-argument, readability-non-const-parameter,
readability-uniqueptr-delete-release, readability-qualified-auto,
-bugprone-exception-escape, readability-redundant-function-ptr-dereference,
-bugprone-reserved-identifier, readability-redundant-smartptr-get,
-bugprone-unused-raii, readability-redundant-string-cstr,
' readability-simplify-subscript-expr,
readability-static-accessed-through-instance,
readability-static-definition-in-anonymous-namespace,
readability-string-compare,
readability-suspicious-call-argument,
readability-uniqueptr-delete-release,
-bugprone-easily-swappable-parameters,
-bugprone-exception-escape,
-bugprone-reserved-identifier,
-bugprone-unused-raii,
CheckOptions: CheckOptions:
- key: performance-for-range-copy.WarnOnAllAutoCopies - key: performance-for-range-copy.WarnOnAllAutoCopies
value: true value: true
- key: performance-inefficient-string-concatenation.StrictMode
value: true
- key: performance-unnecessary-value-param.AllowedTypes - key: performance-unnecessary-value-param.AllowedTypes
value: 'exception_ptr$;' value: 'exception_ptr$;'
- key: readability-implicit-bool-conversion.AllowPointerConditions - key: readability-implicit-bool-conversion.AllowPointerConditions
value: true value: true
HeaderFilterRegex: 'pybind11/.*h' HeaderFilterRegex: 'pybind11/.*h'
WarningsAsErrors: '*'

1
pybind11/.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
docs/*.svg binary

View File

@ -93,11 +93,10 @@ cmake --build build -j4
Tips: Tips:
* You can use `virtualenv` (from PyPI) instead of `venv` (which is Python 3 * You can use `virtualenv` (faster, from PyPI) instead of `venv`.
only).
* You can select any name for your environment folder; if it contains "env" it * You can select any name for your environment folder; if it contains "env" it
will be ignored by git. will be ignored by git.
* If you dont have CMake 3.14+, just add “cmake” to the pip install command. * If you don't have CMake 3.14+, just add "cmake" to the pip install command.
* You can use `-DPYBIND11_FINDPYTHON=ON` to use FindPython on CMake 3.12+ * You can use `-DPYBIND11_FINDPYTHON=ON` to use FindPython on CMake 3.12+
* In classic mode, you may need to set `-DPYTHON_EXECUTABLE=/path/to/python`. * In classic mode, you may need to set `-DPYTHON_EXECUTABLE=/path/to/python`.
FindPython uses `-DPython_ROOT_DIR=/path/to` or FindPython uses `-DPython_ROOT_DIR=/path/to` or
@ -105,7 +104,7 @@ Tips:
### Configuration options ### Configuration options
In CMake, configuration options are given with “-D”. Options are stored in the In CMake, configuration options are given with "-D". Options are stored in the
build directory, in the `CMakeCache.txt` file, so they are remembered for each build directory, in the `CMakeCache.txt` file, so they are remembered for each
build directory. Two selections are special - the generator, given with `-G`, build directory. Two selections are special - the generator, given with `-G`,
and the compiler, which is selected based on environment variables `CXX` and and the compiler, which is selected based on environment variables `CXX` and
@ -115,7 +114,7 @@ after the initial run.
The valid options are: The valid options are:
* `-DCMAKE_BUILD_TYPE`: Release, Debug, MinSizeRel, RelWithDebInfo * `-DCMAKE_BUILD_TYPE`: Release, Debug, MinSizeRel, RelWithDebInfo
* `-DPYBIND11_FINDPYTHON=ON`: Use CMake 3.12+s FindPython instead of the * `-DPYBIND11_FINDPYTHON=ON`: Use CMake 3.12+'s FindPython instead of the
classic, deprecated, custom FindPythonLibs classic, deprecated, custom FindPythonLibs
* `-DPYBIND11_NOPYTHON=ON`: Disable all Python searching (disables tests) * `-DPYBIND11_NOPYTHON=ON`: Disable all Python searching (disables tests)
* `-DBUILD_TESTING=ON`: Enable the tests * `-DBUILD_TESTING=ON`: Enable the tests
@ -236,12 +235,14 @@ directory inside your pybind11 git clone. Files will be modified in place,
so you can use git to monitor the changes. so you can use git to monitor the changes.
```bash ```bash
docker run --rm -v $PWD:/mounted_pybind11 -it silkeh/clang:12 docker run --rm -v $PWD:/mounted_pybind11 -it silkeh/clang:13
apt-get update && apt-get install -y python3-dev python3-pytest apt-get update && apt-get install -y python3-dev python3-pytest
cmake -S /mounted_pybind11/ -B build -DCMAKE_CXX_CLANG_TIDY="$(which clang-tidy);-fix" -DDOWNLOAD_EIGEN=ON -DDOWNLOAD_CATCH=ON -DCMAKE_CXX_STANDARD=17 cmake -S /mounted_pybind11/ -B build -DCMAKE_CXX_CLANG_TIDY="$(which clang-tidy);--use-color" -DDOWNLOAD_EIGEN=ON -DDOWNLOAD_CATCH=ON -DCMAKE_CXX_STANDARD=17
cmake --build build -j 2 -- --keep-going cmake --build build -j 2
``` ```
You can add `--fix` to the options list if you want.
### Include what you use ### Include what you use
To run include what you use, install (`brew install include-what-you-use` on To run include what you use, install (`brew install include-what-you-use` on
@ -257,7 +258,7 @@ The report is sent to stderr; you can pipe it into a file if you wish.
### Build recipes ### Build recipes
This builds with the Intel compiler (assuming it is in your path, along with a This builds with the Intel compiler (assuming it is in your path, along with a
recent CMake and Python 3): recent CMake and Python):
```bash ```bash
python3 -m venv venv python3 -m venv venv

View File

@ -5,12 +5,3 @@ updates:
directory: "/" directory: "/"
schedule: schedule:
interval: "daily" interval: "daily"
ignore:
# Official actions have moving tags like v1
# that are used, so they don't need updates here
- dependency-name: "actions/checkout"
- dependency-name: "actions/setup-python"
- dependency-name: "actions/cache"
- dependency-name: "actions/upload-artifact"
- dependency-name: "actions/download-artifact"
- dependency-name: "actions/labeler"

32
pybind11/.github/matchers/pylint.json vendored Normal file
View File

@ -0,0 +1,32 @@
{
"problemMatcher": [
{
"severity": "warning",
"pattern": [
{
"regexp": "^([^:]+):(\\d+):(\\d+): ([A-DF-Z]\\d+): \\033\\[[\\d;]+m([^\\033]+).*$",
"file": 1,
"line": 2,
"column": 3,
"code": 4,
"message": 5
}
],
"owner": "pylint-warning"
},
{
"severity": "error",
"pattern": [
{
"regexp": "^([^:]+):(\\d+):(\\d+): (E\\d+): \\033\\[[\\d;]+m([^\\033]+).*$",
"file": 1,
"line": 2,
"column": 3,
"code": 4,
"message": 5
}
],
"owner": "pylint-error"
}
]
}

View File

@ -15,6 +15,8 @@ concurrency:
env: env:
PIP_ONLY_BINARY: numpy PIP_ONLY_BINARY: numpy
FORCE_COLOR: 3
PYTEST_TIMEOUT: 300
jobs: jobs:
# This is the "main" test suite, which tests a large number of different # This is the "main" test suite, which tests a large number of different
@ -25,13 +27,13 @@ jobs:
matrix: matrix:
runs-on: [ubuntu-latest, windows-2022, macos-latest] runs-on: [ubuntu-latest, windows-2022, macos-latest]
python: python:
- '2.7'
- '3.5'
- '3.6' - '3.6'
- '3.9' - '3.9'
- '3.10' - '3.10'
- 'pypy-3.7-v7.3.7' - '3.11-dev'
- 'pypy-3.8-v7.3.7' - 'pypy-3.7'
- 'pypy-3.8'
- 'pypy-3.9'
# Items in here will either be added to the build matrix (if not # Items in here will either be added to the build matrix (if not
# present), or add new keys to an existing matrix element if all the # present), or add new keys to an existing matrix element if all the
@ -45,26 +47,26 @@ jobs:
args: > args: >
-DPYBIND11_FINDPYTHON=ON -DPYBIND11_FINDPYTHON=ON
-DCMAKE_CXX_FLAGS="-D_=1" -DCMAKE_CXX_FLAGS="-D_=1"
- runs-on: windows-latest - runs-on: ubuntu-latest
python: 'pypy-3.8'
args: >
-DPYBIND11_FINDPYTHON=ON
- runs-on: windows-2019
python: '3.6' python: '3.6'
args: > args: >
-DPYBIND11_FINDPYTHON=ON -DPYBIND11_FINDPYTHON=ON
- runs-on: macos-latest
python: 'pypy-2.7'
# Inject a couple Windows 2019 runs # Inject a couple Windows 2019 runs
- runs-on: windows-2019 - runs-on: windows-2019
python: '3.9' python: '3.9'
- runs-on: windows-2019
python: '2.7'
name: "🐍 ${{ matrix.python }} • ${{ matrix.runs-on }} • x64 ${{ matrix.args }}" name: "🐍 ${{ matrix.python }} • ${{ matrix.runs-on }} • x64 ${{ matrix.args }}"
runs-on: ${{ matrix.runs-on }} runs-on: ${{ matrix.runs-on }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Setup Python ${{ matrix.python }} - name: Setup Python ${{ matrix.python }}
uses: actions/setup-python@v2 uses: actions/setup-python@v4
with: with:
python-version: ${{ matrix.python }} python-version: ${{ matrix.python }}
@ -82,7 +84,7 @@ jobs:
- name: Cache wheels - name: Cache wheels
if: runner.os == 'macOS' if: runner.os == 'macOS'
uses: actions/cache@v2 uses: actions/cache@v3
with: with:
# This path is specific to macOS - we really only need it for PyPy NumPy wheels # This path is specific to macOS - we really only need it for PyPy NumPy wheels
# See https://github.com/actions/cache/blob/master/examples.md#python---pip # See https://github.com/actions/cache/blob/master/examples.md#python---pip
@ -168,27 +170,11 @@ jobs:
- name: Interface test - name: Interface test
run: cmake --build build2 --target test_cmake_build run: cmake --build build2 --target test_cmake_build
# Eventually Microsoft might have an action for setting up
# MSVC, but for now, this action works:
- name: Prepare compiler environment for Windows 🐍 2.7
if: matrix.python == 2.7 && runner.os == 'Windows'
uses: ilammy/msvc-dev-cmd@v1.10.0
with:
arch: x64
# This makes two environment variables available in the following step(s)
- name: Set Windows 🐍 2.7 environment variables
if: matrix.python == 2.7 && runner.os == 'Windows'
shell: bash
run: |
echo "DISTUTILS_USE_SDK=1" >> $GITHUB_ENV
echo "MSSdk=1" >> $GITHUB_ENV
# This makes sure the setup_helpers module can build packages using # This makes sure the setup_helpers module can build packages using
# setuptools # setuptools
- name: Setuptools helpers test - name: Setuptools helpers test
run: pytest tests/extra_setuptools run: pytest tests/extra_setuptools
if: "!(matrix.python == '3.5' && matrix.runs-on == 'windows-2022')" if: "!(matrix.runs-on == 'windows-2022')"
deadsnakes: deadsnakes:
@ -200,14 +186,14 @@ jobs:
- python-version: "3.9" - python-version: "3.9"
python-debug: true python-debug: true
valgrind: true valgrind: true
# - python-version: "3.11-dev" - python-version: "3.11-dev"
# python-debug: false python-debug: false
name: "🐍 ${{ matrix.python-version }}${{ matrix.python-debug && '-dbg' || '' }} (deadsnakes)${{ matrix.valgrind && ' • Valgrind' || '' }} • x64" name: "🐍 ${{ matrix.python-version }}${{ matrix.python-debug && '-dbg' || '' }} (deadsnakes)${{ matrix.valgrind && ' • Valgrind' || '' }} • x64"
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Setup Python ${{ matrix.python-version }} (deadsnakes) - name: Setup Python ${{ matrix.python-version }} (deadsnakes)
uses: deadsnakes/action@v2.1.1 uses: deadsnakes/action@v2.1.1
@ -220,7 +206,7 @@ jobs:
- name: Valgrind cache - name: Valgrind cache
if: matrix.valgrind if: matrix.valgrind
uses: actions/cache@v2 uses: actions/cache@v3
id: cache-valgrind id: cache-valgrind
with: with:
path: valgrind path: valgrind
@ -295,12 +281,14 @@ jobs:
std: 20 std: 20
- clang: 10 - clang: 10
std: 17 std: 17
- clang: 14
std: 20
name: "🐍 3 • Clang ${{ matrix.clang }} • C++${{ matrix.std }} • x64" name: "🐍 3 • Clang ${{ matrix.clang }} • C++${{ matrix.std }} • x64"
container: "silkeh/clang:${{ matrix.clang }}" container: "silkeh/clang:${{ matrix.clang }}"
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Add wget and python3 - name: Add wget and python3
run: apt-get update && apt-get install -y python3-dev python3-numpy python3-pytest libeigen3-dev run: apt-get update && apt-get install -y python3-dev python3-numpy python3-pytest libeigen3-dev
@ -330,11 +318,11 @@ jobs:
# Testing NVCC; forces sources to behave like .cu files # Testing NVCC; forces sources to behave like .cu files
cuda: cuda:
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: "🐍 3.8 • CUDA 11 • Ubuntu 20.04" name: "🐍 3.8 • CUDA 11.2 • Ubuntu 20.04"
container: nvidia/cuda:11.0-devel-ubuntu20.04 container: nvidia/cuda:11.2.2-devel-ubuntu20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
# tzdata will try to ask for the timezone, so set the DEBIAN_FRONTEND # tzdata will try to ask for the timezone, so set the DEBIAN_FRONTEND
- name: Install 🐍 3 - name: Install 🐍 3
@ -358,7 +346,7 @@ jobs:
# container: centos:8 # container: centos:8
# #
# steps: # steps:
# - uses: actions/checkout@v2 # - uses: actions/checkout@v3
# #
# - name: Add Python 3 and a few requirements # - name: Add Python 3 and a few requirements
# run: yum update -y && yum install -y git python3-devel python3-numpy python3-pytest make environment-modules # run: yum update -y && yum install -y git python3-devel python3-numpy python3-pytest make environment-modules
@ -397,17 +385,17 @@ jobs:
# Testing on CentOS 7 + PGI compilers, which seems to require more workarounds # Testing on CentOS 7 + PGI compilers, which seems to require more workarounds
centos-nvhpc7: centos-nvhpc7:
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: "🐍 3 • CentOS7 / PGI 20.9 • x64" name: "🐍 3 • CentOS7 / PGI 22.3 • x64"
container: centos:7 container: centos:7
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Add Python 3 and a few requirements - name: Add Python 3 and a few requirements
run: yum update -y && yum install -y epel-release && yum install -y git python3-devel make environment-modules cmake3 run: yum update -y && yum install -y epel-release && yum install -y git python3-devel make environment-modules cmake3 yum-utils
- name: Install NVidia HPC SDK - name: Install NVidia HPC SDK
run: yum -y install https://developer.download.nvidia.com/hpc-sdk/20.9/nvhpc-20-9-20.9-1.x86_64.rpm https://developer.download.nvidia.com/hpc-sdk/20.9/nvhpc-2020-20.9-1.x86_64.rpm run: yum-config-manager --add-repo https://developer.download.nvidia.com/hpc-sdk/rhel/nvhpc.repo && yum -y install nvhpc-22.3
# On CentOS 7, we have to filter a few tests (compiler internal error) # On CentOS 7, we have to filter a few tests (compiler internal error)
# and allow deeper template recursion (not needed on CentOS 8 with a newer # and allow deeper template recursion (not needed on CentOS 8 with a newer
@ -417,7 +405,7 @@ jobs:
shell: bash shell: bash
run: | run: |
source /etc/profile.d/modules.sh source /etc/profile.d/modules.sh
module load /opt/nvidia/hpc_sdk/modulefiles/nvhpc/20.9 module load /opt/nvidia/hpc_sdk/modulefiles/nvhpc/22.3
cmake3 -S . -B build -DDOWNLOAD_CATCH=ON \ cmake3 -S . -B build -DDOWNLOAD_CATCH=ON \
-DCMAKE_CXX_STANDARD=11 \ -DCMAKE_CXX_STANDARD=11 \
-DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") \ -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") \
@ -462,7 +450,7 @@ jobs:
container: "gcc:${{ matrix.gcc }}" container: "gcc:${{ matrix.gcc }}"
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v3
- name: Add Python 3 - name: Add Python 3
run: apt-get update; apt-get install -y python3-dev python3-numpy python3-pytest python3-pip libeigen3-dev run: apt-get update; apt-get install -y python3-dev python3-numpy python3-pytest python3-pip libeigen3-dev
@ -504,7 +492,7 @@ jobs:
name: "🐍 3 • ICC latest • x64" name: "🐍 3 • ICC latest • x64"
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Add apt repo - name: Add apt repo
run: | run: |
@ -599,19 +587,25 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
centos: container:
- centos7 # GCC 4.8 - "centos:7" # GCC 4.8
- stream8 - "almalinux:8"
- "almalinux:9"
name: "🐍 3 • CentOS ${{ matrix.centos }} • x64" name: "🐍 3 • ${{ matrix.container }} • x64"
container: "quay.io/centos/centos:${{ matrix.centos }}" container: "${{ matrix.container }}"
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Add Python 3 - name: Add Python 3 (RHEL 7)
if: matrix.container == 'centos:7'
run: yum update -y && yum install -y python3-devel gcc-c++ make git run: yum update -y && yum install -y python3-devel gcc-c++ make git
- name: Add Python 3 (RHEL 8+)
if: matrix.container != 'centos:7'
run: dnf update -y && dnf install -y python3-devel gcc-c++ make git
- name: Update pip - name: Update pip
run: python3 -m pip install --upgrade pip run: python3 -m pip install --upgrade pip
@ -645,18 +639,18 @@ jobs:
# This tests an "install" with the CMake tools # This tests an "install" with the CMake tools
install-classic: install-classic:
name: "🐍 3.5 • Debian • x86 • Install" name: "🐍 3.7 • Debian • x86 • Install"
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: i386/debian:stretch container: i386/debian:buster
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1 # Required to run inside docker
- name: Install requirements - name: Install requirements
run: | run: |
apt-get update apt-get update
apt-get install -y git make cmake g++ libeigen3-dev python3-dev python3-pip apt-get install -y git make cmake g++ libeigen3-dev python3-dev python3-pip
pip3 install "pytest==3.1.*" pip3 install "pytest==6.*"
- name: Configure for install - name: Configure for install
run: > run: >
@ -687,15 +681,17 @@ jobs:
# This verifies that the documentation is not horribly broken, and does a # This verifies that the documentation is not horribly broken, and does a
# basic sanity check on the SDist. # basic validation check on the SDist.
doxygen: doxygen:
name: "Documentation build test" name: "Documentation build test"
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions/setup-python@v2 - uses: actions/setup-python@v4
with:
python-version: "3.x"
- name: Install Doxygen - name: Install Doxygen
run: sudo apt-get install -y doxygen librsvg2-bin # Changed to rsvg-convert in 20.04 run: sudo apt-get install -y doxygen librsvg2-bin # Changed to rsvg-convert in 20.04
@ -725,27 +721,25 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
python: python:
- 3.5
- 3.6 - 3.6
- 3.7 - 3.7
- 3.8 - 3.8
- 3.9 - 3.9
- pypy-3.6
include: include:
- python: 3.9 - python: 3.9
args: -DCMAKE_CXX_STANDARD=20 -DDOWNLOAD_EIGEN=OFF args: -DCMAKE_CXX_STANDARD=20
- python: 3.8 - python: 3.8
args: -DCMAKE_CXX_STANDARD=17 args: -DCMAKE_CXX_STANDARD=17
name: "🐍 ${{ matrix.python }} • MSVC 2019 • x86 ${{ matrix.args }}" name: "🐍 ${{ matrix.python }} • MSVC 2019 • x86 ${{ matrix.args }}"
runs-on: windows-latest runs-on: windows-2019
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Setup Python ${{ matrix.python }} - name: Setup Python ${{ matrix.python }}
uses: actions/setup-python@v2 uses: actions/setup-python@v4
with: with:
python-version: ${{ matrix.python }} python-version: ${{ matrix.python }}
architecture: x86 architecture: x86
@ -777,25 +771,31 @@ jobs:
- name: Python tests - name: Python tests
run: cmake --build build -t pytest run: cmake --build build -t pytest
win32-msvc2015: win32-debug:
name: "🐍 ${{ matrix.python }} • MSVC 2015 • x64"
runs-on: windows-latest
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
python: python:
- 2.7 - 3.8
- 3.6 - 3.9
- 3.7
# todo: check/cpptest does not support 3.8+ yet include:
- python: 3.9
args: -DCMAKE_CXX_STANDARD=20
- python: 3.8
args: -DCMAKE_CXX_STANDARD=17
name: "🐍 ${{ matrix.python }} • MSVC 2019 (Debug) • x86 ${{ matrix.args }}"
runs-on: windows-2019
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Setup 🐍 ${{ matrix.python }} - name: Setup Python ${{ matrix.python }}
uses: actions/setup-python@v2 uses: actions/setup-python@v4
with: with:
python-version: ${{ matrix.python }} python-version: ${{ matrix.python }}
architecture: x86
- name: Update CMake - name: Update CMake
uses: jwlawson/actions-setup-cmake@v1.12 uses: jwlawson/actions-setup-cmake@v1.12
@ -803,82 +803,73 @@ jobs:
- name: Prepare MSVC - name: Prepare MSVC
uses: ilammy/msvc-dev-cmd@v1.10.0 uses: ilammy/msvc-dev-cmd@v1.10.0
with: with:
toolset: 14.0 arch: x86
- name: Prepare env - name: Prepare env
run: | run: |
python -m pip install -r tests/requirements.txt python -m pip install -r tests/requirements.txt
# First build - C++11 mode and inplace # First build - C++11 mode and inplace
- name: Configure - name: Configure ${{ matrix.args }}
run: > run: >
cmake -S . -B build cmake -S . -B build
-G "Visual Studio 14 2015" -A x64 -G "Visual Studio 16 2019" -A Win32
-DCMAKE_BUILD_TYPE=Debug
-DPYBIND11_WERROR=ON -DPYBIND11_WERROR=ON
-DDOWNLOAD_CATCH=ON -DDOWNLOAD_CATCH=ON
-DDOWNLOAD_EIGEN=ON -DDOWNLOAD_EIGEN=ON
${{ matrix.args }}
- name: Build C++11
run: cmake --build build --config Debug -j 2
- name: Build C++14 - name: Python tests
run: cmake --build build -j 2 run: cmake --build build --config Debug -t pytest
- name: Run all checks
run: cmake --build build -t check
win32-msvc2017: windows-2022:
name: "🐍 ${{ matrix.python }} • MSVC 2017 • x64"
runs-on: windows-2016
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
python: python:
- 2.7 - 3.9
- 3.5
- 3.7
std:
- 14
include: name: "🐍 ${{ matrix.python }} • MSVC 2022 C++20 • x64"
- python: 2.7 runs-on: windows-2022
std: 17
args: >
-DCMAKE_CXX_FLAGS="/permissive- /EHsc /GR"
- python: 3.7
std: 17
args: >
-DCMAKE_CXX_FLAGS="/permissive- /EHsc /GR"
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Setup 🐍 ${{ matrix.python }} - name: Setup Python ${{ matrix.python }}
uses: actions/setup-python@v2 uses: actions/setup-python@v4
with: with:
python-version: ${{ matrix.python }} python-version: ${{ matrix.python }}
- name: Prepare env
run: |
python3 -m pip install -r tests/requirements.txt
- name: Update CMake - name: Update CMake
uses: jwlawson/actions-setup-cmake@v1.12 uses: jwlawson/actions-setup-cmake@v1.12
- name: Prepare env - name: Configure C++20
run: |
python -m pip install -r tests/requirements.txt
# First build - C++11 mode and inplace
- name: Configure
run: > run: >
cmake -S . -B build cmake -S . -B build
-G "Visual Studio 15 2017" -A x64
-DPYBIND11_WERROR=ON -DPYBIND11_WERROR=ON
-DDOWNLOAD_CATCH=ON -DDOWNLOAD_CATCH=ON
-DDOWNLOAD_EIGEN=ON -DDOWNLOAD_EIGEN=ON
-DCMAKE_CXX_STANDARD=${{ matrix.std }} -DCMAKE_CXX_STANDARD=20
${{ matrix.args }}
- name: Build ${{ matrix.std }} - name: Build C++20
run: cmake --build build -j 2 run: cmake --build build -j 2
- name: Run all checks - name: Python tests
run: cmake --build build -t check run: cmake --build build --target pytest
- name: C++20 tests
run: cmake --build build --target cpptest -j 2
- name: Interface test C++20
run: cmake --build build --target test_cmake_build
mingw: mingw:
name: "🐍 3 • windows-latest • ${{ matrix.sys }}" name: "🐍 3 • windows-latest • ${{ matrix.sys }}"
@ -909,12 +900,12 @@ jobs:
mingw-w64-${{matrix.env}}-boost mingw-w64-${{matrix.env}}-boost
mingw-w64-${{matrix.env}}-catch mingw-w64-${{matrix.env}}-catch
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Configure C++11 - name: Configure C++11
# LTO leads to many undefined reference like # LTO leads to many undefined reference like
# `pybind11::detail::function_call::function_call(pybind11::detail::function_call&&) # `pybind11::detail::function_call::function_call(pybind11::detail::function_call&&)
run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=11 -S . -B build run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=11 -DDOWNLOAD_CATCH=ON -S . -B build
- name: Build C++11 - name: Build C++11
run: cmake --build build -j 2 run: cmake --build build -j 2
@ -932,7 +923,7 @@ jobs:
run: git clean -fdx run: git clean -fdx
- name: Configure C++14 - name: Configure C++14
run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=14 -S . -B build2 run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=14 -DDOWNLOAD_CATCH=ON -S . -B build2
- name: Build C++14 - name: Build C++14
run: cmake --build build2 -j 2 run: cmake --build build2 -j 2
@ -950,7 +941,7 @@ jobs:
run: git clean -fdx run: git clean -fdx
- name: Configure C++17 - name: Configure C++17
run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=17 -S . -B build3 run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=17 -DDOWNLOAD_CATCH=ON -S . -B build3
- name: Build C++17 - name: Build C++17
run: cmake --build build3 -j 2 run: cmake --build build3 -j 2

View File

@ -18,7 +18,7 @@ jobs:
matrix: matrix:
runs-on: [ubuntu-latest, macos-latest, windows-latest] runs-on: [ubuntu-latest, macos-latest, windows-latest]
arch: [x64] arch: [x64]
cmake: ["3.21"] cmake: ["3.23"]
include: include:
- runs-on: ubuntu-latest - runs-on: ubuntu-latest
@ -29,22 +29,18 @@ jobs:
arch: x64 arch: x64
cmake: 3.7 cmake: 3.7
- runs-on: windows-2016 - runs-on: windows-2019
arch: x86 arch: x64 # x86 compilers seem to be missing on 2019 image
cmake: 3.8
- runs-on: windows-2016
arch: x86
cmake: 3.18 cmake: 3.18
name: 🐍 3.7 • CMake ${{ matrix.cmake }} • ${{ matrix.runs-on }} name: 🐍 3.7 • CMake ${{ matrix.cmake }} • ${{ matrix.runs-on }}
runs-on: ${{ matrix.runs-on }} runs-on: ${{ matrix.runs-on }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Setup Python 3.7 - name: Setup Python 3.7
uses: actions/setup-python@v2 uses: actions/setup-python@v4
with: with:
python-version: 3.7 python-version: 3.7
architecture: ${{ matrix.arch }} architecture: ${{ matrix.arch }}

View File

@ -12,14 +12,21 @@ on:
- stable - stable
- "v*" - "v*"
env:
FORCE_COLOR: 3
jobs: jobs:
pre-commit: pre-commit:
name: Format name: Format
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions/setup-python@v2 - uses: actions/setup-python@v4
- uses: pre-commit/action@v2.0.3 with:
python-version: "3.x"
- name: Add matchers
run: echo "::add-matcher::$GITHUB_WORKSPACE/.github/matchers/pylint.json"
- uses: pre-commit/action@v3.0.0
with: with:
# Slow hooks are marked with manual - slow is okay here, run them too # Slow hooks are marked with manual - slow is okay here, run them too
extra_args: --hook-stage manual --all-files extra_args: --hook-stage manual --all-files
@ -29,9 +36,9 @@ jobs:
# in .github/CONTRIBUTING.md and update as needed. # in .github/CONTRIBUTING.md and update as needed.
name: Clang-Tidy name: Clang-Tidy
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: silkeh/clang:12 container: silkeh/clang:13
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Install requirements - name: Install requirements
run: apt-get update && apt-get install -y python3-dev python3-pytest run: apt-get update && apt-get install -y python3-dev python3-pytest
@ -39,7 +46,7 @@ jobs:
- name: Configure - name: Configure
run: > run: >
cmake -S . -B build cmake -S . -B build
-DCMAKE_CXX_CLANG_TIDY="$(which clang-tidy)" -DCMAKE_CXX_CLANG_TIDY="$(which clang-tidy);--use-color;--warnings-as-errors=*"
-DDOWNLOAD_EIGEN=ON -DDOWNLOAD_EIGEN=ON
-DDOWNLOAD_CATCH=ON -DDOWNLOAD_CATCH=ON
-DCMAKE_CXX_STANDARD=17 -DCMAKE_CXX_STANDARD=17

View File

@ -17,19 +17,19 @@ env:
jobs: jobs:
# This builds the sdists and wheels and makes sure the files are exactly as # This builds the sdists and wheels and makes sure the files are exactly as
# expected. Using Windows and Python 2.7, since that is often the most # expected. Using Windows and Python 3.6, since that is often the most
# challenging matrix element. # challenging matrix element.
test-packaging: test-packaging:
name: 🐍 2.7 • 📦 tests • windows-latest name: 🐍 3.6 • 📦 tests • windows-latest
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Setup 🐍 2.7 - name: Setup 🐍 3.6
uses: actions/setup-python@v2 uses: actions/setup-python@v4
with: with:
python-version: 2.7 python-version: 3.6
- name: Prepare env - name: Prepare env
run: | run: |
@ -46,10 +46,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Setup 🐍 3.8 - name: Setup 🐍 3.8
uses: actions/setup-python@v2 uses: actions/setup-python@v4
with: with:
python-version: 3.8 python-version: 3.8
@ -69,13 +69,13 @@ jobs:
run: twine check dist/* run: twine check dist/*
- name: Save standard package - name: Save standard package
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v3
with: with:
name: standard name: standard
path: dist/pybind11-* path: dist/pybind11-*
- name: Save global package - name: Save global package
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v3
with: with:
name: global name: global
path: dist/pybind11_global-* path: dist/pybind11_global-*
@ -90,10 +90,12 @@ jobs:
needs: [packaging] needs: [packaging]
steps: steps:
- uses: actions/setup-python@v2 - uses: actions/setup-python@v4
with:
python-version: "3.x"
# Downloads all to directories matching the artifact names # Downloads all to directories matching the artifact names
- uses: actions/download-artifact@v2 - uses: actions/download-artifact@v3
- name: Publish standard package - name: Publish standard package
uses: pypa/gh-action-pypi-publish@v1.5.0 uses: pypa/gh-action-pypi-publish@v1.5.0

View File

@ -14,15 +14,15 @@ env:
jobs: jobs:
standard: standard:
name: "🐍 3.11 dev • ubuntu-latest • x64" name: "🐍 3.11 latest internals • ubuntu-latest • x64"
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: "contains(github.event.pull_request.labels.*.name, 'python dev')" if: "contains(github.event.pull_request.labels.*.name, 'python dev')"
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Setup Python 3.11 - name: Setup Python 3.11
uses: actions/setup-python@v2 uses: actions/setup-python@v4
with: with:
python-version: "3.11-dev" python-version: "3.11-dev"

View File

@ -15,7 +15,7 @@
repos: repos:
# Standard hooks # Standard hooks
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.1.0 rev: "v4.3.0"
hooks: hooks:
- id: check-added-large-files - id: check-added-large-files
- id: check-case-conflict - id: check-case-conflict
@ -29,73 +29,92 @@ repos:
- id: mixed-line-ending - id: mixed-line-ending
- id: requirements-txt-fixer - id: requirements-txt-fixer
- id: trailing-whitespace - id: trailing-whitespace
- id: fix-encoding-pragma
exclude: ^noxfile.py$
# Upgrade old Python syntax
- repo: https://github.com/asottile/pyupgrade - repo: https://github.com/asottile/pyupgrade
rev: v2.31.0 rev: "v2.37.1"
hooks: hooks:
- id: pyupgrade - id: pyupgrade
args: [--py36-plus]
# Nicely sort includes
- repo: https://github.com/PyCQA/isort - repo: https://github.com/PyCQA/isort
rev: 5.10.1 rev: "5.10.1"
hooks: hooks:
- id: isort - id: isort
# Black, the code formatter, natively supports pre-commit # Black, the code formatter, natively supports pre-commit
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 21.12b0 # Keep in sync with blacken-docs rev: "22.6.0" # Keep in sync with blacken-docs
hooks: hooks:
- id: black - id: black
# Also code format the docs
- repo: https://github.com/asottile/blacken-docs - repo: https://github.com/asottile/blacken-docs
rev: v1.12.0 rev: "v1.12.1"
hooks: hooks:
- id: blacken-docs - id: blacken-docs
additional_dependencies: additional_dependencies:
- black==21.12b0 # keep in sync with black hook - black==22.6.0 # keep in sync with black hook
# Changes tabs to spaces # Changes tabs to spaces
- repo: https://github.com/Lucas-C/pre-commit-hooks - repo: https://github.com/Lucas-C/pre-commit-hooks
rev: v1.1.10 rev: "v1.3.0"
hooks: hooks:
- id: remove-tabs - id: remove-tabs
- repo: https://github.com/sirosen/texthooks
rev: "0.3.1"
hooks:
- id: fix-ligatures
- id: fix-smartquotes
# Autoremoves unused imports # Autoremoves unused imports
- repo: https://github.com/hadialqattan/pycln - repo: https://github.com/hadialqattan/pycln
rev: v1.1.0 rev: "v2.0.1"
hooks: hooks:
- id: pycln - id: pycln
stages: [manual]
# Checking for common mistakes
- repo: https://github.com/pre-commit/pygrep-hooks - repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.9.0 rev: "v1.9.0"
hooks: hooks:
- id: python-check-blanket-noqa - id: python-check-blanket-noqa
- id: python-check-blanket-type-ignore - id: python-check-blanket-type-ignore
- id: python-no-log-warn - id: python-no-log-warn
- id: python-use-type-annotations
- id: rst-backticks - id: rst-backticks
- id: rst-directive-colons - id: rst-directive-colons
- id: rst-inline-touching-normal - id: rst-inline-touching-normal
# Flake8 also supports pre-commit natively (same author) # Automatically remove noqa that are not used
- repo: https://github.com/PyCQA/flake8 - repo: https://github.com/asottile/yesqa
rev: 4.0.1 rev: "v1.3.0"
hooks: hooks:
- id: flake8 - id: yesqa
additional_dependencies: &flake8_dependencies additional_dependencies: &flake8_dependencies
- flake8-bugbear - flake8-bugbear
- pep8-naming - pep8-naming
exclude: ^(docs/.*|tools/.*)$
- repo: https://github.com/asottile/yesqa # Flake8 also supports pre-commit natively (same author)
rev: v1.3.0 - repo: https://github.com/PyCQA/flake8
rev: "4.0.1"
hooks: hooks:
- id: yesqa - id: flake8
exclude: ^(docs/.*|tools/.*)$
additional_dependencies: *flake8_dependencies additional_dependencies: *flake8_dependencies
# PyLint has native support - not always usable, but works for us
- repo: https://github.com/PyCQA/pylint
rev: "v2.14.4"
hooks:
- id: pylint
files: ^pybind11
# CMake formatting # CMake formatting
- repo: https://github.com/cheshirekow/cmake-format-precommit - repo: https://github.com/cheshirekow/cmake-format-precommit
rev: v0.6.13 rev: "v0.6.13"
hooks: hooks:
- id: cmake-format - id: cmake-format
additional_dependencies: [pyyaml] additional_dependencies: [pyyaml]
@ -104,48 +123,48 @@ repos:
# Check static types with mypy # Check static types with mypy
- repo: https://github.com/pre-commit/mirrors-mypy - repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.931 rev: "v0.961"
hooks: hooks:
- id: mypy - id: mypy
# Running per-file misbehaves a bit, so just run on all files, it's fast args: []
pass_filenames: false exclude: ^(tests|docs)/
additional_dependencies: [typed_ast] additional_dependencies: [nox, rich]
# Checks the manifest for missing files (native support) # Checks the manifest for missing files (native support)
- repo: https://github.com/mgedmin/check-manifest - repo: https://github.com/mgedmin/check-manifest
rev: "0.47" rev: "0.48"
hooks: hooks:
- id: check-manifest - id: check-manifest
# This is a slow hook, so only run this if --hook-stage manual is passed # This is a slow hook, so only run this if --hook-stage manual is passed
stages: [manual] stages: [manual]
additional_dependencies: [cmake, ninja] additional_dependencies: [cmake, ninja]
# Check for spelling
- repo: https://github.com/codespell-project/codespell - repo: https://github.com/codespell-project/codespell
rev: v2.1.0 rev: "v2.1.0"
hooks: hooks:
- id: codespell - id: codespell
exclude: ".supp$" exclude: ".supp$"
args: ["-L", "nd,ot,thist"] args: ["-L", "nd,ot,thist"]
# Check for common shell mistakes
- repo: https://github.com/shellcheck-py/shellcheck-py - repo: https://github.com/shellcheck-py/shellcheck-py
rev: v0.8.0.3 rev: "v0.8.0.4"
hooks: hooks:
- id: shellcheck - id: shellcheck
# The original pybind11 checks for a few C++ style items # Disallow some common capitalization mistakes
- repo: local - repo: local
hooks: hooks:
- id: disallow-caps - id: disallow-caps
name: Disallow improper capitalization name: Disallow improper capitalization
language: pygrep language: pygrep
entry: PyBind|Numpy|Cmake|CCache|PyTest entry: PyBind|Numpy|Cmake|CCache|PyTest
exclude: .pre-commit-config.yaml exclude: ^\.pre-commit-config.yaml$
- repo: local # Clang format the codebase automatically
- repo: https://github.com/pre-commit/mirrors-clang-format
rev: "v14.0.6"
hooks: hooks:
- id: check-style - id: clang-format
name: Classic check-style types_or: [c++, c, cuda]
language: system
types:
- c++
entry: ./tools/check-style.sh

View File

@ -1,6 +1,5 @@
recursive-include pybind11/include/pybind11 *.h recursive-include pybind11/include/pybind11 *.h
recursive-include pybind11 *.py recursive-include pybind11 *.py
recursive-include pybind11 py.typed recursive-include pybind11 py.typed
recursive-include pybind11 *.pyi
include pybind11/share/cmake/pybind11/*.cmake include pybind11/share/cmake/pybind11/*.cmake
include LICENSE README.rst pyproject.toml setup.py setup.cfg include LICENSE README.rst pyproject.toml setup.py setup.cfg

View File

@ -32,9 +32,9 @@ this heavy machinery has become an excessively large and unnecessary
dependency. dependency.
Think of this library as a tiny self-contained version of Boost.Python Think of this library as a tiny self-contained version of Boost.Python
with everything stripped away that isnt relevant for binding with everything stripped away that isn't relevant for binding
generation. Without comments, the core header files only require ~4K generation. Without comments, the core header files only require ~4K
lines of code and depend on Python (2.7 or 3.5+, or PyPy) and the C++ lines of code and depend on Python (3.6+, or PyPy) and the C++
standard library. This compact implementation was possible thanks to standard library. This compact implementation was possible thanks to
some of the new C++11 language features (specifically: tuples, lambda some of the new C++11 language features (specifically: tuples, lambda
functions and variadic templates). Since its creation, this library has functions and variadic templates). Since its creation, this library has
@ -78,8 +78,8 @@ Goodies
In addition to the core functionality, pybind11 provides some extra In addition to the core functionality, pybind11 provides some extra
goodies: goodies:
- Python 2.7, 3.5+, and PyPy/PyPy3 7.3 are supported with an - Python 3.6+, and PyPy3 7.3 are supported with an implementation-agnostic
implementation-agnostic interface. interface (pybind11 2.9 was the last version to support Python 2 and 3.5).
- It is possible to bind C++11 lambda functions with captured - It is possible to bind C++11 lambda functions with captured
variables. The lambda capture data is stored inside the resulting variables. The lambda capture data is stored inside the resulting
@ -88,8 +88,8 @@ goodies:
- pybind11 uses C++11 move constructors and move assignment operators - pybind11 uses C++11 move constructors and move assignment operators
whenever possible to efficiently transfer custom data types. whenever possible to efficiently transfer custom data types.
- Its easy to expose the internal storage of custom data types through - It's easy to expose the internal storage of custom data types through
Pythons buffer protocols. This is handy e.g. for fast conversion Pythons' buffer protocols. This is handy e.g. for fast conversion
between C++ matrix classes like Eigen and NumPy without expensive between C++ matrix classes like Eigen and NumPy without expensive
copy operations. copy operations.
@ -119,10 +119,10 @@ goodies:
Supported compilers Supported compilers
------------------- -------------------
1. Clang/LLVM 3.3 or newer (for Apple Xcodes clang, this is 5.0.0 or 1. Clang/LLVM 3.3 or newer (for Apple Xcode's clang, this is 5.0.0 or
newer) newer)
2. GCC 4.8 or newer 2. GCC 4.8 or newer
3. Microsoft Visual Studio 2015 Update 3 or newer 3. Microsoft Visual Studio 2017 or newer
4. Intel classic C++ compiler 18 or newer (ICC 20.2 tested in CI) 4. Intel classic C++ compiler 18 or newer (ICC 20.2 tested in CI)
5. Cygwin/GCC (previously tested on 2.5.1) 5. Cygwin/GCC (previously tested on 2.5.1)
6. NVCC (CUDA 11.0 tested in CI) 6. NVCC (CUDA 11.0 tested in CI)

View File

@ -18,5 +18,4 @@ ALIASES += "endrst=\endverbatim"
QUIET = YES QUIET = YES
WARNINGS = YES WARNINGS = YES
WARN_IF_UNDOCUMENTED = NO WARN_IF_UNDOCUMENTED = NO
PREDEFINED = PY_MAJOR_VERSION=3 \ PREDEFINED = PYBIND11_NOINLINE
PYBIND11_NOINLINE

3
pybind11/docs/_static/css/custom.css vendored Normal file
View File

@ -0,0 +1,3 @@
.highlight .go {
color: #707070;
}

View File

@ -1,11 +0,0 @@
.wy-table-responsive table td,
.wy-table-responsive table th {
white-space: initial !important;
}
.rst-content table.docutils td {
vertical-align: top !important;
}
div[class^='highlight'] pre {
white-space: pre;
white-space: pre-wrap;
}

View File

@ -167,5 +167,4 @@ as arguments and return values, refer to the section on binding :ref:`classes`.
+------------------------------------+---------------------------+-----------------------------------+ +------------------------------------+---------------------------+-----------------------------------+
.. [#] ``std::filesystem::path`` is converted to ``pathlib.Path`` and .. [#] ``std::filesystem::path`` is converted to ``pathlib.Path`` and
``os.PathLike`` is converted to ``std::filesystem::path``, but this requires ``os.PathLike`` is converted to ``std::filesystem::path``.
Python 3.6 (for ``__fspath__`` support).

View File

@ -87,8 +87,6 @@ included to tell pybind11 how to visit the variant.
pybind11 only supports the modern implementation of ``boost::variant`` pybind11 only supports the modern implementation of ``boost::variant``
which makes use of variadic templates. This requires Boost 1.56 or newer. which makes use of variadic templates. This requires Boost 1.56 or newer.
Additionally, on Windows, MSVC 2017 is required because ``boost::variant``
falls back to the old non-variadic implementation on MSVC 2015.
.. _opaque: .. _opaque:

View File

@ -1,14 +1,6 @@
Strings, bytes and Unicode conversions Strings, bytes and Unicode conversions
###################################### ######################################
.. note::
This section discusses string handling in terms of Python 3 strings. For
Python 2.7, replace all occurrences of ``str`` with ``unicode`` and
``bytes`` with ``str``. Python 2.7 users may find it best to use ``from
__future__ import unicode_literals`` to avoid unintentionally using ``str``
instead of ``unicode``.
Passing Python strings to C++ Passing Python strings to C++
============================= =============================
@ -58,9 +50,9 @@ Passing bytes to C++
-------------------- --------------------
A Python ``bytes`` object will be passed to C++ functions that accept A Python ``bytes`` object will be passed to C++ functions that accept
``std::string`` or ``char*`` *without* conversion. On Python 3, in order to ``std::string`` or ``char*`` *without* conversion. In order to make a function
make a function *only* accept ``bytes`` (and not ``str``), declare it as taking *only* accept ``bytes`` (and not ``str``), declare it as taking a ``py::bytes``
a ``py::bytes`` argument. argument.
Returning C++ strings to Python Returning C++ strings to Python
@ -204,11 +196,6 @@ decoded to Python ``str``.
} }
); );
.. warning::
Wide character strings may not work as described on Python 2.7 or Python
3.3 compiled with ``--enable-unicode=ucs2``.
Strings in multibyte encodings such as Shift-JIS must transcoded to a Strings in multibyte encodings such as Shift-JIS must transcoded to a
UTF-8/16/32 before being returned to Python. UTF-8/16/32 before being returned to Python.

View File

@ -133,14 +133,14 @@ a virtual method call.
>>> from example import * >>> from example import *
>>> d = Dog() >>> d = Dog()
>>> call_go(d) >>> call_go(d)
u'woof! woof! woof! ' 'woof! woof! woof! '
>>> class Cat(Animal): >>> class Cat(Animal):
... def go(self, n_times): ... def go(self, n_times):
... return "meow! " * n_times ... return "meow! " * n_times
... ...
>>> c = Cat() >>> c = Cat()
>>> call_go(c) >>> call_go(c)
u'meow! meow! meow! ' 'meow! meow! meow! '
If you are defining a custom constructor in a derived Python class, you *must* If you are defining a custom constructor in a derived Python class, you *must*
ensure that you explicitly call the bound C++ constructor using ``__init__``, ensure that you explicitly call the bound C++ constructor using ``__init__``,
@ -813,26 +813,21 @@ An instance can now be pickled as follows:
.. code-block:: python .. code-block:: python
try: import pickle
import cPickle as pickle # Use cPickle on Python 2.7
except ImportError:
import pickle
p = Pickleable("test_value") p = Pickleable("test_value")
p.setExtra(15) p.setExtra(15)
data = pickle.dumps(p, 2) data = pickle.dumps(p)
.. note:: .. note::
Note that only the cPickle module is supported on Python 2.7. If given, the second argument to ``dumps`` must be 2 or larger - 0 and 1 are
not supported. Newer versions are also fine; for instance, specify ``-1`` to
The second argument to ``dumps`` is also crucial: it selects the pickle always use the latest available version. Beware: failure to follow these
protocol version 2, since the older version 1 is not supported. Newer instructions will cause important pybind11 memory allocation routines to be
versions are also fine—for instance, specify ``-1`` to always use the skipped during unpickling, which will likely lead to memory corruption
latest available version. Beware: failure to follow these instructions and/or segmentation faults. Python defaults to version 3 (Python 3-3.7) and
will cause important pybind11 memory allocation routines to be skipped version 4 for Python 3.8+.
during unpickling, which will likely lead to memory corruption and/or
segmentation faults.
.. seealso:: .. seealso::
@ -849,11 +844,9 @@ Python normally uses references in assignments. Sometimes a real copy is needed
to prevent changing all copies. The ``copy`` module [#f5]_ provides these to prevent changing all copies. The ``copy`` module [#f5]_ provides these
capabilities. capabilities.
On Python 3, a class with pickle support is automatically also (deep)copy A class with pickle support is automatically also (deep)copy
compatible. However, performance can be improved by adding custom compatible. However, performance can be improved by adding custom
``__copy__`` and ``__deepcopy__`` methods. With Python 2.7, these custom methods ``__copy__`` and ``__deepcopy__`` methods.
are mandatory for (deep)copy compatibility, because pybind11 only supports
cPickle.
For simple classes (deep)copy can be enabled by using the copy constructor, For simple classes (deep)copy can be enabled by using the copy constructor,
which should look as follows: which should look as follows:
@ -1125,13 +1118,6 @@ described trampoline:
py::class_<A, Trampoline>(m, "A") // <-- `Trampoline` here py::class_<A, Trampoline>(m, "A") // <-- `Trampoline` here
.def("foo", &Publicist::foo); // <-- `Publicist` here, not `Trampoline`! .def("foo", &Publicist::foo); // <-- `Publicist` here, not `Trampoline`!
.. note::
MSVC 2015 has a compiler bug (fixed in version 2017) which
requires a more explicit function binding in the form of
``.def("foo", static_cast<int (A::*)() const>(&Publicist::foo));``
where ``int (A::*)() const`` is the type of ``A::foo``.
Binding final classes Binding final classes
===================== =====================

View File

@ -328,8 +328,8 @@ an invalid state.
Chaining exceptions ('raise from') Chaining exceptions ('raise from')
================================== ==================================
In Python 3.3 a mechanism for indicating that exceptions were caused by other Python has a mechanism for indicating that exceptions were caused by other
exceptions was introduced: exceptions:
.. code-block:: py .. code-block:: py
@ -340,7 +340,7 @@ exceptions was introduced:
To do a similar thing in pybind11, you can use the ``py::raise_from`` function. It To do a similar thing in pybind11, you can use the ``py::raise_from`` function. It
sets the current python error indicator, so to continue propagating the exception sets the current python error indicator, so to continue propagating the exception
you should ``throw py::error_already_set()`` (Python 3 only). you should ``throw py::error_already_set()``.
.. code-block:: cpp .. code-block:: cpp

View File

@ -372,7 +372,7 @@ like so:
Keyword-only arguments Keyword-only arguments
====================== ======================
Python 3 introduced keyword-only arguments by specifying an unnamed ``*`` Python implements keyword-only arguments by specifying an unnamed ``*``
argument in a function definition: argument in a function definition:
.. code-block:: python .. code-block:: python
@ -395,19 +395,18 @@ argument annotations when registering the function:
m.def("f", [](int a, int b) { /* ... */ }, m.def("f", [](int a, int b) { /* ... */ },
py::arg("a"), py::kw_only(), py::arg("b")); py::arg("a"), py::kw_only(), py::arg("b"));
Note that you currently cannot combine this with a ``py::args`` argument. This
feature does *not* require Python 3 to work.
.. versionadded:: 2.6 .. versionadded:: 2.6
As of pybind11 2.9, a ``py::args`` argument implies that any following arguments A ``py::args`` argument implies that any following arguments are keyword-only,
are keyword-only, as if ``py::kw_only()`` had been specified in the same as if ``py::kw_only()`` had been specified in the same relative location of the
relative location of the argument list as the ``py::args`` argument. The argument list as the ``py::args`` argument. The ``py::kw_only()`` may be
``py::kw_only()`` may be included to be explicit about this, but is not included to be explicit about this, but is not required.
required. (Prior to 2.9 ``py::args`` may only occur at the end of the argument
list, or immediately before a ``py::kwargs`` argument at the end). .. versionchanged:: 2.9
This can now be combined with ``py::args``. Before, ``py::args`` could only
occur at the end of the argument list, or immediately before a ``py::kwargs``
argument at the end.
.. versionadded:: 2.9
Positional-only arguments Positional-only arguments
========================= =========================

View File

@ -87,7 +87,7 @@ buffer objects (e.g. a NumPy matrix).
/* Request a buffer descriptor from Python */ /* Request a buffer descriptor from Python */
py::buffer_info info = b.request(); py::buffer_info info = b.request();
/* Some sanity checks ... */ /* Some basic validation checks ... */
if (info.format != py::format_descriptor<Scalar>::format()) if (info.format != py::format_descriptor<Scalar>::format())
throw std::runtime_error("Incompatible format: expected a double array!"); throw std::runtime_error("Incompatible format: expected a double array!");
@ -395,11 +395,9 @@ uses of ``py::array``:
Ellipsis Ellipsis
======== ========
Python 3 provides a convenient ``...`` ellipsis notation that is often used to Python provides a convenient ``...`` ellipsis notation that is often used to
slice multidimensional arrays. For instance, the following snippet extracts the slice multidimensional arrays. For instance, the following snippet extracts the
middle dimensions of a tensor with the first and last index set to zero. middle dimensions of a tensor with the first and last index set to zero.
In Python 2, the syntactic sugar ``...`` is not available, but the singleton
``Ellipsis`` (of type ``ellipsis``) can still be used directly.
.. code-block:: python .. code-block:: python
@ -414,8 +412,6 @@ operation on the C++ side:
py::array a = /* A NumPy array */; py::array a = /* A NumPy array */;
py::array b = a[py::make_tuple(0, py::ellipsis(), 0)]; py::array b = a[py::make_tuple(0, py::ellipsis(), 0)];
.. versionchanged:: 2.6
``py::ellipsis()`` is now also available in Python 2.
Memory view Memory view
=========== ===========
@ -455,9 +451,5 @@ We can also use ``memoryview::from_memory`` for a simple 1D contiguous buffer:
); );
}) })
.. note::
``memoryview::from_memory`` is not available in Python 2.
.. versionchanged:: 2.6 .. versionchanged:: 2.6
``memoryview::from_memory`` added. ``memoryview::from_memory`` added.

View File

@ -32,8 +32,7 @@ The last line will both compile and run the tests.
Windows Windows
------- -------
On Windows, only **Visual Studio 2015** and newer are supported since pybind11 relies On Windows, only **Visual Studio 2017** and newer are supported.
on various C++11 language features that break older versions of Visual Studio.
.. Note:: .. Note::
@ -166,12 +165,12 @@ load and execute the example:
.. code-block:: pycon .. code-block:: pycon
$ python $ python
Python 2.7.10 (default, Aug 22 2015, 20:33:39) Python 3.9.10 (main, Jan 15 2022, 11:48:04)
[GCC 4.2.1 Compatible Apple LLVM 7.0.0 (clang-700.0.59.1)] on darwin [Clang 13.0.0 (clang-1300.0.29.3)] on darwin
Type "help", "copyright", "credits" or "license" for more information. Type "help", "copyright", "credits" or "license" for more information.
>>> import example >>> import example
>>> example.add(1, 2) >>> example.add(1, 2)
3L 3
>>> >>>
.. _keyword_args: .. _keyword_args:

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
import datetime as dt import datetime as dt
import os import os
import random import random
@ -12,20 +11,20 @@ def generate_dummy_code_pybind11(nclasses=10):
bindings = "" bindings = ""
for cl in range(nclasses): for cl in range(nclasses):
decl += "class cl%03i;\n" % cl decl += f"class cl{cl:03};\n"
decl += "\n" decl += "\n"
for cl in range(nclasses): for cl in range(nclasses):
decl += "class cl%03i {\n" % cl decl += f"class {cl:03} {{\n"
decl += "public:\n" decl += "public:\n"
bindings += ' py::class_<cl%03i>(m, "cl%03i")\n' % (cl, cl) bindings += f' py::class_<cl{cl:03}>(m, "cl{cl:03}")\n'
for fn in range(nfns): for fn in range(nfns):
ret = random.randint(0, nclasses - 1) ret = random.randint(0, nclasses - 1)
params = [random.randint(0, nclasses - 1) for i in range(nargs)] params = [random.randint(0, nclasses - 1) for i in range(nargs)]
decl += " cl%03i *fn_%03i(" % (ret, fn) decl += f" cl{ret:03} *fn_{fn:03}("
decl += ", ".join("cl%03i *" % p for p in params) decl += ", ".join(f"cl{p:03} *" for p in params)
decl += ");\n" decl += ");\n"
bindings += ' .def("fn_%03i", &cl%03i::fn_%03i)\n' % (fn, cl, fn) bindings += f' .def("fn_{fn:03}", &cl{cl:03}::fn_{fn:03})\n'
decl += "};\n\n" decl += "};\n\n"
bindings += " ;\n" bindings += " ;\n"
@ -43,23 +42,20 @@ def generate_dummy_code_boost(nclasses=10):
bindings = "" bindings = ""
for cl in range(nclasses): for cl in range(nclasses):
decl += "class cl%03i;\n" % cl decl += f"class cl{cl:03};\n"
decl += "\n" decl += "\n"
for cl in range(nclasses): for cl in range(nclasses):
decl += "class cl%03i {\n" % cl decl += "class cl%03i {\n" % cl
decl += "public:\n" decl += "public:\n"
bindings += ' py::class_<cl%03i>("cl%03i")\n' % (cl, cl) bindings += f' py::class_<cl{cl:03}>("cl{cl:03}")\n'
for fn in range(nfns): for fn in range(nfns):
ret = random.randint(0, nclasses - 1) ret = random.randint(0, nclasses - 1)
params = [random.randint(0, nclasses - 1) for i in range(nargs)] params = [random.randint(0, nclasses - 1) for i in range(nargs)]
decl += " cl%03i *fn_%03i(" % (ret, fn) decl += f" cl{ret:03} *fn_{fn:03}("
decl += ", ".join("cl%03i *" % p for p in params) decl += ", ".join(f"cl{p:03} *" for p in params)
decl += ");\n" decl += ");\n"
bindings += ( bindings += f' .def("fn_{fn:03}", &cl{cl:03}::fn_{fn:03}, py::return_value_policy<py::manage_new_object>())\n'
' .def("fn_%03i", &cl%03i::fn_%03i, py::return_value_policy<py::manage_new_object>())\n'
% (fn, cl, fn)
)
decl += "};\n\n" decl += "};\n\n"
bindings += " ;\n" bindings += " ;\n"
@ -75,7 +71,7 @@ def generate_dummy_code_boost(nclasses=10):
for codegen in [generate_dummy_code_pybind11, generate_dummy_code_boost]: for codegen in [generate_dummy_code_pybind11, generate_dummy_code_boost]:
print("{") print("{")
for i in range(0, 10): for i in range(0, 10):
nclasses = 2 ** i nclasses = 2**i
with open("test.cpp", "w") as f: with open("test.cpp", "w") as f:
f.write(codegen(nclasses)) f.write(codegen(nclasses))
n1 = dt.datetime.now() n1 = dt.datetime.now()

View File

@ -6,6 +6,272 @@ Changelog
Starting with version 1.8.0, pybind11 releases use a `semantic versioning Starting with version 1.8.0, pybind11 releases use a `semantic versioning
<http://semver.org>`_ policy. <http://semver.org>`_ policy.
Changes will be added here periodically from the "Suggested changelog entry"
block in pull request descriptions.
Version 2.10.0 (Jul 15, 2022)
-----------------------------
Removed support for Python 2.7, Python 3.5, and MSVC 2015. Support for MSVC
2017 is limited due to availability of CI runners; we highly recommend MSVC
2019 or 2022 be used. Initial support added for Python 3.11.
New features:
* ``py::anyset`` & ``py::frozenset`` were added, with copying (cast) to
``std::set`` (similar to ``set``).
`#3901 <https://github.com/pybind/pybind11/pull/3901>`_
* Support bytearray casting to string.
`#3707 <https://github.com/pybind/pybind11/pull/3707>`_
* ``type_caster<std::monostate>`` was added. ``std::monostate`` is a tag type
that allows ``std::variant`` to act as an optional, or allows default
construction of a ``std::variant`` holding a non-default constructible type.
`#3818 <https://github.com/pybind/pybind11/pull/3818>`_
* ``pybind11::capsule::set_name`` added to mutate the name of the capsule instance.
`#3866 <https://github.com/pybind/pybind11/pull/3866>`_
* NumPy: dtype constructor from type number added, accessors corresponding to
Python API ``dtype.num``, ``dtype.byteorder``, ``dtype.flags`` and
``dtype.alignment`` added.
`#3868 <https://github.com/pybind/pybind11/pull/3868>`_
Changes:
* Python 3.6 is now the minimum supported version.
`#3688 <https://github.com/pybind/pybind11/pull/3688>`_
`#3719 <https://github.com/pybind/pybind11/pull/3719>`_
* The minimum version for MSVC is now 2017.
`#3722 <https://github.com/pybind/pybind11/pull/3722>`_
* Fix issues with CPython 3.11 betas and add to supported test matrix.
`#3923 <https://github.com/pybind/pybind11/pull/3923>`_
* ``error_already_set`` is now safer and more performant, especially for
exceptions with long tracebacks, by delaying computation.
`#1895 <https://github.com/pybind/pybind11/pull/1895>`_
* Improve exception handling in python ``str`` bindings.
`#3826 <https://github.com/pybind/pybind11/pull/3826>`_
* The bindings for capsules now have more consistent exception handling.
`#3825 <https://github.com/pybind/pybind11/pull/3825>`_
* ``PYBIND11_OBJECT_CVT`` and ``PYBIND11_OBJECT_CVT_DEFAULT`` macro can now be
used to define classes in namespaces other than pybind11.
`#3797 <https://github.com/pybind/pybind11/pull/3797>`_
* Error printing code now uses ``PYBIND11_DETAILED_ERROR_MESSAGES`` instead of
requiring ``NDEBUG``, allowing use with release builds if desired.
`#3913 <https://github.com/pybind/pybind11/pull/3913>`_
* Implicit conversion of the literal ``0`` to ``pybind11::handle`` is now disabled.
`#4008 <https://github.com/pybind/pybind11/pull/4008>`_
Bug fixes:
* Fix exception handling when ``pybind11::weakref()`` fails.
`#3739 <https://github.com/pybind/pybind11/pull/3739>`_
* ``module_::def_submodule`` was missing proper error handling. This is fixed now.
`#3973 <https://github.com/pybind/pybind11/pull/3973>`_
* The behavior or ``error_already_set`` was made safer and the highly opaque
"Unknown internal error occurred" message was replaced with a more helpful
message.
`#3982 <https://github.com/pybind/pybind11/pull/3982>`_
* ``error_already_set::what()`` now handles non-normalized exceptions correctly.
`#3971 <https://github.com/pybind/pybind11/pull/3971>`_
* Support older C++ compilers where filesystem is not yet part of the standard
library and is instead included in ``std::experimental::filesystem``.
`#3840 <https://github.com/pybind/pybind11/pull/3840>`_
* Fix ``-Wfree-nonheap-object`` warnings produced by GCC by avoiding returning
pointers to static objects with ``return_value_policy::take_ownership``.
`#3946 <https://github.com/pybind/pybind11/pull/3946>`_
* Fix cast from pytype rvalue to another pytype.
`#3949 <https://github.com/pybind/pybind11/pull/3949>`_
* Ensure proper behavior when garbage collecting classes with dynamic attributes in Python >=3.9.
`#4051 <https://github.com/pybind/pybind11/pull/4051>`_
* A couple long-standing ``PYBIND11_NAMESPACE``
``__attribute__((visibility("hidden")))`` inconsistencies are now fixed
(affects only unusual environments).
`#4043 <https://github.com/pybind/pybind11/pull/4043>`_
* ``pybind11::detail::get_internals()`` is now resilient to in-flight Python
exceptions.
`#3981 <https://github.com/pybind/pybind11/pull/3981>`_
* Arrays with a dimension of size 0 are now properly converted to dynamic Eigen
matrices (more common in NumPy 1.23).
`#4038 <https://github.com/pybind/pybind11/pull/4038>`_
* Avoid catching unrelated errors when importing NumPy.
`#3974 <https://github.com/pybind/pybind11/pull/3974>`_
Performance and style:
* Added an accessor overload of ``(object &&key)`` to reference steal the
object when using python types as keys. This prevents unnecessary reference
count overhead for attr, dictionary, tuple, and sequence look ups. Added
additional regression tests. Fixed a performance bug the caused accessor
assignments to potentially perform unnecessary copies.
`#3970 <https://github.com/pybind/pybind11/pull/3970>`_
* Perfect forward all args of ``make_iterator``.
`#3980 <https://github.com/pybind/pybind11/pull/3980>`_
* Avoid potential bug in pycapsule destructor by adding an ``error_guard`` to
one of the dtors.
`#3958 <https://github.com/pybind/pybind11/pull/3958>`_
* Optimize dictionary access in ``strip_padding`` for numpy.
`#3994 <https://github.com/pybind/pybind11/pull/3994>`_
* ``stl_bind.h`` bindings now take slice args as a const-ref.
`#3852 <https://github.com/pybind/pybind11/pull/3852>`_
* Made slice constructor more consistent, and improve performance of some
casters by allowing reference stealing.
`#3845 <https://github.com/pybind/pybind11/pull/3845>`_
* Change numpy dtype from_args method to use const ref.
`#3878 <https://github.com/pybind/pybind11/pull/3878>`_
* Follow rule of three to ensure ``PyErr_Restore`` is called only once.
`#3872 <https://github.com/pybind/pybind11/pull/3872>`_
* Added missing perfect forwarding for ``make_iterator`` functions.
`#3860 <https://github.com/pybind/pybind11/pull/3860>`_
* Optimize c++ to python function casting by using the rvalue caster.
`#3966 <https://github.com/pybind/pybind11/pull/3966>`_
* Optimize Eigen sparse matrix casting by removing unnecessary temporary.
`#4064 <https://github.com/pybind/pybind11/pull/4064>`_
* Avoid potential implicit copy/assignment constructors causing double free in
``strdup_gaurd``.
`#3905 <https://github.com/pybind/pybind11/pull/3905>`_
* Enable clang-tidy checks ``misc-definitions-in-headers``,
``modernize-loop-convert``, and ``modernize-use-nullptr``.
`#3881 <https://github.com/pybind/pybind11/pull/3881>`_
`#3988 <https://github.com/pybind/pybind11/pull/3988>`_
Build system improvements:
* CMake: Fix file extension on Windows with cp36 and cp37 using FindPython.
`#3919 <https://github.com/pybind/pybind11/pull/3919>`_
* CMake: Support multiple Python targets (such as on vcpkg).
`#3948 <https://github.com/pybind/pybind11/pull/3948>`_
* CMake: Fix issue with NVCC on Windows.
`#3947 <https://github.com/pybind/pybind11/pull/3947>`_
* CMake: Drop the bitness check on cross compiles (like targeting WebAssembly
via Emscripten).
`#3959 <https://github.com/pybind/pybind11/pull/3959>`_
* Add MSVC builds in debug mode to CI.
`#3784 <https://github.com/pybind/pybind11/pull/3784>`_
* MSVC 2022 C++20 coverage was added to GitHub Actions, including Eigen.
`#3732 <https://github.com/pybind/pybind11/pull/3732>`_,
`#3741 <https://github.com/pybind/pybind11/pull/3741>`_
Backend and tidying up:
* New theme for the documentation.
`#3109 <https://github.com/pybind/pybind11/pull/3109>`_
* Remove idioms in code comments. Use more inclusive language.
`#3809 <https://github.com/pybind/pybind11/pull/3809>`_
* ``#include <iostream>`` was removed from the ``pybind11/stl.h`` header. Your
project may break if it has a transitive dependency on this include. The fix
is to "Include What You Use".
`#3928 <https://github.com/pybind/pybind11/pull/3928>`_
* Avoid ``setup.py <command>`` usage in internal tests.
`#3734 <https://github.com/pybind/pybind11/pull/3734>`_
Version 2.9.2 (Mar 29, 2022)
----------------------------
Changes:
* Enum now has an ``__index__`` method on Python <3.8 too.
`#3700 <https://github.com/pybind/pybind11/pull/3700>`_
* Local internals are now cleared after finalizing the interpreter.
`#3744 <https://github.com/pybind/pybind11/pull/3744>`_
Bug fixes:
* Better support for Python 3.11 alphas.
`#3694 <https://github.com/pybind/pybind11/pull/3694>`_
* ``PYBIND11_TYPE_CASTER`` now uses fully qualified symbols, so it can be used
outside of ``pybind11::detail``.
`#3758 <https://github.com/pybind/pybind11/pull/3758>`_
* Some fixes for PyPy 3.9.
`#3768 <https://github.com/pybind/pybind11/pull/3768>`_
* Fixed a potential memleak in PyPy in ``get_type_override``.
`#3774 <https://github.com/pybind/pybind11/pull/3774>`_
* Fix usage of ``VISIBILITY_INLINES_HIDDEN``.
`#3721 <https://github.com/pybind/pybind11/pull/3721>`_
Build system improvements:
* Uses ``sysconfig`` module to determine installation locations on Python >=
3.10, instead of ``distutils`` which has been deprecated.
`#3764 <https://github.com/pybind/pybind11/pull/3764>`_
* Support Catch 2.13.5+ (supporting GLIBC 2.34+).
`#3679 <https://github.com/pybind/pybind11/pull/3679>`_
* Fix test failures with numpy 1.22 by ignoring whitespace when comparing
``str()`` of dtypes.
`#3682 <https://github.com/pybind/pybind11/pull/3682>`_
Backend and tidying up:
* clang-tidy: added ``readability-qualified-auto``,
``readability-braces-around-statements``,
``cppcoreguidelines-prefer-member-initializer``,
``clang-analyzer-optin.performance.Padding``,
``cppcoreguidelines-pro-type-static-cast-downcast``, and
``readability-inconsistent-declaration-parameter-name``.
`#3702 <https://github.com/pybind/pybind11/pull/3702>`_,
`#3699 <https://github.com/pybind/pybind11/pull/3699>`_,
`#3716 <https://github.com/pybind/pybind11/pull/3716>`_,
`#3709 <https://github.com/pybind/pybind11/pull/3709>`_
* clang-format was added to the pre-commit actions, and the entire code base
automatically reformatted (after several iterations preparing for this leap).
`#3713 <https://github.com/pybind/pybind11/pull/3713>`_
Version 2.9.1 (Feb 2, 2022) Version 2.9.1 (Feb 2, 2022)
--------------------------- ---------------------------
@ -794,7 +1060,7 @@ Packaging / building improvements:
`#2338 <https://github.com/pybind/pybind11/pull/2338>`_ and `#2338 <https://github.com/pybind/pybind11/pull/2338>`_ and
`#2370 <https://github.com/pybind/pybind11/pull/2370>`_ `#2370 <https://github.com/pybind/pybind11/pull/2370>`_
* Full integration with CMakes C++ standard system and compile features * Full integration with CMake's C++ standard system and compile features
replaces ``PYBIND11_CPP_STANDARD``. replaces ``PYBIND11_CPP_STANDARD``.
* Generated config file is now portable to different Python/compiler/CMake * Generated config file is now portable to different Python/compiler/CMake

View File

@ -48,10 +48,10 @@ interactive Python session demonstrating this example is shown below:
>>> print(p) >>> print(p)
<example.Pet object at 0x10cd98060> <example.Pet object at 0x10cd98060>
>>> p.getName() >>> p.getName()
u'Molly' 'Molly'
>>> p.setName("Charly") >>> p.setName("Charly")
>>> p.getName() >>> p.getName()
u'Charly' 'Charly'
.. seealso:: .. seealso::
@ -124,10 +124,10 @@ This makes it possible to write
>>> p = example.Pet("Molly") >>> p = example.Pet("Molly")
>>> p.name >>> p.name
u'Molly' 'Molly'
>>> p.name = "Charly" >>> p.name = "Charly"
>>> p.name >>> p.name
u'Charly' 'Charly'
Now suppose that ``Pet::name`` was a private internal variable Now suppose that ``Pet::name`` was a private internal variable
that can only be accessed via setters and getters. that can only be accessed via setters and getters.
@ -282,9 +282,9 @@ expose fields and methods of both types:
>>> p = example.Dog("Molly") >>> p = example.Dog("Molly")
>>> p.name >>> p.name
u'Molly' 'Molly'
>>> p.bark() >>> p.bark()
u'woof!' 'woof!'
The C++ classes defined above are regular non-polymorphic types with an The C++ classes defined above are regular non-polymorphic types with an
inheritance relationship. This is reflected in Python: inheritance relationship. This is reflected in Python:
@ -332,7 +332,7 @@ will automatically recognize this:
>>> type(p) >>> type(p)
PolymorphicDog # automatically downcast PolymorphicDog # automatically downcast
>>> p.bark() >>> p.bark()
u'woof!' 'woof!'
Given a pointer to a polymorphic base, pybind11 performs automatic downcasting Given a pointer to a polymorphic base, pybind11 performs automatic downcasting
to the actual derived type. Note that this goes beyond the usual situation in to the actual derived type. Note that this goes beyond the usual situation in
@ -434,8 +434,7 @@ you can use ``py::detail::overload_cast_impl`` with an additional set of parenth
.def("set", overload_cast_<int>()(&Pet::set), "Set the pet's age") .def("set", overload_cast_<int>()(&Pet::set), "Set the pet's age")
.def("set", overload_cast_<const std::string &>()(&Pet::set), "Set the pet's name"); .def("set", overload_cast_<const std::string &>()(&Pet::set), "Set the pet's name");
.. [#cpp14] A compiler which supports the ``-std=c++14`` flag .. [#cpp14] A compiler which supports the ``-std=c++14`` flag.
or Visual Studio 2015 Update 2 and newer.
.. note:: .. note::
@ -483,7 +482,7 @@ The binding code for this example looks as follows:
.value("Cat", Pet::Kind::Cat) .value("Cat", Pet::Kind::Cat)
.export_values(); .export_values();
py::class_<Pet::Attributes> attributes(pet, "Attributes") py::class_<Pet::Attributes>(pet, "Attributes")
.def(py::init<>()) .def(py::init<>())
.def_readwrite("age", &Pet::Attributes::age); .def_readwrite("age", &Pet::Attributes::age);

View File

@ -417,10 +417,10 @@ existing targets instead:
.. code-block:: cmake .. code-block:: cmake
cmake_minimum_required(VERSION 3.15...3.19) cmake_minimum_required(VERSION 3.15...3.22)
project(example LANGUAGES CXX) project(example LANGUAGES CXX)
find_package(Python COMPONENTS Interpreter Development REQUIRED) find_package(Python 3.6 COMPONENTS Interpreter Development REQUIRED)
find_package(pybind11 CONFIG REQUIRED) find_package(pybind11 CONFIG REQUIRED)
# or add_subdirectory(pybind11) # or add_subdirectory(pybind11)
@ -433,9 +433,8 @@ algorithms from the CMake invocation, with ``-DPYBIND11_FINDPYTHON=ON``.
.. warning:: .. warning::
If you use FindPython2 and FindPython3 to dual-target Python, use the If you use FindPython to multi-target Python versions, use the individual
individual targets listed below, and avoid targets that directly include targets listed below, and avoid targets that directly include Python parts.
Python parts.
There are `many ways to hint or force a discovery of a specific Python There are `many ways to hint or force a discovery of a specific Python
installation <https://cmake.org/cmake/help/latest/module/FindPython.html>`_), installation <https://cmake.org/cmake/help/latest/module/FindPython.html>`_),
@ -462,11 +461,8 @@ available in all modes. The targets provided are:
``pybind11::headers`` ``pybind11::headers``
Just the pybind11 headers and minimum compile requirements Just the pybind11 headers and minimum compile requirements
``pybind11::python2_no_register``
Quiets the warning/error when mixing C++14 or higher and Python 2
``pybind11::pybind11`` ``pybind11::pybind11``
Python headers + ``pybind11::headers`` + ``pybind11::python2_no_register`` (Python 2 only) Python headers + ``pybind11::headers``
``pybind11::python_link_helper`` ``pybind11::python_link_helper``
Just the "linking" part of pybind11:module Just the "linking" part of pybind11:module
@ -475,7 +471,7 @@ available in all modes. The targets provided are:
Everything for extension modules - ``pybind11::pybind11`` + ``Python::Module`` (FindPython CMake 3.15+) or ``pybind11::python_link_helper`` Everything for extension modules - ``pybind11::pybind11`` + ``Python::Module`` (FindPython CMake 3.15+) or ``pybind11::python_link_helper``
``pybind11::embed`` ``pybind11::embed``
Everything for embedding the Python interpreter - ``pybind11::pybind11`` + ``Python::Embed`` (FindPython) or Python libs Everything for embedding the Python interpreter - ``pybind11::pybind11`` + ``Python::Python`` (FindPython) or Python libs
``pybind11::lto`` / ``pybind11::thin_lto`` ``pybind11::lto`` / ``pybind11::thin_lto``
An alternative to `INTERPROCEDURAL_OPTIMIZATION` for adding link-time optimization. An alternative to `INTERPROCEDURAL_OPTIMIZATION` for adding link-time optimization.
@ -509,7 +505,10 @@ You can use these targets to build complex applications. For example, the
target_link_libraries(example PRIVATE pybind11::module pybind11::lto pybind11::windows_extras) target_link_libraries(example PRIVATE pybind11::module pybind11::lto pybind11::windows_extras)
pybind11_extension(example) pybind11_extension(example)
pybind11_strip(example) if(NOT MSVC AND NOT ${CMAKE_BUILD_TYPE} MATCHES Debug|RelWithDebInfo)
# Strip unnecessary sections of the binary on Linux/macOS
pybind11_strip(example)
endif()
set_target_properties(example PROPERTIES CXX_VISIBILITY_PRESET "hidden" set_target_properties(example PROPERTIES CXX_VISIBILITY_PRESET "hidden"
CUDA_VISIBILITY_PRESET "hidden") CUDA_VISIBILITY_PRESET "hidden")
@ -577,21 +576,12 @@ On Linux, you can compile an example such as the one given in
$ c++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) example.cpp -o example$(python3-config --extension-suffix) $ c++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) example.cpp -o example$(python3-config --extension-suffix)
The flags given here assume that you're using Python 3. For Python 2, just
change the executable appropriately (to ``python`` or ``python2``).
The ``python3 -m pybind11 --includes`` command fetches the include paths for The ``python3 -m pybind11 --includes`` command fetches the include paths for
both pybind11 and Python headers. This assumes that pybind11 has been installed both pybind11 and Python headers. This assumes that pybind11 has been installed
using ``pip`` or ``conda``. If it hasn't, you can also manually specify using ``pip`` or ``conda``. If it hasn't, you can also manually specify
``-I <path-to-pybind11>/include`` together with the Python includes path ``-I <path-to-pybind11>/include`` together with the Python includes path
``python3-config --includes``. ``python3-config --includes``.
Note that Python 2.7 modules don't use a special suffix, so you should simply
use ``example.so`` instead of ``example$(python3-config --extension-suffix)``.
Besides, the ``--extension-suffix`` option may or may not be available, depending
on the distribution; in the latter case, the module extension can be manually
set to ``.so``.
On macOS: the build command is almost the same but it also requires passing On macOS: the build command is almost the same but it also requires passing
the ``-undefined dynamic_lookup`` flag so as to ignore missing symbols when the ``-undefined dynamic_lookup`` flag so as to ignore missing symbols when
building the module: building the module:

View File

@ -1,5 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*-
# #
# pybind11 documentation build configuration file, created by # pybind11 documentation build configuration file, created by
# sphinx-quickstart on Sun Oct 11 19:23:48 2015. # sphinx-quickstart on Sun Oct 11 19:23:48 2015.
@ -36,6 +35,7 @@ DIR = Path(__file__).parent.resolve()
# ones. # ones.
extensions = [ extensions = [
"breathe", "breathe",
"sphinx_copybutton",
"sphinxcontrib.rsvgconverter", "sphinxcontrib.rsvgconverter",
"sphinxcontrib.moderncmakedomain", "sphinxcontrib.moderncmakedomain",
] ]
@ -126,23 +126,7 @@ todo_include_todos = False
# The theme to use for HTML and HTML Help pages. See the documentation for # The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes. # a list of builtin themes.
on_rtd = os.environ.get("READTHEDOCS", None) == "True" html_theme = "furo"
if not on_rtd: # only import and set the theme if we're building docs locally
import sphinx_rtd_theme
html_theme = "sphinx_rtd_theme"
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
html_context = {"css_files": ["_static/theme_overrides.css"]}
else:
html_context = {
"css_files": [
"//media.readthedocs.org/css/sphinx_rtd_theme.css",
"//media.readthedocs.org/css/readthedocs-doc-embed.css",
"_static/theme_overrides.css",
]
}
# Theme options are theme-specific and customize the look and feel of a theme # Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the # further. For a list of options available for each theme, see the
@ -173,6 +157,10 @@ else:
# so a file named "default.css" will overwrite the builtin "default.css". # so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"] html_static_path = ["_static"]
html_css_files = [
"css/custom.css",
]
# Add any extra paths that contain custom files (such as robots.txt or # Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied # .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation. # directly to the root of the documentation.
@ -345,9 +333,9 @@ def generate_doxygen_xml(app):
subprocess.call(["doxygen", "--version"]) subprocess.call(["doxygen", "--version"])
retcode = subprocess.call(["doxygen"], cwd=app.confdir) retcode = subprocess.call(["doxygen"], cwd=app.confdir)
if retcode < 0: if retcode < 0:
sys.stderr.write("doxygen error code: {}\n".format(-retcode)) sys.stderr.write(f"doxygen error code: {-retcode}\n")
except OSError as e: except OSError as e:
sys.stderr.write("doxygen execution failed: {}\n".format(e)) sys.stderr.write(f"doxygen execution failed: {e}\n")
def prepare(app): def prepare(app):

View File

@ -8,9 +8,7 @@ Frequently asked questions
filename of the extension library (without suffixes such as ``.so``). filename of the extension library (without suffixes such as ``.so``).
2. If the above did not fix the issue, you are likely using an incompatible 2. If the above did not fix the issue, you are likely using an incompatible
version of Python (for instance, the extension library was compiled against version of Python that does not match what you compiled with.
Python 2, while the interpreter is running on top of some version of Python
3, or vice versa).
"Symbol not found: ``__Py_ZeroStruct`` / ``_PyInstanceMethod_Type``" "Symbol not found: ``__Py_ZeroStruct`` / ``_PyInstanceMethod_Type``"
======================================================================== ========================================================================
@ -147,7 +145,7 @@ using C++14 template metaprogramming.
.. _`faq:hidden_visibility`: .. _`faq:hidden_visibility`:
"SomeClass declared with greater visibility than the type of its field SomeClass::member [-Wattributes]" "'SomeClass' declared with greater visibility than the type of its field 'SomeClass::member' [-Wattributes]"
============================================================================================================ ============================================================================================================
This error typically indicates that you are compiling without the required This error typically indicates that you are compiling without the required
@ -222,20 +220,6 @@ In addition to decreasing binary size, ``-fvisibility=hidden`` also avoids
potential serious issues when loading multiple modules and is required for potential serious issues when loading multiple modules and is required for
proper pybind operation. See the previous FAQ entry for more details. proper pybind operation. See the previous FAQ entry for more details.
Working with ancient Visual Studio 2008 builds on Windows
=========================================================
The official Windows distributions of Python are compiled using truly
ancient versions of Visual Studio that lack good C++11 support. Some users
implicitly assume that it would be impossible to load a plugin built with
Visual Studio 2015 into a Python distribution that was compiled using Visual
Studio 2008. However, no such issue exists: it's perfectly legitimate to
interface DLLs that are built with different compilers and/or C libraries.
Common gotchas to watch out for involve not ``free()``-ing memory region
that that were ``malloc()``-ed in another shared library, using data
structures with incompatible ABIs, and so on. pybind11 is very careful not
to make these types of mistakes.
How can I properly handle Ctrl-C in long-running functions? How can I properly handle Ctrl-C in long-running functions?
=========================================================== ===========================================================
@ -289,27 +273,7 @@ Conflicts can arise, however, when using pybind11 in a project that *also* uses
the CMake Python detection in a system with several Python versions installed. the CMake Python detection in a system with several Python versions installed.
This difference may cause inconsistencies and errors if *both* mechanisms are This difference may cause inconsistencies and errors if *both* mechanisms are
used in the same project. Consider the following CMake code executed in a used in the same project.
system with Python 2.7 and 3.x installed:
.. code-block:: cmake
find_package(PythonInterp)
find_package(PythonLibs)
find_package(pybind11)
It will detect Python 2.7 and pybind11 will pick it as well.
In contrast this code:
.. code-block:: cmake
find_package(pybind11)
find_package(PythonInterp)
find_package(PythonLibs)
will detect Python 3.x for pybind11 and may crash on
``find_package(PythonLibs)`` afterwards.
There are three possible solutions: There are three possible solutions:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 60 KiB

View File

@ -1,5 +1,6 @@
breathe==4.31.0 breathe==4.34.0
sphinx==3.5.4 furo==2022.6.21
sphinx_rtd_theme==1.0.0 sphinx==5.0.2
sphinxcontrib-moderncmakedomain==3.19 sphinx-copybutton==0.5.0
sphinxcontrib-svg2pdfconverter==1.1.1 sphinxcontrib-moderncmakedomain==3.21.4
sphinxcontrib-svg2pdfconverter==1.2.0

View File

@ -524,7 +524,7 @@ include a declaration of the form:
PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr<T>) PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr<T>)
Continuing to do so wont cause an error or even a deprecation warning, Continuing to do so won't cause an error or even a deprecation warning,
but it's completely redundant. but it's completely redundant.

View File

@ -10,6 +10,7 @@
#pragma once #pragma once
#include "detail/common.h"
#include "cast.h" #include "cast.h"
#include <functional> #include <functional>
@ -20,65 +21,72 @@ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
/// @{ /// @{
/// Annotation for methods /// Annotation for methods
struct is_method { handle class_; struct is_method {
handle class_;
explicit is_method(const handle &c) : class_(c) {} explicit is_method(const handle &c) : class_(c) {}
}; };
/// Annotation for operators /// Annotation for operators
struct is_operator { }; struct is_operator {};
/// Annotation for classes that cannot be subclassed /// Annotation for classes that cannot be subclassed
struct is_final { }; struct is_final {};
/// Annotation for parent scope /// Annotation for parent scope
struct scope { handle value; struct scope {
handle value;
explicit scope(const handle &s) : value(s) {} explicit scope(const handle &s) : value(s) {}
}; };
/// Annotation for documentation /// Annotation for documentation
struct doc { const char *value; struct doc {
const char *value;
explicit doc(const char *value) : value(value) {} explicit doc(const char *value) : value(value) {}
}; };
/// Annotation for function names /// Annotation for function names
struct name { const char *value; struct name {
const char *value;
explicit name(const char *value) : value(value) {} explicit name(const char *value) : value(value) {}
}; };
/// Annotation indicating that a function is an overload associated with a given "sibling" /// Annotation indicating that a function is an overload associated with a given "sibling"
struct sibling { handle value; struct sibling {
handle value;
explicit sibling(const handle &value) : value(value.ptr()) {} explicit sibling(const handle &value) : value(value.ptr()) {}
}; };
/// Annotation indicating that a class derives from another given type /// Annotation indicating that a class derives from another given type
template <typename T> struct base { template <typename T>
struct base {
PYBIND11_DEPRECATED("base<T>() was deprecated in favor of specifying 'T' as a template argument to class_") PYBIND11_DEPRECATED(
base() { } // NOLINT(modernize-use-equals-default): breaks MSVC 2015 when adding an attribute "base<T>() was deprecated in favor of specifying 'T' as a template argument to class_")
base() = default;
}; };
/// Keep patient alive while nurse lives /// Keep patient alive while nurse lives
template <size_t Nurse, size_t Patient> struct keep_alive { }; template <size_t Nurse, size_t Patient>
struct keep_alive {};
/// Annotation indicating that a class is involved in a multiple inheritance relationship /// Annotation indicating that a class is involved in a multiple inheritance relationship
struct multiple_inheritance { }; struct multiple_inheritance {};
/// Annotation which enables dynamic attributes, i.e. adds `__dict__` to a class /// Annotation which enables dynamic attributes, i.e. adds `__dict__` to a class
struct dynamic_attr { }; struct dynamic_attr {};
/// Annotation which enables the buffer protocol for a type /// Annotation which enables the buffer protocol for a type
struct buffer_protocol { }; struct buffer_protocol {};
/// Annotation which requests that a special metaclass is created for a type /// Annotation which requests that a special metaclass is created for a type
struct metaclass { struct metaclass {
handle value; handle value;
PYBIND11_DEPRECATED("py::metaclass() is no longer required. It's turned on by default now.") PYBIND11_DEPRECATED("py::metaclass() is no longer required. It's turned on by default now.")
// NOLINTNEXTLINE(modernize-use-equals-default): breaks MSVC 2015 when adding an attribute metaclass() = default;
metaclass() {}
/// Override pybind11's default metaclass /// Override pybind11's default metaclass
explicit metaclass(handle value) : value(value) { } explicit metaclass(handle value) : value(value) {}
}; };
/// Specifies a custom callback with signature `void (PyHeapTypeObject*)` that /// Specifies a custom callback with signature `void (PyHeapTypeObject*)` that
@ -99,15 +107,16 @@ struct custom_type_setup {
}; };
/// Annotation that marks a class as local to the module: /// Annotation that marks a class as local to the module:
struct module_local { const bool value; struct module_local {
const bool value;
constexpr explicit module_local(bool v = true) : value(v) {} constexpr explicit module_local(bool v = true) : value(v) {}
}; };
/// Annotation to mark enums as an arithmetic type /// Annotation to mark enums as an arithmetic type
struct arithmetic { }; struct arithmetic {};
/// Mark a function for addition at the beginning of the existing overload chain instead of the end /// Mark a function for addition at the beginning of the existing overload chain instead of the end
struct prepend { }; struct prepend {};
/** \rst /** \rst
A call policy which places one or more guard variables (``Ts...``) around the function call. A call policy which places one or more guard variables (``Ts...``) around the function call.
@ -127,9 +136,13 @@ struct prepend { };
return foo(args...); // forwarded arguments return foo(args...); // forwarded arguments
}); });
\endrst */ \endrst */
template <typename... Ts> struct call_guard; template <typename... Ts>
struct call_guard;
template <> struct call_guard<> { using type = detail::void_type; }; template <>
struct call_guard<> {
using type = detail::void_type;
};
template <typename T> template <typename T>
struct call_guard<T> { struct call_guard<T> {
@ -154,7 +167,8 @@ PYBIND11_NAMESPACE_BEGIN(detail)
enum op_id : int; enum op_id : int;
enum op_type : int; enum op_type : int;
struct undefined_t; struct undefined_t;
template <op_id id, op_type ot, typename L = undefined_t, typename R = undefined_t> struct op_; template <op_id id, op_type ot, typename L = undefined_t, typename R = undefined_t>
struct op_;
void keep_alive_impl(size_t Nurse, size_t Patient, function_call &call, handle ret); void keep_alive_impl(size_t Nurse, size_t Patient, function_call &call, handle ret);
/// Internal data structure which holds metadata about a keyword argument /// Internal data structure which holds metadata about a keyword argument
@ -166,15 +180,16 @@ struct argument_record {
bool none : 1; ///< True if None is allowed when loading bool none : 1; ///< True if None is allowed when loading
argument_record(const char *name, const char *descr, handle value, bool convert, bool none) argument_record(const char *name, const char *descr, handle value, bool convert, bool none)
: name(name), descr(descr), value(value), convert(convert), none(none) { } : name(name), descr(descr), value(value), convert(convert), none(none) {}
}; };
/// Internal data structure which holds metadata about a bound function (signature, overloads, etc.) /// Internal data structure which holds metadata about a bound function (signature, overloads,
/// etc.)
struct function_record { struct function_record {
function_record() function_record()
: is_constructor(false), is_new_style_constructor(false), is_stateless(false), : is_constructor(false), is_new_style_constructor(false), is_stateless(false),
is_operator(false), is_method(false), has_args(false), is_operator(false), is_method(false), has_args(false), has_kwargs(false),
has_kwargs(false), prepend(false) { } prepend(false) {}
/// Function name /// Function name
char *name = nullptr; /* why no C++ strings? They generate heavier code.. */ char *name = nullptr; /* why no C++ strings? They generate heavier code.. */
@ -189,13 +204,13 @@ struct function_record {
std::vector<argument_record> args; std::vector<argument_record> args;
/// Pointer to lambda function which converts arguments and performs the actual call /// Pointer to lambda function which converts arguments and performs the actual call
handle (*impl) (function_call &) = nullptr; handle (*impl)(function_call &) = nullptr;
/// Storage for the wrapped function pointer and captured data, if any /// Storage for the wrapped function pointer and captured data, if any
void *data[3] = { }; void *data[3] = {};
/// Pointer to custom destructor for 'data' (if needed) /// Pointer to custom destructor for 'data' (if needed)
void (*free_data) (function_record *ptr) = nullptr; void (*free_data)(function_record *ptr) = nullptr;
/// Return value policy associated with this function /// Return value policy associated with this function
return_value_policy policy = return_value_policy::automatic; return_value_policy policy = return_value_policy::automatic;
@ -251,7 +266,7 @@ struct function_record {
struct type_record { struct type_record {
PYBIND11_NOINLINE type_record() PYBIND11_NOINLINE type_record()
: multiple_inheritance(false), dynamic_attr(false), buffer_protocol(false), : multiple_inheritance(false), dynamic_attr(false), buffer_protocol(false),
default_holder(true), module_local(false), is_final(false) { } default_holder(true), module_local(false), is_final(false) {}
/// Handle to the parent scope /// Handle to the parent scope
handle scope; handle scope;
@ -310,42 +325,45 @@ struct type_record {
/// Is the class inheritable from python classes? /// Is the class inheritable from python classes?
bool is_final : 1; bool is_final : 1;
PYBIND11_NOINLINE void add_base(const std::type_info &base, void *(*caster)(void *)) { PYBIND11_NOINLINE void add_base(const std::type_info &base, void *(*caster)(void *) ) {
auto base_info = detail::get_type_info(base, false); auto *base_info = detail::get_type_info(base, false);
if (!base_info) { if (!base_info) {
std::string tname(base.name()); std::string tname(base.name());
detail::clean_type_id(tname); detail::clean_type_id(tname);
pybind11_fail("generic_type: type \"" + std::string(name) + pybind11_fail("generic_type: type \"" + std::string(name)
"\" referenced unknown base type \"" + tname + "\""); + "\" referenced unknown base type \"" + tname + "\"");
} }
if (default_holder != base_info->default_holder) { if (default_holder != base_info->default_holder) {
std::string tname(base.name()); std::string tname(base.name());
detail::clean_type_id(tname); detail::clean_type_id(tname);
pybind11_fail("generic_type: type \"" + std::string(name) + "\" " + pybind11_fail("generic_type: type \"" + std::string(name) + "\" "
(default_holder ? "does not have" : "has") + + (default_holder ? "does not have" : "has")
" a non-default holder type while its base \"" + tname + "\" " + + " a non-default holder type while its base \"" + tname + "\" "
(base_info->default_holder ? "does not" : "does")); + (base_info->default_holder ? "does not" : "does"));
} }
bases.append((PyObject *) base_info->type); bases.append((PyObject *) base_info->type);
if (base_info->type->tp_dictoffset != 0) #if PY_VERSION_HEX < 0x030B0000
dynamic_attr = true; dynamic_attr |= base_info->type->tp_dictoffset != 0;
#else
dynamic_attr |= (base_info->type->tp_flags & Py_TPFLAGS_MANAGED_DICT) != 0;
#endif
if (caster) if (caster) {
base_info->implicit_casts.emplace_back(type, caster); base_info->implicit_casts.emplace_back(type, caster);
}
} }
}; };
inline function_call::function_call(const function_record &f, handle p) : inline function_call::function_call(const function_record &f, handle p) : func(f), parent(p) {
func(f), parent(p) {
args.reserve(f.nargs); args.reserve(f.nargs);
args_convert.reserve(f.nargs); args_convert.reserve(f.nargs);
} }
/// Tag for a new-style `__init__` defined in `detail/init.h` /// Tag for a new-style `__init__` defined in `detail/init.h`
struct is_new_style_constructor { }; struct is_new_style_constructor {};
/** /**
* Partial template specializations to process custom attributes provided to * Partial template specializations to process custom attributes provided to
@ -353,74 +371,97 @@ struct is_new_style_constructor { };
* fields in the type_record and function_record data structures or executed at * fields in the type_record and function_record data structures or executed at
* runtime to deal with custom call policies (e.g. keep_alive). * runtime to deal with custom call policies (e.g. keep_alive).
*/ */
template <typename T, typename SFINAE = void> struct process_attribute; template <typename T, typename SFINAE = void>
struct process_attribute;
template <typename T> struct process_attribute_default { template <typename T>
struct process_attribute_default {
/// Default implementation: do nothing /// Default implementation: do nothing
static void init(const T &, function_record *) { } static void init(const T &, function_record *) {}
static void init(const T &, type_record *) { } static void init(const T &, type_record *) {}
static void precall(function_call &) { } static void precall(function_call &) {}
static void postcall(function_call &, handle) { } static void postcall(function_call &, handle) {}
}; };
/// Process an attribute specifying the function's name /// Process an attribute specifying the function's name
template <> struct process_attribute<name> : process_attribute_default<name> { template <>
struct process_attribute<name> : process_attribute_default<name> {
static void init(const name &n, function_record *r) { r->name = const_cast<char *>(n.value); } static void init(const name &n, function_record *r) { r->name = const_cast<char *>(n.value); }
}; };
/// Process an attribute specifying the function's docstring /// Process an attribute specifying the function's docstring
template <> struct process_attribute<doc> : process_attribute_default<doc> { template <>
struct process_attribute<doc> : process_attribute_default<doc> {
static void init(const doc &n, function_record *r) { r->doc = const_cast<char *>(n.value); } static void init(const doc &n, function_record *r) { r->doc = const_cast<char *>(n.value); }
}; };
/// Process an attribute specifying the function's docstring (provided as a C-style string) /// Process an attribute specifying the function's docstring (provided as a C-style string)
template <> struct process_attribute<const char *> : process_attribute_default<const char *> { template <>
struct process_attribute<const char *> : process_attribute_default<const char *> {
static void init(const char *d, function_record *r) { r->doc = const_cast<char *>(d); } static void init(const char *d, function_record *r) { r->doc = const_cast<char *>(d); }
static void init(const char *d, type_record *r) { r->doc = const_cast<char *>(d); } static void init(const char *d, type_record *r) { r->doc = const_cast<char *>(d); }
}; };
template <> struct process_attribute<char *> : process_attribute<const char *> { }; template <>
struct process_attribute<char *> : process_attribute<const char *> {};
/// Process an attribute indicating the function's return value policy /// Process an attribute indicating the function's return value policy
template <> struct process_attribute<return_value_policy> : process_attribute_default<return_value_policy> { template <>
struct process_attribute<return_value_policy> : process_attribute_default<return_value_policy> {
static void init(const return_value_policy &p, function_record *r) { r->policy = p; } static void init(const return_value_policy &p, function_record *r) { r->policy = p; }
}; };
/// Process an attribute which indicates that this is an overloaded function associated with a given sibling /// Process an attribute which indicates that this is an overloaded function associated with a
template <> struct process_attribute<sibling> : process_attribute_default<sibling> { /// given sibling
template <>
struct process_attribute<sibling> : process_attribute_default<sibling> {
static void init(const sibling &s, function_record *r) { r->sibling = s.value; } static void init(const sibling &s, function_record *r) { r->sibling = s.value; }
}; };
/// Process an attribute which indicates that this function is a method /// Process an attribute which indicates that this function is a method
template <> struct process_attribute<is_method> : process_attribute_default<is_method> { template <>
static void init(const is_method &s, function_record *r) { r->is_method = true; r->scope = s.class_; } struct process_attribute<is_method> : process_attribute_default<is_method> {
static void init(const is_method &s, function_record *r) {
r->is_method = true;
r->scope = s.class_;
}
}; };
/// Process an attribute which indicates the parent scope of a method /// Process an attribute which indicates the parent scope of a method
template <> struct process_attribute<scope> : process_attribute_default<scope> { template <>
struct process_attribute<scope> : process_attribute_default<scope> {
static void init(const scope &s, function_record *r) { r->scope = s.value; } static void init(const scope &s, function_record *r) { r->scope = s.value; }
}; };
/// Process an attribute which indicates that this function is an operator /// Process an attribute which indicates that this function is an operator
template <> struct process_attribute<is_operator> : process_attribute_default<is_operator> { template <>
struct process_attribute<is_operator> : process_attribute_default<is_operator> {
static void init(const is_operator &, function_record *r) { r->is_operator = true; } static void init(const is_operator &, function_record *r) { r->is_operator = true; }
}; };
template <> struct process_attribute<is_new_style_constructor> : process_attribute_default<is_new_style_constructor> { template <>
static void init(const is_new_style_constructor &, function_record *r) { r->is_new_style_constructor = true; } struct process_attribute<is_new_style_constructor>
: process_attribute_default<is_new_style_constructor> {
static void init(const is_new_style_constructor &, function_record *r) {
r->is_new_style_constructor = true;
}
}; };
inline void check_kw_only_arg(const arg &a, function_record *r) { inline void check_kw_only_arg(const arg &a, function_record *r) {
if (r->args.size() > r->nargs_pos && (!a.name || a.name[0] == '\0')) if (r->args.size() > r->nargs_pos && (!a.name || a.name[0] == '\0')) {
pybind11_fail("arg(): cannot specify an unnamed argument after a kw_only() annotation or args() argument"); pybind11_fail("arg(): cannot specify an unnamed argument after a kw_only() annotation or "
"args() argument");
}
} }
inline void append_self_arg_if_needed(function_record *r) { inline void append_self_arg_if_needed(function_record *r) {
if (r->is_method && r->args.empty()) if (r->is_method && r->args.empty()) {
r->args.emplace_back("self", nullptr, handle(), /*convert=*/ true, /*none=*/ false); r->args.emplace_back("self", nullptr, handle(), /*convert=*/true, /*none=*/false);
}
} }
/// Process a keyword argument attribute (*without* a default value) /// Process a keyword argument attribute (*without* a default value)
template <> struct process_attribute<arg> : process_attribute_default<arg> { template <>
struct process_attribute<arg> : process_attribute_default<arg> {
static void init(const arg &a, function_record *r) { static void init(const arg &a, function_record *r) {
append_self_arg_if_needed(r); append_self_arg_if_needed(r);
r->args.emplace_back(a.name, nullptr, handle(), !a.flag_noconvert, a.flag_none); r->args.emplace_back(a.name, nullptr, handle(), !a.flag_noconvert, a.flag_none);
@ -430,30 +471,38 @@ 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 <> struct process_attribute<arg_v> : process_attribute_default<arg_v> { template <>
struct process_attribute<arg_v> : process_attribute_default<arg_v> {
static void init(const arg_v &a, function_record *r) { static void init(const arg_v &a, function_record *r) {
if (r->is_method && r->args.empty()) if (r->is_method && r->args.empty()) {
r->args.emplace_back("self", /*descr=*/ nullptr, /*parent=*/ handle(), /*convert=*/ true, /*none=*/ false); r->args.emplace_back(
"self", /*descr=*/nullptr, /*parent=*/handle(), /*convert=*/true, /*none=*/false);
}
if (!a.value) { if (!a.value) {
#if !defined(NDEBUG) #if defined(PYBIND11_DETAILED_ERROR_MESSAGES)
std::string descr("'"); std::string descr("'");
if (a.name) descr += std::string(a.name) + ": "; if (a.name) {
descr += std::string(a.name) + ": ";
}
descr += a.type + "'"; descr += a.type + "'";
if (r->is_method) { if (r->is_method) {
if (r->name) if (r->name) {
descr += " in method '" + (std::string) str(r->scope) + "." + (std::string) r->name + "'"; descr += " in method '" + (std::string) str(r->scope) + "."
else + (std::string) r->name + "'";
} else {
descr += " in method of '" + (std::string) str(r->scope) + "'"; descr += " in method of '" + (std::string) str(r->scope) + "'";
}
} else if (r->name) { } else if (r->name) {
descr += " in function '" + (std::string) r->name + "'"; descr += " in function '" + (std::string) r->name + "'";
} }
pybind11_fail("arg(): could not convert default argument " pybind11_fail("arg(): could not convert default argument " + descr
+ descr + " into a Python object (type not registered yet?)"); + " into a Python object (type not registered yet?)");
#else #else
pybind11_fail("arg(): could not convert default argument " pybind11_fail("arg(): could not convert default argument "
"into a Python object (type not registered yet?). " "into a Python object (type not registered yet?). "
"Compile in debug mode for more information."); "#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for "
"more information.");
#endif #endif
} }
r->args.emplace_back(a.name, a.descr, a.value.inc_ref(), !a.flag_noconvert, a.flag_none); r->args.emplace_back(a.name, a.descr, a.value.inc_ref(), !a.flag_noconvert, a.flag_none);
@ -463,29 +512,36 @@ template <> struct process_attribute<arg_v> : process_attribute_default<arg_v> {
}; };
/// Process a keyword-only-arguments-follow pseudo argument /// Process a keyword-only-arguments-follow pseudo argument
template <> struct process_attribute<kw_only> : process_attribute_default<kw_only> { template <>
struct process_attribute<kw_only> : process_attribute_default<kw_only> {
static void init(const kw_only &, function_record *r) { static void init(const kw_only &, function_record *r) {
append_self_arg_if_needed(r); append_self_arg_if_needed(r);
if (r->has_args && r->nargs_pos != static_cast<std::uint16_t>(r->args.size())) if (r->has_args && r->nargs_pos != static_cast<std::uint16_t>(r->args.size())) {
pybind11_fail("Mismatched args() and kw_only(): they must occur at the same relative argument location (or omit kw_only() entirely)"); pybind11_fail("Mismatched args() and kw_only(): they must occur at the same relative "
"argument location (or omit kw_only() entirely)");
}
r->nargs_pos = static_cast<std::uint16_t>(r->args.size()); r->nargs_pos = static_cast<std::uint16_t>(r->args.size());
} }
}; };
/// Process a positional-only-argument maker /// Process a positional-only-argument maker
template <> struct process_attribute<pos_only> : process_attribute_default<pos_only> { template <>
struct process_attribute<pos_only> : process_attribute_default<pos_only> {
static void init(const pos_only &, function_record *r) { static void init(const pos_only &, function_record *r) {
append_self_arg_if_needed(r); append_self_arg_if_needed(r);
r->nargs_pos_only = static_cast<std::uint16_t>(r->args.size()); r->nargs_pos_only = static_cast<std::uint16_t>(r->args.size());
if (r->nargs_pos_only > r->nargs_pos) if (r->nargs_pos_only > r->nargs_pos) {
pybind11_fail("pos_only(): cannot follow a py::args() argument"); pybind11_fail("pos_only(): cannot follow a py::args() argument");
// It also can't follow a kw_only, but a static_assert in pybind11.h checks that }
// It also can't follow a kw_only, but a static_assert in pybind11.h checks that
} }
}; };
/// Process a parent class attribute. Single inheritance only (class_ itself already guarantees that) /// Process a parent class attribute. Single inheritance only (class_ itself already guarantees
/// that)
template <typename T> template <typename T>
struct process_attribute<T, enable_if_t<is_pyobject<T>::value>> : process_attribute_default<handle> { struct process_attribute<T, enable_if_t<is_pyobject<T>::value>>
: process_attribute_default<handle> {
static void init(const handle &h, type_record *r) { r->bases.append(h); } static void init(const handle &h, type_record *r) { r->bases.append(h); }
}; };
@ -498,7 +554,9 @@ struct process_attribute<base<T>> : process_attribute_default<base<T>> {
/// Process a multiple inheritance attribute /// Process a multiple inheritance attribute
template <> template <>
struct process_attribute<multiple_inheritance> : process_attribute_default<multiple_inheritance> { struct process_attribute<multiple_inheritance> : process_attribute_default<multiple_inheritance> {
static void init(const multiple_inheritance &, type_record *r) { r->multiple_inheritance = true; } static void init(const multiple_inheritance &, type_record *r) {
r->multiple_inheritance = true;
}
}; };
template <> template <>
@ -544,34 +602,41 @@ template <>
struct process_attribute<arithmetic> : process_attribute_default<arithmetic> {}; struct process_attribute<arithmetic> : process_attribute_default<arithmetic> {};
template <typename... Ts> template <typename... Ts>
struct process_attribute<call_guard<Ts...>> : process_attribute_default<call_guard<Ts...>> { }; struct process_attribute<call_guard<Ts...>> : process_attribute_default<call_guard<Ts...>> {};
/** /**
* Process a keep_alive call policy -- invokes keep_alive_impl during the * Process a keep_alive call policy -- invokes keep_alive_impl during the
* pre-call handler if both Nurse, Patient != 0 and use the post-call handler * pre-call handler if both Nurse, Patient != 0 and use the post-call handler
* otherwise * otherwise
*/ */
template <size_t Nurse, size_t Patient> struct process_attribute<keep_alive<Nurse, Patient>> : public process_attribute_default<keep_alive<Nurse, Patient>> { template <size_t Nurse, size_t Patient>
struct process_attribute<keep_alive<Nurse, Patient>>
: public process_attribute_default<keep_alive<Nurse, Patient>> {
template <size_t N = Nurse, size_t P = Patient, enable_if_t<N != 0 && P != 0, int> = 0> template <size_t N = Nurse, size_t P = Patient, enable_if_t<N != 0 && P != 0, int> = 0>
static void precall(function_call &call) { keep_alive_impl(Nurse, Patient, call, handle()); } static void precall(function_call &call) {
keep_alive_impl(Nurse, Patient, call, handle());
}
template <size_t N = Nurse, size_t P = Patient, enable_if_t<N != 0 && P != 0, int> = 0> template <size_t N = Nurse, size_t P = Patient, enable_if_t<N != 0 && P != 0, int> = 0>
static void postcall(function_call &, handle) { } static void postcall(function_call &, handle) {}
template <size_t N = Nurse, size_t P = Patient, enable_if_t<N == 0 || P == 0, int> = 0> template <size_t N = Nurse, size_t P = Patient, enable_if_t<N == 0 || P == 0, int> = 0>
static void precall(function_call &) { } static void precall(function_call &) {}
template <size_t N = Nurse, size_t P = Patient, enable_if_t<N == 0 || P == 0, int> = 0> template <size_t N = Nurse, size_t P = Patient, enable_if_t<N == 0 || P == 0, int> = 0>
static void postcall(function_call &call, handle ret) { keep_alive_impl(Nurse, Patient, call, ret); } static void postcall(function_call &call, handle ret) {
keep_alive_impl(Nurse, Patient, call, ret);
}
}; };
/// Recursively iterate over variadic template arguments /// Recursively iterate over variadic template arguments
template <typename... Args> struct process_attributes { template <typename... Args>
static void init(const Args&... args, function_record *r) { struct process_attributes {
static void init(const Args &...args, function_record *r) {
PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(r); PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(r);
PYBIND11_WORKAROUND_INCORRECT_GCC_UNUSED_BUT_SET_PARAMETER(r); PYBIND11_WORKAROUND_INCORRECT_GCC_UNUSED_BUT_SET_PARAMETER(r);
using expander = int[]; using expander = int[];
(void) expander{ (void) expander{
0, ((void) process_attribute<typename std::decay<Args>::type>::init(args, r), 0)...}; 0, ((void) process_attribute<typename std::decay<Args>::type>::init(args, r), 0)...};
} }
static void init(const Args&... args, type_record *r) { static void init(const Args &...args, type_record *r) {
PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(r); PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(r);
PYBIND11_WORKAROUND_INCORRECT_GCC_UNUSED_BUT_SET_PARAMETER(r); PYBIND11_WORKAROUND_INCORRECT_GCC_UNUSED_BUT_SET_PARAMETER(r);
using expander = int[]; using expander = int[];
@ -603,7 +668,7 @@ using extract_guard_t = typename exactly_one_t<is_call_guard, call_guard<>, Extr
/// 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...),
size_t self = constexpr_sum(std::is_same<is_method, Extra>::value...)> size_t self = constexpr_sum(std::is_same<is_method, Extra>::value...)>
constexpr bool expected_num_args(size_t nargs, bool has_args, bool has_kwargs) { constexpr bool expected_num_args(size_t nargs, bool has_args, bool has_kwargs) {
PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(nargs, has_args, has_kwargs); PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(nargs, has_args, has_kwargs);
return named == 0 || (self + named + size_t(has_args) + size_t(has_kwargs)) == nargs; return named == 0 || (self + named + size_t(has_args) + size_t(has_kwargs)) == nargs;

View File

@ -19,9 +19,11 @@ PYBIND11_NAMESPACE_BEGIN(detail)
inline std::vector<ssize_t> c_strides(const std::vector<ssize_t> &shape, ssize_t itemsize) { inline std::vector<ssize_t> c_strides(const std::vector<ssize_t> &shape, ssize_t itemsize) {
auto ndim = shape.size(); auto ndim = shape.size();
std::vector<ssize_t> strides(ndim, itemsize); std::vector<ssize_t> strides(ndim, itemsize);
if (ndim > 0) if (ndim > 0) {
for (size_t i = ndim - 1; i > 0; --i) for (size_t i = ndim - 1; i > 0; --i) {
strides[i - 1] = strides[i] * shape[i]; strides[i - 1] = strides[i] * shape[i];
}
}
return strides; return strides;
} }
@ -29,8 +31,9 @@ inline std::vector<ssize_t> c_strides(const std::vector<ssize_t> &shape, ssize_t
inline std::vector<ssize_t> f_strides(const std::vector<ssize_t> &shape, ssize_t itemsize) { inline std::vector<ssize_t> f_strides(const std::vector<ssize_t> &shape, ssize_t itemsize) {
auto ndim = shape.size(); auto ndim = shape.size();
std::vector<ssize_t> strides(ndim, itemsize); std::vector<ssize_t> strides(ndim, itemsize);
for (size_t i = 1; i < ndim; ++i) for (size_t i = 1; i < ndim; ++i) {
strides[i] = strides[i - 1] * shape[i - 1]; strides[i] = strides[i - 1] * shape[i - 1];
}
return strides; return strides;
} }
@ -41,55 +44,85 @@ struct buffer_info {
void *ptr = nullptr; // Pointer to the underlying storage void *ptr = nullptr; // Pointer to the underlying storage
ssize_t itemsize = 0; // Size of individual items in bytes ssize_t itemsize = 0; // Size of individual items in bytes
ssize_t size = 0; // Total number of entries ssize_t size = 0; // Total number of entries
std::string format; // For homogeneous buffers, this should be set to format_descriptor<T>::format() std::string format; // For homogeneous buffers, this should be set to
// format_descriptor<T>::format()
ssize_t ndim = 0; // Number of dimensions ssize_t ndim = 0; // Number of dimensions
std::vector<ssize_t> shape; // Shape of the tensor (1 entry per dimension) std::vector<ssize_t> shape; // Shape of the tensor (1 entry per dimension)
std::vector<ssize_t> strides; // Number of bytes between adjacent entries (for each per dimension) std::vector<ssize_t> strides; // Number of bytes between adjacent entries
// (for each per dimension)
bool readonly = false; // flag to indicate if the underlying storage may be written to bool readonly = false; // flag to indicate if the underlying storage may be written to
buffer_info() = default; buffer_info() = default;
buffer_info(void *ptr, ssize_t itemsize, const std::string &format, ssize_t ndim, buffer_info(void *ptr,
detail::any_container<ssize_t> shape_in, detail::any_container<ssize_t> strides_in, bool readonly=false) ssize_t itemsize,
: ptr(ptr), itemsize(itemsize), size(1), format(format), ndim(ndim), const std::string &format,
shape(std::move(shape_in)), strides(std::move(strides_in)), readonly(readonly) { ssize_t ndim,
if (ndim != (ssize_t) shape.size() || ndim != (ssize_t) strides.size()) detail::any_container<ssize_t> shape_in,
detail::any_container<ssize_t> strides_in,
bool readonly = false)
: ptr(ptr), itemsize(itemsize), size(1), format(format), ndim(ndim),
shape(std::move(shape_in)), strides(std::move(strides_in)), readonly(readonly) {
if (ndim != (ssize_t) shape.size() || ndim != (ssize_t) strides.size()) {
pybind11_fail("buffer_info: ndim doesn't match shape and/or strides length"); pybind11_fail("buffer_info: ndim doesn't match shape and/or strides length");
for (size_t i = 0; i < (size_t) ndim; ++i) }
for (size_t i = 0; i < (size_t) ndim; ++i) {
size *= shape[i]; size *= shape[i];
}
} }
template <typename T> template <typename T>
buffer_info(T *ptr, detail::any_container<ssize_t> shape_in, detail::any_container<ssize_t> strides_in, bool readonly=false) buffer_info(T *ptr,
: buffer_info(private_ctr_tag(), ptr, sizeof(T), format_descriptor<T>::format(), static_cast<ssize_t>(shape_in->size()), std::move(shape_in), std::move(strides_in), readonly) { } detail::any_container<ssize_t> shape_in,
detail::any_container<ssize_t> strides_in,
bool readonly = false)
: buffer_info(private_ctr_tag(),
ptr,
sizeof(T),
format_descriptor<T>::format(),
static_cast<ssize_t>(shape_in->size()),
std::move(shape_in),
std::move(strides_in),
readonly) {}
buffer_info(void *ptr, ssize_t itemsize, const std::string &format, ssize_t size, bool readonly=false) buffer_info(void *ptr,
: buffer_info(ptr, itemsize, format, 1, {size}, {itemsize}, readonly) { } ssize_t itemsize,
const std::string &format,
ssize_t size,
bool readonly = false)
: buffer_info(ptr, itemsize, format, 1, {size}, {itemsize}, readonly) {}
template <typename T> template <typename T>
buffer_info(T *ptr, ssize_t size, bool readonly=false) buffer_info(T *ptr, ssize_t size, bool readonly = false)
: buffer_info(ptr, sizeof(T), format_descriptor<T>::format(), size, readonly) { } : buffer_info(ptr, sizeof(T), format_descriptor<T>::format(), size, readonly) {}
template <typename T> template <typename T>
buffer_info(const T *ptr, ssize_t size, bool readonly=true) buffer_info(const T *ptr, ssize_t size, bool readonly = true)
: buffer_info(const_cast<T*>(ptr), sizeof(T), format_descriptor<T>::format(), size, readonly) { } : buffer_info(
const_cast<T *>(ptr), sizeof(T), format_descriptor<T>::format(), size, readonly) {}
explicit buffer_info(Py_buffer *view, bool ownview = true) explicit buffer_info(Py_buffer *view, bool ownview = true)
: buffer_info(view->buf, view->itemsize, view->format, view->ndim, : buffer_info(
view->buf,
view->itemsize,
view->format,
view->ndim,
{view->shape, view->shape + view->ndim}, {view->shape, view->shape + view->ndim},
/* Though buffer::request() requests PyBUF_STRIDES, ctypes objects /* Though buffer::request() requests PyBUF_STRIDES, ctypes objects
* ignore this flag and return a view with NULL strides. * ignore this flag and return a view with NULL strides.
* When strides are NULL, build them manually. */ * When strides are NULL, build them manually. */
view->strides view->strides
? std::vector<ssize_t>(view->strides, view->strides + view->ndim) ? std::vector<ssize_t>(view->strides, view->strides + view->ndim)
: detail::c_strides({view->shape, view->shape + view->ndim}, view->itemsize), : detail::c_strides({view->shape, view->shape + view->ndim}, view->itemsize),
(view->readonly != 0)) { (view->readonly != 0)) {
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
this->m_view = view; this->m_view = view;
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
this->ownview = ownview; this->ownview = ownview;
} }
buffer_info(const buffer_info &) = delete; buffer_info(const buffer_info &) = delete;
buffer_info& operator=(const buffer_info &) = delete; buffer_info &operator=(const buffer_info &) = delete;
buffer_info(buffer_info &&other) noexcept { (*this) = std::move(other); } buffer_info(buffer_info &&other) noexcept { (*this) = std::move(other); }
@ -108,17 +141,28 @@ struct buffer_info {
} }
~buffer_info() { ~buffer_info() {
if (m_view && ownview) { PyBuffer_Release(m_view); delete m_view; } if (m_view && ownview) {
PyBuffer_Release(m_view);
delete m_view;
}
} }
Py_buffer *view() const { return m_view; } Py_buffer *view() const { return m_view; }
Py_buffer *&view() { return m_view; } Py_buffer *&view() { return m_view; }
private:
struct private_ctr_tag { };
buffer_info(private_ctr_tag, void *ptr, ssize_t itemsize, const std::string &format, ssize_t ndim, private:
detail::any_container<ssize_t> &&shape_in, detail::any_container<ssize_t> &&strides_in, bool readonly) struct private_ctr_tag {};
: buffer_info(ptr, itemsize, format, ndim, std::move(shape_in), std::move(strides_in), readonly) { }
buffer_info(private_ctr_tag,
void *ptr,
ssize_t itemsize,
const std::string &format,
ssize_t ndim,
detail::any_container<ssize_t> &&shape_in,
detail::any_container<ssize_t> &&strides_in,
bool readonly)
: buffer_info(
ptr, itemsize, format, ndim, std::move(shape_in), std::move(strides_in), readonly) {}
Py_buffer *m_view = nullptr; Py_buffer *m_view = nullptr;
bool ownview = false; bool ownview = false;
@ -126,17 +170,22 @@ private:
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
template <typename T, typename SFINAE = void> struct compare_buffer_info { template <typename T, typename SFINAE = void>
static bool compare(const buffer_info& b) { struct compare_buffer_info {
static bool compare(const buffer_info &b) {
return b.format == format_descriptor<T>::format() && b.itemsize == (ssize_t) sizeof(T); return b.format == format_descriptor<T>::format() && b.itemsize == (ssize_t) sizeof(T);
} }
}; };
template <typename T> struct compare_buffer_info<T, detail::enable_if_t<std::is_integral<T>::value>> { template <typename T>
static bool compare(const buffer_info& b) { struct compare_buffer_info<T, detail::enable_if_t<std::is_integral<T>::value>> {
return (size_t) b.itemsize == sizeof(T) && (b.format == format_descriptor<T>::value || static bool compare(const buffer_info &b) {
((sizeof(T) == sizeof(long)) && b.format == (std::is_unsigned<T>::value ? "L" : "l")) || return (size_t) b.itemsize == sizeof(T)
((sizeof(T) == sizeof(size_t)) && b.format == (std::is_unsigned<T>::value ? "N" : "n"))); && (b.format == format_descriptor<T>::value
|| ((sizeof(T) == sizeof(long))
&& b.format == (std::is_unsigned<T>::value ? "L" : "l"))
|| ((sizeof(T) == sizeof(size_t))
&& b.format == (std::is_unsigned<T>::value ? "N" : "n")));
} }
}; };

File diff suppressed because it is too large Load Diff

View File

@ -15,61 +15,59 @@
#include <chrono> #include <chrono>
#include <cmath> #include <cmath>
#include <ctime> #include <ctime>
#include <mutex>
#include <datetime.h> #include <datetime.h>
#include <mutex>
// Backport the PyDateTime_DELTA functions from Python3.3 if required
#ifndef PyDateTime_DELTA_GET_DAYS
#define PyDateTime_DELTA_GET_DAYS(o) (((PyDateTime_Delta*)o)->days)
#endif
#ifndef PyDateTime_DELTA_GET_SECONDS
#define PyDateTime_DELTA_GET_SECONDS(o) (((PyDateTime_Delta*)o)->seconds)
#endif
#ifndef PyDateTime_DELTA_GET_MICROSECONDS
#define PyDateTime_DELTA_GET_MICROSECONDS(o) (((PyDateTime_Delta*)o)->microseconds)
#endif
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
template <typename type> class duration_caster { template <typename type>
class duration_caster {
public: public:
using rep = typename type::rep; using rep = typename type::rep;
using period = typename type::period; using period = typename type::period;
using days = std::chrono::duration<int_least32_t, std::ratio<86400>>; // signed 25 bits required by the standard. // signed 25 bits required by the standard.
using days = std::chrono::duration<int_least32_t, std::ratio<86400>>;
bool load(handle src, bool) { bool load(handle src, bool) {
using namespace std::chrono; using namespace std::chrono;
// Lazy initialise the PyDateTime import // Lazy initialise the PyDateTime import
if (!PyDateTimeAPI) { PyDateTime_IMPORT; } if (!PyDateTimeAPI) {
PyDateTime_IMPORT;
}
if (!src) return false; if (!src) {
return false;
}
// If invoked with datetime.delta object // If invoked with datetime.delta object
if (PyDelta_Check(src.ptr())) { if (PyDelta_Check(src.ptr())) {
value = type(duration_cast<duration<rep, period>>( value = type(duration_cast<duration<rep, period>>(
days(PyDateTime_DELTA_GET_DAYS(src.ptr())) days(PyDateTime_DELTA_GET_DAYS(src.ptr()))
+ seconds(PyDateTime_DELTA_GET_SECONDS(src.ptr())) + seconds(PyDateTime_DELTA_GET_SECONDS(src.ptr()))
+ microseconds(PyDateTime_DELTA_GET_MICROSECONDS(src.ptr())))); + microseconds(PyDateTime_DELTA_GET_MICROSECONDS(src.ptr()))));
return true; return true;
} }
// If invoked with a float we assume it is seconds and convert // If invoked with a float we assume it is seconds and convert
if (PyFloat_Check(src.ptr())) { if (PyFloat_Check(src.ptr())) {
value = type(duration_cast<duration<rep, period>>(duration<double>(PyFloat_AsDouble(src.ptr())))); value = type(duration_cast<duration<rep, period>>(
duration<double>(PyFloat_AsDouble(src.ptr()))));
return true; return true;
} }
return false; return false;
} }
// If this is a duration just return it back // If this is a duration just return it back
static const std::chrono::duration<rep, period>& get_duration(const std::chrono::duration<rep, period> &src) { static const std::chrono::duration<rep, period> &
get_duration(const std::chrono::duration<rep, period> &src) {
return src; return src;
} }
// If this is a time_point get the time_since_epoch // If this is a time_point get the time_since_epoch
template <typename Clock> static std::chrono::duration<rep, period> get_duration(const std::chrono::time_point<Clock, std::chrono::duration<rep, period>> &src) { template <typename Clock>
static std::chrono::duration<rep, period>
get_duration(const std::chrono::time_point<Clock, std::chrono::duration<rep, period>> &src) {
return src.time_since_epoch(); return src.time_since_epoch();
} }
@ -81,9 +79,12 @@ public:
auto d = get_duration(src); auto d = get_duration(src);
// Lazy initialise the PyDateTime import // Lazy initialise the PyDateTime import
if (!PyDateTimeAPI) { PyDateTime_IMPORT; } if (!PyDateTimeAPI) {
PyDateTime_IMPORT;
}
// Declare these special duration types so the conversions happen with the correct primitive types (int) // Declare these special duration types so the conversions happen with the correct
// primitive types (int)
using dd_t = duration<int, std::ratio<86400>>; using dd_t = duration<int, std::ratio<86400>>;
using ss_t = duration<int, std::ratio<1>>; using ss_t = duration<int, std::ratio<1>>;
using us_t = duration<int, std::micro>; using us_t = duration<int, std::micro>;
@ -115,76 +116,89 @@ inline std::tm *localtime_thread_safe(const std::time_t *time, std::tm *buf) {
} }
// This is for casting times on the system clock into datetime.datetime instances // This is for casting times on the system clock into datetime.datetime instances
template <typename Duration> class type_caster<std::chrono::time_point<std::chrono::system_clock, Duration>> { template <typename Duration>
class type_caster<std::chrono::time_point<std::chrono::system_clock, Duration>> {
public: public:
using type = std::chrono::time_point<std::chrono::system_clock, Duration>; using type = std::chrono::time_point<std::chrono::system_clock, Duration>;
bool load(handle src, bool) { bool load(handle src, bool) {
using namespace std::chrono; using namespace std::chrono;
// Lazy initialise the PyDateTime import // Lazy initialise the PyDateTime import
if (!PyDateTimeAPI) { PyDateTime_IMPORT; } if (!PyDateTimeAPI) {
PyDateTime_IMPORT;
}
if (!src) return false; if (!src) {
return false;
}
std::tm cal; std::tm cal;
microseconds msecs; microseconds msecs;
if (PyDateTime_Check(src.ptr())) { if (PyDateTime_Check(src.ptr())) {
cal.tm_sec = PyDateTime_DATE_GET_SECOND(src.ptr()); cal.tm_sec = PyDateTime_DATE_GET_SECOND(src.ptr());
cal.tm_min = PyDateTime_DATE_GET_MINUTE(src.ptr()); cal.tm_min = PyDateTime_DATE_GET_MINUTE(src.ptr());
cal.tm_hour = PyDateTime_DATE_GET_HOUR(src.ptr()); cal.tm_hour = PyDateTime_DATE_GET_HOUR(src.ptr());
cal.tm_mday = PyDateTime_GET_DAY(src.ptr()); cal.tm_mday = PyDateTime_GET_DAY(src.ptr());
cal.tm_mon = PyDateTime_GET_MONTH(src.ptr()) - 1; cal.tm_mon = PyDateTime_GET_MONTH(src.ptr()) - 1;
cal.tm_year = PyDateTime_GET_YEAR(src.ptr()) - 1900; cal.tm_year = PyDateTime_GET_YEAR(src.ptr()) - 1900;
cal.tm_isdst = -1; cal.tm_isdst = -1;
msecs = microseconds(PyDateTime_DATE_GET_MICROSECOND(src.ptr())); msecs = microseconds(PyDateTime_DATE_GET_MICROSECOND(src.ptr()));
} else if (PyDate_Check(src.ptr())) { } else if (PyDate_Check(src.ptr())) {
cal.tm_sec = 0; cal.tm_sec = 0;
cal.tm_min = 0; cal.tm_min = 0;
cal.tm_hour = 0; cal.tm_hour = 0;
cal.tm_mday = PyDateTime_GET_DAY(src.ptr()); cal.tm_mday = PyDateTime_GET_DAY(src.ptr());
cal.tm_mon = PyDateTime_GET_MONTH(src.ptr()) - 1; cal.tm_mon = PyDateTime_GET_MONTH(src.ptr()) - 1;
cal.tm_year = PyDateTime_GET_YEAR(src.ptr()) - 1900; cal.tm_year = PyDateTime_GET_YEAR(src.ptr()) - 1900;
cal.tm_isdst = -1; cal.tm_isdst = -1;
msecs = microseconds(0); msecs = microseconds(0);
} else if (PyTime_Check(src.ptr())) { } else if (PyTime_Check(src.ptr())) {
cal.tm_sec = PyDateTime_TIME_GET_SECOND(src.ptr()); cal.tm_sec = PyDateTime_TIME_GET_SECOND(src.ptr());
cal.tm_min = PyDateTime_TIME_GET_MINUTE(src.ptr()); cal.tm_min = PyDateTime_TIME_GET_MINUTE(src.ptr());
cal.tm_hour = PyDateTime_TIME_GET_HOUR(src.ptr()); cal.tm_hour = PyDateTime_TIME_GET_HOUR(src.ptr());
cal.tm_mday = 1; // This date (day, month, year) = (1, 0, 70) cal.tm_mday = 1; // This date (day, month, year) = (1, 0, 70)
cal.tm_mon = 0; // represents 1-Jan-1970, which is the first cal.tm_mon = 0; // represents 1-Jan-1970, which is the first
cal.tm_year = 70; // earliest available date for Python's datetime cal.tm_year = 70; // earliest available date for Python's datetime
cal.tm_isdst = -1; cal.tm_isdst = -1;
msecs = microseconds(PyDateTime_TIME_GET_MICROSECOND(src.ptr())); msecs = microseconds(PyDateTime_TIME_GET_MICROSECOND(src.ptr()));
} else {
return false;
} }
else return false;
value = time_point_cast<Duration>(system_clock::from_time_t(std::mktime(&cal)) + msecs); value = time_point_cast<Duration>(system_clock::from_time_t(std::mktime(&cal)) + msecs);
return true; return true;
} }
static handle cast(const std::chrono::time_point<std::chrono::system_clock, Duration> &src, return_value_policy /* policy */, handle /* parent */) { static handle cast(const std::chrono::time_point<std::chrono::system_clock, Duration> &src,
return_value_policy /* policy */,
handle /* parent */) {
using namespace std::chrono; using namespace std::chrono;
// Lazy initialise the PyDateTime import // Lazy initialise the PyDateTime import
if (!PyDateTimeAPI) { PyDateTime_IMPORT; } if (!PyDateTimeAPI) {
PyDateTime_IMPORT;
}
// Get out microseconds, and make sure they are positive, to avoid bug in eastern hemisphere time zones // Get out microseconds, and make sure they are positive, to avoid bug in eastern
// (cfr. https://github.com/pybind/pybind11/issues/2417) // hemisphere time zones (cfr. https://github.com/pybind/pybind11/issues/2417)
using us_t = duration<int, std::micro>; using us_t = duration<int, std::micro>;
auto us = duration_cast<us_t>(src.time_since_epoch() % seconds(1)); auto us = duration_cast<us_t>(src.time_since_epoch() % seconds(1));
if (us.count() < 0) if (us.count() < 0) {
us += seconds(1); us += seconds(1);
}
// Subtract microseconds BEFORE `system_clock::to_time_t`, because: // Subtract microseconds BEFORE `system_clock::to_time_t`, because:
// > If std::time_t has lower precision, it is implementation-defined whether the value is rounded or truncated. // > If std::time_t has lower precision, it is implementation-defined whether the value is
// (https://en.cppreference.com/w/cpp/chrono/system_clock/to_time_t) // rounded or truncated. (https://en.cppreference.com/w/cpp/chrono/system_clock/to_time_t)
std::time_t tt = system_clock::to_time_t(time_point_cast<system_clock::duration>(src - us)); std::time_t tt
= system_clock::to_time_t(time_point_cast<system_clock::duration>(src - us));
std::tm localtime; std::tm localtime;
std::tm *localtime_ptr = localtime_thread_safe(&tt, &localtime); std::tm *localtime_ptr = localtime_thread_safe(&tt, &localtime);
if (!localtime_ptr) if (!localtime_ptr) {
throw cast_error("Unable to represent system_clock in local time"); throw cast_error("Unable to represent system_clock in local time");
}
return PyDateTime_FromDateAndTime(localtime.tm_year + 1900, return PyDateTime_FromDateAndTime(localtime.tm_year + 1900,
localtime.tm_mon + 1, localtime.tm_mon + 1,
localtime.tm_mday, localtime.tm_mday,
@ -199,13 +213,13 @@ public:
// Other clocks that are not the system clock are not measured as datetime.datetime objects // Other clocks that are not the system clock are not measured as datetime.datetime objects
// since they are not measured on calendar time. So instead we just make them timedeltas // since they are not measured on calendar time. So instead we just make them timedeltas
// Or if they have passed us a time as a float we convert that // Or if they have passed us a time as a float we convert that
template <typename Clock, typename Duration> class type_caster<std::chrono::time_point<Clock, Duration>> template <typename Clock, typename Duration>
: public duration_caster<std::chrono::time_point<Clock, Duration>> { class type_caster<std::chrono::time_point<Clock, Duration>>
}; : public duration_caster<std::chrono::time_point<Clock, Duration>> {};
template <typename Rep, typename Period> class type_caster<std::chrono::duration<Rep, Period>> template <typename Rep, typename Period>
: public duration_caster<std::chrono::duration<Rep, Period>> { class type_caster<std::chrono::duration<Rep, Period>>
}; : public duration_caster<std::chrono::duration<Rep, Period>> {};
PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@ -10,42 +10,50 @@
#pragma once #pragma once
#include "pybind11.h" #include "pybind11.h"
#include <complex> #include <complex>
/// glibc defines I as a macro which breaks things, e.g., boost template names /// glibc defines I as a macro which breaks things, e.g., boost template names
#ifdef I #ifdef I
# undef I # undef I
#endif #endif
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
template <typename T> struct format_descriptor<std::complex<T>, detail::enable_if_t<std::is_floating_point<T>::value>> { template <typename T>
struct format_descriptor<std::complex<T>, detail::enable_if_t<std::is_floating_point<T>::value>> {
static constexpr const char c = format_descriptor<T>::c; static constexpr const char c = format_descriptor<T>::c;
static constexpr const char value[3] = { 'Z', c, '\0' }; static constexpr const char value[3] = {'Z', c, '\0'};
static std::string format() { return std::string(value); } static std::string format() { return std::string(value); }
}; };
#ifndef PYBIND11_CPP17 #ifndef PYBIND11_CPP17
template <typename T> constexpr const char format_descriptor< template <typename T>
std::complex<T>, detail::enable_if_t<std::is_floating_point<T>::value>>::value[3]; constexpr const char
format_descriptor<std::complex<T>,
detail::enable_if_t<std::is_floating_point<T>::value>>::value[3];
#endif #endif
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
template <typename T> struct is_fmt_numeric<std::complex<T>, detail::enable_if_t<std::is_floating_point<T>::value>> { template <typename T>
struct is_fmt_numeric<std::complex<T>, detail::enable_if_t<std::is_floating_point<T>::value>> {
static constexpr bool value = true; static constexpr bool value = true;
static constexpr int index = is_fmt_numeric<T>::index + 3; static constexpr int index = is_fmt_numeric<T>::index + 3;
}; };
template <typename T> class type_caster<std::complex<T>> { template <typename T>
class type_caster<std::complex<T>> {
public: public:
bool load(handle src, bool convert) { bool load(handle src, bool convert) {
if (!src) if (!src) {
return false; return false;
if (!convert && !PyComplex_Check(src.ptr())) }
if (!convert && !PyComplex_Check(src.ptr())) {
return false; return false;
}
Py_complex result = PyComplex_AsCComplex(src.ptr()); Py_complex result = PyComplex_AsCComplex(src.ptr());
if (result.real == -1.0 && PyErr_Occurred()) { if (result.real == -1.0 && PyErr_Occurred()) {
PyErr_Clear(); PyErr_Clear();
@ -55,7 +63,8 @@ public:
return true; return true;
} }
static handle cast(const std::complex<T> &src, return_value_policy /* policy */, handle /* parent */) { static handle
cast(const std::complex<T> &src, return_value_policy /* policy */, handle /* parent */) {
return PyComplex_FromDoubles((double) src.real(), (double) src.imag()); return PyComplex_FromDoubles((double) src.real(), (double) src.imag());
} }

View File

@ -15,13 +15,14 @@
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
#if PY_VERSION_HEX >= 0x03030000 && !defined(PYPY_VERSION) #if !defined(PYPY_VERSION)
# define PYBIND11_BUILTIN_QUALNAME # define PYBIND11_BUILTIN_QUALNAME
# define PYBIND11_SET_OLDPY_QUALNAME(obj, nameobj) # define PYBIND11_SET_OLDPY_QUALNAME(obj, nameobj)
#else #else
// In pre-3.3 Python, we still set __qualname__ so that we can produce reliable function type // In PyPy, we still set __qualname__ so that we can produce reliable function type
// signatures; in 3.3+ this macro expands to nothing: // signatures; in CPython this macro expands to nothing:
# define PYBIND11_SET_OLDPY_QUALNAME(obj, nameobj) setattr((PyObject *) obj, "__qualname__", nameobj) # define PYBIND11_SET_OLDPY_QUALNAME(obj, nameobj) \
setattr((PyObject *) obj, "__qualname__", nameobj)
#endif #endif
inline std::string get_fully_qualified_tp_name(PyTypeObject *type) { inline std::string get_fully_qualified_tp_name(PyTypeObject *type) {
@ -65,24 +66,26 @@ inline PyTypeObject *make_static_property_type() {
issue no Python C API calls which could potentially invoke the issue no Python C API calls which could potentially invoke the
garbage collector (the GC will call type_traverse(), which will in garbage collector (the GC will call type_traverse(), which will in
turn find the newly constructed type in an invalid state) */ turn find the newly constructed type in an invalid state) */
auto heap_type = (PyHeapTypeObject *) PyType_Type.tp_alloc(&PyType_Type, 0); auto *heap_type = (PyHeapTypeObject *) PyType_Type.tp_alloc(&PyType_Type, 0);
if (!heap_type) if (!heap_type) {
pybind11_fail("make_static_property_type(): error allocating type!"); pybind11_fail("make_static_property_type(): error allocating type!");
}
heap_type->ht_name = name_obj.inc_ref().ptr(); heap_type->ht_name = name_obj.inc_ref().ptr();
#ifdef PYBIND11_BUILTIN_QUALNAME # ifdef PYBIND11_BUILTIN_QUALNAME
heap_type->ht_qualname = name_obj.inc_ref().ptr(); heap_type->ht_qualname = name_obj.inc_ref().ptr();
#endif # endif
auto type = &heap_type->ht_type; auto *type = &heap_type->ht_type;
type->tp_name = name; type->tp_name = name;
type->tp_base = type_incref(&PyProperty_Type); type->tp_base = type_incref(&PyProperty_Type);
type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE; type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE;
type->tp_descr_get = pybind11_static_get; type->tp_descr_get = pybind11_static_get;
type->tp_descr_set = pybind11_static_set; type->tp_descr_set = pybind11_static_set;
if (PyType_Ready(type) < 0) if (PyType_Ready(type) < 0) {
pybind11_fail("make_static_property_type(): failure in PyType_Ready()!"); pybind11_fail("make_static_property_type(): failure in PyType_Ready()!");
}
setattr((PyObject *) type, "__module__", str("pybind11_builtins")); setattr((PyObject *) type, "__module__", str("pybind11_builtins"));
PYBIND11_SET_OLDPY_QUALNAME(type, name_obj); PYBIND11_SET_OLDPY_QUALNAME(type, name_obj);
@ -98,15 +101,17 @@ inline PyTypeObject *make_static_property_type() {
inline PyTypeObject *make_static_property_type() { inline PyTypeObject *make_static_property_type() {
auto d = dict(); auto d = dict();
PyObject *result = PyRun_String(R"(\ PyObject *result = PyRun_String(R"(\
class pybind11_static_property(property): class pybind11_static_property(property):
def __get__(self, obj, cls): def __get__(self, obj, cls):
return property.__get__(self, cls, cls) return property.__get__(self, cls, cls)
def __set__(self, obj, value): def __set__(self, obj, value):
cls = obj if isinstance(obj, type) else type(obj) cls = obj if isinstance(obj, type) else type(obj)
property.__set__(self, cls, value) property.__set__(self, cls, value)
)", Py_file_input, d.ptr(), d.ptr() )",
); Py_file_input,
d.ptr(),
d.ptr());
if (result == nullptr) if (result == nullptr)
throw error_already_set(); throw error_already_set();
Py_DECREF(result); Py_DECREF(result);
@ -119,7 +124,7 @@ inline PyTypeObject *make_static_property_type() {
By default, Python replaces the `static_property` itself, but for wrapped C++ types By default, Python replaces the `static_property` itself, but for wrapped C++ types
we need to call `static_property.__set__()` in order to propagate the new value to we need to call `static_property.__set__()` in order to propagate the new value to
the underlying C++ data structure. */ the underlying C++ data structure. */
extern "C" inline int pybind11_meta_setattro(PyObject* obj, PyObject* name, PyObject* value) { extern "C" inline int pybind11_meta_setattro(PyObject *obj, PyObject *name, PyObject *value) {
// Use `_PyType_Lookup()` instead of `PyObject_GetAttr()` in order to get the raw // Use `_PyType_Lookup()` instead of `PyObject_GetAttr()` in order to get the raw
// descriptor (`property`) instead of calling `tp_descr_get` (`property.__get__()`). // descriptor (`property`) instead of calling `tp_descr_get` (`property.__get__()`).
PyObject *descr = _PyType_Lookup((PyTypeObject *) obj, name); PyObject *descr = _PyType_Lookup((PyTypeObject *) obj, name);
@ -128,7 +133,7 @@ extern "C" inline int pybind11_meta_setattro(PyObject* obj, PyObject* name, PyOb
// 1. `Type.static_prop = value` --> descr_set: `Type.static_prop.__set__(value)` // 1. `Type.static_prop = value` --> descr_set: `Type.static_prop.__set__(value)`
// 2. `Type.static_prop = other_static_prop` --> setattro: replace existing `static_prop` // 2. `Type.static_prop = other_static_prop` --> setattro: replace existing `static_prop`
// 3. `Type.regular_attribute = value` --> setattro: regular attribute assignment // 3. `Type.regular_attribute = value` --> setattro: regular attribute assignment
const auto static_prop = (PyObject *) get_internals().static_property_type; auto *const static_prop = (PyObject *) get_internals().static_property_type;
const auto call_descr_set = (descr != nullptr) && (value != nullptr) const auto call_descr_set = (descr != nullptr) && (value != nullptr)
&& (PyObject_IsInstance(descr, static_prop) != 0) && (PyObject_IsInstance(descr, static_prop) != 0)
&& (PyObject_IsInstance(value, static_prop) == 0); && (PyObject_IsInstance(value, static_prop) == 0);
@ -150,7 +155,6 @@ extern "C" inline int pybind11_meta_setattro(PyObject* obj, PyObject* name, PyOb
} }
} }
#if PY_MAJOR_VERSION >= 3
/** /**
* Python 3's PyInstanceMethod_Type hides itself via its tp_descr_get, which prevents aliasing * Python 3's PyInstanceMethod_Type hides itself via its tp_descr_get, which prevents aliasing
* methods via cls.attr("m2") = cls.attr("m1"): instead the tp_descr_get returns a plain function, * methods via cls.attr("m2") = cls.attr("m1"): instead the tp_descr_get returns a plain function,
@ -165,7 +169,6 @@ extern "C" inline PyObject *pybind11_meta_getattro(PyObject *obj, PyObject *name
} }
return PyType_Type.tp_getattro(obj, name); return PyType_Type.tp_getattro(obj, name);
} }
#endif
/// metaclass `__call__` function that is used to create all pybind11 objects. /// metaclass `__call__` function that is used to create all pybind11 objects.
extern "C" inline PyObject *pybind11_meta_call(PyObject *type, PyObject *args, PyObject *kwargs) { extern "C" inline PyObject *pybind11_meta_call(PyObject *type, PyObject *args, PyObject *kwargs) {
@ -177,12 +180,13 @@ extern "C" inline PyObject *pybind11_meta_call(PyObject *type, PyObject *args, P
} }
// This must be a pybind11 instance // This must be a pybind11 instance
auto instance = reinterpret_cast<detail::instance *>(self); auto *instance = reinterpret_cast<detail::instance *>(self);
// Ensure that the base __init__ function(s) were called // Ensure that the base __init__ function(s) were called
for (const auto &vh : values_and_holders(instance)) { for (const auto &vh : values_and_holders(instance)) {
if (!vh.holder_constructed()) { if (!vh.holder_constructed()) {
PyErr_Format(PyExc_TypeError, "%.200s.__init__() must be called when overriding __init__", PyErr_Format(PyExc_TypeError,
"%.200s.__init__() must be called when overriding __init__",
get_fully_qualified_tp_name(vh.type->type).c_str()); get_fully_qualified_tp_name(vh.type->type).c_str());
Py_DECREF(self); Py_DECREF(self);
return nullptr; return nullptr;
@ -201,27 +205,28 @@ extern "C" inline void pybind11_meta_dealloc(PyObject *obj) {
// 1) be found in internals.registered_types_py // 1) be found in internals.registered_types_py
// 2) have exactly one associated `detail::type_info` // 2) have exactly one associated `detail::type_info`
auto found_type = internals.registered_types_py.find(type); auto found_type = internals.registered_types_py.find(type);
if (found_type != internals.registered_types_py.end() && if (found_type != internals.registered_types_py.end() && found_type->second.size() == 1
found_type->second.size() == 1 && && found_type->second[0]->type == type) {
found_type->second[0]->type == type) {
auto *tinfo = found_type->second[0]; auto *tinfo = found_type->second[0];
auto tindex = std::type_index(*tinfo->cpptype); auto tindex = std::type_index(*tinfo->cpptype);
internals.direct_conversions.erase(tindex); internals.direct_conversions.erase(tindex);
if (tinfo->module_local) if (tinfo->module_local) {
get_local_internals().registered_types_cpp.erase(tindex); get_local_internals().registered_types_cpp.erase(tindex);
else } else {
internals.registered_types_cpp.erase(tindex); internals.registered_types_cpp.erase(tindex);
}
internals.registered_types_py.erase(tinfo->type); internals.registered_types_py.erase(tinfo->type);
// Actually just `std::erase_if`, but that's only available in C++20 // Actually just `std::erase_if`, but that's only available in C++20
auto &cache = internals.inactive_override_cache; auto &cache = internals.inactive_override_cache;
for (auto it = cache.begin(), last = cache.end(); it != last; ) { for (auto it = cache.begin(), last = cache.end(); it != last;) {
if (it->first == (PyObject *) tinfo->type) if (it->first == (PyObject *) tinfo->type) {
it = cache.erase(it); it = cache.erase(it);
else } else {
++it; ++it;
}
} }
delete tinfo; delete tinfo;
@ -233,7 +238,7 @@ extern "C" inline void pybind11_meta_dealloc(PyObject *obj) {
/** This metaclass is assigned by default to all pybind11 types and is required in order /** This metaclass is assigned by default to all pybind11 types and is required in order
for static properties to function correctly. Users may override this using `py::metaclass`. for static properties to function correctly. Users may override this using `py::metaclass`.
Return value: New reference. */ Return value: New reference. */
inline PyTypeObject* make_default_metaclass() { inline PyTypeObject *make_default_metaclass() {
constexpr auto *name = "pybind11_type"; constexpr auto *name = "pybind11_type";
auto name_obj = reinterpret_steal<object>(PYBIND11_FROM_STRING(name)); auto name_obj = reinterpret_steal<object>(PYBIND11_FROM_STRING(name));
@ -241,16 +246,17 @@ inline PyTypeObject* make_default_metaclass() {
issue no Python C API calls which could potentially invoke the issue no Python C API calls which could potentially invoke the
garbage collector (the GC will call type_traverse(), which will in garbage collector (the GC will call type_traverse(), which will in
turn find the newly constructed type in an invalid state) */ turn find the newly constructed type in an invalid state) */
auto heap_type = (PyHeapTypeObject *) PyType_Type.tp_alloc(&PyType_Type, 0); auto *heap_type = (PyHeapTypeObject *) PyType_Type.tp_alloc(&PyType_Type, 0);
if (!heap_type) if (!heap_type) {
pybind11_fail("make_default_metaclass(): error allocating metaclass!"); pybind11_fail("make_default_metaclass(): error allocating metaclass!");
}
heap_type->ht_name = name_obj.inc_ref().ptr(); heap_type->ht_name = name_obj.inc_ref().ptr();
#ifdef PYBIND11_BUILTIN_QUALNAME #ifdef PYBIND11_BUILTIN_QUALNAME
heap_type->ht_qualname = name_obj.inc_ref().ptr(); heap_type->ht_qualname = name_obj.inc_ref().ptr();
#endif #endif
auto type = &heap_type->ht_type; auto *type = &heap_type->ht_type;
type->tp_name = name; type->tp_name = name;
type->tp_base = type_incref(&PyType_Type); type->tp_base = type_incref(&PyType_Type);
type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE; type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE;
@ -258,14 +264,13 @@ inline PyTypeObject* make_default_metaclass() {
type->tp_call = pybind11_meta_call; type->tp_call = pybind11_meta_call;
type->tp_setattro = pybind11_meta_setattro; type->tp_setattro = pybind11_meta_setattro;
#if PY_MAJOR_VERSION >= 3
type->tp_getattro = pybind11_meta_getattro; type->tp_getattro = pybind11_meta_getattro;
#endif
type->tp_dealloc = pybind11_meta_dealloc; type->tp_dealloc = pybind11_meta_dealloc;
if (PyType_Ready(type) < 0) if (PyType_Ready(type) < 0) {
pybind11_fail("make_default_metaclass(): failure in PyType_Ready()!"); pybind11_fail("make_default_metaclass(): failure in PyType_Ready()!");
}
setattr((PyObject *) type, "__module__", str("pybind11_builtins")); setattr((PyObject *) type, "__module__", str("pybind11_builtins"));
PYBIND11_SET_OLDPY_QUALNAME(type, name_obj); PYBIND11_SET_OLDPY_QUALNAME(type, name_obj);
@ -275,16 +280,20 @@ inline PyTypeObject* make_default_metaclass() {
/// For multiple inheritance types we need to recursively register/deregister base pointers for any /// For multiple inheritance types we need to recursively register/deregister base pointers for any
/// base classes with pointers that are difference from the instance value pointer so that we can /// base classes with pointers that are difference from the instance value pointer so that we can
/// correctly recognize an offset base class pointer. This calls a function with any offset base ptrs. /// correctly recognize an offset base class pointer. This calls a function with any offset base
inline void traverse_offset_bases(void *valueptr, const detail::type_info *tinfo, instance *self, /// ptrs.
bool (*f)(void * /*parentptr*/, instance * /*self*/)) { inline void traverse_offset_bases(void *valueptr,
const detail::type_info *tinfo,
instance *self,
bool (*f)(void * /*parentptr*/, instance * /*self*/)) {
for (handle h : reinterpret_borrow<tuple>(tinfo->type->tp_bases)) { for (handle h : reinterpret_borrow<tuple>(tinfo->type->tp_bases)) {
if (auto parent_tinfo = get_type_info((PyTypeObject *) h.ptr())) { if (auto *parent_tinfo = get_type_info((PyTypeObject *) h.ptr())) {
for (auto &c : parent_tinfo->implicit_casts) { for (auto &c : parent_tinfo->implicit_casts) {
if (c.first == tinfo->cpptype) { if (c.first == tinfo->cpptype) {
auto *parentptr = c.second(valueptr); auto *parentptr = c.second(valueptr);
if (parentptr != valueptr) if (parentptr != valueptr) {
f(parentptr, self); f(parentptr, self);
}
traverse_offset_bases(parentptr, parent_tinfo, self, f); traverse_offset_bases(parentptr, parent_tinfo, self, f);
break; break;
} }
@ -311,31 +320,33 @@ inline bool deregister_instance_impl(void *ptr, instance *self) {
inline void register_instance(instance *self, void *valptr, const type_info *tinfo) { inline void register_instance(instance *self, void *valptr, const type_info *tinfo) {
register_instance_impl(valptr, self); register_instance_impl(valptr, self);
if (!tinfo->simple_ancestors) if (!tinfo->simple_ancestors) {
traverse_offset_bases(valptr, tinfo, self, register_instance_impl); traverse_offset_bases(valptr, tinfo, self, register_instance_impl);
}
} }
inline bool deregister_instance(instance *self, void *valptr, const type_info *tinfo) { inline bool deregister_instance(instance *self, void *valptr, const type_info *tinfo) {
bool ret = deregister_instance_impl(valptr, self); bool ret = deregister_instance_impl(valptr, self);
if (!tinfo->simple_ancestors) if (!tinfo->simple_ancestors) {
traverse_offset_bases(valptr, tinfo, self, deregister_instance_impl); traverse_offset_bases(valptr, tinfo, self, deregister_instance_impl);
}
return ret; return ret;
} }
/// Instance creation function for all pybind11 types. It allocates the internal instance layout for /// Instance creation function for all pybind11 types. It allocates the internal instance layout
/// holding C++ objects and holders. Allocation is done lazily (the first time the instance is cast /// for holding C++ objects and holders. Allocation is done lazily (the first time the instance is
/// to a reference or pointer), and initialization is done by an `__init__` function. /// cast to a reference or pointer), and initialization is done by an `__init__` function.
inline PyObject *make_new_instance(PyTypeObject *type) { inline PyObject *make_new_instance(PyTypeObject *type) {
#if defined(PYPY_VERSION) #if defined(PYPY_VERSION)
// PyPy gets tp_basicsize wrong (issue 2482) under multiple inheritance when the first inherited // PyPy gets tp_basicsize wrong (issue 2482) under multiple inheritance when the first
// object is a plain Python type (i.e. not derived from an extension type). Fix it. // inherited object is a plain Python type (i.e. not derived from an extension type). Fix it.
ssize_t instance_size = static_cast<ssize_t>(sizeof(instance)); ssize_t instance_size = static_cast<ssize_t>(sizeof(instance));
if (type->tp_basicsize < instance_size) { if (type->tp_basicsize < instance_size) {
type->tp_basicsize = instance_size; type->tp_basicsize = instance_size;
} }
#endif #endif
PyObject *self = type->tp_alloc(type, 0); PyObject *self = type->tp_alloc(type, 0);
auto inst = reinterpret_cast<instance *>(self); auto *inst = reinterpret_cast<instance *>(self);
// Allocate the value/holder internals: // Allocate the value/holder internals:
inst->allocate_layout(); inst->allocate_layout();
@ -360,14 +371,14 @@ extern "C" inline int pybind11_object_init(PyObject *self, PyObject *, PyObject
inline void add_patient(PyObject *nurse, PyObject *patient) { inline void add_patient(PyObject *nurse, PyObject *patient) {
auto &internals = get_internals(); auto &internals = get_internals();
auto instance = reinterpret_cast<detail::instance *>(nurse); auto *instance = reinterpret_cast<detail::instance *>(nurse);
instance->has_patients = true; instance->has_patients = true;
Py_INCREF(patient); Py_INCREF(patient);
internals.patients[nurse].push_back(patient); internals.patients[nurse].push_back(patient);
} }
inline void clear_patients(PyObject *self) { inline void clear_patients(PyObject *self) {
auto instance = reinterpret_cast<detail::instance *>(self); auto *instance = reinterpret_cast<detail::instance *>(self);
auto &internals = get_internals(); auto &internals = get_internals();
auto pos = internals.patients.find(self); auto pos = internals.patients.find(self);
assert(pos != internals.patients.end()); assert(pos != internals.patients.end());
@ -377,14 +388,15 @@ inline void clear_patients(PyObject *self) {
auto patients = std::move(pos->second); auto patients = std::move(pos->second);
internals.patients.erase(pos); internals.patients.erase(pos);
instance->has_patients = false; instance->has_patients = false;
for (PyObject *&patient : patients) for (PyObject *&patient : patients) {
Py_CLEAR(patient); Py_CLEAR(patient);
}
} }
/// Clears all internal data from the instance and removes it from registered instances in /// Clears all internal data from the instance and removes it from registered instances in
/// preparation for deallocation. /// preparation for deallocation.
inline void clear_instance(PyObject *self) { inline void clear_instance(PyObject *self) {
auto instance = reinterpret_cast<detail::instance *>(self); auto *instance = reinterpret_cast<detail::instance *>(self);
// Deallocate any values/holders, if present: // Deallocate any values/holders, if present:
for (auto &v_h : values_and_holders(instance)) { for (auto &v_h : values_and_holders(instance)) {
@ -392,25 +404,32 @@ inline void clear_instance(PyObject *self) {
// We have to deregister before we call dealloc because, for virtual MI types, we still // We have to deregister before we call dealloc because, for virtual MI types, we still
// need to be able to get the parent pointers. // need to be able to get the parent pointers.
if (v_h.instance_registered() && !deregister_instance(instance, v_h.value_ptr(), v_h.type)) if (v_h.instance_registered()
pybind11_fail("pybind11_object_dealloc(): Tried to deallocate unregistered instance!"); && !deregister_instance(instance, v_h.value_ptr(), v_h.type)) {
pybind11_fail(
"pybind11_object_dealloc(): Tried to deallocate unregistered instance!");
}
if (instance->owned || v_h.holder_constructed()) if (instance->owned || v_h.holder_constructed()) {
v_h.type->dealloc(v_h); v_h.type->dealloc(v_h);
}
} }
} }
// Deallocate the value/holder layout internals: // Deallocate the value/holder layout internals:
instance->deallocate_layout(); instance->deallocate_layout();
if (instance->weakrefs) if (instance->weakrefs) {
PyObject_ClearWeakRefs(self); PyObject_ClearWeakRefs(self);
}
PyObject **dict_ptr = _PyObject_GetDictPtr(self); PyObject **dict_ptr = _PyObject_GetDictPtr(self);
if (dict_ptr) if (dict_ptr) {
Py_CLEAR(*dict_ptr); Py_CLEAR(*dict_ptr);
}
if (instance->has_patients) if (instance->has_patients) {
clear_patients(self); clear_patients(self);
}
} }
/// Instance destructor function for all pybind11 types. It calls `type_info.dealloc` /// Instance destructor function for all pybind11 types. It calls `type_info.dealloc`
@ -418,7 +437,7 @@ inline void clear_instance(PyObject *self) {
extern "C" inline void pybind11_object_dealloc(PyObject *self) { extern "C" inline void pybind11_object_dealloc(PyObject *self) {
clear_instance(self); clear_instance(self);
auto type = Py_TYPE(self); auto *type = Py_TYPE(self);
type->tp_free(self); type->tp_free(self);
#if PY_VERSION_HEX < 0x03080000 #if PY_VERSION_HEX < 0x03080000
@ -436,6 +455,8 @@ extern "C" inline void pybind11_object_dealloc(PyObject *self) {
#endif #endif
} }
std::string error_string();
/** Create the type which can be used as a common base for all classes. This is /** Create the type which can be used as a common base for all classes. This is
needed in order to satisfy Python's requirements for multiple inheritance. needed in order to satisfy Python's requirements for multiple inheritance.
Return value: New reference. */ Return value: New reference. */
@ -447,16 +468,17 @@ inline PyObject *make_object_base_type(PyTypeObject *metaclass) {
issue no Python C API calls which could potentially invoke the issue no Python C API calls which could potentially invoke the
garbage collector (the GC will call type_traverse(), which will in garbage collector (the GC will call type_traverse(), which will in
turn find the newly constructed type in an invalid state) */ turn find the newly constructed type in an invalid state) */
auto heap_type = (PyHeapTypeObject *) metaclass->tp_alloc(metaclass, 0); auto *heap_type = (PyHeapTypeObject *) metaclass->tp_alloc(metaclass, 0);
if (!heap_type) if (!heap_type) {
pybind11_fail("make_object_base_type(): error allocating type!"); pybind11_fail("make_object_base_type(): error allocating type!");
}
heap_type->ht_name = name_obj.inc_ref().ptr(); heap_type->ht_name = name_obj.inc_ref().ptr();
#ifdef PYBIND11_BUILTIN_QUALNAME #ifdef PYBIND11_BUILTIN_QUALNAME
heap_type->ht_qualname = name_obj.inc_ref().ptr(); heap_type->ht_qualname = name_obj.inc_ref().ptr();
#endif #endif
auto type = &heap_type->ht_type; auto *type = &heap_type->ht_type;
type->tp_name = name; type->tp_name = name;
type->tp_base = type_incref(&PyBaseObject_Type); type->tp_base = type_incref(&PyBaseObject_Type);
type->tp_basicsize = static_cast<ssize_t>(sizeof(instance)); type->tp_basicsize = static_cast<ssize_t>(sizeof(instance));
@ -469,8 +491,9 @@ inline PyObject *make_object_base_type(PyTypeObject *metaclass) {
/* Support weak references (needed for the keep_alive feature) */ /* Support weak references (needed for the keep_alive feature) */
type->tp_weaklistoffset = offsetof(instance, weakrefs); type->tp_weaklistoffset = offsetof(instance, weakrefs);
if (PyType_Ready(type) < 0) if (PyType_Ready(type) < 0) {
pybind11_fail("PyType_Ready failed in make_object_base_type():" + error_string()); pybind11_fail("PyType_Ready failed in make_object_base_type(): " + error_string());
}
setattr((PyObject *) type, "__module__", str("pybind11_builtins")); setattr((PyObject *) type, "__module__", str("pybind11_builtins"));
PYBIND11_SET_OLDPY_QUALNAME(type, name_obj); PYBIND11_SET_OLDPY_QUALNAME(type, name_obj);
@ -482,8 +505,9 @@ inline PyObject *make_object_base_type(PyTypeObject *metaclass) {
/// dynamic_attr: Support for `d = instance.__dict__`. /// dynamic_attr: Support for `d = instance.__dict__`.
extern "C" inline PyObject *pybind11_get_dict(PyObject *self, void *) { extern "C" inline PyObject *pybind11_get_dict(PyObject *self, void *) {
PyObject *&dict = *_PyObject_GetDictPtr(self); PyObject *&dict = *_PyObject_GetDictPtr(self);
if (!dict) if (!dict) {
dict = PyDict_New(); dict = PyDict_New();
}
Py_XINCREF(dict); Py_XINCREF(dict);
return dict; return dict;
} }
@ -491,7 +515,8 @@ extern "C" inline PyObject *pybind11_get_dict(PyObject *self, void *) {
/// dynamic_attr: Support for `instance.__dict__ = dict()`. /// dynamic_attr: Support for `instance.__dict__ = dict()`.
extern "C" inline int pybind11_set_dict(PyObject *self, PyObject *new_dict, void *) { extern "C" inline int pybind11_set_dict(PyObject *self, PyObject *new_dict, void *) {
if (!PyDict_Check(new_dict)) { if (!PyDict_Check(new_dict)) {
PyErr_Format(PyExc_TypeError, "__dict__ must be set to a dictionary, not a '%.200s'", PyErr_Format(PyExc_TypeError,
"__dict__ must be set to a dictionary, not a '%.200s'",
get_fully_qualified_tp_name(Py_TYPE(new_dict)).c_str()); get_fully_qualified_tp_name(Py_TYPE(new_dict)).c_str());
return -1; return -1;
} }
@ -506,6 +531,10 @@ extern "C" inline int pybind11_set_dict(PyObject *self, PyObject *new_dict, void
extern "C" inline int pybind11_traverse(PyObject *self, visitproc visit, void *arg) { extern "C" inline int pybind11_traverse(PyObject *self, visitproc visit, void *arg) {
PyObject *&dict = *_PyObject_GetDictPtr(self); PyObject *&dict = *_PyObject_GetDictPtr(self);
Py_VISIT(dict); Py_VISIT(dict);
// https://docs.python.org/3/c-api/typeobj.html#c.PyTypeObject.tp_traverse
#if PY_VERSION_HEX >= 0x03090000
Py_VISIT(Py_TYPE(self));
#endif
return 0; return 0;
} }
@ -518,17 +547,20 @@ extern "C" inline int pybind11_clear(PyObject *self) {
/// Give instances of this type a `__dict__` and opt into garbage collection. /// Give instances of this type a `__dict__` and opt into garbage collection.
inline void enable_dynamic_attributes(PyHeapTypeObject *heap_type) { inline void enable_dynamic_attributes(PyHeapTypeObject *heap_type) {
auto type = &heap_type->ht_type; auto *type = &heap_type->ht_type;
type->tp_flags |= Py_TPFLAGS_HAVE_GC; type->tp_flags |= Py_TPFLAGS_HAVE_GC;
type->tp_dictoffset = type->tp_basicsize; // place dict at the end #if PY_VERSION_HEX < 0x030B0000
type->tp_basicsize += (ssize_t)sizeof(PyObject *); // and allocate enough space for it type->tp_dictoffset = type->tp_basicsize; // place dict at the end
type->tp_basicsize += (ssize_t) sizeof(PyObject *); // and allocate enough space for it
#else
type->tp_flags |= Py_TPFLAGS_MANAGED_DICT;
#endif
type->tp_traverse = pybind11_traverse; type->tp_traverse = pybind11_traverse;
type->tp_clear = pybind11_clear; type->tp_clear = pybind11_clear;
static PyGetSetDef getset[] = { static PyGetSetDef getset[] = {
{const_cast<char*>("__dict__"), pybind11_get_dict, pybind11_set_dict, nullptr, nullptr}, {const_cast<char *>("__dict__"), pybind11_get_dict, pybind11_set_dict, nullptr, nullptr},
{nullptr, nullptr, nullptr, nullptr, nullptr} {nullptr, nullptr, nullptr, nullptr, nullptr}};
};
type->tp_getset = getset; type->tp_getset = getset;
} }
@ -538,12 +570,14 @@ extern "C" inline int pybind11_getbuffer(PyObject *obj, Py_buffer *view, int fla
type_info *tinfo = nullptr; type_info *tinfo = nullptr;
for (auto type : reinterpret_borrow<tuple>(Py_TYPE(obj)->tp_mro)) { for (auto type : reinterpret_borrow<tuple>(Py_TYPE(obj)->tp_mro)) {
tinfo = get_type_info((PyTypeObject *) type.ptr()); tinfo = get_type_info((PyTypeObject *) type.ptr());
if (tinfo && tinfo->get_buffer) if (tinfo && tinfo->get_buffer) {
break; break;
}
} }
if (view == nullptr || !tinfo || !tinfo->get_buffer) { if (view == nullptr || !tinfo || !tinfo->get_buffer) {
if (view) if (view) {
view->obj = nullptr; view->obj = nullptr;
}
PyErr_SetString(PyExc_BufferError, "pybind11_getbuffer(): Internal error"); PyErr_SetString(PyExc_BufferError, "pybind11_getbuffer(): Internal error");
return -1; return -1;
} }
@ -561,15 +595,17 @@ extern "C" inline int pybind11_getbuffer(PyObject *obj, Py_buffer *view, int fla
view->buf = info->ptr; view->buf = info->ptr;
view->itemsize = info->itemsize; view->itemsize = info->itemsize;
view->len = view->itemsize; view->len = view->itemsize;
for (auto s : info->shape) for (auto s : info->shape) {
view->len *= s; view->len *= s;
}
view->readonly = static_cast<int>(info->readonly); view->readonly = static_cast<int>(info->readonly);
if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT) if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT) {
view->format = const_cast<char *>(info->format.c_str()); view->format = const_cast<char *>(info->format.c_str());
}
if ((flags & PyBUF_STRIDES) == PyBUF_STRIDES) { if ((flags & PyBUF_STRIDES) == PyBUF_STRIDES) {
view->ndim = (int) info->ndim; view->ndim = (int) info->ndim;
view->strides = &info->strides[0]; view->strides = info->strides.data();
view->shape = &info->shape[0]; view->shape = info->shape.data();
} }
Py_INCREF(view->obj); Py_INCREF(view->obj);
return 0; return 0;
@ -583,9 +619,6 @@ extern "C" inline void pybind11_releasebuffer(PyObject *, Py_buffer *view) {
/// Give this type a buffer interface. /// Give this type a buffer interface.
inline void enable_buffer_protocol(PyHeapTypeObject *heap_type) { inline void enable_buffer_protocol(PyHeapTypeObject *heap_type) {
heap_type->ht_type.tp_as_buffer = &heap_type->as_buffer; heap_type->ht_type.tp_as_buffer = &heap_type->as_buffer;
#if PY_MAJOR_VERSION < 3
heap_type->ht_type.tp_flags |= Py_TPFLAGS_HAVE_NEWBUFFER;
#endif
heap_type->as_buffer.bf_getbuffer = pybind11_getbuffer; heap_type->as_buffer.bf_getbuffer = pybind11_getbuffer;
heap_type->as_buffer.bf_releasebuffer = pybind11_releasebuffer; heap_type->as_buffer.bf_releasebuffer = pybind11_releasebuffer;
@ -593,32 +626,29 @@ inline void enable_buffer_protocol(PyHeapTypeObject *heap_type) {
/** Create a brand new Python type according to the `type_record` specification. /** Create a brand new Python type according to the `type_record` specification.
Return value: New reference. */ Return value: New reference. */
inline PyObject* make_new_python_type(const type_record &rec) { inline PyObject *make_new_python_type(const type_record &rec) {
auto name = reinterpret_steal<object>(PYBIND11_FROM_STRING(rec.name)); auto name = reinterpret_steal<object>(PYBIND11_FROM_STRING(rec.name));
auto qualname = name; auto qualname = name;
if (rec.scope && !PyModule_Check(rec.scope.ptr()) && hasattr(rec.scope, "__qualname__")) { if (rec.scope && !PyModule_Check(rec.scope.ptr()) && hasattr(rec.scope, "__qualname__")) {
#if PY_MAJOR_VERSION >= 3
qualname = reinterpret_steal<object>( qualname = reinterpret_steal<object>(
PyUnicode_FromFormat("%U.%U", rec.scope.attr("__qualname__").ptr(), name.ptr())); PyUnicode_FromFormat("%U.%U", rec.scope.attr("__qualname__").ptr(), name.ptr()));
#else
qualname = str(rec.scope.attr("__qualname__").cast<std::string>() + "." + rec.name);
#endif
} }
object module_; object module_;
if (rec.scope) { if (rec.scope) {
if (hasattr(rec.scope, "__module__")) if (hasattr(rec.scope, "__module__")) {
module_ = rec.scope.attr("__module__"); module_ = rec.scope.attr("__module__");
else if (hasattr(rec.scope, "__name__")) } else if (hasattr(rec.scope, "__name__")) {
module_ = rec.scope.attr("__name__"); module_ = rec.scope.attr("__name__");
}
} }
auto full_name = c_str( const auto *full_name = c_str(
#if !defined(PYPY_VERSION) #if !defined(PYPY_VERSION)
module_ ? str(module_).cast<std::string>() + "." + rec.name : module_ ? str(module_).cast<std::string>() + "." + rec.name :
#endif #endif
rec.name); rec.name);
char *tp_doc = nullptr; char *tp_doc = nullptr;
if (rec.doc && options::show_user_defined_docstrings()) { if (rec.doc && options::show_user_defined_docstrings()) {
@ -631,32 +661,33 @@ inline PyObject* make_new_python_type(const type_record &rec) {
auto &internals = get_internals(); auto &internals = get_internals();
auto bases = tuple(rec.bases); auto bases = tuple(rec.bases);
auto base = (bases.empty()) ? internals.instance_base auto *base = (bases.empty()) ? internals.instance_base : bases[0].ptr();
: bases[0].ptr();
/* Danger zone: from now (and until PyType_Ready), make sure to /* Danger zone: from now (and until PyType_Ready), make sure to
issue no Python C API calls which could potentially invoke the issue no Python C API calls which could potentially invoke the
garbage collector (the GC will call type_traverse(), which will in garbage collector (the GC will call type_traverse(), which will in
turn find the newly constructed type in an invalid state) */ turn find the newly constructed type in an invalid state) */
auto metaclass = rec.metaclass.ptr() ? (PyTypeObject *) rec.metaclass.ptr() auto *metaclass
: internals.default_metaclass; = rec.metaclass.ptr() ? (PyTypeObject *) rec.metaclass.ptr() : internals.default_metaclass;
auto heap_type = (PyHeapTypeObject *) metaclass->tp_alloc(metaclass, 0); auto *heap_type = (PyHeapTypeObject *) metaclass->tp_alloc(metaclass, 0);
if (!heap_type) if (!heap_type) {
pybind11_fail(std::string(rec.name) + ": Unable to create type object!"); pybind11_fail(std::string(rec.name) + ": Unable to create type object!");
}
heap_type->ht_name = name.release().ptr(); heap_type->ht_name = name.release().ptr();
#ifdef PYBIND11_BUILTIN_QUALNAME #ifdef PYBIND11_BUILTIN_QUALNAME
heap_type->ht_qualname = qualname.inc_ref().ptr(); heap_type->ht_qualname = qualname.inc_ref().ptr();
#endif #endif
auto type = &heap_type->ht_type; auto *type = &heap_type->ht_type;
type->tp_name = full_name; type->tp_name = full_name;
type->tp_doc = tp_doc; type->tp_doc = tp_doc;
type->tp_base = type_incref((PyTypeObject *)base); type->tp_base = type_incref((PyTypeObject *) base);
type->tp_basicsize = static_cast<ssize_t>(sizeof(instance)); type->tp_basicsize = static_cast<ssize_t>(sizeof(instance));
if (!bases.empty()) if (!bases.empty()) {
type->tp_bases = bases.release().ptr(); type->tp_bases = bases.release().ptr();
}
/* Don't inherit base __init__ */ /* Don't inherit base __init__ */
type->tp_init = pybind11_object_init; type->tp_init = pybind11_object_init;
@ -665,40 +696,42 @@ inline PyObject* make_new_python_type(const type_record &rec) {
type->tp_as_number = &heap_type->as_number; type->tp_as_number = &heap_type->as_number;
type->tp_as_sequence = &heap_type->as_sequence; type->tp_as_sequence = &heap_type->as_sequence;
type->tp_as_mapping = &heap_type->as_mapping; type->tp_as_mapping = &heap_type->as_mapping;
#if PY_VERSION_HEX >= 0x03050000
type->tp_as_async = &heap_type->as_async; type->tp_as_async = &heap_type->as_async;
#endif
/* Flags */ /* Flags */
type->tp_flags |= Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE; type->tp_flags |= Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE;
#if PY_MAJOR_VERSION < 3 if (!rec.is_final) {
type->tp_flags |= Py_TPFLAGS_CHECKTYPES;
#endif
if (!rec.is_final)
type->tp_flags |= Py_TPFLAGS_BASETYPE; type->tp_flags |= Py_TPFLAGS_BASETYPE;
}
if (rec.dynamic_attr) if (rec.dynamic_attr) {
enable_dynamic_attributes(heap_type); enable_dynamic_attributes(heap_type);
}
if (rec.buffer_protocol) if (rec.buffer_protocol) {
enable_buffer_protocol(heap_type); enable_buffer_protocol(heap_type);
}
if (rec.custom_type_setup_callback) if (rec.custom_type_setup_callback) {
rec.custom_type_setup_callback(heap_type); rec.custom_type_setup_callback(heap_type);
}
if (PyType_Ready(type) < 0) if (PyType_Ready(type) < 0) {
pybind11_fail(std::string(rec.name) + ": PyType_Ready failed (" + error_string() + ")!"); pybind11_fail(std::string(rec.name) + ": PyType_Ready failed: " + error_string());
}
assert(!rec.dynamic_attr || PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC)); assert(!rec.dynamic_attr || PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC));
/* Register type with the parent scope */ /* Register type with the parent scope */
if (rec.scope) if (rec.scope) {
setattr(rec.scope, rec.name, (PyObject *) type); setattr(rec.scope, rec.name, (PyObject *) type);
else } else {
Py_INCREF(type); // Keep it alive forever (reference leak) Py_INCREF(type); // Keep it alive forever (reference leak)
}
if (module_) // Needed by pydoc if (module_) { // Needed by pydoc
setattr((PyObject *) type, "__module__", module_); setattr((PyObject *) type, "__module__", module_);
}
PYBIND11_SET_OLDPY_QUALNAME(type, qualname); PYBIND11_SET_OLDPY_QUALNAME(type, qualname);

File diff suppressed because it is too large Load Diff

View File

@ -15,9 +15,9 @@ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
#if !defined(_MSC_VER) #if !defined(_MSC_VER)
# define PYBIND11_DESCR_CONSTEXPR static constexpr # define PYBIND11_DESCR_CONSTEXPR static constexpr
#else #else
# define PYBIND11_DESCR_CONSTEXPR const # define PYBIND11_DESCR_CONSTEXPR const
#endif #endif
/* Concatenate type signatures at compile time */ /* Concatenate type signatures at compile time */
@ -27,14 +27,14 @@ struct descr {
constexpr descr() = default; constexpr descr() = default;
// NOLINTNEXTLINE(google-explicit-constructor) // NOLINTNEXTLINE(google-explicit-constructor)
constexpr descr(char const (&s)[N+1]) : descr(s, make_index_sequence<N>()) { } constexpr descr(char const (&s)[N + 1]) : descr(s, make_index_sequence<N>()) {}
template <size_t... Is> template <size_t... Is>
constexpr descr(char const (&s)[N+1], index_sequence<Is...>) : text{s[Is]..., '\0'} { } constexpr descr(char const (&s)[N + 1], index_sequence<Is...>) : text{s[Is]..., '\0'} {}
template <typename... Chars> template <typename... Chars>
// NOLINTNEXTLINE(google-explicit-constructor) // NOLINTNEXTLINE(google-explicit-constructor)
constexpr descr(char c, Chars... cs) : text{c, static_cast<char>(cs)..., '\0'} { } constexpr descr(char c, Chars... cs) : text{c, static_cast<char>(cs)..., '\0'} {}
static constexpr std::array<const std::type_info *, sizeof...(Ts) + 1> types() { static constexpr std::array<const std::type_info *, sizeof...(Ts) + 1> types() {
return {{&typeid(Ts)..., nullptr}}; return {{&typeid(Ts)..., nullptr}};
@ -42,81 +42,106 @@ struct descr {
}; };
template <size_t N1, size_t N2, typename... Ts1, typename... Ts2, size_t... Is1, size_t... Is2> template <size_t N1, size_t N2, typename... Ts1, typename... Ts2, size_t... Is1, size_t... Is2>
constexpr descr<N1 + N2, Ts1..., Ts2...> plus_impl(const descr<N1, Ts1...> &a, const descr<N2, Ts2...> &b, constexpr descr<N1 + N2, Ts1..., Ts2...> plus_impl(const descr<N1, Ts1...> &a,
index_sequence<Is1...>, index_sequence<Is2...>) { const descr<N2, Ts2...> &b,
index_sequence<Is1...>,
index_sequence<Is2...>) {
PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(b); PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(b);
return {a.text[Is1]..., b.text[Is2]...}; return {a.text[Is1]..., b.text[Is2]...};
} }
template <size_t N1, size_t N2, typename... Ts1, typename... Ts2> template <size_t N1, size_t N2, typename... Ts1, typename... Ts2>
constexpr descr<N1 + N2, Ts1..., Ts2...> operator+(const descr<N1, Ts1...> &a, const descr<N2, Ts2...> &b) { constexpr descr<N1 + N2, Ts1..., Ts2...> operator+(const descr<N1, Ts1...> &a,
const descr<N2, Ts2...> &b) {
return plus_impl(a, b, make_index_sequence<N1>(), make_index_sequence<N2>()); return plus_impl(a, b, make_index_sequence<N1>(), make_index_sequence<N2>());
} }
template <size_t N> template <size_t N>
constexpr descr<N - 1> const_name(char const(&text)[N]) { return descr<N - 1>(text); } constexpr descr<N - 1> const_name(char const (&text)[N]) {
constexpr descr<0> const_name(char const(&)[1]) { return {}; } return descr<N - 1>(text);
}
constexpr descr<0> const_name(char const (&)[1]) { return {}; }
template <size_t Rem, size_t... Digits> struct int_to_str : int_to_str<Rem/10, Rem%10, Digits...> { }; template <size_t Rem, size_t... Digits>
template <size_t...Digits> struct int_to_str<0, Digits...> { struct int_to_str : int_to_str<Rem / 10, Rem % 10, Digits...> {};
template <size_t... Digits>
struct int_to_str<0, Digits...> {
// WARNING: This only works with C++17 or higher. // WARNING: This only works with C++17 or higher.
static constexpr auto digits = descr<sizeof...(Digits)>(('0' + Digits)...); static constexpr auto digits = descr<sizeof...(Digits)>(('0' + Digits)...);
}; };
// Ternary description (like std::conditional) // Ternary description (like std::conditional)
template <bool B, size_t N1, size_t N2> template <bool B, size_t N1, size_t N2>
constexpr enable_if_t<B, descr<N1 - 1>> const_name(char const(&text1)[N1], char const(&)[N2]) { constexpr enable_if_t<B, descr<N1 - 1>> const_name(char const (&text1)[N1], char const (&)[N2]) {
return const_name(text1); return const_name(text1);
} }
template <bool B, size_t N1, size_t N2> template <bool B, size_t N1, size_t N2>
constexpr enable_if_t<!B, descr<N2 - 1>> const_name(char const(&)[N1], char const(&text2)[N2]) { constexpr enable_if_t<!B, descr<N2 - 1>> const_name(char const (&)[N1], char const (&text2)[N2]) {
return const_name(text2); return const_name(text2);
} }
template <bool B, typename T1, typename T2> template <bool B, typename T1, typename T2>
constexpr enable_if_t<B, T1> const_name(const T1 &d, const T2 &) { return d; } constexpr enable_if_t<B, T1> const_name(const T1 &d, const T2 &) {
return d;
}
template <bool B, typename T1, typename T2> template <bool B, typename T1, typename T2>
constexpr enable_if_t<!B, T2> const_name(const T1 &, const T2 &d) { return d; } constexpr enable_if_t<!B, T2> const_name(const T1 &, const T2 &d) {
return d;
}
template <size_t Size> template <size_t Size>
auto constexpr const_name() -> remove_cv_t<decltype(int_to_str<Size / 10, Size % 10>::digits)> { auto constexpr const_name() -> remove_cv_t<decltype(int_to_str<Size / 10, Size % 10>::digits)> {
return int_to_str<Size / 10, Size % 10>::digits; return int_to_str<Size / 10, Size % 10>::digits;
} }
template <typename Type> constexpr descr<1, Type> const_name() { return {'%'}; } template <typename Type>
constexpr descr<1, Type> const_name() {
return {'%'};
}
// If "_" is defined as a macro, py::detail::_ cannot be provided. // If "_" is defined as a macro, py::detail::_ cannot be provided.
// It is therefore best to use py::detail::const_name universally. // It is therefore best to use py::detail::const_name universally.
// This block is for backward compatibility only. // This block is for backward compatibility only.
// (The const_name code is repeated to avoid introducing a "_" #define ourselves.) // (The const_name code is repeated to avoid introducing a "_" #define ourselves.)
#ifndef _ #ifndef _
#define PYBIND11_DETAIL_UNDERSCORE_BACKWARD_COMPATIBILITY # define PYBIND11_DETAIL_UNDERSCORE_BACKWARD_COMPATIBILITY
template <size_t N> template <size_t N>
constexpr descr<N-1> _(char const(&text)[N]) { return const_name<N>(text); } constexpr descr<N - 1> _(char const (&text)[N]) {
template <bool B, size_t N1, size_t N2> return const_name<N>(text);
constexpr enable_if_t<B, descr<N1 - 1>> _(char const(&text1)[N1], char const(&text2)[N2]) {
return const_name<B,N1,N2>(text1, text2);
} }
template <bool B, size_t N1, size_t N2> template <bool B, size_t N1, size_t N2>
constexpr enable_if_t<!B, descr<N2 - 1>> _(char const(&text1)[N1], char const(&text2)[N2]) { constexpr enable_if_t<B, descr<N1 - 1>> _(char const (&text1)[N1], char const (&text2)[N2]) {
return const_name<B,N1,N2>(text1, text2); return const_name<B, N1, N2>(text1, text2);
}
template <bool B, size_t N1, size_t N2>
constexpr enable_if_t<!B, descr<N2 - 1>> _(char const (&text1)[N1], char const (&text2)[N2]) {
return const_name<B, N1, N2>(text1, text2);
} }
template <bool B, typename T1, typename T2> template <bool B, typename T1, typename T2>
constexpr enable_if_t<B, T1> _(const T1 &d1, const T2 &d2) { return const_name<B,T1,T2>(d1, d2); } constexpr enable_if_t<B, T1> _(const T1 &d1, const T2 &d2) {
return const_name<B, T1, T2>(d1, d2);
}
template <bool B, typename T1, typename T2> template <bool B, typename T1, typename T2>
constexpr enable_if_t<!B, T2> _(const T1 &d1, const T2 &d2) { return const_name<B,T1,T2>(d1, d2); } constexpr enable_if_t<!B, T2> _(const T1 &d1, const T2 &d2) {
return const_name<B, T1, T2>(d1, d2);
}
template <size_t Size> template <size_t Size>
auto constexpr _() -> remove_cv_t<decltype(int_to_str<Size / 10, Size % 10>::digits)> { auto constexpr _() -> remove_cv_t<decltype(int_to_str<Size / 10, Size % 10>::digits)> {
return const_name<Size>(); return const_name<Size>();
} }
template <typename Type> constexpr descr<1, Type> _() { return const_name<Type>(); } template <typename Type>
#endif // #ifndef _ constexpr descr<1, Type> _() {
return const_name<Type>();
}
#endif // #ifndef _
constexpr descr<0> concat() { return {}; } constexpr descr<0> concat() { return {}; }
template <size_t N, typename... Ts> template <size_t N, typename... Ts>
constexpr descr<N, Ts...> concat(const descr<N, Ts...> &descr) { return descr; } constexpr descr<N, Ts...> concat(const descr<N, Ts...> &descr) {
return descr;
}
template <size_t N, typename... Ts, typename... Args> template <size_t N, typename... Ts, typename... Args>
constexpr auto concat(const descr<N, Ts...> &d, const Args &...args) constexpr auto concat(const descr<N, Ts...> &d, const Args &...args)

View File

@ -22,7 +22,8 @@ public:
return true; return true;
} }
template <typename> using cast_op_type = value_and_holder &; template <typename>
using cast_op_type = value_and_holder &;
explicit operator value_and_holder &() { return *value; } explicit operator value_and_holder &() { return *value; }
static constexpr auto name = const_name<value_and_holder>(); static constexpr auto name = const_name<value_and_holder>();
@ -33,15 +34,21 @@ private:
PYBIND11_NAMESPACE_BEGIN(initimpl) PYBIND11_NAMESPACE_BEGIN(initimpl)
inline void no_nullptr(void *ptr) { inline void no_nullptr(void *ptr) {
if (!ptr) throw type_error("pybind11::init(): factory function returned nullptr"); if (!ptr) {
throw type_error("pybind11::init(): factory function returned nullptr");
}
} }
// Implementing functions for all forms of py::init<...> and py::init(...) // Implementing functions for all forms of py::init<...> and py::init(...)
template <typename Class> using Cpp = typename Class::type; template <typename Class>
template <typename Class> using Alias = typename Class::type_alias; using Cpp = typename Class::type;
template <typename Class> using Holder = typename Class::holder_type; template <typename Class>
using Alias = typename Class::type_alias;
template <typename Class>
using Holder = typename Class::holder_type;
template <typename Class> using is_alias_constructible = std::is_constructible<Alias<Class>, Cpp<Class> &&>; template <typename Class>
using is_alias_constructible = std::is_constructible<Alias<Class>, Cpp<Class> &&>;
// Takes a Cpp pointer and returns true if it actually is a polymorphic Alias instance. // Takes a Cpp pointer and returns true if it actually is a polymorphic Alias instance.
template <typename Class, enable_if_t<Class::has_alias, int> = 0> template <typename Class, enable_if_t<Class::has_alias, int> = 0>
@ -50,17 +57,27 @@ bool is_alias(Cpp<Class> *ptr) {
} }
// Failing fallback version of the above for a no-alias class (always returns false) // Failing fallback version of the above for a no-alias class (always returns false)
template <typename /*Class*/> template <typename /*Class*/>
constexpr bool is_alias(void *) { return false; } constexpr bool is_alias(void *) {
return false;
}
// Constructs and returns a new object; if the given arguments don't map to a constructor, we fall // Constructs and returns a new object; if the given arguments don't map to a constructor, we fall
// back to brace aggregate initiailization so that for aggregate initialization can be used with // back to brace aggregate initiailization so that for aggregate initialization can be used with
// py::init, e.g. `py::init<int, int>` to initialize a `struct T { int a; int b; }`. For // py::init, e.g. `py::init<int, int>` to initialize a `struct T { int a; int b; }`. For
// non-aggregate types, we need to use an ordinary T(...) constructor (invoking as `T{...}` usually // non-aggregate types, we need to use an ordinary T(...) constructor (invoking as `T{...}` usually
// works, but will not do the expected thing when `T` has an `initializer_list<T>` constructor). // works, but will not do the expected thing when `T` has an `initializer_list<T>` constructor).
template <typename Class, typename... Args, detail::enable_if_t<std::is_constructible<Class, Args...>::value, int> = 0> template <typename Class,
inline Class *construct_or_initialize(Args &&...args) { return new Class(std::forward<Args>(args)...); } typename... Args,
template <typename Class, typename... Args, detail::enable_if_t<!std::is_constructible<Class, Args...>::value, int> = 0> detail::enable_if_t<std::is_constructible<Class, Args...>::value, int> = 0>
inline Class *construct_or_initialize(Args &&...args) { return new Class{std::forward<Args>(args)...}; } inline Class *construct_or_initialize(Args &&...args) {
return new Class(std::forward<Args>(args)...);
}
template <typename Class,
typename... Args,
detail::enable_if_t<!std::is_constructible<Class, Args...>::value, int> = 0>
inline Class *construct_or_initialize(Args &&...args) {
return new Class{std::forward<Args>(args)...};
}
// Attempts to constructs an alias using a `Alias(Cpp &&)` constructor. This allows types with // Attempts to constructs an alias using a `Alias(Cpp &&)` constructor. This allows types with
// an alias to provide only a single Cpp factory function as long as the Alias can be // an alias to provide only a single Cpp factory function as long as the Alias can be
@ -69,12 +86,14 @@ inline Class *construct_or_initialize(Args &&...args) { return new Class{std::fo
// inherit all the base class constructors. // inherit all the base class constructors.
template <typename Class> template <typename Class>
void construct_alias_from_cpp(std::true_type /*is_alias_constructible*/, void construct_alias_from_cpp(std::true_type /*is_alias_constructible*/,
value_and_holder &v_h, Cpp<Class> &&base) { value_and_holder &v_h,
Cpp<Class> &&base) {
v_h.value_ptr() = new Alias<Class>(std::move(base)); v_h.value_ptr() = new Alias<Class>(std::move(base));
} }
template <typename Class> template <typename Class>
[[noreturn]] void construct_alias_from_cpp(std::false_type /*!is_alias_constructible*/, [[noreturn]] void construct_alias_from_cpp(std::false_type /*!is_alias_constructible*/,
value_and_holder &, Cpp<Class> &&) { value_and_holder &,
Cpp<Class> &&) {
throw type_error("pybind11::init(): unable to convert returned instance to required " throw type_error("pybind11::init(): unable to convert returned instance to required "
"alias class: no `Alias<Class>(Class &&)` constructor available"); "alias class: no `Alias<Class>(Class &&)` constructor available");
} }
@ -84,8 +103,8 @@ template <typename Class>
template <typename Class> template <typename Class>
void construct(...) { void construct(...) {
static_assert(!std::is_same<Class, Class>::value /* always false */, static_assert(!std::is_same<Class, Class>::value /* always false */,
"pybind11::init(): init function must return a compatible pointer, " "pybind11::init(): init function must return a compatible pointer, "
"holder, or value"); "holder, or value");
} }
// Pointer return v1: the factory function returns a class pointer for a registered class. // Pointer return v1: the factory function returns a class pointer for a registered class.
@ -106,7 +125,7 @@ void construct(value_and_holder &v_h, Cpp<Class> *ptr, bool need_alias) {
// the holder and destruction happens when we leave the C++ scope, and the holder // the holder and destruction happens when we leave the C++ scope, and the holder
// class gets to handle the destruction however it likes. // class gets to handle the destruction however it likes.
v_h.value_ptr() = ptr; v_h.value_ptr() = ptr;
v_h.set_instance_registered(true); // To prevent init_instance from registering it v_h.set_instance_registered(true); // To prevent init_instance from registering it
v_h.type->init_instance(v_h.inst, nullptr); // Set up the holder v_h.type->init_instance(v_h.inst, nullptr); // Set up the holder
Holder<Class> temp_holder(std::move(v_h.holder<Holder<Class>>())); // Steal the holder Holder<Class> temp_holder(std::move(v_h.holder<Holder<Class>>())); // Steal the holder
v_h.type->dealloc(v_h); // Destroys the moved-out holder remains, resets value ptr to null v_h.type->dealloc(v_h); // Destroys the moved-out holder remains, resets value ptr to null
@ -129,16 +148,18 @@ void construct(value_and_holder &v_h, Alias<Class> *alias_ptr, bool) {
// Holder return: copy its pointer, and move or copy the returned holder into the new instance's // Holder return: copy its pointer, and move or copy the returned holder into the new instance's
// holder. This also handles types like std::shared_ptr<T> and std::unique_ptr<T> where T is a // holder. This also handles types like std::shared_ptr<T> and std::unique_ptr<T> where T is a
// derived type (through those holder's implicit conversion from derived class holder constructors). // derived type (through those holder's implicit conversion from derived class holder
// constructors).
template <typename Class> template <typename Class>
void construct(value_and_holder &v_h, Holder<Class> holder, bool need_alias) { void construct(value_and_holder &v_h, Holder<Class> holder, bool need_alias) {
PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias); PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias);
auto *ptr = holder_helper<Holder<Class>>::get(holder); auto *ptr = holder_helper<Holder<Class>>::get(holder);
no_nullptr(ptr); no_nullptr(ptr);
// If we need an alias, check that the held pointer is actually an alias instance // If we need an alias, check that the held pointer is actually an alias instance
if (PYBIND11_SILENCE_MSVC_C4127(Class::has_alias) && need_alias && !is_alias<Class>(ptr)) if (PYBIND11_SILENCE_MSVC_C4127(Class::has_alias) && need_alias && !is_alias<Class>(ptr)) {
throw type_error("pybind11::init(): construction failed: returned holder-wrapped instance " throw type_error("pybind11::init(): construction failed: returned holder-wrapped instance "
"is not an alias instance"); "is not an alias instance");
}
v_h.value_ptr() = ptr; v_h.value_ptr() = ptr;
v_h.type->init_instance(v_h.inst, &holder); v_h.type->init_instance(v_h.inst, &holder);
@ -152,11 +173,12 @@ template <typename Class>
void construct(value_and_holder &v_h, Cpp<Class> &&result, bool need_alias) { void construct(value_and_holder &v_h, Cpp<Class> &&result, bool need_alias) {
PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias); PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias);
static_assert(std::is_move_constructible<Cpp<Class>>::value, static_assert(std::is_move_constructible<Cpp<Class>>::value,
"pybind11::init() return-by-value factory function requires a movable class"); "pybind11::init() return-by-value factory function requires a movable class");
if (PYBIND11_SILENCE_MSVC_C4127(Class::has_alias) && need_alias) if (PYBIND11_SILENCE_MSVC_C4127(Class::has_alias) && need_alias) {
construct_alias_from_cpp<Class>(is_alias_constructible<Class>{}, v_h, std::move(result)); construct_alias_from_cpp<Class>(is_alias_constructible<Class>{}, v_h, std::move(result));
else } else {
v_h.value_ptr() = new Cpp<Class>(std::move(result)); v_h.value_ptr() = new Cpp<Class>(std::move(result));
}
} }
// return-by-value version 2: returning a value of the alias type itself. We move-construct an // return-by-value version 2: returning a value of the alias type itself. We move-construct an
@ -164,7 +186,8 @@ void construct(value_and_holder &v_h, Cpp<Class> &&result, bool need_alias) {
// cases where Alias initialization is always desired. // cases where Alias initialization is always desired.
template <typename Class> template <typename Class>
void construct(value_and_holder &v_h, Alias<Class> &&result, bool) { void construct(value_and_holder &v_h, Alias<Class> &&result, bool) {
static_assert(std::is_move_constructible<Alias<Class>>::value, static_assert(
std::is_move_constructible<Alias<Class>>::value,
"pybind11::init() return-by-alias-value factory function requires a movable alias class"); "pybind11::init() return-by-alias-value factory function requires a movable alias class");
v_h.value_ptr() = new Alias<Class>(std::move(result)); v_h.value_ptr() = new Alias<Class>(std::move(result));
} }
@ -173,48 +196,76 @@ void construct(value_and_holder &v_h, Alias<Class> &&result, bool) {
template <typename... Args> template <typename... Args>
struct constructor { struct constructor {
template <typename Class, typename... Extra, enable_if_t<!Class::has_alias, int> = 0> template <typename Class, typename... Extra, enable_if_t<!Class::has_alias, int> = 0>
static void execute(Class &cl, const Extra&... extra) { static void execute(Class &cl, const Extra &...extra) {
cl.def("__init__", [](value_and_holder &v_h, Args... args) { cl.def(
v_h.value_ptr() = construct_or_initialize<Cpp<Class>>(std::forward<Args>(args)...); "__init__",
}, is_new_style_constructor(), extra...); [](value_and_holder &v_h, Args... args) {
}
template <typename Class, typename... Extra,
enable_if_t<Class::has_alias &&
std::is_constructible<Cpp<Class>, Args...>::value, int> = 0>
static void execute(Class &cl, const Extra&... extra) {
cl.def("__init__", [](value_and_holder &v_h, Args... args) {
if (Py_TYPE(v_h.inst) == v_h.type->type)
v_h.value_ptr() = construct_or_initialize<Cpp<Class>>(std::forward<Args>(args)...); v_h.value_ptr() = construct_or_initialize<Cpp<Class>>(std::forward<Args>(args)...);
else },
v_h.value_ptr() = construct_or_initialize<Alias<Class>>(std::forward<Args>(args)...); is_new_style_constructor(),
}, is_new_style_constructor(), extra...); extra...);
} }
template <typename Class, typename... Extra, template <typename Class,
enable_if_t<Class::has_alias && typename... Extra,
!std::is_constructible<Cpp<Class>, Args...>::value, int> = 0> enable_if_t<Class::has_alias && std::is_constructible<Cpp<Class>, Args...>::value,
static void execute(Class &cl, const Extra&... extra) { int> = 0>
cl.def("__init__", [](value_and_holder &v_h, Args... args) { static void execute(Class &cl, const Extra &...extra) {
v_h.value_ptr() = construct_or_initialize<Alias<Class>>(std::forward<Args>(args)...); cl.def(
}, is_new_style_constructor(), extra...); "__init__",
[](value_and_holder &v_h, Args... args) {
if (Py_TYPE(v_h.inst) == v_h.type->type) {
v_h.value_ptr()
= construct_or_initialize<Cpp<Class>>(std::forward<Args>(args)...);
} else {
v_h.value_ptr()
= construct_or_initialize<Alias<Class>>(std::forward<Args>(args)...);
}
},
is_new_style_constructor(),
extra...);
}
template <typename Class,
typename... Extra,
enable_if_t<Class::has_alias && !std::is_constructible<Cpp<Class>, Args...>::value,
int> = 0>
static void execute(Class &cl, const Extra &...extra) {
cl.def(
"__init__",
[](value_and_holder &v_h, Args... args) {
v_h.value_ptr()
= construct_or_initialize<Alias<Class>>(std::forward<Args>(args)...);
},
is_new_style_constructor(),
extra...);
} }
}; };
// Implementing class for py::init_alias<...>() // Implementing class for py::init_alias<...>()
template <typename... Args> struct alias_constructor { template <typename... Args>
template <typename Class, typename... Extra, struct alias_constructor {
enable_if_t<Class::has_alias && std::is_constructible<Alias<Class>, Args...>::value, int> = 0> template <typename Class,
static void execute(Class &cl, const Extra&... extra) { typename... Extra,
cl.def("__init__", [](value_and_holder &v_h, Args... args) { enable_if_t<Class::has_alias && std::is_constructible<Alias<Class>, Args...>::value,
v_h.value_ptr() = construct_or_initialize<Alias<Class>>(std::forward<Args>(args)...); int> = 0>
}, is_new_style_constructor(), extra...); static void execute(Class &cl, const Extra &...extra) {
cl.def(
"__init__",
[](value_and_holder &v_h, Args... args) {
v_h.value_ptr()
= construct_or_initialize<Alias<Class>>(std::forward<Args>(args)...);
},
is_new_style_constructor(),
extra...);
} }
}; };
// Implementation class for py::init(Func) and py::init(Func, AliasFunc) // Implementation class for py::init(Func) and py::init(Func, AliasFunc)
template <typename CFunc, typename AFunc = void_type (*)(), template <typename CFunc,
typename = function_signature_t<CFunc>, typename = function_signature_t<AFunc>> typename AFunc = void_type (*)(),
typename = function_signature_t<CFunc>,
typename = function_signature_t<AFunc>>
struct factory; struct factory;
// Specialization for py::init(Func) // Specialization for py::init(Func)
@ -232,22 +283,32 @@ struct factory<Func, void_type (*)(), Return(Args...)> {
// instance, or the alias needs to be constructible from a `Class &&` argument. // instance, or the alias needs to be constructible from a `Class &&` argument.
template <typename Class, typename... Extra> template <typename Class, typename... Extra>
void execute(Class &cl, const Extra &...extra) && { void execute(Class &cl, const Extra &...extra) && {
#if defined(PYBIND11_CPP14) #if defined(PYBIND11_CPP14)
cl.def("__init__", [func = std::move(class_factory)] cl.def(
#else "__init__",
[func = std::move(class_factory)]
#else
auto &func = class_factory; auto &func = class_factory;
cl.def("__init__", [func] cl.def(
#endif "__init__",
(value_and_holder &v_h, Args... args) { [func]
construct<Class>(v_h, func(std::forward<Args>(args)...), #endif
Py_TYPE(v_h.inst) != v_h.type->type); (value_and_holder &v_h, Args... args) {
}, is_new_style_constructor(), extra...); construct<Class>(
v_h, func(std::forward<Args>(args)...), Py_TYPE(v_h.inst) != v_h.type->type);
},
is_new_style_constructor(),
extra...);
} }
}; };
// Specialization for py::init(Func, AliasFunc) // Specialization for py::init(Func, AliasFunc)
template <typename CFunc, typename AFunc, template <typename CFunc,
typename CReturn, typename... CArgs, typename AReturn, typename... AArgs> typename AFunc,
typename CReturn,
typename... CArgs,
typename AReturn,
typename... AArgs>
struct factory<CFunc, AFunc, CReturn(CArgs...), AReturn(AArgs...)> { struct factory<CFunc, AFunc, CReturn(CArgs...), AReturn(AArgs...)> {
static_assert(sizeof...(CArgs) == sizeof...(AArgs), static_assert(sizeof...(CArgs) == sizeof...(AArgs),
"pybind11::init(class_factory, alias_factory): class and alias factories " "pybind11::init(class_factory, alias_factory): class and alias factories "
@ -260,29 +321,37 @@ struct factory<CFunc, AFunc, CReturn(CArgs...), AReturn(AArgs...)> {
remove_reference_t<AFunc> alias_factory; remove_reference_t<AFunc> alias_factory;
factory(CFunc &&c, AFunc &&a) factory(CFunc &&c, AFunc &&a)
: class_factory(std::forward<CFunc>(c)), alias_factory(std::forward<AFunc>(a)) { } : class_factory(std::forward<CFunc>(c)), alias_factory(std::forward<AFunc>(a)) {}
// The class factory is called when the `self` type passed to `__init__` is the direct // The class factory is called when the `self` type passed to `__init__` is the direct
// class (i.e. not inherited), the alias factory when `self` is a Python-side subtype. // class (i.e. not inherited), the alias factory when `self` is a Python-side subtype.
template <typename Class, typename... Extra> template <typename Class, typename... Extra>
void execute(Class &cl, const Extra&... extra) && { void execute(Class &cl, const Extra &...extra) && {
static_assert(Class::has_alias, "The two-argument version of `py::init()` can " static_assert(Class::has_alias,
"only be used if the class has an alias"); "The two-argument version of `py::init()` can "
#if defined(PYBIND11_CPP14) "only be used if the class has an alias");
cl.def("__init__", [class_func = std::move(class_factory), alias_func = std::move(alias_factory)] #if defined(PYBIND11_CPP14)
#else cl.def(
"__init__",
[class_func = std::move(class_factory), alias_func = std::move(alias_factory)]
#else
auto &class_func = class_factory; auto &class_func = class_factory;
auto &alias_func = alias_factory; auto &alias_func = alias_factory;
cl.def("__init__", [class_func, alias_func] cl.def(
#endif "__init__",
(value_and_holder &v_h, CArgs... args) { [class_func, alias_func]
if (Py_TYPE(v_h.inst) == v_h.type->type) #endif
// If the instance type equals the registered type we don't have inheritance, so (value_and_holder &v_h, CArgs... args) {
// don't need the alias and can construct using the class function: if (Py_TYPE(v_h.inst) == v_h.type->type) {
construct<Class>(v_h, class_func(std::forward<CArgs>(args)...), false); // If the instance type equals the registered type we don't have inheritance,
else // so don't need the alias and can construct using the class function:
construct<Class>(v_h, alias_func(std::forward<CArgs>(args)...), true); construct<Class>(v_h, class_func(std::forward<CArgs>(args)...), false);
}, is_new_style_constructor(), extra...); } else {
construct<Class>(v_h, alias_func(std::forward<CArgs>(args)...), true);
}
},
is_new_style_constructor(),
extra...);
} }
}; };
@ -293,7 +362,9 @@ void setstate(value_and_holder &v_h, T &&result, bool need_alias) {
} }
/// Set both the C++ and Python states /// Set both the C++ and Python states
template <typename Class, typename T, typename O, template <typename Class,
typename T,
typename O,
enable_if_t<std::is_convertible<O, handle>::value, int> = 0> enable_if_t<std::is_convertible<O, handle>::value, int> = 0>
void setstate(value_and_holder &v_h, std::pair<T, O> &&result, bool need_alias) { void setstate(value_and_holder &v_h, std::pair<T, O> &&result, bool need_alias) {
construct<Class>(v_h, std::move(result.first), need_alias); construct<Class>(v_h, std::move(result.first), need_alias);
@ -307,12 +378,18 @@ void setstate(value_and_holder &v_h, std::pair<T, O> &&result, bool need_alias)
} }
/// Implementation for py::pickle(GetState, SetState) /// Implementation for py::pickle(GetState, SetState)
template <typename Get, typename Set, template <typename Get,
typename = function_signature_t<Get>, typename = function_signature_t<Set>> typename Set,
typename = function_signature_t<Get>,
typename = function_signature_t<Set>>
struct pickle_factory; struct pickle_factory;
template <typename Get, typename Set, template <typename Get,
typename RetState, typename Self, typename NewInstance, typename ArgState> typename Set,
typename RetState,
typename Self,
typename NewInstance,
typename ArgState>
struct pickle_factory<Get, Set, RetState(Self), NewInstance(ArgState)> { struct pickle_factory<Get, Set, RetState(Self), NewInstance(ArgState)> {
static_assert(std::is_same<intrinsic_t<RetState>, intrinsic_t<ArgState>>::value, static_assert(std::is_same<intrinsic_t<RetState>, intrinsic_t<ArgState>>::value,
"The type returned by `__getstate__` must be the same " "The type returned by `__getstate__` must be the same "
@ -321,26 +398,31 @@ struct pickle_factory<Get, Set, RetState(Self), NewInstance(ArgState)> {
remove_reference_t<Get> get; remove_reference_t<Get> get;
remove_reference_t<Set> set; remove_reference_t<Set> set;
pickle_factory(Get get, Set set) pickle_factory(Get get, Set set) : get(std::forward<Get>(get)), set(std::forward<Set>(set)) {}
: get(std::forward<Get>(get)), set(std::forward<Set>(set)) { }
template <typename Class, typename... Extra> template <typename Class, typename... Extra>
void execute(Class &cl, const Extra &...extra) && { void execute(Class &cl, const Extra &...extra) && {
cl.def("__getstate__", std::move(get)); cl.def("__getstate__", std::move(get));
#if defined(PYBIND11_CPP14) #if defined(PYBIND11_CPP14)
cl.def("__setstate__", [func = std::move(set)] cl.def(
"__setstate__",
[func = std::move(set)]
#else #else
auto &func = set; auto &func = set;
cl.def("__setstate__", [func] cl.def(
"__setstate__",
[func]
#endif #endif
(value_and_holder &v_h, ArgState state) { (value_and_holder &v_h, ArgState state) {
setstate<Class>(v_h, func(std::forward<ArgState>(state)), setstate<Class>(
Py_TYPE(v_h.inst) != v_h.type->type); v_h, func(std::forward<ArgState>(state)), Py_TYPE(v_h.inst) != v_h.type->type);
}, is_new_style_constructor(), extra...); },
is_new_style_constructor(),
extra...);
} }
}; };
PYBIND11_NAMESPACE_END(initimpl) PYBIND11_NAMESPACE_END(initimpl)
PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(pybind11) PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@ -10,6 +10,7 @@
#pragma once #pragma once
#include "../pytypes.h" #include "../pytypes.h"
#include <exception> #include <exception>
/// Tracks the `internals` and `type_info` ABI version independent of the main library version. /// Tracks the `internals` and `type_info` ABI version independent of the main library version.
@ -81,7 +82,7 @@ inline PyObject *make_object_base_type(PyTypeObject *metaclass);
# define PYBIND11_TLS_KEY_INIT(var) PYBIND11_TLS_KEY_REF var = 0; # define PYBIND11_TLS_KEY_INIT(var) PYBIND11_TLS_KEY_REF var = 0;
# define PYBIND11_TLS_KEY_CREATE(var) (((var) = PyThread_create_key()) != -1) # define PYBIND11_TLS_KEY_CREATE(var) (((var) = PyThread_create_key()) != -1)
# define PYBIND11_TLS_GET_VALUE(key) PyThread_get_key_value((key)) # define PYBIND11_TLS_GET_VALUE(key) PyThread_get_key_value((key))
# if PY_MAJOR_VERSION < 3 || defined(PYPY_VERSION) # if defined(PYPY_VERSION)
// On CPython < 3.4 and on PyPy, `PyThread_set_key_value` strangely does not set // On CPython < 3.4 and on PyPy, `PyThread_set_key_value` strangely does not set
// the value if it has already been set. Instead, it must first be deleted and // the value if it has already been set. Instead, it must first be deleted and
// then set again. // then set again.
@ -118,8 +119,9 @@ struct type_hash {
size_t operator()(const std::type_index &t) const { size_t operator()(const std::type_index &t) const {
size_t hash = 5381; size_t hash = 5381;
const char *ptr = t.name(); const char *ptr = t.name();
while (auto c = static_cast<unsigned char>(*ptr++)) while (auto c = static_cast<unsigned char>(*ptr++)) {
hash = (hash * 33) ^ c; hash = (hash * 33) ^ c;
}
return hash; return hash;
} }
}; };
@ -135,9 +137,9 @@ template <typename value_type>
using type_map = std::unordered_map<std::type_index, value_type, type_hash, type_equal_to>; using type_map = std::unordered_map<std::type_index, value_type, type_hash, type_equal_to>;
struct override_hash { struct override_hash {
inline size_t operator()(const std::pair<const PyObject *, const char *>& v) const { inline size_t operator()(const std::pair<const PyObject *, const char *> &v) const {
size_t value = std::hash<const void *>()(v.first); size_t value = std::hash<const void *>()(v.first);
value ^= std::hash<const void *>()(v.second) + 0x9e3779b9 + (value<<6) + (value>>2); value ^= std::hash<const void *>()(v.second) + 0x9e3779b9 + (value << 6) + (value >> 2);
return value; return value;
} }
}; };
@ -146,18 +148,23 @@ struct override_hash {
/// Whenever binary incompatible changes are made to this structure, /// Whenever binary incompatible changes are made to this structure,
/// `PYBIND11_INTERNALS_VERSION` must be incremented. /// `PYBIND11_INTERNALS_VERSION` must be incremented.
struct internals { struct internals {
type_map<type_info *> registered_types_cpp; // std::type_index -> pybind11's type information // std::type_index -> pybind11's type information
std::unordered_map<PyTypeObject *, std::vector<type_info *>> registered_types_py; // PyTypeObject* -> base type_info(s) type_map<type_info *> registered_types_cpp;
std::unordered_multimap<const void *, instance*> registered_instances; // void * -> instance* // PyTypeObject* -> base type_info(s)
std::unordered_set<std::pair<const PyObject *, const char *>, override_hash> inactive_override_cache; std::unordered_map<PyTypeObject *, std::vector<type_info *>> registered_types_py;
std::unordered_multimap<const void *, instance *> registered_instances; // void * -> instance*
std::unordered_set<std::pair<const PyObject *, const char *>, override_hash>
inactive_override_cache;
type_map<std::vector<bool (*)(PyObject *, void *&)>> direct_conversions; type_map<std::vector<bool (*)(PyObject *, void *&)>> direct_conversions;
std::unordered_map<const PyObject *, std::vector<PyObject *>> patients; std::unordered_map<const PyObject *, std::vector<PyObject *>> patients;
std::forward_list<ExceptionTranslator> registered_exception_translators; std::forward_list<ExceptionTranslator> registered_exception_translators;
std::unordered_map<std::string, void *> shared_data; // Custom data to be shared across extensions std::unordered_map<std::string, void *> shared_data; // Custom data to be shared across
// extensions
#if PYBIND11_INTERNALS_VERSION == 4 #if PYBIND11_INTERNALS_VERSION == 4
std::vector<PyObject *> unused_loader_patient_stack_remove_at_v5; std::vector<PyObject *> unused_loader_patient_stack_remove_at_v5;
#endif #endif
std::forward_list<std::string> static_strings; // Stores the std::strings backing detail::c_str() std::forward_list<std::string> static_strings; // Stores the std::strings backing
// detail::c_str()
PyTypeObject *static_property_type; PyTypeObject *static_property_type;
PyTypeObject *default_metaclass; PyTypeObject *default_metaclass;
PyObject *instance_base; PyObject *instance_base;
@ -193,8 +200,8 @@ struct type_info {
void *(*operator_new)(size_t); void *(*operator_new)(size_t);
void (*init_instance)(instance *, const void *); void (*init_instance)(instance *, const void *);
void (*dealloc)(value_and_holder &v_h); void (*dealloc)(value_and_holder &v_h);
std::vector<PyObject *(*)(PyObject *, PyTypeObject *)> implicit_conversions; std::vector<PyObject *(*) (PyObject *, PyTypeObject *)> implicit_conversions;
std::vector<std::pair<const std::type_info *, void *(*)(void *)>> implicit_casts; std::vector<std::pair<const std::type_info *, void *(*) (void *)>> implicit_casts;
std::vector<bool (*)(PyObject *, void *&)> *direct_conversions; std::vector<bool (*)(PyObject *, void *&)> *direct_conversions;
buffer_info *(*get_buffer)(PyObject *, void *) = nullptr; buffer_info *(*get_buffer)(PyObject *, void *) = nullptr;
void *get_buffer_data = nullptr; void *get_buffer_data = nullptr;
@ -214,67 +221,71 @@ struct type_info {
/// On MSVC, debug and release builds are not ABI-compatible! /// On MSVC, debug and release builds are not ABI-compatible!
#if defined(_MSC_VER) && defined(_DEBUG) #if defined(_MSC_VER) && defined(_DEBUG)
# define PYBIND11_BUILD_TYPE "_debug" # define PYBIND11_BUILD_TYPE "_debug"
#else #else
# define PYBIND11_BUILD_TYPE "" # define PYBIND11_BUILD_TYPE ""
#endif #endif
/// Let's assume that different compilers are ABI-incompatible. /// Let's assume that different compilers are ABI-incompatible.
/// A user can manually set this string if they know their /// A user can manually set this string if they know their
/// compiler is compatible. /// compiler is compatible.
#ifndef PYBIND11_COMPILER_TYPE #ifndef PYBIND11_COMPILER_TYPE
# if defined(_MSC_VER) # if defined(_MSC_VER)
# define PYBIND11_COMPILER_TYPE "_msvc" # define PYBIND11_COMPILER_TYPE "_msvc"
# elif defined(__INTEL_COMPILER) # elif defined(__INTEL_COMPILER)
# define PYBIND11_COMPILER_TYPE "_icc" # define PYBIND11_COMPILER_TYPE "_icc"
# elif defined(__clang__) # elif defined(__clang__)
# define PYBIND11_COMPILER_TYPE "_clang" # define PYBIND11_COMPILER_TYPE "_clang"
# elif defined(__PGI) # elif defined(__PGI)
# define PYBIND11_COMPILER_TYPE "_pgi" # define PYBIND11_COMPILER_TYPE "_pgi"
# elif defined(__MINGW32__) # elif defined(__MINGW32__)
# define PYBIND11_COMPILER_TYPE "_mingw" # define PYBIND11_COMPILER_TYPE "_mingw"
# elif defined(__CYGWIN__) # elif defined(__CYGWIN__)
# define PYBIND11_COMPILER_TYPE "_gcc_cygwin" # define PYBIND11_COMPILER_TYPE "_gcc_cygwin"
# elif defined(__GNUC__) # elif defined(__GNUC__)
# define PYBIND11_COMPILER_TYPE "_gcc" # define PYBIND11_COMPILER_TYPE "_gcc"
# else # else
# define PYBIND11_COMPILER_TYPE "_unknown" # define PYBIND11_COMPILER_TYPE "_unknown"
# endif # endif
#endif #endif
/// Also standard libs /// Also standard libs
#ifndef PYBIND11_STDLIB #ifndef PYBIND11_STDLIB
# if defined(_LIBCPP_VERSION) # if defined(_LIBCPP_VERSION)
# define PYBIND11_STDLIB "_libcpp" # define PYBIND11_STDLIB "_libcpp"
# elif defined(__GLIBCXX__) || defined(__GLIBCPP__) # elif defined(__GLIBCXX__) || defined(__GLIBCPP__)
# define PYBIND11_STDLIB "_libstdcpp" # define PYBIND11_STDLIB "_libstdcpp"
# else # else
# define PYBIND11_STDLIB "" # define PYBIND11_STDLIB ""
# endif # endif
#endif #endif
/// On Linux/OSX, changes in __GXX_ABI_VERSION__ indicate ABI incompatibility. /// On Linux/OSX, changes in __GXX_ABI_VERSION__ indicate ABI incompatibility.
#ifndef PYBIND11_BUILD_ABI #ifndef PYBIND11_BUILD_ABI
# if defined(__GXX_ABI_VERSION) # if defined(__GXX_ABI_VERSION)
# define PYBIND11_BUILD_ABI "_cxxabi" PYBIND11_TOSTRING(__GXX_ABI_VERSION) # define PYBIND11_BUILD_ABI "_cxxabi" PYBIND11_TOSTRING(__GXX_ABI_VERSION)
# else # else
# define PYBIND11_BUILD_ABI "" # define PYBIND11_BUILD_ABI ""
# endif # endif
#endif #endif
#ifndef PYBIND11_INTERNALS_KIND #ifndef PYBIND11_INTERNALS_KIND
# if defined(WITH_THREAD) # if defined(WITH_THREAD)
# define PYBIND11_INTERNALS_KIND "" # define PYBIND11_INTERNALS_KIND ""
# else # else
# define PYBIND11_INTERNALS_KIND "_without_thread" # define PYBIND11_INTERNALS_KIND "_without_thread"
# endif # endif
#endif #endif
#define PYBIND11_INTERNALS_ID "__pybind11_internals_v" \ #define PYBIND11_INTERNALS_ID \
PYBIND11_TOSTRING(PYBIND11_INTERNALS_VERSION) PYBIND11_INTERNALS_KIND PYBIND11_COMPILER_TYPE PYBIND11_STDLIB PYBIND11_BUILD_ABI PYBIND11_BUILD_TYPE "__" "__pybind11_internals_v" PYBIND11_TOSTRING(PYBIND11_INTERNALS_VERSION) \
PYBIND11_INTERNALS_KIND PYBIND11_COMPILER_TYPE PYBIND11_STDLIB PYBIND11_BUILD_ABI \
PYBIND11_BUILD_TYPE "__"
#define PYBIND11_MODULE_LOCAL_ID "__pybind11_module_local_v" \ #define PYBIND11_MODULE_LOCAL_ID \
PYBIND11_TOSTRING(PYBIND11_INTERNALS_VERSION) PYBIND11_INTERNALS_KIND PYBIND11_COMPILER_TYPE PYBIND11_STDLIB PYBIND11_BUILD_ABI PYBIND11_BUILD_TYPE "__" "__pybind11_module_local_v" PYBIND11_TOSTRING(PYBIND11_INTERNALS_VERSION) \
PYBIND11_INTERNALS_KIND PYBIND11_COMPILER_TYPE PYBIND11_STDLIB PYBIND11_BUILD_ABI \
PYBIND11_BUILD_TYPE "__"
/// Each module locally stores a pointer to the `internals` data. The data /// Each module locally stores a pointer to the `internals` data. The data
/// itself is shared among modules with the same `PYBIND11_INTERNALS_ID`. /// itself is shared among modules with the same `PYBIND11_INTERNALS_ID`.
@ -283,7 +294,6 @@ inline internals **&get_internals_pp() {
return internals_pp; return internals_pp;
} }
#if PY_VERSION_HEX >= 0x03030000
// forward decl // forward decl
inline void translate_exception(std::exception_ptr); inline void translate_exception(std::exception_ptr);
@ -301,27 +311,17 @@ bool handle_nested_exception(const T &exc, const std::exception_ptr &p) {
template <class T, template <class T,
enable_if_t<!std::is_same<std::nested_exception, remove_cvref_t<T>>::value, int> = 0> enable_if_t<!std::is_same<std::nested_exception, remove_cvref_t<T>>::value, int> = 0>
bool handle_nested_exception(const T &exc, const std::exception_ptr &p) { bool handle_nested_exception(const T &exc, const std::exception_ptr &p) {
if (auto *nep = dynamic_cast<const std::nested_exception *>(std::addressof(exc))) { if (const auto *nep = dynamic_cast<const std::nested_exception *>(std::addressof(exc))) {
return handle_nested_exception(*nep, p); return handle_nested_exception(*nep, p);
} }
return false; return false;
} }
#else
template <class T>
bool handle_nested_exception(const T &, std::exception_ptr &) {
return false;
}
#endif
inline bool raise_err(PyObject *exc_type, const char *msg) { inline bool raise_err(PyObject *exc_type, const char *msg) {
#if PY_VERSION_HEX >= 0x03030000
if (PyErr_Occurred()) { if (PyErr_Occurred()) {
raise_from(exc_type, msg); raise_from(exc_type, msg);
return true; return true;
} }
#endif
PyErr_SetString(exc_type, msg); PyErr_SetString(exc_type, msg);
return false; return false;
} }
@ -338,7 +338,7 @@ inline void translate_exception(std::exception_ptr p) {
return; return;
} catch (const builtin_exception &e) { } catch (const builtin_exception &e) {
// Could not use template since it's an abstract class. // Could not use template since it's an abstract class.
if (auto *nep = dynamic_cast<const std::nested_exception *>(std::addressof(e))) { if (const auto *nep = dynamic_cast<const std::nested_exception *>(std::addressof(e))) {
handle_nested_exception(*nep, p); handle_nested_exception(*nep, p);
} }
e.set_error(); e.set_error();
@ -388,9 +388,15 @@ inline void translate_exception(std::exception_ptr p) {
#if !defined(__GLIBCXX__) #if !defined(__GLIBCXX__)
inline void translate_local_exception(std::exception_ptr p) { inline void translate_local_exception(std::exception_ptr p) {
try { try {
if (p) std::rethrow_exception(p); if (p) {
} catch (error_already_set &e) { e.restore(); return; std::rethrow_exception(p);
} catch (const builtin_exception &e) { e.set_error(); return; }
} catch (error_already_set &e) {
e.restore();
return;
} catch (const builtin_exception &e) {
e.set_error();
return;
} }
} }
#endif #endif
@ -398,16 +404,18 @@ inline void translate_local_exception(std::exception_ptr p) {
/// Return a reference to the current `internals` data /// Return a reference to the current `internals` data
PYBIND11_NOINLINE internals &get_internals() { PYBIND11_NOINLINE internals &get_internals() {
auto **&internals_pp = get_internals_pp(); auto **&internals_pp = get_internals_pp();
if (internals_pp && *internals_pp) if (internals_pp && *internals_pp) {
return **internals_pp; return **internals_pp;
}
// Ensure that the GIL is held since we will need to make Python calls. // Ensure that the GIL is held since we will need to make Python calls.
// Cannot use py::gil_scoped_acquire here since that constructor calls get_internals. // Cannot use py::gil_scoped_acquire here since that constructor calls get_internals.
struct gil_scoped_acquire_local { struct gil_scoped_acquire_local {
gil_scoped_acquire_local() : state (PyGILState_Ensure()) {} gil_scoped_acquire_local() : state(PyGILState_Ensure()) {}
~gil_scoped_acquire_local() { PyGILState_Release(state); } ~gil_scoped_acquire_local() { PyGILState_Release(state); }
const PyGILState_STATE state; const PyGILState_STATE state;
} gil; } gil;
error_scope err_scope;
PYBIND11_STR_TYPE id(PYBIND11_INTERNALS_ID); PYBIND11_STR_TYPE id(PYBIND11_INTERNALS_ID);
auto builtins = handle(PyEval_GetBuiltins()); auto builtins = handle(PyEval_GetBuiltins());
@ -425,7 +433,9 @@ PYBIND11_NOINLINE internals &get_internals() {
(*internals_pp)->registered_exception_translators.push_front(&translate_local_exception); (*internals_pp)->registered_exception_translators.push_front(&translate_local_exception);
#endif #endif
} else { } else {
if (!internals_pp) internals_pp = new internals*(); if (!internals_pp) {
internals_pp = new internals *();
}
auto *&internals_ptr = *internals_pp; auto *&internals_ptr = *internals_pp;
internals_ptr = new internals(); internals_ptr = new internals();
#if defined(WITH_THREAD) #if defined(WITH_THREAD)
@ -502,11 +512,10 @@ struct local_internals {
/// Works like `get_internals`, but for things which are locally registered. /// Works like `get_internals`, but for things which are locally registered.
inline local_internals &get_local_internals() { inline local_internals &get_local_internals() {
static local_internals locals; static local_internals locals;
return locals; return locals;
} }
/// Constructs a std::string with the given arguments, stores it in `internals`, and returns its /// Constructs a std::string with the given arguments, stores it in `internals`, and returns its
/// `c_str()`. Such strings objects have a long storage duration -- the internal strings are only /// `c_str()`. Such strings objects have a long storage duration -- the internal strings are only
/// cleared when the program exits or after interpreter shutdown (when embedding), and so are /// cleared when the program exits or after interpreter shutdown (when embedding), and so are
@ -538,7 +547,7 @@ PYBIND11_NOINLINE void *set_shared_data(const std::string &name, void *data) {
/// Returns a typed reference to a shared data entry (by using `get_shared_data()`) if /// Returns a typed reference to a shared data entry (by using `get_shared_data()`) if
/// such entry exists. Otherwise, a new object of default-constructible type `T` is /// such entry exists. Otherwise, a new object of default-constructible type `T` is
/// added to the shared data under the given name and a reference to it is returned. /// added to the shared data under the given name and a reference to it is returned.
template<typename T> template <typename T>
T &get_or_create_shared_data(const std::string &name) { T &get_or_create_shared_data(const std::string &name) {
auto &internals = detail::get_internals(); auto &internals = detail::get_internals();
auto it = internals.shared_data.find(name); auto it = internals.shared_data.find(name);

File diff suppressed because it is too large Load Diff

View File

@ -13,18 +13,21 @@
#include <cstdlib> #include <cstdlib>
#if defined(__GNUG__) #if defined(__GNUG__)
#include <cxxabi.h> # include <cxxabi.h>
#endif #endif
#include "common.h" #include "common.h"
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
/// Erase all occurrences of a substring /// Erase all occurrences of a substring
inline void erase_all(std::string &string, const std::string &search) { inline void erase_all(std::string &string, const std::string &search) {
for (size_t pos = 0;;) { for (size_t pos = 0;;) {
pos = string.find(search, pos); pos = string.find(search, pos);
if (pos == std::string::npos) break; if (pos == std::string::npos) {
break;
}
string.erase(pos, search.length()); string.erase(pos, search.length());
} }
} }
@ -32,10 +35,11 @@ inline void erase_all(std::string &string, const std::string &search) {
PYBIND11_NOINLINE void clean_type_id(std::string &name) { PYBIND11_NOINLINE void clean_type_id(std::string &name) {
#if defined(__GNUG__) #if defined(__GNUG__)
int status = 0; int status = 0;
std::unique_ptr<char, void (*)(void *)> res { std::unique_ptr<char, void (*)(void *)> res{
abi::__cxa_demangle(name.c_str(), nullptr, nullptr, &status), std::free }; abi::__cxa_demangle(name.c_str(), nullptr, nullptr, &status), std::free};
if (status == 0) if (status == 0) {
name = res.get(); name = res.get();
}
#else #else
detail::erase_all(name, "class "); detail::erase_all(name, "class ");
detail::erase_all(name, "struct "); detail::erase_all(name, "struct ");
@ -43,13 +47,19 @@ PYBIND11_NOINLINE void clean_type_id(std::string &name) {
#endif #endif
detail::erase_all(name, "pybind11::"); detail::erase_all(name, "pybind11::");
} }
PYBIND11_NAMESPACE_END(detail)
/// Return a string representation of a C++ type inline std::string clean_type_id(const char *typeid_name) {
template <typename T> static std::string type_id() { std::string name(typeid_name);
std::string name(typeid(T).name());
detail::clean_type_id(name); detail::clean_type_id(name);
return name; return name;
} }
PYBIND11_NAMESPACE_END(detail)
/// Return a string representation of a C++ type
template <typename T>
static std::string type_id() {
return detail::clean_type_id(typeid(T).name());
}
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@ -21,155 +21,187 @@
// make it version specific, or even remove it later, but considering that // make it version specific, or even remove it later, but considering that
// 1. C4127 is generally far more distracting than useful for modern template code, and // 1. C4127 is generally far more distracting than useful for modern template code, and
// 2. we definitely want to ignore any MSVC warnings originating from Eigen code, // 2. we definitely want to ignore any MSVC warnings originating from Eigen code,
// it is probably best to keep this around indefinitely. // it is probably best to keep this around indefinitely.
#if defined(_MSC_VER) #if defined(_MSC_VER)
# pragma warning(push) # pragma warning(push)
# pragma warning(disable: 4127) // C4127: conditional expression is constant # pragma warning(disable : 4127) // C4127: conditional expression is constant
# pragma warning(disable : 5054) // https://github.com/pybind/pybind11/pull/3741
// C5054: operator '&': deprecated between enumerations of different types
#endif #endif
#include <Eigen/Core> #include <Eigen/Core>
#include <Eigen/SparseCore> #include <Eigen/SparseCore>
#if defined(_MSC_VER) #if defined(_MSC_VER)
# pragma warning(pop) # pragma warning(pop)
#endif #endif
// Eigen prior to 3.2.7 doesn't have proper move constructors--but worse, some classes get implicit // Eigen prior to 3.2.7 doesn't have proper move constructors--but worse, some classes get implicit
// move constructors that break things. We could detect this an explicitly copy, but an extra copy // move constructors that break things. We could detect this an explicitly copy, but an extra copy
// of matrices seems highly undesirable. // of matrices seems highly undesirable.
static_assert(EIGEN_VERSION_AT_LEAST(3,2,7), "Eigen support in pybind11 requires Eigen >= 3.2.7"); static_assert(EIGEN_VERSION_AT_LEAST(3, 2, 7),
"Eigen support in pybind11 requires Eigen >= 3.2.7");
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
// Provide a convenience alias for easier pass-by-ref usage with fully dynamic strides: // Provide a convenience alias for easier pass-by-ref usage with fully dynamic strides:
using EigenDStride = Eigen::Stride<Eigen::Dynamic, Eigen::Dynamic>; using EigenDStride = Eigen::Stride<Eigen::Dynamic, Eigen::Dynamic>;
template <typename MatrixType> using EigenDRef = Eigen::Ref<MatrixType, 0, EigenDStride>; template <typename MatrixType>
template <typename MatrixType> using EigenDMap = Eigen::Map<MatrixType, 0, EigenDStride>; using EigenDRef = Eigen::Ref<MatrixType, 0, EigenDStride>;
template <typename MatrixType>
using EigenDMap = Eigen::Map<MatrixType, 0, EigenDStride>;
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
#if EIGEN_VERSION_AT_LEAST(3,3,0) #if EIGEN_VERSION_AT_LEAST(3, 3, 0)
using EigenIndex = Eigen::Index; using EigenIndex = Eigen::Index;
template<typename Scalar, int Flags, typename StorageIndex> template <typename Scalar, int Flags, typename StorageIndex>
using EigenMapSparseMatrix = Eigen::Map<Eigen::SparseMatrix<Scalar, Flags, StorageIndex>>; using EigenMapSparseMatrix = Eigen::Map<Eigen::SparseMatrix<Scalar, Flags, StorageIndex>>;
#else #else
using EigenIndex = EIGEN_DEFAULT_DENSE_INDEX_TYPE; using EigenIndex = EIGEN_DEFAULT_DENSE_INDEX_TYPE;
template<typename Scalar, int Flags, typename StorageIndex> template <typename Scalar, int Flags, typename StorageIndex>
using EigenMapSparseMatrix = Eigen::MappedSparseMatrix<Scalar, Flags, StorageIndex>; using EigenMapSparseMatrix = Eigen::MappedSparseMatrix<Scalar, Flags, StorageIndex>;
#endif #endif
// Matches Eigen::Map, Eigen::Ref, blocks, etc: // Matches Eigen::Map, Eigen::Ref, blocks, etc:
template <typename T> using is_eigen_dense_map = all_of<is_template_base_of<Eigen::DenseBase, T>, std::is_base_of<Eigen::MapBase<T, Eigen::ReadOnlyAccessors>, T>>; template <typename T>
template <typename T> using is_eigen_mutable_map = std::is_base_of<Eigen::MapBase<T, Eigen::WriteAccessors>, T>; using is_eigen_dense_map = all_of<is_template_base_of<Eigen::DenseBase, T>,
template <typename T> using is_eigen_dense_plain = all_of<negation<is_eigen_dense_map<T>>, is_template_base_of<Eigen::PlainObjectBase, T>>; std::is_base_of<Eigen::MapBase<T, Eigen::ReadOnlyAccessors>, T>>;
template <typename T> using is_eigen_sparse = is_template_base_of<Eigen::SparseMatrixBase, T>; template <typename T>
using is_eigen_mutable_map = std::is_base_of<Eigen::MapBase<T, Eigen::WriteAccessors>, T>;
template <typename T>
using is_eigen_dense_plain
= all_of<negation<is_eigen_dense_map<T>>, is_template_base_of<Eigen::PlainObjectBase, T>>;
template <typename T>
using is_eigen_sparse = is_template_base_of<Eigen::SparseMatrixBase, T>;
// Test for objects inheriting from EigenBase<Derived> that aren't captured by the above. This // Test for objects inheriting from EigenBase<Derived> that aren't captured by the above. This
// basically covers anything that can be assigned to a dense matrix but that don't have a typical // basically covers anything that can be assigned to a dense matrix but that don't have a typical
// matrix data layout that can be copied from their .data(). For example, DiagonalMatrix and // matrix data layout that can be copied from their .data(). For example, DiagonalMatrix and
// SelfAdjointView fall into this category. // SelfAdjointView fall into this category.
template <typename T> using is_eigen_other = all_of< template <typename T>
is_template_base_of<Eigen::EigenBase, T>, using is_eigen_other
negation<any_of<is_eigen_dense_map<T>, is_eigen_dense_plain<T>, is_eigen_sparse<T>>> = all_of<is_template_base_of<Eigen::EigenBase, T>,
>; negation<any_of<is_eigen_dense_map<T>, is_eigen_dense_plain<T>, is_eigen_sparse<T>>>>;
// Captures numpy/eigen conformability status (returned by EigenProps::conformable()): // Captures numpy/eigen conformability status (returned by EigenProps::conformable()):
template <bool EigenRowMajor> struct EigenConformable { template <bool EigenRowMajor>
struct EigenConformable {
bool conformable = false; bool conformable = false;
EigenIndex rows = 0, cols = 0; EigenIndex rows = 0, cols = 0;
EigenDStride stride{0, 0}; // Only valid if negativestrides is false! EigenDStride stride{0, 0}; // Only valid if negativestrides is false!
bool negativestrides = false; // If true, do not use stride! bool negativestrides = false; // If true, do not use stride!
// NOLINTNEXTLINE(google-explicit-constructor) // NOLINTNEXTLINE(google-explicit-constructor)
EigenConformable(bool fits = false) : conformable{fits} {} EigenConformable(bool fits = false) : conformable{fits} {}
// Matrix type: // Matrix type:
EigenConformable(EigenIndex r, EigenIndex c, EigenConformable(EigenIndex r, EigenIndex c, EigenIndex rstride, EigenIndex cstride)
EigenIndex rstride, EigenIndex cstride) : : conformable{true}, rows{r}, cols{c},
conformable{true}, rows{r}, cols{c}, // TODO: when Eigen bug #747 is fixed, remove the tests for non-negativity.
//TODO: when Eigen bug #747 is fixed, remove the tests for non-negativity. http://eigen.tuxfamily.org/bz/show_bug.cgi?id=747 // http://eigen.tuxfamily.org/bz/show_bug.cgi?id=747
stride{EigenRowMajor ? (rstride > 0 ? rstride : 0) : (cstride > 0 ? cstride : 0) /* outer stride */, stride{EigenRowMajor ? (rstride > 0 ? rstride : 0)
EigenRowMajor ? (cstride > 0 ? cstride : 0) : (rstride > 0 ? rstride : 0) /* inner stride */ }, : (cstride > 0 ? cstride : 0) /* outer stride */,
negativestrides{rstride < 0 || cstride < 0} { EigenRowMajor ? (cstride > 0 ? cstride : 0)
: (rstride > 0 ? rstride : 0) /* inner stride */},
} negativestrides{rstride < 0 || cstride < 0} {}
// Vector type: // Vector type:
EigenConformable(EigenIndex r, EigenIndex c, EigenIndex stride) EigenConformable(EigenIndex r, EigenIndex c, EigenIndex stride)
: EigenConformable(r, c, r == 1 ? c*stride : stride, c == 1 ? r : r*stride) {} : EigenConformable(r, c, r == 1 ? c * stride : stride, c == 1 ? r : r * stride) {}
template <typename props> bool stride_compatible() const { template <typename props>
bool stride_compatible() const {
// To have compatible strides, we need (on both dimensions) one of fully dynamic strides, // To have compatible strides, we need (on both dimensions) one of fully dynamic strides,
// matching strides, or a dimension size of 1 (in which case the stride value is irrelevant) // matching strides, or a dimension size of 1 (in which case the stride value is
return // irrelevant). Alternatively, if any dimension size is 0, the strides are not relevant
!negativestrides && // (and numpy ≥ 1.23 sets the strides to 0 in that case, so we need to check explicitly).
(props::inner_stride == Eigen::Dynamic || props::inner_stride == stride.inner() || if (negativestrides) {
(EigenRowMajor ? cols : rows) == 1) && return false;
(props::outer_stride == Eigen::Dynamic || props::outer_stride == stride.outer() || }
(EigenRowMajor ? rows : cols) == 1); if (rows == 0 || cols == 0) {
return true;
}
return (props::inner_stride == Eigen::Dynamic || props::inner_stride == stride.inner()
|| (EigenRowMajor ? cols : rows) == 1)
&& (props::outer_stride == Eigen::Dynamic || props::outer_stride == stride.outer()
|| (EigenRowMajor ? rows : cols) == 1);
} }
// NOLINTNEXTLINE(google-explicit-constructor) // NOLINTNEXTLINE(google-explicit-constructor)
operator bool() const { return conformable; } operator bool() const { return conformable; }
}; };
template <typename Type> struct eigen_extract_stride { using type = Type; }; template <typename Type>
struct eigen_extract_stride {
using type = Type;
};
template <typename PlainObjectType, int MapOptions, typename StrideType> template <typename PlainObjectType, int MapOptions, typename StrideType>
struct eigen_extract_stride<Eigen::Map<PlainObjectType, MapOptions, StrideType>> { using type = StrideType; }; struct eigen_extract_stride<Eigen::Map<PlainObjectType, MapOptions, StrideType>> {
using type = StrideType;
};
template <typename PlainObjectType, int Options, typename StrideType> template <typename PlainObjectType, int Options, typename StrideType>
struct eigen_extract_stride<Eigen::Ref<PlainObjectType, Options, StrideType>> { using type = StrideType; }; struct eigen_extract_stride<Eigen::Ref<PlainObjectType, Options, StrideType>> {
using type = StrideType;
};
// Helper struct for extracting information from an Eigen type // Helper struct for extracting information from an Eigen type
template <typename Type_> struct EigenProps { template <typename Type_>
struct EigenProps {
using Type = Type_; using Type = Type_;
using Scalar = typename Type::Scalar; using Scalar = typename Type::Scalar;
using StrideType = typename eigen_extract_stride<Type>::type; using StrideType = typename eigen_extract_stride<Type>::type;
static constexpr EigenIndex static constexpr EigenIndex rows = Type::RowsAtCompileTime, cols = Type::ColsAtCompileTime,
rows = Type::RowsAtCompileTime, size = Type::SizeAtCompileTime;
cols = Type::ColsAtCompileTime, static constexpr bool row_major = Type::IsRowMajor,
size = Type::SizeAtCompileTime; vector
static constexpr bool = Type::IsVectorAtCompileTime, // At least one dimension has fixed size 1
row_major = Type::IsRowMajor, fixed_rows = rows != Eigen::Dynamic, fixed_cols = cols != Eigen::Dynamic,
vector = Type::IsVectorAtCompileTime, // At least one dimension has fixed size 1 fixed = size != Eigen::Dynamic, // Fully-fixed size
fixed_rows = rows != Eigen::Dynamic, dynamic = !fixed_rows && !fixed_cols; // Fully-dynamic size
fixed_cols = cols != Eigen::Dynamic,
fixed = size != Eigen::Dynamic, // Fully-fixed size
dynamic = !fixed_rows && !fixed_cols; // Fully-dynamic size
template <EigenIndex i, EigenIndex ifzero> using if_zero = std::integral_constant<EigenIndex, i == 0 ? ifzero : i>; template <EigenIndex i, EigenIndex ifzero>
static constexpr EigenIndex inner_stride = if_zero<StrideType::InnerStrideAtCompileTime, 1>::value, using if_zero = std::integral_constant<EigenIndex, i == 0 ? ifzero : i>;
outer_stride = if_zero<StrideType::OuterStrideAtCompileTime, static constexpr EigenIndex inner_stride
vector ? size : row_major ? cols : rows>::value; = if_zero<StrideType::InnerStrideAtCompileTime, 1>::value,
static constexpr bool dynamic_stride = inner_stride == Eigen::Dynamic && outer_stride == Eigen::Dynamic; outer_stride = if_zero < StrideType::OuterStrideAtCompileTime,
static constexpr bool requires_row_major = !dynamic_stride && !vector && (row_major ? inner_stride : outer_stride) == 1; vector ? size
static constexpr bool requires_col_major = !dynamic_stride && !vector && (row_major ? outer_stride : inner_stride) == 1; : row_major ? cols
: rows > ::value;
static constexpr bool dynamic_stride
= inner_stride == Eigen::Dynamic && outer_stride == Eigen::Dynamic;
static constexpr bool requires_row_major
= !dynamic_stride && !vector && (row_major ? inner_stride : outer_stride) == 1;
static constexpr bool requires_col_major
= !dynamic_stride && !vector && (row_major ? outer_stride : inner_stride) == 1;
// Takes an input array and determines whether we can make it fit into the Eigen type. If // Takes an input array and determines whether we can make it fit into the Eigen type. If
// the array is a vector, we attempt to fit it into either an Eigen 1xN or Nx1 vector // the array is a vector, we attempt to fit it into either an Eigen 1xN or Nx1 vector
// (preferring the latter if it will fit in either, i.e. for a fully dynamic matrix type). // (preferring the latter if it will fit in either, i.e. for a fully dynamic matrix type).
static EigenConformable<row_major> conformable(const array &a) { static EigenConformable<row_major> conformable(const array &a) {
const auto dims = a.ndim(); const auto dims = a.ndim();
if (dims < 1 || dims > 2) if (dims < 1 || dims > 2) {
return false; return false;
}
if (dims == 2) { // Matrix type: require exact match (or dynamic) if (dims == 2) { // Matrix type: require exact match (or dynamic)
EigenIndex EigenIndex np_rows = a.shape(0), np_cols = a.shape(1),
np_rows = a.shape(0), np_rstride = a.strides(0) / static_cast<ssize_t>(sizeof(Scalar)),
np_cols = a.shape(1), np_cstride = a.strides(1) / static_cast<ssize_t>(sizeof(Scalar));
np_rstride = a.strides(0) / static_cast<ssize_t>(sizeof(Scalar)), if ((PYBIND11_SILENCE_MSVC_C4127(fixed_rows) && np_rows != rows)
np_cstride = a.strides(1) / static_cast<ssize_t>(sizeof(Scalar)); || (PYBIND11_SILENCE_MSVC_C4127(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 false;
}
return {np_rows, np_cols, np_rstride, np_cstride}; return {np_rows, np_cols, np_rstride, np_cstride};
} }
// Otherwise we're storing an n-vector. Only one of the strides will be used, but whichever // Otherwise we're storing an n-vector. Only one of the strides will be used, but
// is used, we want the (single) numpy stride value. // whichever is used, we want the (single) numpy stride value.
const EigenIndex n = a.shape(0), const EigenIndex n = a.shape(0),
stride = a.strides(0) / static_cast<ssize_t>(sizeof(Scalar)); stride = a.strides(0) / static_cast<ssize_t>(sizeof(Scalar));
if (vector) { // Eigen type is a compile-time vector if (vector) { // Eigen type is a compile-time vector
if (PYBIND11_SILENCE_MSVC_C4127(fixed) && size != n) if (PYBIND11_SILENCE_MSVC_C4127(fixed) && size != n) {
return false; // Vector size mismatch return false; // Vector size mismatch
}
return {rows == 1 ? 1 : n, cols == 1 ? 1 : n, stride}; return {rows == 1 ? 1 : n, cols == 1 ? 1 : n, stride};
} }
if (fixed) { if (fixed) {
@ -179,48 +211,59 @@ template <typename Type_> struct EigenProps {
if (fixed_cols) { if (fixed_cols) {
// Since this isn't a vector, cols must be != 1. We allow this only if it exactly // Since this isn't a vector, cols must be != 1. We allow this only if it exactly
// equals the number of elements (rows is Dynamic, and so 1 row is allowed). // equals the number of elements (rows is Dynamic, and so 1 row is allowed).
if (cols != n) return false; if (cols != n) {
return false;
}
return {1, n, stride}; return {1, n, stride};
} // Otherwise it's either fully dynamic, or column dynamic; both become a column vector } // Otherwise it's either fully dynamic, or column dynamic; both become a column vector
if (PYBIND11_SILENCE_MSVC_C4127(fixed_rows) && rows != n) return false; if (PYBIND11_SILENCE_MSVC_C4127(fixed_rows) && rows != n) {
return {n, 1, stride}; return false;
}
return {n, 1, stride};
} }
static constexpr bool show_writeable = is_eigen_dense_map<Type>::value && is_eigen_mutable_map<Type>::value; static constexpr bool show_writeable
= is_eigen_dense_map<Type>::value && is_eigen_mutable_map<Type>::value;
static constexpr bool show_order = is_eigen_dense_map<Type>::value; static constexpr bool show_order = is_eigen_dense_map<Type>::value;
static constexpr bool show_c_contiguous = show_order && requires_row_major; static constexpr bool show_c_contiguous = show_order && requires_row_major;
static constexpr bool show_f_contiguous = !show_c_contiguous && show_order && requires_col_major; static constexpr bool show_f_contiguous
= !show_c_contiguous && show_order && requires_col_major;
static constexpr auto descriptor = static constexpr auto descriptor
const_name("numpy.ndarray[") + npy_format_descriptor<Scalar>::name + = const_name("numpy.ndarray[") + npy_format_descriptor<Scalar>::name + const_name("[")
const_name("[") + const_name<fixed_rows>(const_name<(size_t) rows>(), const_name("m")) + + const_name<fixed_rows>(const_name<(size_t) rows>(), const_name("m")) + const_name(", ")
const_name(", ") + const_name<fixed_cols>(const_name<(size_t) cols>(), const_name("n")) + + const_name<fixed_cols>(const_name<(size_t) cols>(), const_name("n")) + const_name("]")
const_name("]") + +
// For a reference type (e.g. Ref<MatrixXd>) we have other constraints that might need to be // For a reference type (e.g. Ref<MatrixXd>) we have other constraints that might need to
// satisfied: writeable=True (for a mutable reference), and, depending on the map's stride // be satisfied: writeable=True (for a mutable reference), and, depending on the map's
// options, possibly f_contiguous or c_contiguous. We include them in the descriptor output // stride options, possibly f_contiguous or c_contiguous. We include them in the
// to provide some hint as to why a TypeError is occurring (otherwise it can be confusing to // descriptor output to provide some hint as to why a TypeError is occurring (otherwise
// see that a function accepts a 'numpy.ndarray[float64[3,2]]' and an error message that you // it can be confusing to see that a function accepts a 'numpy.ndarray[float64[3,2]]' and
// *gave* a numpy.ndarray of the right type and dimensions. // an error message that you *gave* a numpy.ndarray of the right type and dimensions.
const_name<show_writeable>(", flags.writeable", "") + const_name<show_writeable>(", flags.writeable", "")
const_name<show_c_contiguous>(", flags.c_contiguous", "") + + const_name<show_c_contiguous>(", flags.c_contiguous", "")
const_name<show_f_contiguous>(", flags.f_contiguous", "") + + const_name<show_f_contiguous>(", flags.f_contiguous", "") + const_name("]");
const_name("]");
}; };
// Casts an Eigen type to numpy array. If given a base, the numpy array references the src data, // Casts an Eigen type to numpy array. If given a base, the numpy array references the src data,
// otherwise it'll make a copy. writeable lets you turn off the writeable flag for the array. // otherwise it'll make a copy. writeable lets you turn off the writeable flag for the array.
template <typename props> handle eigen_array_cast(typename props::Type const &src, handle base = handle(), bool writeable = true) { template <typename props>
handle
eigen_array_cast(typename props::Type const &src, handle base = handle(), bool writeable = true) {
constexpr ssize_t elem_size = sizeof(typename props::Scalar); constexpr ssize_t elem_size = sizeof(typename props::Scalar);
array a; array a;
if (props::vector) if (props::vector) {
a = array({ src.size() }, { elem_size * src.innerStride() }, src.data(), base); a = array({src.size()}, {elem_size * src.innerStride()}, src.data(), base);
else } else {
a = array({ src.rows(), src.cols() }, { elem_size * src.rowStride(), elem_size * src.colStride() }, a = array({src.rows(), src.cols()},
src.data(), base); {elem_size * src.rowStride(), elem_size * src.colStride()},
src.data(),
base);
}
if (!writeable) if (!writeable) {
array_proxy(a.ptr())->flags &= ~detail::npy_api::NPY_ARRAY_WRITEABLE_; array_proxy(a.ptr())->flags &= ~detail::npy_api::NPY_ARRAY_WRITEABLE_;
}
return a.release(); return a.release();
} }
@ -236,10 +279,10 @@ handle eigen_ref_array(Type &src, handle parent = none()) {
return eigen_array_cast<props>(src, parent, !std::is_const<Type>::value); return eigen_array_cast<props>(src, parent, !std::is_const<Type>::value);
} }
// Takes a pointer to some dense, plain Eigen type, builds a capsule around it, then returns a numpy // Takes a pointer to some dense, plain Eigen type, builds a capsule around it, then returns a
// array that references the encapsulated data with a python-side reference to the capsule to tie // numpy array that references the encapsulated data with a python-side reference to the capsule to
// its destruction to that of any dependent python objects. Const-ness is determined by whether or // tie its destruction to that of any dependent python objects. Const-ness is determined by
// not the Type of the pointer given is const. // whether or not the Type of the pointer given is const.
template <typename props, typename Type, typename = enable_if_t<is_eigen_dense_plain<Type>::value>> template <typename props, typename Type, typename = enable_if_t<is_eigen_dense_plain<Type>::value>>
handle eigen_encapsulate(Type *src) { handle eigen_encapsulate(Type *src) {
capsule base(src, [](void *o) { delete static_cast<Type *>(o); }); capsule base(src, [](void *o) { delete static_cast<Type *>(o); });
@ -248,35 +291,42 @@ handle eigen_encapsulate(Type *src) {
// Type caster for regular, dense matrix types (e.g. MatrixXd), but not maps/refs/etc. of dense // Type caster for regular, dense matrix types (e.g. MatrixXd), but not maps/refs/etc. of dense
// types. // types.
template<typename Type> template <typename Type>
struct type_caster<Type, enable_if_t<is_eigen_dense_plain<Type>::value>> { struct type_caster<Type, enable_if_t<is_eigen_dense_plain<Type>::value>> {
using Scalar = typename Type::Scalar; using Scalar = typename Type::Scalar;
using props = EigenProps<Type>; using props = EigenProps<Type>;
bool load(handle src, bool convert) { bool load(handle src, bool convert) {
// If we're in no-convert mode, only load if given an array of the correct type // If we're in no-convert mode, only load if given an array of the correct type
if (!convert && !isinstance<array_t<Scalar>>(src)) if (!convert && !isinstance<array_t<Scalar>>(src)) {
return false; return false;
}
// Coerce into an array, but don't do type conversion yet; the copy below handles it. // Coerce into an array, but don't do type conversion yet; the copy below handles it.
auto buf = array::ensure(src); auto buf = array::ensure(src);
if (!buf) if (!buf) {
return false; return false;
}
auto dims = buf.ndim(); auto dims = buf.ndim();
if (dims < 1 || dims > 2) if (dims < 1 || dims > 2) {
return false; return false;
}
auto fits = props::conformable(buf); auto fits = props::conformable(buf);
if (!fits) if (!fits) {
return false; return false;
}
// Allocate the new type, then build a numpy reference into it // Allocate the new type, then build a numpy reference into it
value = Type(fits.rows, fits.cols); value = Type(fits.rows, fits.cols);
auto ref = reinterpret_steal<array>(eigen_ref_array<props>(value)); auto ref = reinterpret_steal<array>(eigen_ref_array<props>(value));
if (dims == 1) ref = ref.squeeze(); if (dims == 1) {
else if (ref.ndim() == 1) buf = buf.squeeze(); ref = ref.squeeze();
} else if (ref.ndim() == 1) {
buf = buf.squeeze();
}
int result = detail::npy_api::get().PyArray_CopyInto_(ref.ptr(), buf.ptr()); int result = detail::npy_api::get().PyArray_CopyInto_(ref.ptr(), buf.ptr());
@ -289,7 +339,6 @@ struct type_caster<Type, enable_if_t<is_eigen_dense_plain<Type>::value>> {
} }
private: private:
// Cast implementation // Cast implementation
template <typename CType> template <typename CType>
static handle cast_impl(CType *src, return_value_policy policy, handle parent) { static handle cast_impl(CType *src, return_value_policy policy, handle parent) {
@ -312,7 +361,6 @@ private:
} }
public: public:
// Normal returned non-reference, non-const value: // Normal returned non-reference, non-const value:
static handle cast(Type &&src, return_value_policy /* policy */, handle parent) { static handle cast(Type &&src, return_value_policy /* policy */, handle parent) {
return cast_impl(&src, return_value_policy::move, parent); return cast_impl(&src, return_value_policy::move, parent);
@ -323,14 +371,18 @@ public:
} }
// lvalue reference return; default (automatic) becomes copy // lvalue reference return; default (automatic) becomes copy
static handle cast(Type &src, return_value_policy policy, handle parent) { static handle cast(Type &src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic || policy == return_value_policy::automatic_reference) if (policy == return_value_policy::automatic
|| policy == return_value_policy::automatic_reference) {
policy = return_value_policy::copy; policy = return_value_policy::copy;
}
return cast_impl(&src, policy, parent); return cast_impl(&src, policy, parent);
} }
// const lvalue reference return; default (automatic) becomes copy // const lvalue reference return; default (automatic) becomes copy
static handle cast(const Type &src, return_value_policy policy, handle parent) { static handle cast(const Type &src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic || policy == return_value_policy::automatic_reference) if (policy == return_value_policy::automatic
|| policy == return_value_policy::automatic_reference) {
policy = return_value_policy::copy; policy = return_value_policy::copy;
}
return cast(&src, policy, parent); return cast(&src, policy, parent);
} }
// non-const pointer return // non-const pointer return
@ -345,30 +397,31 @@ public:
static constexpr auto name = props::descriptor; static constexpr auto name = props::descriptor;
// NOLINTNEXTLINE(google-explicit-constructor) // NOLINTNEXTLINE(google-explicit-constructor)
operator Type*() { return &value; } operator Type *() { return &value; }
// NOLINTNEXTLINE(google-explicit-constructor) // NOLINTNEXTLINE(google-explicit-constructor)
operator Type&() { return value; } operator Type &() { return value; }
// NOLINTNEXTLINE(google-explicit-constructor) // NOLINTNEXTLINE(google-explicit-constructor)
operator Type&&() && { return std::move(value); } operator Type &&() && { return std::move(value); }
template <typename T> using cast_op_type = movable_cast_op_type<T>; template <typename T>
using cast_op_type = movable_cast_op_type<T>;
private: private:
Type value; Type value;
}; };
// Base class for casting reference/map/block/etc. objects back to python. // Base class for casting reference/map/block/etc. objects back to python.
template <typename MapType> struct eigen_map_caster { template <typename MapType>
struct eigen_map_caster {
private: private:
using props = EigenProps<MapType>; using props = EigenProps<MapType>;
public: public:
// Directly referencing a ref/map's data is a bit dangerous (whatever the map/ref points to has // Directly referencing a ref/map's data is a bit dangerous (whatever the map/ref points to has
// to stay around), but we'll allow it under the assumption that you know what you're doing (and // to stay around), but we'll allow it under the assumption that you know what you're doing
// have an appropriate keep_alive in place). We return a numpy array pointing directly at the // (and have an appropriate keep_alive in place). We return a numpy array pointing directly at
// ref's data (The numpy array ends up read-only if the ref was to a const matrix type.) Note // the ref's data (The numpy array ends up read-only if the ref was to a const matrix type.)
// that this means you need to ensure you don't destroy the object in some other way (e.g. with // Note that this means you need to ensure you don't destroy the object in some other way (e.g.
// an appropriate keep_alive, or with a reference to a statically allocated matrix). // with an appropriate keep_alive, or with a reference to a statically allocated matrix).
static handle cast(const MapType &src, return_value_policy policy, handle parent) { static handle cast(const MapType &src, return_value_policy policy, handle parent) {
switch (policy) { switch (policy) {
case return_value_policy::copy: case return_value_policy::copy:
@ -392,43 +445,50 @@ public:
// you end up here if you try anyway. // you end up here if you try anyway.
bool load(handle, bool) = delete; bool load(handle, bool) = delete;
operator MapType() = delete; operator MapType() = delete;
template <typename> using cast_op_type = MapType; template <typename>
using cast_op_type = MapType;
}; };
// We can return any map-like object (but can only load Refs, specialized next): // We can return any map-like object (but can only load Refs, specialized next):
template <typename Type> struct type_caster<Type, enable_if_t<is_eigen_dense_map<Type>::value>> template <typename Type>
: eigen_map_caster<Type> {}; struct type_caster<Type, enable_if_t<is_eigen_dense_map<Type>::value>> : eigen_map_caster<Type> {};
// Loader for Ref<...> arguments. See the documentation for info on how to make this work without // Loader for Ref<...> arguments. See the documentation for info on how to make this work without
// copying (it requires some extra effort in many cases). // copying (it requires some extra effort in many cases).
template <typename PlainObjectType, typename StrideType> template <typename PlainObjectType, typename StrideType>
struct type_caster< struct type_caster<
Eigen::Ref<PlainObjectType, 0, StrideType>, Eigen::Ref<PlainObjectType, 0, StrideType>,
enable_if_t<is_eigen_dense_map<Eigen::Ref<PlainObjectType, 0, StrideType>>::value> enable_if_t<is_eigen_dense_map<Eigen::Ref<PlainObjectType, 0, StrideType>>::value>>
> : public eigen_map_caster<Eigen::Ref<PlainObjectType, 0, StrideType>> { : public eigen_map_caster<Eigen::Ref<PlainObjectType, 0, StrideType>> {
private: private:
using Type = Eigen::Ref<PlainObjectType, 0, StrideType>; using Type = Eigen::Ref<PlainObjectType, 0, StrideType>;
using props = EigenProps<Type>; using props = EigenProps<Type>;
using Scalar = typename props::Scalar; using Scalar = typename props::Scalar;
using MapType = Eigen::Map<PlainObjectType, 0, StrideType>; using MapType = Eigen::Map<PlainObjectType, 0, StrideType>;
using Array = array_t<Scalar, array::forcecast | using Array
((props::row_major ? props::inner_stride : props::outer_stride) == 1 ? array::c_style : = array_t<Scalar,
(props::row_major ? props::outer_stride : props::inner_stride) == 1 ? array::f_style : 0)>; array::forcecast
| ((props::row_major ? props::inner_stride : props::outer_stride) == 1
? array::c_style
: (props::row_major ? props::outer_stride : props::inner_stride) == 1
? array::f_style
: 0)>;
static constexpr bool need_writeable = is_eigen_mutable_map<Type>::value; static constexpr bool need_writeable = is_eigen_mutable_map<Type>::value;
// Delay construction (these have no default constructor) // Delay construction (these have no default constructor)
std::unique_ptr<MapType> map; std::unique_ptr<MapType> map;
std::unique_ptr<Type> ref; std::unique_ptr<Type> ref;
// Our array. When possible, this is just a numpy array pointing to the source data, but // Our array. When possible, this is just a numpy array pointing to the source data, but
// sometimes we can't avoid copying (e.g. input is not a numpy array at all, has an incompatible // sometimes we can't avoid copying (e.g. input is not a numpy array at all, has an
// layout, or is an array of a type that needs to be converted). Using a numpy temporary // incompatible layout, or is an array of a type that needs to be converted). Using a numpy
// (rather than an Eigen temporary) saves an extra copy when we need both type conversion and // temporary (rather than an Eigen temporary) saves an extra copy when we need both type
// storage order conversion. (Note that we refuse to use this temporary copy when loading an // conversion and storage order conversion. (Note that we refuse to use this temporary copy
// argument for a Ref<M> with M non-const, i.e. a read-write reference). // when loading an argument for a Ref<M> with M non-const, i.e. a read-write reference).
Array copy_or_ref; Array copy_or_ref;
public: public:
bool load(handle src, bool convert) { bool load(handle src, bool convert) {
// First check whether what we have is already an array of the right type. If not, we can't // First check whether what we have is already an array of the right type. If not, we
// avoid a copy (because the copy is also going to do type conversion). // can't avoid a copy (because the copy is also going to do type conversion).
bool need_copy = !isinstance<Array>(src); bool need_copy = !isinstance<Array>(src);
EigenConformable<props::row_major> fits; EigenConformable<props::row_major> fits;
@ -439,13 +499,15 @@ public:
if (aref && (!need_writeable || aref.writeable())) { if (aref && (!need_writeable || aref.writeable())) {
fits = props::conformable(aref); fits = props::conformable(aref);
if (!fits) return false; // Incompatible dimensions if (!fits) {
if (!fits.template stride_compatible<props>()) return false; // Incompatible dimensions
}
if (!fits.template stride_compatible<props>()) {
need_copy = true; need_copy = true;
else } else {
copy_or_ref = std::move(aref); copy_or_ref = std::move(aref);
} }
else { } else {
need_copy = true; need_copy = true;
} }
} }
@ -454,66 +516,93 @@ public:
// We need to copy: If we need a mutable reference, or we're not supposed to convert // We need to copy: If we need a mutable reference, or we're not supposed to convert
// (either because we're in the no-convert overload pass, or because we're explicitly // (either because we're in the no-convert overload pass, or because we're explicitly
// instructed not to copy (via `py::arg().noconvert()`) we have to fail loading. // instructed not to copy (via `py::arg().noconvert()`) we have to fail loading.
if (!convert || need_writeable) return false; if (!convert || need_writeable) {
return false;
}
Array copy = Array::ensure(src); Array copy = Array::ensure(src);
if (!copy) return false; if (!copy) {
fits = props::conformable(copy);
if (!fits || !fits.template stride_compatible<props>())
return false; return false;
}
fits = props::conformable(copy);
if (!fits || !fits.template stride_compatible<props>()) {
return false;
}
copy_or_ref = std::move(copy); copy_or_ref = std::move(copy);
loader_life_support::add_patient(copy_or_ref); loader_life_support::add_patient(copy_or_ref);
} }
ref.reset(); ref.reset();
map.reset(new MapType(data(copy_or_ref), fits.rows, fits.cols, make_stride(fits.stride.outer(), fits.stride.inner()))); map.reset(new MapType(data(copy_or_ref),
fits.rows,
fits.cols,
make_stride(fits.stride.outer(), fits.stride.inner())));
ref.reset(new Type(*map)); ref.reset(new Type(*map));
return true; return true;
} }
// NOLINTNEXTLINE(google-explicit-constructor) // NOLINTNEXTLINE(google-explicit-constructor)
operator Type*() { return ref.get(); } operator Type *() { return ref.get(); }
// NOLINTNEXTLINE(google-explicit-constructor) // NOLINTNEXTLINE(google-explicit-constructor)
operator Type&() { return *ref; } operator Type &() { return *ref; }
template <typename _T> using cast_op_type = pybind11::detail::cast_op_type<_T>; template <typename _T>
using cast_op_type = pybind11::detail::cast_op_type<_T>;
private: private:
template <typename T = Type, enable_if_t<is_eigen_mutable_map<T>::value, int> = 0> template <typename T = Type, enable_if_t<is_eigen_mutable_map<T>::value, int> = 0>
Scalar *data(Array &a) { return a.mutable_data(); } Scalar *data(Array &a) {
return a.mutable_data();
}
template <typename T = Type, enable_if_t<!is_eigen_mutable_map<T>::value, int> = 0> template <typename T = Type, enable_if_t<!is_eigen_mutable_map<T>::value, int> = 0>
const Scalar *data(Array &a) { return a.data(); } const Scalar *data(Array &a) {
return a.data();
}
// Attempt to figure out a constructor of `Stride` that will work. // Attempt to figure out a constructor of `Stride` that will work.
// If both strides are fixed, use a default constructor: // If both strides are fixed, use a default constructor:
template <typename S> using stride_ctor_default = bool_constant< template <typename S>
S::InnerStrideAtCompileTime != Eigen::Dynamic && S::OuterStrideAtCompileTime != Eigen::Dynamic && using stride_ctor_default = bool_constant<S::InnerStrideAtCompileTime != Eigen::Dynamic
std::is_default_constructible<S>::value>; && S::OuterStrideAtCompileTime != Eigen::Dynamic
&& std::is_default_constructible<S>::value>;
// Otherwise, if there is a two-index constructor, assume it is (outer,inner) like // Otherwise, if there is a two-index constructor, assume it is (outer,inner) like
// Eigen::Stride, and use it: // Eigen::Stride, and use it:
template <typename S> using stride_ctor_dual = bool_constant< template <typename S>
!stride_ctor_default<S>::value && std::is_constructible<S, EigenIndex, EigenIndex>::value>; using stride_ctor_dual
= bool_constant<!stride_ctor_default<S>::value
&& std::is_constructible<S, EigenIndex, EigenIndex>::value>;
// Otherwise, if there is a one-index constructor, and just one of the strides is dynamic, use // Otherwise, if there is a one-index constructor, and just one of the strides is dynamic, use
// it (passing whichever stride is dynamic). // it (passing whichever stride is dynamic).
template <typename S> using stride_ctor_outer = bool_constant< template <typename S>
!any_of<stride_ctor_default<S>, stride_ctor_dual<S>>::value && using stride_ctor_outer
S::OuterStrideAtCompileTime == Eigen::Dynamic && S::InnerStrideAtCompileTime != Eigen::Dynamic && = bool_constant<!any_of<stride_ctor_default<S>, stride_ctor_dual<S>>::value
std::is_constructible<S, EigenIndex>::value>; && S::OuterStrideAtCompileTime == Eigen::Dynamic
template <typename S> using stride_ctor_inner = bool_constant< && S::InnerStrideAtCompileTime != Eigen::Dynamic
!any_of<stride_ctor_default<S>, stride_ctor_dual<S>>::value && && std::is_constructible<S, EigenIndex>::value>;
S::InnerStrideAtCompileTime == Eigen::Dynamic && S::OuterStrideAtCompileTime != Eigen::Dynamic && template <typename S>
std::is_constructible<S, EigenIndex>::value>; using stride_ctor_inner
= bool_constant<!any_of<stride_ctor_default<S>, stride_ctor_dual<S>>::value
&& S::InnerStrideAtCompileTime == Eigen::Dynamic
&& S::OuterStrideAtCompileTime != Eigen::Dynamic
&& std::is_constructible<S, EigenIndex>::value>;
template <typename S = StrideType, enable_if_t<stride_ctor_default<S>::value, int> = 0> template <typename S = StrideType, enable_if_t<stride_ctor_default<S>::value, int> = 0>
static S make_stride(EigenIndex, EigenIndex) { return S(); } static S make_stride(EigenIndex, EigenIndex) {
return S();
}
template <typename S = StrideType, enable_if_t<stride_ctor_dual<S>::value, int> = 0> template <typename S = StrideType, enable_if_t<stride_ctor_dual<S>::value, int> = 0>
static S make_stride(EigenIndex outer, EigenIndex inner) { return S(outer, inner); } static S make_stride(EigenIndex outer, EigenIndex inner) {
return S(outer, inner);
}
template <typename S = StrideType, enable_if_t<stride_ctor_outer<S>::value, int> = 0> template <typename S = StrideType, enable_if_t<stride_ctor_outer<S>::value, int> = 0>
static S make_stride(EigenIndex outer, EigenIndex) { return S(outer); } static S make_stride(EigenIndex outer, EigenIndex) {
return S(outer);
}
template <typename S = StrideType, enable_if_t<stride_ctor_inner<S>::value, int> = 0> template <typename S = StrideType, enable_if_t<stride_ctor_inner<S>::value, int> = 0>
static S make_stride(EigenIndex, EigenIndex inner) { return S(inner); } static S make_stride(EigenIndex, EigenIndex inner) {
return S(inner);
}
}; };
// type_caster for special matrix types (e.g. DiagonalMatrix), which are EigenBase, but not // type_caster for special matrix types (e.g. DiagonalMatrix), which are EigenBase, but not
@ -523,14 +612,18 @@ private:
template <typename Type> template <typename Type>
struct type_caster<Type, enable_if_t<is_eigen_other<Type>::value>> { struct type_caster<Type, enable_if_t<is_eigen_other<Type>::value>> {
protected: protected:
using Matrix = Eigen::Matrix<typename Type::Scalar, Type::RowsAtCompileTime, Type::ColsAtCompileTime>; using Matrix
= Eigen::Matrix<typename Type::Scalar, Type::RowsAtCompileTime, Type::ColsAtCompileTime>;
using props = EigenProps<Matrix>; using props = EigenProps<Matrix>;
public: 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 */) {
handle h = eigen_encapsulate<props>(new Matrix(src)); handle h = eigen_encapsulate<props>(new Matrix(src));
return h; return h;
} }
static handle cast(const Type *src, return_value_policy policy, handle parent) { return cast(*src, policy, parent); } static handle cast(const Type *src, return_value_policy policy, handle parent) {
return cast(*src, policy, parent);
}
static constexpr auto name = props::descriptor; static constexpr auto name = props::descriptor;
@ -539,10 +632,11 @@ public:
// you end up here if you try anyway. // you end up here if you try anyway.
bool load(handle, bool) = delete; bool load(handle, bool) = delete;
operator Type() = delete; operator Type() = delete;
template <typename> using cast_op_type = Type; template <typename>
using cast_op_type = Type;
}; };
template<typename Type> template <typename Type>
struct type_caster<Type, enable_if_t<is_eigen_sparse<Type>::value>> { struct type_caster<Type, enable_if_t<is_eigen_sparse<Type>::value>> {
using Scalar = typename Type::Scalar; using Scalar = typename Type::Scalar;
using StorageIndex = remove_reference_t<decltype(*std::declval<Type>().outerIndexPtr())>; using StorageIndex = remove_reference_t<decltype(*std::declval<Type>().outerIndexPtr())>;
@ -550,13 +644,13 @@ struct type_caster<Type, enable_if_t<is_eigen_sparse<Type>::value>> {
static constexpr bool rowMajor = Type::IsRowMajor; static constexpr bool rowMajor = Type::IsRowMajor;
bool load(handle src, bool) { bool load(handle src, bool) {
if (!src) if (!src) {
return false; return false;
}
auto obj = reinterpret_borrow<object>(src); auto obj = reinterpret_borrow<object>(src);
object sparse_module = module_::import("scipy.sparse"); object sparse_module = module_::import("scipy.sparse");
object matrix_type = sparse_module.attr( object matrix_type = sparse_module.attr(rowMajor ? "csr_matrix" : "csc_matrix");
rowMajor ? "csr_matrix" : "csc_matrix");
if (!type::handle_of(obj).is(matrix_type)) { if (!type::handle_of(obj).is(matrix_type)) {
try { try {
@ -572,36 +666,42 @@ struct type_caster<Type, enable_if_t<is_eigen_sparse<Type>::value>> {
auto shape = pybind11::tuple((pybind11::object) obj.attr("shape")); auto shape = pybind11::tuple((pybind11::object) obj.attr("shape"));
auto nnz = obj.attr("nnz").cast<Index>(); auto nnz = obj.attr("nnz").cast<Index>();
if (!values || !innerIndices || !outerIndices) if (!values || !innerIndices || !outerIndices) {
return false; return false;
}
value = EigenMapSparseMatrix<Scalar, value = EigenMapSparseMatrix<Scalar,
Type::Flags & (Eigen::RowMajor | Eigen::ColMajor), Type::Flags &(Eigen::RowMajor | Eigen::ColMajor),
StorageIndex>( StorageIndex>(shape[0].cast<Index>(),
shape[0].cast<Index>(), shape[1].cast<Index>(), nnz, shape[1].cast<Index>(),
outerIndices.mutable_data(), innerIndices.mutable_data(), values.mutable_data()); std::move(nnz),
outerIndices.mutable_data(),
innerIndices.mutable_data(),
values.mutable_data());
return true; return true;
} }
static handle cast(const Type &src, return_value_policy /* policy */, handle /* parent */) { static handle cast(const Type &src, return_value_policy /* policy */, handle /* parent */) {
const_cast<Type&>(src).makeCompressed(); const_cast<Type &>(src).makeCompressed();
object matrix_type = module_::import("scipy.sparse").attr( object matrix_type
rowMajor ? "csr_matrix" : "csc_matrix"); = module_::import("scipy.sparse").attr(rowMajor ? "csr_matrix" : "csc_matrix");
array data(src.nonZeros(), src.valuePtr()); array data(src.nonZeros(), src.valuePtr());
array outerIndices((rowMajor ? src.rows() : src.cols()) + 1, src.outerIndexPtr()); array outerIndices((rowMajor ? src.rows() : src.cols()) + 1, src.outerIndexPtr());
array innerIndices(src.nonZeros(), src.innerIndexPtr()); array innerIndices(src.nonZeros(), src.innerIndexPtr());
return matrix_type( return matrix_type(pybind11::make_tuple(
std::make_tuple(data, innerIndices, outerIndices), std::move(data), std::move(innerIndices), std::move(outerIndices)),
std::make_pair(src.rows(), src.cols()) pybind11::make_tuple(src.rows(), src.cols()))
).release(); .release();
} }
PYBIND11_TYPE_CASTER(Type, const_name<(Type::IsRowMajor) != 0>("scipy.sparse.csr_matrix[", "scipy.sparse.csc_matrix[") PYBIND11_TYPE_CASTER(Type,
+ npy_format_descriptor<Scalar>::name + const_name("]")); const_name<(Type::IsRowMajor) != 0>("scipy.sparse.csr_matrix[",
"scipy.sparse.csc_matrix[")
+ npy_format_descriptor<Scalar>::name + const_name("]"));
}; };
PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(detail)

View File

@ -16,22 +16,12 @@
#include <vector> #include <vector>
#if defined(PYPY_VERSION) #if defined(PYPY_VERSION)
# error Embedding the interpreter is not supported with PyPy # error Embedding the interpreter is not supported with PyPy
#endif #endif
#if PY_MAJOR_VERSION >= 3 #define PYBIND11_EMBEDDED_MODULE_IMPL(name) \
# define PYBIND11_EMBEDDED_MODULE_IMPL(name) \ extern "C" PyObject *pybind11_init_impl_##name(); \
extern "C" PyObject *pybind11_init_impl_##name(); \ extern "C" PyObject *pybind11_init_impl_##name() { return pybind11_init_wrapper_##name(); }
extern "C" PyObject *pybind11_init_impl_##name() { \
return pybind11_init_wrapper_##name(); \
}
#else
# define PYBIND11_EMBEDDED_MODULE_IMPL(name) \
extern "C" void pybind11_init_impl_##name(); \
extern "C" void pybind11_init_impl_##name() { \
pybind11_init_wrapper_##name(); \
}
#endif
/** \rst /** \rst
Add a new module to the table of builtins for the interpreter. Must be Add a new module to the table of builtins for the interpreter. Must be
@ -71,102 +61,31 @@ PYBIND11_NAMESPACE_BEGIN(detail)
/// Python 2.7/3.x compatible version of `PyImport_AppendInittab` and error checks. /// Python 2.7/3.x compatible version of `PyImport_AppendInittab` and error checks.
struct embedded_module { struct embedded_module {
#if PY_MAJOR_VERSION >= 3 using init_t = PyObject *(*) ();
using init_t = PyObject *(*)();
#else
using init_t = void (*)();
#endif
embedded_module(const char *name, init_t init) { embedded_module(const char *name, init_t init) {
if (Py_IsInitialized() != 0) if (Py_IsInitialized() != 0) {
pybind11_fail("Can't add new modules after the interpreter has been initialized"); pybind11_fail("Can't add new modules after the interpreter has been initialized");
}
auto result = PyImport_AppendInittab(name, init); auto result = PyImport_AppendInittab(name, init);
if (result == -1) if (result == -1) {
pybind11_fail("Insufficient memory to add a new module"); pybind11_fail("Insufficient memory to add a new module");
}
} }
}; };
struct wide_char_arg_deleter { struct wide_char_arg_deleter {
void operator()(wchar_t *ptr) const { 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 // API docs: https://docs.python.org/3/c-api/sys.html#c.Py_DecodeLocale
PyMem_RawFree(ptr); PyMem_RawFree(ptr);
#else
delete[] ptr;
#endif
} }
}; };
inline wchar_t *widen_chars(const char *safe_arg) { inline wchar_t *widen_chars(const char *safe_arg) {
#if PY_VERSION_HEX >= 0x030500f0
wchar_t *widened_arg = Py_DecodeLocale(safe_arg, nullptr); wchar_t *widened_arg = Py_DecodeLocale(safe_arg, nullptr);
#else
wchar_t *widened_arg = nullptr;
// warning C4996: 'mbstowcs': This function or variable may be unsafe.
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable:4996)
#endif
# if defined(HAVE_BROKEN_MBSTOWCS) && HAVE_BROKEN_MBSTOWCS
size_t count = std::strlen(safe_arg);
# else
size_t count = std::mbstowcs(nullptr, safe_arg, 0);
# endif
if (count != static_cast<size_t>(-1)) {
widened_arg = new wchar_t[count + 1];
std::mbstowcs(widened_arg, safe_arg, count + 1);
}
#if defined(_MSC_VER)
#pragma warning(pop)
#endif
#endif
return widened_arg; 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) PYBIND11_NAMESPACE_END(detail)
/** \rst /** \rst
@ -192,12 +111,68 @@ inline void initialize_interpreter(bool init_signal_handlers = true,
int argc = 0, int argc = 0,
const char *const *argv = nullptr, const char *const *argv = nullptr,
bool add_program_dir_to_path = true) { bool add_program_dir_to_path = true) {
if (Py_IsInitialized() != 0) if (Py_IsInitialized() != 0) {
pybind11_fail("The interpreter is already running"); pybind11_fail("The interpreter is already running");
}
#if PY_VERSION_HEX < 0x030B0000
Py_InitializeEx(init_signal_handlers ? 1 : 0); Py_InitializeEx(init_signal_handlers ? 1 : 0);
detail::set_interpreter_argv(argc, argv, 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);
// 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[], detail::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(detail::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();
PySys_SetArgvEx(argc, pysys_argv, static_cast<int>(add_program_dir_to_path));
#else
PyConfig config;
PyConfig_InitIsolatedConfig(&config);
config.install_signal_handlers = init_signal_handlers ? 1 : 0;
PyStatus status = PyConfig_SetBytesArgv(&config, argc, const_cast<char *const *>(argv));
if (PyStatus_Exception(status)) {
// A failure here indicates a character-encoding failure or the python
// interpreter out of memory. Give up.
PyConfig_Clear(&config);
throw std::runtime_error(PyStatus_IsError(status) ? status.err_msg
: "Failed to prepare CPython");
}
status = Py_InitializeFromConfig(&config);
PyConfig_Clear(&config);
if (PyStatus_Exception(status)) {
throw std::runtime_error(PyStatus_IsError(status) ? status.err_msg
: "Failed to init CPython");
}
if (add_program_dir_to_path) {
PyRun_SimpleString("import sys, os.path; "
"sys.path.insert(0, "
"os.path.abspath(os.path.dirname(sys.argv[0])) "
"if sys.argv and os.path.exists(sys.argv[0]) else '')");
}
#endif
} }
/** \rst /** \rst
@ -244,8 +219,13 @@ inline void finalize_interpreter() {
// during destruction), so we get the pointer-pointer here and check it after Py_Finalize(). // during destruction), so we get the pointer-pointer here and check it after Py_Finalize().
detail::internals **internals_ptr_ptr = detail::get_internals_pp(); detail::internals **internals_ptr_ptr = detail::get_internals_pp();
// It could also be stashed in builtins, so look there too: // It could also be stashed in builtins, so look there too:
if (builtins.contains(id) && isinstance<capsule>(builtins[id])) if (builtins.contains(id) && isinstance<capsule>(builtins[id])) {
internals_ptr_ptr = capsule(builtins[id]); internals_ptr_ptr = capsule(builtins[id]);
}
// Local internals contains data managed by the current interpreter, so we must clear them to
// avoid undefined behaviors when initializing another interpreter
detail::get_local_internals().registered_types_cpp.clear();
detail::get_local_internals().registered_exception_translators.clear();
Py_Finalize(); Py_Finalize();
@ -285,8 +265,9 @@ public:
scoped_interpreter &operator=(scoped_interpreter &&) = delete; scoped_interpreter &operator=(scoped_interpreter &&) = delete;
~scoped_interpreter() { ~scoped_interpreter() {
if (is_valid) if (is_valid) {
finalize_interpreter(); finalize_interpreter();
}
} }
private: private:

View File

@ -11,24 +11,24 @@
#pragma once #pragma once
#include <utility>
#include "pybind11.h" #include "pybind11.h"
#include <utility>
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
inline void ensure_builtins_in_globals(object &global) { inline void ensure_builtins_in_globals(object &global) {
#if defined(PYPY_VERSION) || PY_VERSION_HEX < 0x03080000 #if defined(PYPY_VERSION) || PY_VERSION_HEX < 0x03080000
// Running exec and eval on Python 2 and 3 adds `builtins` module under // Running exec and eval adds `builtins` module under `__builtins__` key to
// `__builtins__` key to globals if not yet present. // globals if not yet present. Python 3.8 made PyRun_String behave
// Python 3.8 made PyRun_String behave similarly. Let's also do that for // similarly. Let's also do that for older versions, for consistency. This
// older versions, for consistency. This was missing from PyPy3.8 7.3.7. // was missing from PyPy3.8 7.3.7.
if (!global.contains("__builtins__")) if (!global.contains("__builtins__"))
global["__builtins__"] = module_::import(PYBIND11_BUILTINS_MODULE); global["__builtins__"] = module_::import(PYBIND11_BUILTINS_MODULE);
#else #else
(void) global; (void) global;
#endif #endif
} }
PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(detail)
@ -46,8 +46,9 @@ enum eval_mode {
template <eval_mode mode = eval_expr> template <eval_mode mode = eval_expr>
object eval(const str &expr, object global = globals(), object local = object()) { object eval(const str &expr, object global = globals(), object local = object()) {
if (!local) if (!local) {
local = global; local = global;
}
detail::ensure_builtins_in_globals(global); detail::ensure_builtins_in_globals(global);
@ -57,24 +58,31 @@ object eval(const str &expr, object global = globals(), object local = object())
int start = 0; int start = 0;
switch (mode) { switch (mode) {
case eval_expr: start = Py_eval_input; break; case eval_expr:
case eval_single_statement: start = Py_single_input; break; start = Py_eval_input;
case eval_statements: start = Py_file_input; break; break;
default: pybind11_fail("invalid evaluation mode"); case eval_single_statement:
start = Py_single_input;
break;
case eval_statements:
start = Py_file_input;
break;
default:
pybind11_fail("invalid evaluation mode");
} }
PyObject *result = PyRun_String(buffer.c_str(), start, global.ptr(), local.ptr()); PyObject *result = PyRun_String(buffer.c_str(), start, global.ptr(), local.ptr());
if (!result) if (!result) {
throw error_already_set(); throw error_already_set();
}
return reinterpret_steal<object>(result); return reinterpret_steal<object>(result);
} }
template <eval_mode mode = eval_expr, size_t N> template <eval_mode mode = eval_expr, size_t N>
object eval(const char (&s)[N], object global = globals(), object local = object()) { object eval(const char (&s)[N], object global = globals(), object local = object()) {
/* Support raw string literals by removing common leading whitespace */ /* Support raw string literals by removing common leading whitespace */
auto expr = (s[0] == '\n') ? str(module_::import("textwrap").attr("dedent")(s)) auto expr = (s[0] == '\n') ? str(module_::import("textwrap").attr("dedent")(s)) : str(s);
: str(s); return eval<mode>(expr, std::move(global), std::move(local));
return eval<mode>(expr, global, local);
} }
inline void exec(const str &expr, object global = globals(), object local = object()) { inline void exec(const str &expr, object global = globals(), object local = object()) {
@ -83,10 +91,10 @@ inline void exec(const str &expr, object global = globals(), object local = obje
template <size_t N> template <size_t N>
void exec(const char (&s)[N], object global = globals(), object local = object()) { void exec(const char (&s)[N], object global = globals(), object local = object()) {
eval<eval_statements>(s, global, local); eval<eval_statements>(s, std::move(global), std::move(local));
} }
#if defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x03000000 #if defined(PYPY_VERSION)
template <eval_mode mode = eval_statements> template <eval_mode mode = eval_statements>
object eval_file(str, object, object) { object eval_file(str, object, object) {
pybind11_fail("eval_file not supported in PyPy3. Use eval"); pybind11_fail("eval_file not supported in PyPy3. Use eval");
@ -102,60 +110,45 @@ object eval_file(str) {
#else #else
template <eval_mode mode = eval_statements> template <eval_mode mode = eval_statements>
object eval_file(str fname, object global = globals(), object local = object()) { object eval_file(str fname, object global = globals(), object local = object()) {
if (!local) if (!local) {
local = global; local = global;
}
detail::ensure_builtins_in_globals(global); detail::ensure_builtins_in_globals(global);
int start = 0; int start = 0;
switch (mode) { switch (mode) {
case eval_expr: start = Py_eval_input; break; case eval_expr:
case eval_single_statement: start = Py_single_input; break; start = Py_eval_input;
case eval_statements: start = Py_file_input; break; break;
default: pybind11_fail("invalid evaluation mode"); case eval_single_statement:
start = Py_single_input;
break;
case eval_statements:
start = Py_file_input;
break;
default:
pybind11_fail("invalid evaluation mode");
} }
int closeFile = 1; int closeFile = 1;
std::string fname_str = (std::string) fname; std::string fname_str = (std::string) fname;
#if PY_VERSION_HEX >= 0x03040000
FILE *f = _Py_fopen_obj(fname.ptr(), "r"); FILE *f = _Py_fopen_obj(fname.ptr(), "r");
#elif PY_VERSION_HEX >= 0x03000000
FILE *f = _Py_fopen(fname.ptr(), "r");
#else
/* No unicode support in open() :( */
auto fobj = reinterpret_steal<object>(PyFile_FromString(
const_cast<char *>(fname_str.c_str()),
const_cast<char*>("r")));
FILE *f = nullptr;
if (fobj)
f = PyFile_AsFile(fobj.ptr());
closeFile = 0;
#endif
if (!f) { if (!f) {
PyErr_Clear(); PyErr_Clear();
pybind11_fail("File \"" + fname_str + "\" could not be opened!"); 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__")) { if (!global.contains("__file__")) {
global["__file__"] = std::move(fname); global["__file__"] = std::move(fname);
} }
#endif
#if PY_VERSION_HEX < 0x03000000 && defined(PYPY_VERSION) PyObject *result
PyObject *result = PyRun_File(f, fname_str.c_str(), start, global.ptr(), = PyRun_FileEx(f, fname_str.c_str(), start, global.ptr(), local.ptr(), closeFile);
local.ptr());
(void) closeFile;
#else
PyObject *result = PyRun_FileEx(f, fname_str.c_str(), start, global.ptr(),
local.ptr(), closeFile);
#endif
if (!result) if (!result) {
throw error_already_set(); throw error_already_set();
}
return reinterpret_steal<object>(result); return reinterpret_steal<object>(result);
} }
#endif #endif

View File

@ -10,6 +10,7 @@
#pragma once #pragma once
#include "pybind11.h" #include "pybind11.h"
#include <functional> #include <functional>
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
@ -19,18 +20,21 @@ template <typename Return, typename... Args>
struct type_caster<std::function<Return(Args...)>> { struct type_caster<std::function<Return(Args...)>> {
using type = std::function<Return(Args...)>; using type = std::function<Return(Args...)>;
using retval_type = conditional_t<std::is_same<Return, void>::value, void_type, Return>; using retval_type = conditional_t<std::is_same<Return, void>::value, void_type, Return>;
using function_type = Return (*) (Args...); using function_type = Return (*)(Args...);
public: public:
bool load(handle src, bool convert) { bool load(handle src, bool convert) {
if (src.is_none()) { if (src.is_none()) {
// Defer accepting None to other overloads (if we aren't in convert mode): // Defer accepting None to other overloads (if we aren't in convert mode):
if (!convert) return false; if (!convert) {
return false;
}
return true; return true;
} }
if (!isinstance<function>(src)) if (!isinstance<function>(src)) {
return false; return false;
}
auto func = reinterpret_borrow<function>(src); auto func = reinterpret_borrow<function>(src);
@ -43,10 +47,10 @@ public:
captured variables), in which case the roundtrip can be avoided. captured variables), in which case the roundtrip can be avoided.
*/ */
if (auto cfunc = func.cpp_function()) { if (auto cfunc = func.cpp_function()) {
auto cfunc_self = PyCFunction_GET_SELF(cfunc.ptr()); auto *cfunc_self = PyCFunction_GET_SELF(cfunc.ptr());
if (isinstance<capsule>(cfunc_self)) { if (isinstance<capsule>(cfunc_self)) {
auto c = reinterpret_borrow<capsule>(cfunc_self); auto c = reinterpret_borrow<capsule>(cfunc_self);
auto rec = (function_record *) c; auto *rec = (function_record *) c;
while (rec != nullptr) { while (rec != nullptr) {
if (rec->is_stateless if (rec->is_stateless
@ -73,7 +77,9 @@ public:
// This triggers a syntax error under very special conditions (very weird indeed). // This triggers a syntax error under very special conditions (very weird indeed).
explicit explicit
#endif #endif
func_handle(function &&f_) noexcept : f(std::move(f_)) {} func_handle(function &&f_) noexcept
: f(std::move(f_)) {
}
func_handle(const func_handle &f_) { operator=(f_); } func_handle(const func_handle &f_) { operator=(f_); }
func_handle &operator=(const func_handle &f_) { func_handle &operator=(const func_handle &f_) {
gil_scoped_acquire acq; gil_scoped_acquire acq;
@ -92,9 +98,8 @@ public:
explicit 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 { Return operator()(Args... args) const {
gil_scoped_acquire acq; gil_scoped_acquire acq;
object retval(hfunc.f(std::forward<Args>(args)...)); // casts the returned object as a rvalue to the return type
/* Visual studio 2015 parser issue: need parentheses around this expression */ return hfunc.f(std::forward<Args>(args)...).template cast<Return>();
return (retval.template cast<Return>());
} }
}; };
@ -104,17 +109,21 @@ public:
template <typename Func> template <typename Func>
static handle cast(Func &&f_, return_value_policy policy, handle /* parent */) { static handle cast(Func &&f_, return_value_policy policy, handle /* parent */) {
if (!f_) if (!f_) {
return none().inc_ref(); return none().inc_ref();
}
auto result = f_.template target<function_type>(); auto result = f_.template target<function_type>();
if (result) if (result) {
return cpp_function(*result, policy).release(); return cpp_function(*result, policy).release();
}
return cpp_function(std::forward<Func>(f_), policy).release(); return cpp_function(std::forward<Func>(f_), policy).release();
} }
PYBIND11_TYPE_CASTER(type, const_name("Callable[[") + concat(make_caster<Args>::name...) + const_name("], ") PYBIND11_TYPE_CASTER(type,
+ make_caster<retval_type>::name + const_name("]")); const_name("Callable[[") + concat(make_caster<Args>::name...)
+ const_name("], ") + make_caster<retval_type>::name
+ const_name("]"));
}; };
PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(detail)

View File

@ -14,7 +14,6 @@
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
// forward declarations // forward declarations
@ -22,7 +21,6 @@ PyThreadState *get_thread_state_unchecked();
PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(detail)
#if defined(WITH_THREAD) && !defined(PYPY_VERSION) #if defined(WITH_THREAD) && !defined(PYPY_VERSION)
/* The functions below essentially reproduce the PyGILState_* API using a RAII /* The functions below essentially reproduce the PyGILState_* API using a RAII
@ -64,10 +62,11 @@ public:
if (!tstate) { if (!tstate) {
tstate = PyThreadState_New(internals.istate); tstate = PyThreadState_New(internals.istate);
#if !defined(NDEBUG) # if defined(PYBIND11_DETAILED_ERROR_MESSAGES)
if (!tstate) if (!tstate) {
pybind11_fail("scoped_acquire: could not create thread state!"); pybind11_fail("scoped_acquire: could not create thread state!");
#endif }
# endif
tstate->gilstate_counter = 0; tstate->gilstate_counter = 0;
PYBIND11_TLS_REPLACE_VALUE(internals.tstate, tstate); PYBIND11_TLS_REPLACE_VALUE(internals.tstate, tstate);
} else { } else {
@ -81,26 +80,28 @@ public:
inc_ref(); inc_ref();
} }
void inc_ref() { void inc_ref() { ++tstate->gilstate_counter; }
++tstate->gilstate_counter;
}
PYBIND11_NOINLINE void dec_ref() { PYBIND11_NOINLINE void dec_ref() {
--tstate->gilstate_counter; --tstate->gilstate_counter;
#if !defined(NDEBUG) # if defined(PYBIND11_DETAILED_ERROR_MESSAGES)
if (detail::get_thread_state_unchecked() != tstate) if (detail::get_thread_state_unchecked() != tstate) {
pybind11_fail("scoped_acquire::dec_ref(): thread state must be current!"); pybind11_fail("scoped_acquire::dec_ref(): thread state must be current!");
if (tstate->gilstate_counter < 0) }
pybind11_fail("scoped_acquire::dec_ref(): reference count underflow!"); if (tstate->gilstate_counter < 0) {
#endif pybind11_fail("scoped_acquire::dec_ref(): reference count underflow!");
}
# endif
if (tstate->gilstate_counter == 0) { if (tstate->gilstate_counter == 0) {
#if !defined(NDEBUG) # if defined(PYBIND11_DETAILED_ERROR_MESSAGES)
if (!release) if (!release) {
pybind11_fail("scoped_acquire::dec_ref(): internal error!"); pybind11_fail("scoped_acquire::dec_ref(): internal error!");
#endif }
# endif
PyThreadState_Clear(tstate); PyThreadState_Clear(tstate);
if (active) if (active) {
PyThreadState_DeleteCurrent(); PyThreadState_DeleteCurrent();
}
PYBIND11_TLS_DELETE_VALUE(detail::get_internals().tstate); PYBIND11_TLS_DELETE_VALUE(detail::get_internals().tstate);
release = false; release = false;
} }
@ -111,15 +112,15 @@ public:
/// could be shutting down when this is called, as thread deletion is not /// could be shutting down when this is called, as thread deletion is not
/// allowed during shutdown. Check _Py_IsFinalizing() on Python 3.7+, and /// allowed during shutdown. Check _Py_IsFinalizing() on Python 3.7+, and
/// protect subsequent code. /// protect subsequent code.
PYBIND11_NOINLINE void disarm() { PYBIND11_NOINLINE void disarm() { active = false; }
active = false;
}
PYBIND11_NOINLINE ~gil_scoped_acquire() { PYBIND11_NOINLINE ~gil_scoped_acquire() {
dec_ref(); dec_ref();
if (release) if (release) {
PyEval_SaveThread(); PyEval_SaveThread();
}
} }
private: private:
PyThreadState *tstate = nullptr; PyThreadState *tstate = nullptr;
bool release = true; bool release = true;
@ -133,8 +134,11 @@ public:
// `internals.tstate` for subsequent `gil_scoped_acquire` calls. Otherwise, an // `internals.tstate` for subsequent `gil_scoped_acquire` calls. Otherwise, an
// initialization race could occur as multiple threads try `gil_scoped_acquire`. // initialization race could occur as multiple threads try `gil_scoped_acquire`.
auto &internals = detail::get_internals(); auto &internals = detail::get_internals();
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
tstate = PyEval_SaveThread(); tstate = PyEval_SaveThread();
if (disassoc) { if (disassoc) {
// Python >= 3.7 can remove this, it's an int before 3.7
// NOLINTNEXTLINE(readability-qualified-auto)
auto key = internals.tstate; auto key = internals.tstate;
PYBIND11_TLS_DELETE_VALUE(key); PYBIND11_TLS_DELETE_VALUE(key);
} }
@ -145,21 +149,24 @@ public:
/// could be shutting down when this is called, as thread deletion is not /// could be shutting down when this is called, as thread deletion is not
/// allowed during shutdown. Check _Py_IsFinalizing() on Python 3.7+, and /// allowed during shutdown. Check _Py_IsFinalizing() on Python 3.7+, and
/// protect subsequent code. /// protect subsequent code.
PYBIND11_NOINLINE void disarm() { PYBIND11_NOINLINE void disarm() { active = false; }
active = false;
}
~gil_scoped_release() { ~gil_scoped_release() {
if (!tstate) if (!tstate) {
return; return;
}
// `PyEval_RestoreThread()` should not be called if runtime is finalizing // `PyEval_RestoreThread()` should not be called if runtime is finalizing
if (active) if (active) {
PyEval_RestoreThread(tstate); PyEval_RestoreThread(tstate);
}
if (disassoc) { if (disassoc) {
// Python >= 3.7 can remove this, it's an int before 3.7
// NOLINTNEXTLINE(readability-qualified-auto)
auto key = detail::get_internals().tstate; auto key = detail::get_internals().tstate;
PYBIND11_TLS_REPLACE_VALUE(key, tstate); PYBIND11_TLS_REPLACE_VALUE(key, tstate);
} }
} }
private: private:
PyThreadState *tstate; PyThreadState *tstate;
bool disassoc; bool disassoc;
@ -168,6 +175,7 @@ private:
#elif defined(PYPY_VERSION) #elif defined(PYPY_VERSION)
class gil_scoped_acquire { class gil_scoped_acquire {
PyGILState_STATE state; PyGILState_STATE state;
public: public:
gil_scoped_acquire() { state = PyGILState_Ensure(); } gil_scoped_acquire() { state = PyGILState_Ensure(); }
~gil_scoped_acquire() { PyGILState_Release(state); } ~gil_scoped_acquire() { PyGILState_Release(state); }
@ -176,6 +184,7 @@ public:
class gil_scoped_release { class gil_scoped_release {
PyThreadState *state; PyThreadState *state;
public: public:
gil_scoped_release() { state = PyEval_SaveThread(); } gil_scoped_release() { state = PyEval_SaveThread(); }
~gil_scoped_release() { PyEval_RestoreThread(state); } ~gil_scoped_release() { PyEval_RestoreThread(state); }

View File

@ -58,36 +58,31 @@ private:
size_t utf8_remainder() const { size_t utf8_remainder() const {
const auto rbase = std::reverse_iterator<char *>(pbase()); const auto rbase = std::reverse_iterator<char *>(pbase());
const auto rpptr = std::reverse_iterator<char *>(pptr()); const auto rpptr = std::reverse_iterator<char *>(pptr());
auto is_ascii = [](char c) { auto is_ascii = [](char c) { return (static_cast<unsigned char>(c) & 0x80) == 0x00; };
return (static_cast<unsigned char>(c) & 0x80) == 0x00; auto is_leading = [](char c) { return (static_cast<unsigned char>(c) & 0xC0) == 0xC0; };
}; auto is_leading_2b = [](char c) { return static_cast<unsigned char>(c) <= 0xDF; };
auto is_leading = [](char c) { auto is_leading_3b = [](char c) { return static_cast<unsigned char>(c) <= 0xEF; };
return (static_cast<unsigned char>(c) & 0xC0) == 0xC0;
};
auto is_leading_2b = [](char c) {
return static_cast<unsigned char>(c) <= 0xDF;
};
auto is_leading_3b = [](char c) {
return static_cast<unsigned char>(c) <= 0xEF;
};
// If the last character is ASCII, there are no incomplete code points // If the last character is ASCII, there are no incomplete code points
if (is_ascii(*rpptr)) if (is_ascii(*rpptr)) {
return 0; return 0;
}
// Otherwise, work back from the end of the buffer and find the first // Otherwise, work back from the end of the buffer and find the first
// UTF-8 leading byte // UTF-8 leading byte
const auto rpend = rbase - rpptr >= 3 ? rpptr + 3 : rbase; const auto rpend = rbase - rpptr >= 3 ? rpptr + 3 : rbase;
const auto leading = std::find_if(rpptr, rpend, is_leading); const auto leading = std::find_if(rpptr, rpend, is_leading);
if (leading == rbase) if (leading == rbase) {
return 0; return 0;
const auto dist = static_cast<size_t>(leading - rpptr); }
size_t remainder = 0; const auto dist = static_cast<size_t>(leading - rpptr);
size_t remainder = 0;
if (dist == 0) if (dist == 0) {
remainder = 1; // 1-byte code point is impossible remainder = 1; // 1-byte code point is impossible
else if (dist == 1) } else if (dist == 1) {
remainder = is_leading_2b(*leading) ? 0 : dist + 1; remainder = is_leading_2b(*leading) ? 0 : dist + 1;
else if (dist == 2) } else if (dist == 2) {
remainder = is_leading_3b(*leading) ? 0 : dist + 1; remainder = is_leading_3b(*leading) ? 0 : dist + 1;
}
// else if (dist >= 3), at least 4 bytes before encountering an UTF-8 // else if (dist >= 3), at least 4 bytes before encountering an UTF-8
// leading byte, either no remainder or invalid UTF-8. // leading byte, either no remainder or invalid UTF-8.
// Invalid UTF-8 will cause an exception later when converting // Invalid UTF-8 will cause an exception later when converting
@ -100,27 +95,26 @@ private:
if (pbase() != pptr()) { // If buffer is not empty if (pbase() != pptr()) { // If buffer is not empty
gil_scoped_acquire tmp; gil_scoped_acquire tmp;
// This subtraction cannot be negative, so dropping the sign. // This subtraction cannot be negative, so dropping the sign.
auto size = static_cast<size_t>(pptr() - pbase()); auto size = static_cast<size_t>(pptr() - pbase());
size_t remainder = utf8_remainder(); size_t remainder = utf8_remainder();
if (size > remainder) { if (size > remainder) {
str line(pbase(), size - remainder); str line(pbase(), size - remainder);
pywrite(line); pywrite(std::move(line));
pyflush(); pyflush();
} }
// Copy the remainder at the end of the buffer to the beginning: // Copy the remainder at the end of the buffer to the beginning:
if (remainder > 0) if (remainder > 0) {
std::memmove(pbase(), pptr() - remainder, remainder); std::memmove(pbase(), pptr() - remainder, remainder);
}
setp(pbase(), epptr()); setp(pbase(), epptr());
pbump(static_cast<int>(remainder)); pbump(static_cast<int>(remainder));
} }
return 0; return 0;
} }
int sync() override { int sync() override { return _sync(); }
return _sync();
}
public: public:
explicit pythonbuf(const object &pyostream, size_t buffer_size = 1024) explicit pythonbuf(const object &pyostream, size_t buffer_size = 1024)
@ -129,17 +123,14 @@ public:
setp(d_buffer.get(), d_buffer.get() + buf_size - 1); setp(d_buffer.get(), d_buffer.get() + buf_size - 1);
} }
pythonbuf(pythonbuf&&) = default; pythonbuf(pythonbuf &&) = default;
/// Sync before destroy /// Sync before destroy
~pythonbuf() override { ~pythonbuf() override { _sync(); }
_sync();
}
}; };
PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(detail)
/** \rst /** \rst
This a move-only guard that redirects output. This a move-only guard that redirects output.
@ -160,7 +151,8 @@ PYBIND11_NAMESPACE_END(detail)
.. code-block:: cpp .. code-block:: cpp
{ {
py::scoped_ostream_redirect output{std::cerr, py::module::import("sys").attr("stderr")}; py::scoped_ostream_redirect output{
std::cerr, py::module::import("sys").attr("stderr")};
std::cout << "Hello, World!"; std::cout << "Hello, World!";
} }
\endrst */ \endrst */
@ -178,9 +170,7 @@ public:
old = costream.rdbuf(&buffer); old = costream.rdbuf(&buffer);
} }
~scoped_ostream_redirect() { ~scoped_ostream_redirect() { costream.rdbuf(old); }
costream.rdbuf(old);
}
scoped_ostream_redirect(const scoped_ostream_redirect &) = delete; scoped_ostream_redirect(const scoped_ostream_redirect &) = delete;
scoped_ostream_redirect(scoped_ostream_redirect &&other) = default; scoped_ostream_redirect(scoped_ostream_redirect &&other) = default;
@ -188,7 +178,6 @@ public:
scoped_ostream_redirect &operator=(scoped_ostream_redirect &&) = delete; scoped_ostream_redirect &operator=(scoped_ostream_redirect &&) = delete;
}; };
/** \rst /** \rst
Like `scoped_ostream_redirect`, but redirects cerr by default. This class Like `scoped_ostream_redirect`, but redirects cerr by default. This class
is provided primary to make ``py::call_guard`` easier to make. is provided primary to make ``py::call_guard`` easier to make.
@ -208,7 +197,6 @@ public:
: scoped_ostream_redirect(costream, pyostream) {} : scoped_ostream_redirect(costream, pyostream) {}
}; };
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
// Class to redirect output as a context manager. C++ backend. // Class to redirect output as a context manager. C++ backend.
@ -223,10 +211,12 @@ public:
: do_stdout_(do_stdout), do_stderr_(do_stderr) {} : do_stdout_(do_stdout), do_stderr_(do_stderr) {}
void enter() { void enter() {
if (do_stdout_) if (do_stdout_) {
redirect_stdout.reset(new scoped_ostream_redirect()); redirect_stdout.reset(new scoped_ostream_redirect());
if (do_stderr_) }
if (do_stderr_) {
redirect_stderr.reset(new scoped_estream_redirect()); redirect_stderr.reset(new scoped_estream_redirect());
}
} }
void exit() { void exit() {

File diff suppressed because it is too large Load Diff

View File

@ -16,12 +16,50 @@ PYBIND11_NAMESPACE_BEGIN(detail)
/// Enumeration with all supported operator types /// Enumeration with all supported operator types
enum op_id : int { enum op_id : int {
op_add, op_sub, op_mul, op_div, op_mod, op_divmod, op_pow, op_lshift, op_add,
op_rshift, op_and, op_xor, op_or, op_neg, op_pos, op_abs, op_invert, op_sub,
op_int, op_long, op_float, op_str, op_cmp, op_gt, op_ge, op_lt, op_le, op_mul,
op_eq, op_ne, op_iadd, op_isub, op_imul, op_idiv, op_imod, op_ilshift, op_div,
op_irshift, op_iand, op_ixor, op_ior, op_complex, op_bool, op_nonzero, op_mod,
op_repr, op_truediv, op_itruediv, op_hash op_divmod,
op_pow,
op_lshift,
op_rshift,
op_and,
op_xor,
op_or,
op_neg,
op_pos,
op_abs,
op_invert,
op_int,
op_long,
op_float,
op_str,
op_cmp,
op_gt,
op_ge,
op_lt,
op_le,
op_eq,
op_ne,
op_iadd,
op_isub,
op_imul,
op_idiv,
op_imod,
op_ilshift,
op_irshift,
op_iand,
op_ixor,
op_ior,
op_complex,
op_bool,
op_nonzero,
op_repr,
op_truediv,
op_itruediv,
op_hash
}; };
enum op_type : int { enum op_type : int {
@ -30,126 +68,126 @@ enum op_type : int {
op_u /* unary operator */ op_u /* unary operator */
}; };
struct self_t { }; struct self_t {};
static const self_t self = self_t(); static const self_t self = self_t();
/// Type for an unused type slot /// Type for an unused type slot
struct undefined_t { }; struct undefined_t {};
/// Don't warn about an unused variable /// Don't warn about an unused variable
inline self_t __self() { return self; } inline self_t __self() { return self; }
/// base template of operator implementations /// base template of operator implementations
template <op_id, op_type, typename B, typename L, typename R> struct op_impl { }; template <op_id, op_type, typename B, typename L, typename R>
struct op_impl {};
/// Operator implementation generator /// Operator implementation generator
template <op_id id, op_type ot, typename L, typename R> struct op_ { template <op_id id, op_type ot, typename L, typename R>
template <typename Class, typename... Extra> void execute(Class &cl, const Extra&... extra) const { struct op_ {
template <typename Class, typename... Extra>
void execute(Class &cl, const Extra &...extra) const {
using Base = typename Class::type; using Base = typename Class::type;
using L_type = conditional_t<std::is_same<L, self_t>::value, Base, L>; using L_type = conditional_t<std::is_same<L, self_t>::value, Base, L>;
using R_type = conditional_t<std::is_same<R, self_t>::value, Base, R>; using R_type = conditional_t<std::is_same<R, self_t>::value, Base, R>;
using op = op_impl<id, ot, Base, L_type, R_type>; using op = op_impl<id, ot, Base, L_type, R_type>;
cl.def(op::name(), &op::execute, is_operator(), extra...); cl.def(op::name(), &op::execute, is_operator(), extra...);
#if PY_MAJOR_VERSION < 3
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
} }
template <typename Class, typename... Extra> void execute_cast(Class &cl, const Extra&... extra) const { template <typename Class, typename... Extra>
void execute_cast(Class &cl, const Extra &...extra) const {
using Base = typename Class::type; using Base = typename Class::type;
using L_type = conditional_t<std::is_same<L, self_t>::value, Base, L>; using L_type = conditional_t<std::is_same<L, self_t>::value, Base, L>;
using R_type = conditional_t<std::is_same<R, self_t>::value, Base, R>; using R_type = conditional_t<std::is_same<R, self_t>::value, Base, R>;
using op = op_impl<id, ot, Base, L_type, R_type>; using op = op_impl<id, ot, Base, L_type, R_type>;
cl.def(op::name(), &op::execute_cast, is_operator(), extra...); cl.def(op::name(), &op::execute_cast, is_operator(), extra...);
#if PY_MAJOR_VERSION < 3
if (id == op_truediv || id == op_itruediv)
cl.def(id == op_itruediv ? "__idiv__" : ot == op_l ? "__div__" : "__rdiv__",
&op::execute, is_operator(), extra...);
#endif
} }
}; };
#define PYBIND11_BINARY_OPERATOR(id, rid, op, expr) \ #define PYBIND11_BINARY_OPERATOR(id, rid, op, expr) \
template <typename B, typename L, typename R> struct op_impl<op_##id, op_l, B, L, R> { \ template <typename B, typename L, typename R> \
static char const* name() { return "__" #id "__"; } \ struct op_impl<op_##id, op_l, B, L, R> { \
static auto execute(const L &l, const R &r) -> decltype(expr) { return (expr); } \ static char const *name() { return "__" #id "__"; } \
static B execute_cast(const L &l, const R &r) { return B(expr); } \ static auto execute(const L &l, const R &r) -> decltype(expr) { return (expr); } \
}; \ static B execute_cast(const L &l, const R &r) { return B(expr); } \
template <typename B, typename L, typename R> struct op_impl<op_##id, op_r, B, L, R> { \ }; \
static char const* name() { return "__" #rid "__"; } \ template <typename B, typename L, typename R> \
static auto execute(const R &r, const L &l) -> decltype(expr) { return (expr); } \ struct op_impl<op_##id, op_r, B, L, R> { \
static B execute_cast(const R &r, const L &l) { return B(expr); } \ static char const *name() { return "__" #rid "__"; } \
}; \ static auto execute(const R &r, const L &l) -> decltype(expr) { return (expr); } \
inline op_<op_##id, op_l, self_t, self_t> op(const self_t &, const self_t &) { \ static B execute_cast(const R &r, const L &l) { return B(expr); } \
return op_<op_##id, op_l, self_t, self_t>(); \ }; \
} \ inline op_<op_##id, op_l, self_t, self_t> op(const self_t &, const self_t &) { \
template <typename T> op_<op_##id, op_l, self_t, T> op(const self_t &, const T &) { \ return op_<op_##id, op_l, self_t, self_t>(); \
return op_<op_##id, op_l, self_t, T>(); \ } \
} \ template <typename T> \
template <typename T> op_<op_##id, op_r, T, self_t> op(const T &, const self_t &) { \ op_<op_##id, op_l, self_t, T> op(const self_t &, const T &) { \
return op_<op_##id, op_r, T, self_t>(); \ return op_<op_##id, op_l, self_t, T>(); \
} } \
template <typename T> \
op_<op_##id, op_r, T, self_t> op(const T &, const self_t &) { \
return op_<op_##id, op_r, T, self_t>(); \
}
#define PYBIND11_INPLACE_OPERATOR(id, op, expr) \ #define PYBIND11_INPLACE_OPERATOR(id, op, expr) \
template <typename B, typename L, typename R> struct op_impl<op_##id, op_l, B, L, R> { \ template <typename B, typename L, typename R> \
static char const* name() { return "__" #id "__"; } \ struct op_impl<op_##id, op_l, B, L, R> { \
static auto execute(L &l, const R &r) -> decltype(expr) { return expr; } \ static char const *name() { return "__" #id "__"; } \
static B execute_cast(L &l, const R &r) { return B(expr); } \ static auto execute(L &l, const R &r) -> decltype(expr) { return expr; } \
}; \ static B execute_cast(L &l, const R &r) { return B(expr); } \
template <typename T> op_<op_##id, op_l, self_t, T> op(const self_t &, const T &) { \ }; \
return op_<op_##id, op_l, self_t, T>(); \ template <typename T> \
} op_<op_##id, op_l, self_t, T> op(const self_t &, const T &) { \
return op_<op_##id, op_l, self_t, T>(); \
}
#define PYBIND11_UNARY_OPERATOR(id, op, expr) \ #define PYBIND11_UNARY_OPERATOR(id, op, expr) \
template <typename B, typename L> struct op_impl<op_##id, op_u, B, L, undefined_t> { \ template <typename B, typename L> \
static char const* name() { return "__" #id "__"; } \ struct op_impl<op_##id, op_u, B, L, undefined_t> { \
static auto execute(const L &l) -> decltype(expr) { return expr; } \ static char const *name() { return "__" #id "__"; } \
static B execute_cast(const L &l) { return B(expr); } \ static auto execute(const L &l) -> decltype(expr) { return expr; } \
}; \ static B execute_cast(const L &l) { return B(expr); } \
inline op_<op_##id, op_u, self_t, undefined_t> op(const self_t &) { \ }; \
return op_<op_##id, op_u, self_t, undefined_t>(); \ inline op_<op_##id, op_u, self_t, undefined_t> op(const self_t &) { \
} return op_<op_##id, op_u, self_t, undefined_t>(); \
}
PYBIND11_BINARY_OPERATOR(sub, rsub, operator-, l - r) PYBIND11_BINARY_OPERATOR(sub, rsub, operator-, l - r)
PYBIND11_BINARY_OPERATOR(add, radd, operator+, l + r) PYBIND11_BINARY_OPERATOR(add, radd, operator+, l + r)
PYBIND11_BINARY_OPERATOR(mul, rmul, operator*, l * r) PYBIND11_BINARY_OPERATOR(mul, rmul, operator*, l *r)
PYBIND11_BINARY_OPERATOR(truediv, rtruediv, operator/, l / r) PYBIND11_BINARY_OPERATOR(truediv, rtruediv, operator/, l / r)
PYBIND11_BINARY_OPERATOR(mod, rmod, operator%, l % r) PYBIND11_BINARY_OPERATOR(mod, rmod, operator%, l % r)
PYBIND11_BINARY_OPERATOR(lshift, rlshift, operator<<, l << r) PYBIND11_BINARY_OPERATOR(lshift, rlshift, operator<<, l << r)
PYBIND11_BINARY_OPERATOR(rshift, rrshift, operator>>, l >> r) PYBIND11_BINARY_OPERATOR(rshift, rrshift, operator>>, l >> r)
PYBIND11_BINARY_OPERATOR(and, rand, operator&, l & r) PYBIND11_BINARY_OPERATOR(and, rand, operator&, l &r)
PYBIND11_BINARY_OPERATOR(xor, rxor, operator^, l ^ r) PYBIND11_BINARY_OPERATOR(xor, rxor, operator^, l ^ r)
PYBIND11_BINARY_OPERATOR(eq, eq, operator==, l == r) PYBIND11_BINARY_OPERATOR(eq, eq, operator==, l == r)
PYBIND11_BINARY_OPERATOR(ne, ne, operator!=, l != r) PYBIND11_BINARY_OPERATOR(ne, ne, operator!=, l != r)
PYBIND11_BINARY_OPERATOR(or, ror, operator|, l | r) PYBIND11_BINARY_OPERATOR(or, ror, operator|, l | r)
PYBIND11_BINARY_OPERATOR(gt, lt, operator>, l > r) PYBIND11_BINARY_OPERATOR(gt, lt, operator>, l > r)
PYBIND11_BINARY_OPERATOR(ge, le, operator>=, l >= r) PYBIND11_BINARY_OPERATOR(ge, le, operator>=, l >= r)
PYBIND11_BINARY_OPERATOR(lt, gt, operator<, l < r) PYBIND11_BINARY_OPERATOR(lt, gt, operator<, l < r)
PYBIND11_BINARY_OPERATOR(le, ge, operator<=, l <= r) PYBIND11_BINARY_OPERATOR(le, ge, operator<=, l <= r)
//PYBIND11_BINARY_OPERATOR(pow, rpow, pow, std::pow(l, r)) // PYBIND11_BINARY_OPERATOR(pow, rpow, pow, std::pow(l, r))
PYBIND11_INPLACE_OPERATOR(iadd, operator+=, l += r) PYBIND11_INPLACE_OPERATOR(iadd, operator+=, l += r)
PYBIND11_INPLACE_OPERATOR(isub, operator-=, l -= r) PYBIND11_INPLACE_OPERATOR(isub, operator-=, l -= r)
PYBIND11_INPLACE_OPERATOR(imul, operator*=, l *= r) PYBIND11_INPLACE_OPERATOR(imul, operator*=, l *= r)
PYBIND11_INPLACE_OPERATOR(itruediv, operator/=, l /= r) PYBIND11_INPLACE_OPERATOR(itruediv, operator/=, l /= r)
PYBIND11_INPLACE_OPERATOR(imod, operator%=, l %= r) PYBIND11_INPLACE_OPERATOR(imod, operator%=, l %= r)
PYBIND11_INPLACE_OPERATOR(ilshift, operator<<=, l <<= r) PYBIND11_INPLACE_OPERATOR(ilshift, operator<<=, l <<= r)
PYBIND11_INPLACE_OPERATOR(irshift, operator>>=, l >>= r) PYBIND11_INPLACE_OPERATOR(irshift, operator>>=, l >>= r)
PYBIND11_INPLACE_OPERATOR(iand, operator&=, l &= r) PYBIND11_INPLACE_OPERATOR(iand, operator&=, l &= r)
PYBIND11_INPLACE_OPERATOR(ixor, operator^=, l ^= r) PYBIND11_INPLACE_OPERATOR(ixor, operator^=, l ^= r)
PYBIND11_INPLACE_OPERATOR(ior, operator|=, l |= r) PYBIND11_INPLACE_OPERATOR(ior, operator|=, l |= r)
PYBIND11_UNARY_OPERATOR(neg, operator-, -l) PYBIND11_UNARY_OPERATOR(neg, operator-, -l)
PYBIND11_UNARY_OPERATOR(pos, operator+, +l) PYBIND11_UNARY_OPERATOR(pos, operator+, +l)
// WARNING: This usage of `abs` should only be done for existing STL overloads. // WARNING: This usage of `abs` should only be done for existing STL overloads.
// Adding overloads directly in to the `std::` namespace is advised against: // Adding overloads directly in to the `std::` namespace is advised against:
// https://en.cppreference.com/w/cpp/language/extending_std // https://en.cppreference.com/w/cpp/language/extending_std
PYBIND11_UNARY_OPERATOR(abs, abs, std::abs(l)) PYBIND11_UNARY_OPERATOR(abs, abs, std::abs(l))
PYBIND11_UNARY_OPERATOR(hash, hash, std::hash<L>()(l)) PYBIND11_UNARY_OPERATOR(hash, hash, std::hash<L>()(l))
PYBIND11_UNARY_OPERATOR(invert, operator~, (~l)) PYBIND11_UNARY_OPERATOR(invert, operator~, (~l))
PYBIND11_UNARY_OPERATOR(bool, operator!, !!l) PYBIND11_UNARY_OPERATOR(bool, operator!, !!l)
PYBIND11_UNARY_OPERATOR(int, int_, (int) l) PYBIND11_UNARY_OPERATOR(int, int_, (int) l)
PYBIND11_UNARY_OPERATOR(float, float_, (double) l) PYBIND11_UNARY_OPERATOR(float, float_, (double) l)
#undef PYBIND11_BINARY_OPERATOR #undef PYBIND11_BINARY_OPERATOR
#undef PYBIND11_INPLACE_OPERATOR #undef PYBIND11_INPLACE_OPERATOR

View File

@ -15,43 +15,54 @@ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
class options { class options {
public: public:
// Default RAII constructor, which leaves settings as they currently are. // Default RAII constructor, which leaves settings as they currently are.
options() : previous_state(global_state()) {} options() : previous_state(global_state()) {}
// Class is non-copyable. // Class is non-copyable.
options(const options&) = delete; options(const options &) = delete;
options& operator=(const options&) = delete; options &operator=(const options &) = delete;
// Destructor, which restores settings that were in effect before. // Destructor, which restores settings that were in effect before.
~options() { ~options() { global_state() = previous_state; }
global_state() = previous_state;
}
// Setter methods (affect the global state): // Setter methods (affect the global state):
options& disable_user_defined_docstrings() & { global_state().show_user_defined_docstrings = false; return *this; } options &disable_user_defined_docstrings() & {
global_state().show_user_defined_docstrings = false;
return *this;
}
options& enable_user_defined_docstrings() & { global_state().show_user_defined_docstrings = true; return *this; } options &enable_user_defined_docstrings() & {
global_state().show_user_defined_docstrings = true;
return *this;
}
options& disable_function_signatures() & { global_state().show_function_signatures = false; return *this; } options &disable_function_signatures() & {
global_state().show_function_signatures = false;
return *this;
}
options& enable_function_signatures() & { global_state().show_function_signatures = true; return *this; } options &enable_function_signatures() & {
global_state().show_function_signatures = true;
return *this;
}
// Getter methods (return the global state): // Getter methods (return the global state):
static bool show_user_defined_docstrings() { return global_state().show_user_defined_docstrings; } static bool show_user_defined_docstrings() {
return global_state().show_user_defined_docstrings;
}
static bool show_function_signatures() { return global_state().show_function_signatures; } static bool show_function_signatures() { return global_state().show_function_signatures; }
// This type is not meant to be allocated on the heap. // This type is not meant to be allocated on the heap.
void* operator new(size_t) = delete; void *operator new(size_t) = delete;
private: private:
struct state { struct state {
bool show_user_defined_docstrings = true; //< Include user-supplied texts in docstrings. bool show_user_defined_docstrings = true; //< Include user-supplied texts in docstrings.
bool show_function_signatures = true; //< Include auto-generated function signatures in docstrings. bool show_function_signatures = true; //< Include auto-generated function signatures
// in docstrings.
}; };
static state &global_state() { static state &global_state() {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -9,26 +9,27 @@
#pragma once #pragma once
#include "detail/common.h"
#include "pybind11.h" #include "pybind11.h"
#include <set> #include "detail/common.h"
#include <unordered_set>
#include <map>
#include <unordered_map>
#include <iostream>
#include <list>
#include <deque> #include <deque>
#include <list>
#include <map>
#include <ostream>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <valarray> #include <valarray>
// See `detail/common.h` for implementation of these guards. // See `detail/common.h` for implementation of these guards.
#if defined(PYBIND11_HAS_OPTIONAL) #if defined(PYBIND11_HAS_OPTIONAL)
# include <optional> # include <optional>
#elif defined(PYBIND11_HAS_EXP_OPTIONAL) #elif defined(PYBIND11_HAS_EXP_OPTIONAL)
# include <experimental/optional> # include <experimental/optional>
#endif #endif
#if defined(PYBIND11_HAS_VARIANT) #if defined(PYBIND11_HAS_VARIANT)
# include <variant> # include <variant>
#endif #endif
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
@ -37,8 +38,9 @@ PYBIND11_NAMESPACE_BEGIN(detail)
/// Extracts an const lvalue reference or rvalue reference for U based on the type of T (e.g. for /// Extracts an const lvalue reference or rvalue reference for U based on the type of T (e.g. for
/// forwarding a container element). Typically used indirect via forwarded_type(), below. /// forwarding a container element). Typically used indirect via forwarded_type(), below.
template <typename T, typename U> template <typename T, typename U>
using forwarded_type = conditional_t< using forwarded_type = conditional_t<std::is_lvalue_reference<T>::value,
std::is_lvalue_reference<T>::value, remove_reference_t<U> &, remove_reference_t<U> &&>; remove_reference_t<U> &,
remove_reference_t<U> &&>;
/// Forwards a value U as rvalue or lvalue according to whether T is rvalue or lvalue; typically /// Forwards a value U as rvalue or lvalue according to whether T is rvalue or lvalue; typically
/// used for forwarding a container's elements. /// used for forwarding a container's elements.
@ -47,19 +49,22 @@ forwarded_type<T, U> forward_like(U &&u) {
return std::forward<detail::forwarded_type<T, U>>(std::forward<U>(u)); return std::forward<detail::forwarded_type<T, U>>(std::forward<U>(u));
} }
template <typename Type, typename Key> struct set_caster { template <typename Type, typename Key>
struct set_caster {
using type = Type; using type = Type;
using key_conv = make_caster<Key>; using key_conv = make_caster<Key>;
bool load(handle src, bool convert) { bool load(handle src, bool convert) {
if (!isinstance<pybind11::set>(src)) if (!isinstance<anyset>(src)) {
return false; return false;
auto s = reinterpret_borrow<pybind11::set>(src); }
auto s = reinterpret_borrow<anyset>(src);
value.clear(); value.clear();
for (auto entry : s) { for (auto entry : s) {
key_conv conv; key_conv conv;
if (!conv.load(entry, convert)) if (!conv.load(entry, convert)) {
return false; return false;
}
value.insert(cast_op<Key &&>(std::move(conv))); value.insert(cast_op<Key &&>(std::move(conv)));
} }
return true; return true;
@ -67,13 +72,16 @@ template <typename Type, typename Key> struct set_caster {
template <typename T> template <typename T>
static handle cast(T &&src, return_value_policy policy, handle parent) { static handle cast(T &&src, return_value_policy policy, handle parent) {
if (!std::is_lvalue_reference<T>::value) if (!std::is_lvalue_reference<T>::value) {
policy = return_value_policy_override<Key>::policy(policy); policy = return_value_policy_override<Key>::policy(policy);
}
pybind11::set s; pybind11::set s;
for (auto &&value : src) { for (auto &&value : src) {
auto value_ = reinterpret_steal<object>(key_conv::cast(forward_like<T>(value), policy, parent)); auto value_ = reinterpret_steal<object>(
if (!value_ || !s.add(value_)) key_conv::cast(forward_like<T>(value), policy, parent));
if (!value_ || !s.add(std::move(value_))) {
return handle(); return handle();
}
} }
return s.release(); return s.release();
} }
@ -81,21 +89,23 @@ template <typename Type, typename Key> struct set_caster {
PYBIND11_TYPE_CASTER(type, const_name("Set[") + key_conv::name + const_name("]")); PYBIND11_TYPE_CASTER(type, const_name("Set[") + key_conv::name + const_name("]"));
}; };
template <typename Type, typename Key, typename Value> struct map_caster { template <typename Type, typename Key, typename Value>
using key_conv = make_caster<Key>; struct map_caster {
using key_conv = make_caster<Key>;
using value_conv = make_caster<Value>; using value_conv = make_caster<Value>;
bool load(handle src, bool convert) { bool load(handle src, bool convert) {
if (!isinstance<dict>(src)) if (!isinstance<dict>(src)) {
return false; return false;
}
auto d = reinterpret_borrow<dict>(src); auto d = reinterpret_borrow<dict>(src);
value.clear(); value.clear();
for (auto it : d) { for (auto it : d) {
key_conv kconv; key_conv kconv;
value_conv vconv; value_conv vconv;
if (!kconv.load(it.first.ptr(), convert) || if (!kconv.load(it.first.ptr(), convert) || !vconv.load(it.second.ptr(), convert)) {
!vconv.load(it.second.ptr(), convert))
return false; return false;
}
value.emplace(cast_op<Key &&>(std::move(kconv)), cast_op<Value &&>(std::move(vconv))); value.emplace(cast_op<Key &&>(std::move(kconv)), cast_op<Value &&>(std::move(vconv)));
} }
return true; return true;
@ -111,31 +121,39 @@ template <typename Type, typename Key, typename Value> struct map_caster {
policy_value = return_value_policy_override<Value>::policy(policy_value); policy_value = return_value_policy_override<Value>::policy(policy_value);
} }
for (auto &&kv : src) { for (auto &&kv : src) {
auto key = reinterpret_steal<object>(key_conv::cast(forward_like<T>(kv.first), policy_key, parent)); auto key = reinterpret_steal<object>(
auto value = reinterpret_steal<object>(value_conv::cast(forward_like<T>(kv.second), policy_value, parent)); key_conv::cast(forward_like<T>(kv.first), policy_key, parent));
if (!key || !value) auto value = reinterpret_steal<object>(
value_conv::cast(forward_like<T>(kv.second), policy_value, parent));
if (!key || !value) {
return handle(); return handle();
d[key] = value; }
d[std::move(key)] = std::move(value);
} }
return d.release(); return d.release();
} }
PYBIND11_TYPE_CASTER(Type, const_name("Dict[") + key_conv::name + const_name(", ") + value_conv::name + const_name("]")); PYBIND11_TYPE_CASTER(Type,
const_name("Dict[") + key_conv::name + const_name(", ") + value_conv::name
+ const_name("]"));
}; };
template <typename Type, typename Value> struct list_caster { template <typename Type, typename Value>
struct list_caster {
using value_conv = make_caster<Value>; using value_conv = make_caster<Value>;
bool load(handle src, bool convert) { bool load(handle src, bool convert) {
if (!isinstance<sequence>(src) || isinstance<bytes>(src) || isinstance<str>(src)) if (!isinstance<sequence>(src) || isinstance<bytes>(src) || isinstance<str>(src)) {
return false; return false;
}
auto s = reinterpret_borrow<sequence>(src); auto s = reinterpret_borrow<sequence>(src);
value.clear(); value.clear();
reserve_maybe(s, &value); reserve_maybe(s, &value);
for (auto it : s) { for (auto it : s) {
value_conv conv; value_conv conv;
if (!conv.load(it, convert)) if (!conv.load(it, convert)) {
return false; return false;
}
value.push_back(cast_op<Value &&>(std::move(conv))); value.push_back(cast_op<Value &&>(std::move(conv)));
} }
return true; return true;
@ -143,7 +161,7 @@ template <typename Type, typename Value> struct list_caster {
private: private:
template < template <
typename T = Type, typename T = Type,
enable_if_t<std::is_same<decltype(std::declval<T>().reserve(0)), void>::value, int> = 0> enable_if_t<std::is_same<decltype(std::declval<T>().reserve(0)), void>::value, int> = 0>
void reserve_maybe(const sequence &s, Type *) { void reserve_maybe(const sequence &s, Type *) {
value.reserve(s.size()); value.reserve(s.size());
@ -153,14 +171,17 @@ private:
public: public:
template <typename T> template <typename T>
static handle cast(T &&src, return_value_policy policy, handle parent) { static handle cast(T &&src, return_value_policy policy, handle parent) {
if (!std::is_lvalue_reference<T>::value) if (!std::is_lvalue_reference<T>::value) {
policy = return_value_policy_override<Value>::policy(policy); policy = return_value_policy_override<Value>::policy(policy);
}
list l(src.size()); list l(src.size());
ssize_t index = 0; ssize_t index = 0;
for (auto &&value : src) { for (auto &&value : src) {
auto value_ = reinterpret_steal<object>(value_conv::cast(forward_like<T>(value), policy, parent)); auto value_ = reinterpret_steal<object>(
if (!value_) value_conv::cast(forward_like<T>(value), policy, parent));
if (!value_) {
return handle(); return handle();
}
PyList_SET_ITEM(l.ptr(), index++, value_.release().ptr()); // steals a reference PyList_SET_ITEM(l.ptr(), index++, value_.release().ptr()); // steals a reference
} }
return l.release(); return l.release();
@ -169,23 +190,25 @@ public:
PYBIND11_TYPE_CASTER(Type, const_name("List[") + value_conv::name + const_name("]")); PYBIND11_TYPE_CASTER(Type, const_name("List[") + value_conv::name + const_name("]"));
}; };
template <typename Type, typename Alloc> struct type_caster<std::vector<Type, Alloc>> template <typename Type, typename Alloc>
: list_caster<std::vector<Type, Alloc>, Type> { }; struct type_caster<std::vector<Type, Alloc>> : list_caster<std::vector<Type, Alloc>, Type> {};
template <typename Type, typename Alloc> struct type_caster<std::deque<Type, Alloc>> template <typename Type, typename Alloc>
: list_caster<std::deque<Type, Alloc>, Type> { }; struct type_caster<std::deque<Type, Alloc>> : list_caster<std::deque<Type, Alloc>, Type> {};
template <typename Type, typename Alloc> struct type_caster<std::list<Type, Alloc>> template <typename Type, typename Alloc>
: list_caster<std::list<Type, Alloc>, Type> { }; struct type_caster<std::list<Type, Alloc>> : list_caster<std::list<Type, Alloc>, Type> {};
template <typename ArrayType, typename Value, bool Resizable, size_t Size = 0> struct array_caster { template <typename ArrayType, typename Value, bool Resizable, size_t Size = 0>
struct array_caster {
using value_conv = make_caster<Value>; using value_conv = make_caster<Value>;
private: private:
template <bool R = Resizable> template <bool R = Resizable>
bool require_size(enable_if_t<R, size_t> size) { bool require_size(enable_if_t<R, size_t> size) {
if (value.size() != size) if (value.size() != size) {
value.resize(size); value.resize(size);
}
return true; return true;
} }
template <bool R = Resizable> template <bool R = Resizable>
@ -195,16 +218,19 @@ private:
public: public:
bool load(handle src, bool convert) { bool load(handle src, bool convert) {
if (!isinstance<sequence>(src)) if (!isinstance<sequence>(src)) {
return false; return false;
}
auto l = reinterpret_borrow<sequence>(src); auto l = reinterpret_borrow<sequence>(src);
if (!require_size(l.size())) if (!require_size(l.size())) {
return false; return false;
}
size_t ctr = 0; size_t ctr = 0;
for (auto it : l) { for (auto it : l) {
value_conv conv; value_conv conv;
if (!conv.load(it, convert)) if (!conv.load(it, convert)) {
return false; return false;
}
value[ctr++] = cast_op<Value &&>(std::move(conv)); value[ctr++] = cast_op<Value &&>(std::move(conv));
} }
return true; return true;
@ -215,43 +241,57 @@ public:
list l(src.size()); list l(src.size());
ssize_t index = 0; ssize_t index = 0;
for (auto &&value : src) { for (auto &&value : src) {
auto value_ = reinterpret_steal<object>(value_conv::cast(forward_like<T>(value), policy, parent)); auto value_ = reinterpret_steal<object>(
if (!value_) value_conv::cast(forward_like<T>(value), policy, parent));
if (!value_) {
return handle(); return handle();
}
PyList_SET_ITEM(l.ptr(), index++, value_.release().ptr()); // steals a reference PyList_SET_ITEM(l.ptr(), index++, value_.release().ptr()); // steals a reference
} }
return l.release(); return l.release();
} }
PYBIND11_TYPE_CASTER(ArrayType, const_name("List[") + value_conv::name + const_name<Resizable>(const_name(""), const_name("[") + const_name<Size>() + const_name("]")) + const_name("]")); PYBIND11_TYPE_CASTER(ArrayType,
const_name("List[") + value_conv::name
+ const_name<Resizable>(const_name(""),
const_name("[") + const_name<Size>()
+ const_name("]"))
+ const_name("]"));
}; };
template <typename Type, size_t Size> struct type_caster<std::array<Type, Size>> template <typename Type, size_t Size>
: array_caster<std::array<Type, Size>, Type, false, Size> { }; struct type_caster<std::array<Type, Size>>
: array_caster<std::array<Type, Size>, Type, false, Size> {};
template <typename Type> struct type_caster<std::valarray<Type>> template <typename Type>
: array_caster<std::valarray<Type>, Type, true> { }; struct type_caster<std::valarray<Type>> : array_caster<std::valarray<Type>, Type, true> {};
template <typename Key, typename Compare, typename Alloc> struct type_caster<std::set<Key, Compare, Alloc>> template <typename Key, typename Compare, typename Alloc>
: set_caster<std::set<Key, Compare, Alloc>, Key> { }; struct type_caster<std::set<Key, Compare, Alloc>>
: set_caster<std::set<Key, Compare, Alloc>, Key> {};
template <typename Key, typename Hash, typename Equal, typename Alloc> struct type_caster<std::unordered_set<Key, Hash, Equal, Alloc>> template <typename Key, typename Hash, typename Equal, typename Alloc>
: set_caster<std::unordered_set<Key, Hash, Equal, Alloc>, Key> { }; struct type_caster<std::unordered_set<Key, Hash, Equal, Alloc>>
: set_caster<std::unordered_set<Key, Hash, Equal, Alloc>, Key> {};
template <typename Key, typename Value, typename Compare, typename Alloc> struct type_caster<std::map<Key, Value, Compare, Alloc>> template <typename Key, typename Value, typename Compare, typename Alloc>
: map_caster<std::map<Key, Value, Compare, Alloc>, Key, Value> { }; struct type_caster<std::map<Key, Value, Compare, Alloc>>
: map_caster<std::map<Key, Value, Compare, Alloc>, Key, Value> {};
template <typename Key, typename Value, typename Hash, typename Equal, typename Alloc> struct type_caster<std::unordered_map<Key, Value, Hash, Equal, Alloc>> template <typename Key, typename Value, typename Hash, typename Equal, typename Alloc>
: map_caster<std::unordered_map<Key, Value, Hash, Equal, Alloc>, Key, Value> { }; struct type_caster<std::unordered_map<Key, Value, Hash, Equal, Alloc>>
: map_caster<std::unordered_map<Key, Value, Hash, Equal, Alloc>, Key, Value> {};
// This type caster is intended to be used for std::optional and std::experimental::optional // This type caster is intended to be used for std::optional and std::experimental::optional
template<typename Type, typename Value = typename Type::value_type> struct optional_caster { template <typename Type, typename Value = typename Type::value_type>
struct optional_caster {
using value_conv = make_caster<Value>; using value_conv = make_caster<Value>;
template <typename T> template <typename T>
static handle cast(T &&src, return_value_policy policy, handle parent) { static handle cast(T &&src, return_value_policy policy, handle parent) {
if (!src) if (!src) {
return none().inc_ref(); return none().inc_ref();
}
if (!std::is_lvalue_reference<T>::value) { if (!std::is_lvalue_reference<T>::value) {
policy = return_value_policy_override<Value>::policy(policy); policy = return_value_policy_override<Value>::policy(policy);
} }
@ -263,11 +303,12 @@ template<typename Type, typename Value = typename Type::value_type> struct optio
return false; return false;
} }
if (src.is_none()) { if (src.is_none()) {
return true; // default-constructed value is already empty return true; // default-constructed value is already empty
} }
value_conv inner_caster; value_conv inner_caster;
if (!inner_caster.load(src, convert)) if (!inner_caster.load(src, convert)) {
return false; return false;
}
value.emplace(cast_op<Value &&>(std::move(inner_caster))); value.emplace(cast_op<Value &&>(std::move(inner_caster)));
return true; return true;
@ -277,18 +318,20 @@ template<typename Type, typename Value = typename Type::value_type> struct optio
}; };
#if defined(PYBIND11_HAS_OPTIONAL) #if defined(PYBIND11_HAS_OPTIONAL)
template<typename T> struct type_caster<std::optional<T>> template <typename T>
: public optional_caster<std::optional<T>> {}; struct type_caster<std::optional<T>> : public optional_caster<std::optional<T>> {};
template<> struct type_caster<std::nullopt_t> template <>
: public void_caster<std::nullopt_t> {}; struct type_caster<std::nullopt_t> : public void_caster<std::nullopt_t> {};
#endif #endif
#if defined(PYBIND11_HAS_EXP_OPTIONAL) #if defined(PYBIND11_HAS_EXP_OPTIONAL)
template<typename T> struct type_caster<std::experimental::optional<T>> template <typename T>
struct type_caster<std::experimental::optional<T>>
: public optional_caster<std::experimental::optional<T>> {}; : public optional_caster<std::experimental::optional<T>> {};
template<> struct type_caster<std::experimental::nullopt_t> template <>
struct type_caster<std::experimental::nullopt_t>
: public void_caster<std::experimental::nullopt_t> {}; : public void_caster<std::experimental::nullopt_t> {};
#endif #endif
@ -309,7 +352,7 @@ struct variant_caster_visitor {
/// `namespace::variant` types which provide a `namespace::visit()` function are handled here /// `namespace::variant` types which provide a `namespace::visit()` function are handled here
/// automatically using argument-dependent lookup. Users can provide specializations for other /// automatically using argument-dependent lookup. Users can provide specializations for other
/// variant-like classes, e.g. `boost::variant` and `boost::apply_visitor`. /// variant-like classes, e.g. `boost::variant` and `boost::apply_visitor`.
template <template<typename...> class Variant> template <template <typename...> class Variant>
struct visit_helper { struct visit_helper {
template <typename... Args> template <typename... Args>
static auto call(Args &&...args) -> decltype(visit(std::forward<Args>(args)...)) { static auto call(Args &&...args) -> decltype(visit(std::forward<Args>(args)...)) {
@ -318,9 +361,10 @@ struct visit_helper {
}; };
/// Generic variant caster /// Generic variant caster
template <typename Variant> struct variant_caster; template <typename Variant>
struct variant_caster;
template <template<typename...> class V, typename... Ts> template <template <typename...> class V, typename... Ts>
struct variant_caster<V<Ts...>> { struct variant_caster<V<Ts...>> {
static_assert(sizeof...(Ts) > 0, "Variant must consist of at least one alternative."); static_assert(sizeof...(Ts) > 0, "Variant must consist of at least one alternative.");
@ -328,7 +372,7 @@ struct variant_caster<V<Ts...>> {
bool load_alternative(handle src, bool convert, type_list<U, Us...>) { bool load_alternative(handle src, bool convert, type_list<U, Us...>) {
auto caster = make_caster<U>(); auto caster = make_caster<U>();
if (caster.load(src, convert)) { if (caster.load(src, convert)) {
value = cast_op<U>(caster); value = cast_op<U>(std::move(caster));
return true; return true;
} }
return load_alternative(src, convert, type_list<Us...>{}); return load_alternative(src, convert, type_list<Us...>{});
@ -341,8 +385,9 @@ struct variant_caster<V<Ts...>> {
// E.g. `py::int_(1).cast<variant<double, int>>()` needs to fill the `int` // E.g. `py::int_(1).cast<variant<double, int>>()` needs to fill the `int`
// slot of the variant. Without two-pass loading `double` would be filled // slot of the variant. Without two-pass loading `double` would be filled
// because it appears first and a conversion is possible. // because it appears first and a conversion is possible.
if (convert && load_alternative(src, false, type_list<Ts...>{})) if (convert && load_alternative(src, false, type_list<Ts...>{})) {
return true; return true;
}
return load_alternative(src, convert, type_list<Ts...>{}); return load_alternative(src, convert, type_list<Ts...>{});
} }
@ -353,12 +398,17 @@ struct variant_caster<V<Ts...>> {
} }
using Type = V<Ts...>; using Type = V<Ts...>;
PYBIND11_TYPE_CASTER(Type, const_name("Union[") + detail::concat(make_caster<Ts>::name...) + const_name("]")); PYBIND11_TYPE_CASTER(Type,
const_name("Union[") + detail::concat(make_caster<Ts>::name...)
+ const_name("]"));
}; };
#if defined(PYBIND11_HAS_VARIANT) #if defined(PYBIND11_HAS_VARIANT)
template <typename... Ts> template <typename... Ts>
struct type_caster<std::variant<Ts...>> : variant_caster<std::variant<Ts...>> { }; struct type_caster<std::variant<Ts...>> : variant_caster<std::variant<Ts...>> {};
template <>
struct type_caster<std::monostate> : public void_caster<std::monostate> {};
#endif #endif
PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(detail)

View File

@ -4,54 +4,60 @@
#pragma once #pragma once
#include "../cast.h"
#include "../pybind11.h" #include "../pybind11.h"
#include "../pytypes.h"
#include "../detail/common.h" #include "../detail/common.h"
#include "../detail/descr.h" #include "../detail/descr.h"
#include "../cast.h"
#include "../pytypes.h"
#include <string> #include <string>
#ifdef __has_include #ifdef __has_include
# if defined(PYBIND11_CPP17) && __has_include(<filesystem>) && \ # if defined(PYBIND11_CPP17)
PY_VERSION_HEX >= 0x03060000 # if __has_include(<filesystem>) && \
# include <filesystem> PY_VERSION_HEX >= 0x03060000
# define PYBIND11_HAS_FILESYSTEM 1 # include <filesystem>
# endif # define PYBIND11_HAS_FILESYSTEM 1
# elif __has_include(<experimental/filesystem>)
# include <experimental/filesystem>
# define PYBIND11_HAS_EXPERIMENTAL_FILESYSTEM 1
# endif
# endif
#endif #endif
#if !defined(PYBIND11_HAS_FILESYSTEM) && !defined(PYBIND11_HAS_FILESYSTEM_IS_OPTIONAL) #if !defined(PYBIND11_HAS_FILESYSTEM) && !defined(PYBIND11_HAS_EXPERIMENTAL_FILESYSTEM) \
&& !defined(PYBIND11_HAS_FILESYSTEM_IS_OPTIONAL)
# error \ # error \
"#include <filesystem> is not available. (Use -DPYBIND11_HAS_FILESYSTEM_IS_OPTIONAL to ignore.)" "Neither #include <filesystem> nor #include <experimental/filesystem is available. (Use -DPYBIND11_HAS_FILESYSTEM_IS_OPTIONAL to ignore.)"
#endif #endif
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
#if defined(PYBIND11_HAS_FILESYSTEM) #if defined(PYBIND11_HAS_FILESYSTEM) || defined(PYBIND11_HAS_EXPERIMENTAL_FILESYSTEM)
template<typename T> struct path_caster { template <typename T>
struct path_caster {
private: private:
static PyObject* unicode_from_fs_native(const std::string& w) { static PyObject *unicode_from_fs_native(const std::string &w) {
#if !defined(PYPY_VERSION) # if !defined(PYPY_VERSION)
return PyUnicode_DecodeFSDefaultAndSize(w.c_str(), ssize_t(w.size())); return PyUnicode_DecodeFSDefaultAndSize(w.c_str(), ssize_t(w.size()));
#else # else
// PyPy mistakenly declares the first parameter as non-const. // PyPy mistakenly declares the first parameter as non-const.
return PyUnicode_DecodeFSDefaultAndSize( return PyUnicode_DecodeFSDefaultAndSize(const_cast<char *>(w.c_str()), ssize_t(w.size()));
const_cast<char*>(w.c_str()), ssize_t(w.size())); # endif
#endif
} }
static PyObject* unicode_from_fs_native(const std::wstring& w) { static PyObject *unicode_from_fs_native(const std::wstring &w) {
return PyUnicode_FromWideChar(w.c_str(), ssize_t(w.size())); return PyUnicode_FromWideChar(w.c_str(), ssize_t(w.size()));
} }
public: public:
static handle cast(const T& path, return_value_policy, handle) { static handle cast(const T &path, return_value_policy, handle) {
if (auto py_str = unicode_from_fs_native(path.native())) { if (auto py_str = unicode_from_fs_native(path.native())) {
return module_::import("pathlib").attr("Path")(reinterpret_steal<object>(py_str)) return module_::import("pathlib")
.release(); .attr("Path")(reinterpret_steal<object>(py_str))
.release();
} }
return nullptr; return nullptr;
} }
@ -60,15 +66,15 @@ public:
// PyUnicode_FSConverter and PyUnicode_FSDecoder normally take care of // PyUnicode_FSConverter and PyUnicode_FSDecoder normally take care of
// calling PyOS_FSPath themselves, but that's broken on PyPy (PyPy // calling PyOS_FSPath themselves, but that's broken on PyPy (PyPy
// issue #3168) so we do it ourselves instead. // issue #3168) so we do it ourselves instead.
PyObject* buf = PyOS_FSPath(handle.ptr()); PyObject *buf = PyOS_FSPath(handle.ptr());
if (!buf) { if (!buf) {
PyErr_Clear(); PyErr_Clear();
return false; return false;
} }
PyObject* native = nullptr; PyObject *native = nullptr;
if constexpr (std::is_same_v<typename T::value_type, char>) { if constexpr (std::is_same_v<typename T::value_type, char>) {
if (PyUnicode_FSConverter(buf, &native) != 0) { if (PyUnicode_FSConverter(buf, &native) != 0) {
if (auto c_str = PyBytes_AsString(native)) { if (auto *c_str = PyBytes_AsString(native)) {
// AsString returns a pointer to the internal buffer, which // AsString returns a pointer to the internal buffer, which
// must not be free'd. // must not be free'd.
value = c_str; value = c_str;
@ -76,9 +82,9 @@ public:
} }
} else if constexpr (std::is_same_v<typename T::value_type, wchar_t>) { } else if constexpr (std::is_same_v<typename T::value_type, wchar_t>) {
if (PyUnicode_FSDecoder(buf, &native) != 0) { if (PyUnicode_FSDecoder(buf, &native) != 0) {
if (auto c_str = PyUnicode_AsWideCharString(native, nullptr)) { if (auto *c_str = PyUnicode_AsWideCharString(native, nullptr)) {
// AsWideCharString returns a new string that must be free'd. // AsWideCharString returns a new string that must be free'd.
value = c_str; // Copies the string. value = c_str; // Copies the string.
PyMem_Free(c_str); PyMem_Free(c_str);
} }
} }
@ -95,9 +101,16 @@ public:
PYBIND11_TYPE_CASTER(T, const_name("os.PathLike")); PYBIND11_TYPE_CASTER(T, const_name("os.PathLike"));
}; };
template<> struct type_caster<std::filesystem::path> #endif // PYBIND11_HAS_FILESYSTEM || defined(PYBIND11_HAS_EXPERIMENTAL_FILESYSTEM)
: public path_caster<std::filesystem::path> {};
#endif // PYBIND11_HAS_FILESYSTEM #if defined(PYBIND11_HAS_FILESYSTEM)
template <>
struct type_caster<std::filesystem::path> : public path_caster<std::filesystem::path> {};
#elif defined(PYBIND11_HAS_EXPERIMENTAL_FILESYSTEM)
template <>
struct type_caster<std::experimental::filesystem::path>
: public path_caster<std::experimental::filesystem::path> {};
#endif
PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@ -19,137 +19,148 @@ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
/* SFINAE helper class used by 'is_comparable */ /* SFINAE helper class used by 'is_comparable */
template <typename T> struct container_traits { template <typename T>
template <typename T2> static std::true_type test_comparable(decltype(std::declval<const T2 &>() == std::declval<const T2 &>())*); struct container_traits {
template <typename T2> static std::false_type test_comparable(...); template <typename T2>
template <typename T2> static std::true_type test_value(typename T2::value_type *); static std::true_type
template <typename T2> static std::false_type test_value(...); test_comparable(decltype(std::declval<const T2 &>() == std::declval<const T2 &>()) *);
template <typename T2> static std::true_type test_pair(typename T2::first_type *, typename T2::second_type *); template <typename T2>
template <typename T2> static std::false_type test_pair(...); static std::false_type test_comparable(...);
template <typename T2>
static std::true_type test_value(typename T2::value_type *);
template <typename T2>
static std::false_type test_value(...);
template <typename T2>
static std::true_type test_pair(typename T2::first_type *, typename T2::second_type *);
template <typename T2>
static std::false_type test_pair(...);
static constexpr const bool is_comparable = std::is_same<std::true_type, decltype(test_comparable<T>(nullptr))>::value; static constexpr const bool is_comparable
static constexpr const bool is_pair = std::is_same<std::true_type, decltype(test_pair<T>(nullptr, nullptr))>::value; = std::is_same<std::true_type, decltype(test_comparable<T>(nullptr))>::value;
static constexpr const bool is_vector = std::is_same<std::true_type, decltype(test_value<T>(nullptr))>::value; static constexpr const bool is_pair
= std::is_same<std::true_type, decltype(test_pair<T>(nullptr, nullptr))>::value;
static constexpr const bool is_vector
= std::is_same<std::true_type, decltype(test_value<T>(nullptr))>::value;
static constexpr const bool is_element = !is_pair && !is_vector; static constexpr const bool is_element = !is_pair && !is_vector;
}; };
/* Default: is_comparable -> std::false_type */ /* Default: is_comparable -> std::false_type */
template <typename T, typename SFINAE = void> template <typename T, typename SFINAE = void>
struct is_comparable : std::false_type { }; struct is_comparable : std::false_type {};
/* For non-map data structures, check whether operator== can be instantiated */ /* For non-map data structures, check whether operator== can be instantiated */
template <typename T> template <typename T>
struct is_comparable< struct is_comparable<
T, enable_if_t<container_traits<T>::is_element && T,
container_traits<T>::is_comparable>> enable_if_t<container_traits<T>::is_element && container_traits<T>::is_comparable>>
: std::true_type { }; : std::true_type {};
/* For a vector/map data structure, recursively check the value type (which is std::pair for maps) */ /* For a vector/map data structure, recursively check the value type
(which is std::pair for maps) */
template <typename T> template <typename T>
struct is_comparable<T, enable_if_t<container_traits<T>::is_vector>> { struct is_comparable<T, enable_if_t<container_traits<T>::is_vector>> {
static constexpr const bool value = static constexpr const bool value = is_comparable<typename T::value_type>::value;
is_comparable<typename T::value_type>::value;
}; };
/* For pairs, recursively check the two data types */ /* For pairs, recursively check the two data types */
template <typename T> template <typename T>
struct is_comparable<T, enable_if_t<container_traits<T>::is_pair>> { struct is_comparable<T, enable_if_t<container_traits<T>::is_pair>> {
static constexpr const bool value = static constexpr const bool value = is_comparable<typename T::first_type>::value
is_comparable<typename T::first_type>::value && && is_comparable<typename T::second_type>::value;
is_comparable<typename T::second_type>::value;
}; };
/* Fallback functions */ /* Fallback functions */
template <typename, typename, typename... Args> void vector_if_copy_constructible(const Args &...) { } template <typename, typename, typename... Args>
template <typename, typename, typename... Args> void vector_if_equal_operator(const Args &...) { } void vector_if_copy_constructible(const Args &...) {}
template <typename, typename, typename... Args> void vector_if_insertion_operator(const Args &...) { } template <typename, typename, typename... Args>
template <typename, typename, typename... Args> void vector_modifiers(const Args &...) { } void vector_if_equal_operator(const Args &...) {}
template <typename, typename, typename... Args>
void vector_if_insertion_operator(const Args &...) {}
template <typename, typename, typename... Args>
void vector_modifiers(const Args &...) {}
template<typename Vector, typename Class_> template <typename Vector, typename Class_>
void vector_if_copy_constructible(enable_if_t<is_copy_constructible<Vector>::value, Class_> &cl) { void vector_if_copy_constructible(enable_if_t<is_copy_constructible<Vector>::value, Class_> &cl) {
cl.def(init<const Vector &>(), "Copy constructor"); cl.def(init<const Vector &>(), "Copy constructor");
} }
template<typename Vector, typename Class_> template <typename Vector, typename Class_>
void vector_if_equal_operator(enable_if_t<is_comparable<Vector>::value, Class_> &cl) { void vector_if_equal_operator(enable_if_t<is_comparable<Vector>::value, Class_> &cl) {
using T = typename Vector::value_type; using T = typename Vector::value_type;
cl.def(self == self); cl.def(self == self);
cl.def(self != self); cl.def(self != self);
cl.def("count", cl.def(
[](const Vector &v, const T &x) { "count",
return std::count(v.begin(), v.end(), x); [](const Vector &v, const T &x) { return std::count(v.begin(), v.end(), x); },
},
arg("x"), arg("x"),
"Return the number of times ``x`` appears in the list" "Return the number of times ``x`` appears in the list");
);
cl.def("remove", [](Vector &v, const T &x) { cl.def(
"remove",
[](Vector &v, const T &x) {
auto p = std::find(v.begin(), v.end(), x); auto p = std::find(v.begin(), v.end(), x);
if (p != v.end()) if (p != v.end()) {
v.erase(p); v.erase(p);
else } else {
throw value_error(); throw value_error();
}
}, },
arg("x"), arg("x"),
"Remove the first item from the list whose value is x. " "Remove the first item from the list whose value is x. "
"It is an error if there is no such item." "It is an error if there is no such item.");
);
cl.def("__contains__", cl.def(
[](const Vector &v, const T &x) { "__contains__",
return std::find(v.begin(), v.end(), x) != v.end(); [](const Vector &v, const T &x) { return std::find(v.begin(), v.end(), x) != v.end(); },
},
arg("x"), arg("x"),
"Return true the container contains ``x``" "Return true the container contains ``x``");
);
} }
// Vector modifiers -- requires a copyable vector_type: // Vector modifiers -- requires a copyable vector_type:
// (Technically, some of these (pop and __delitem__) don't actually require copyability, but it seems // (Technically, some of these (pop and __delitem__) don't actually require copyability, but it
// silly to allow deletion but not insertion, so include them here too.) // seems silly to allow deletion but not insertion, so include them here too.)
template <typename Vector, typename Class_> template <typename Vector, typename Class_>
void vector_modifiers(enable_if_t<is_copy_constructible<typename Vector::value_type>::value, Class_> &cl) { void vector_modifiers(
enable_if_t<is_copy_constructible<typename Vector::value_type>::value, Class_> &cl) {
using T = typename Vector::value_type; using T = typename Vector::value_type;
using SizeType = typename Vector::size_type; using SizeType = typename Vector::size_type;
using DiffType = typename Vector::difference_type; using DiffType = typename Vector::difference_type;
auto wrap_i = [](DiffType i, SizeType n) { auto wrap_i = [](DiffType i, SizeType n) {
if (i < 0) if (i < 0) {
i += n; i += n;
if (i < 0 || (SizeType)i >= n) }
if (i < 0 || (SizeType) i >= n) {
throw index_error(); throw index_error();
}
return i; return i;
}; };
cl.def("append", cl.def(
[](Vector &v, const T &value) { v.push_back(value); }, "append",
arg("x"), [](Vector &v, const T &value) { v.push_back(value); },
"Add an item to the end of the list"); arg("x"),
"Add an item to the end of the list");
cl.def(init([](const iterable &it) { cl.def(init([](const iterable &it) {
auto v = std::unique_ptr<Vector>(new Vector()); auto v = std::unique_ptr<Vector>(new Vector());
v->reserve(len_hint(it)); v->reserve(len_hint(it));
for (handle h : it) for (handle h : it) {
v->push_back(h.cast<T>()); v->push_back(h.cast<T>());
}
return v.release(); return v.release();
})); }));
cl.def("clear", cl.def(
[](Vector &v) { "clear", [](Vector &v) { v.clear(); }, "Clear the contents");
v.clear();
},
"Clear the contents"
);
cl.def("extend", cl.def(
[](Vector &v, const Vector &src) { "extend",
v.insert(v.end(), src.begin(), src.end()); [](Vector &v, const Vector &src) { v.insert(v.end(), src.begin(), src.end()); },
}, arg("L"),
arg("L"), "Extend the list by appending all the items in the given list");
"Extend the list by appending all the items in the given list"
);
cl.def( cl.def(
"extend", "extend",
@ -174,31 +185,36 @@ void vector_modifiers(enable_if_t<is_copy_constructible<typename Vector::value_t
arg("L"), arg("L"),
"Extend the list by appending all the items in the given list"); "Extend the list by appending all the items in the given list");
cl.def("insert", cl.def(
"insert",
[](Vector &v, DiffType i, const T &x) { [](Vector &v, DiffType i, const T &x) {
// Can't use wrap_i; i == v.size() is OK // Can't use wrap_i; i == v.size() is OK
if (i < 0) if (i < 0) {
i += v.size(); i += v.size();
if (i < 0 || (SizeType)i > v.size()) }
if (i < 0 || (SizeType) i > v.size()) {
throw index_error(); throw index_error();
}
v.insert(v.begin() + i, x); v.insert(v.begin() + i, x);
}, },
arg("i") , arg("x"), arg("i"),
"Insert an item at a given position." arg("x"),
); "Insert an item at a given position.");
cl.def("pop", cl.def(
"pop",
[](Vector &v) { [](Vector &v) {
if (v.empty()) if (v.empty()) {
throw index_error(); throw index_error();
}
T t = std::move(v.back()); T t = std::move(v.back());
v.pop_back(); v.pop_back();
return t; return t;
}, },
"Remove and return the last item" "Remove and return the last item");
);
cl.def("pop", cl.def(
"pop",
[wrap_i](Vector &v, DiffType i) { [wrap_i](Vector &v, DiffType i) {
i = wrap_i(i, v.size()); i = wrap_i(i, v.size());
T t = std::move(v[(SizeType) i]); T t = std::move(v[(SizeType) i]);
@ -206,29 +222,27 @@ void vector_modifiers(enable_if_t<is_copy_constructible<typename Vector::value_t
return t; return t;
}, },
arg("i"), arg("i"),
"Remove and return the item at index ``i``" "Remove and return the item at index ``i``");
);
cl.def("__setitem__", cl.def("__setitem__", [wrap_i](Vector &v, DiffType i, const T &t) {
[wrap_i](Vector &v, DiffType i, const T &t) { i = wrap_i(i, v.size());
i = wrap_i(i, v.size()); v[(SizeType) i] = t;
v[(SizeType)i] = t; });
}
);
/// Slicing protocol /// Slicing protocol
cl.def( cl.def(
"__getitem__", "__getitem__",
[](const Vector &v, slice slice) -> Vector * { [](const Vector &v, const slice &slice) -> Vector * {
size_t start = 0, stop = 0, step = 0, slicelength = 0; size_t start = 0, stop = 0, step = 0, slicelength = 0;
if (!slice.compute(v.size(), &start, &stop, &step, &slicelength)) if (!slice.compute(v.size(), &start, &stop, &step, &slicelength)) {
throw error_already_set(); throw error_already_set();
}
auto *seq = new Vector(); auto *seq = new Vector();
seq->reserve((size_t) slicelength); seq->reserve((size_t) slicelength);
for (size_t i=0; i<slicelength; ++i) { for (size_t i = 0; i < slicelength; ++i) {
seq->push_back(v[start]); seq->push_back(v[start]);
start += step; start += step;
} }
@ -239,36 +253,40 @@ void vector_modifiers(enable_if_t<is_copy_constructible<typename Vector::value_t
cl.def( cl.def(
"__setitem__", "__setitem__",
[](Vector &v, slice slice, const Vector &value) { [](Vector &v, const slice &slice, const Vector &value) {
size_t start = 0, stop = 0, step = 0, slicelength = 0; size_t start = 0, stop = 0, step = 0, slicelength = 0;
if (!slice.compute(v.size(), &start, &stop, &step, &slicelength)) if (!slice.compute(v.size(), &start, &stop, &step, &slicelength)) {
throw error_already_set(); throw error_already_set();
}
if (slicelength != value.size()) if (slicelength != value.size()) {
throw std::runtime_error("Left and right hand size of slice assignment have different sizes!"); throw std::runtime_error(
"Left and right hand size of slice assignment have different sizes!");
}
for (size_t i=0; i<slicelength; ++i) { for (size_t i = 0; i < slicelength; ++i) {
v[start] = value[i]; v[start] = value[i];
start += step; start += step;
} }
}, },
"Assign list elements using a slice object"); "Assign list elements using a slice object");
cl.def("__delitem__", cl.def(
"__delitem__",
[wrap_i](Vector &v, DiffType i) { [wrap_i](Vector &v, DiffType i) {
i = wrap_i(i, v.size()); i = wrap_i(i, v.size());
v.erase(v.begin() + i); v.erase(v.begin() + i);
}, },
"Delete the list elements at index ``i``" "Delete the list elements at index ``i``");
);
cl.def( cl.def(
"__delitem__", "__delitem__",
[](Vector &v, slice slice) { [](Vector &v, const slice &slice) {
size_t start = 0, stop = 0, step = 0, slicelength = 0; size_t start = 0, stop = 0, step = 0, slicelength = 0;
if (!slice.compute(v.size(), &start, &stop, &step, &slicelength)) if (!slice.compute(v.size(), &start, &stop, &step, &slicelength)) {
throw error_already_set(); throw error_already_set();
}
if (step == 1 && false) { if (step == 1 && false) {
v.erase(v.begin() + (DiffType) start, v.begin() + DiffType(start + slicelength)); v.erase(v.begin() + (DiffType) start, v.begin() + DiffType(start + slicelength));
@ -284,8 +302,10 @@ void vector_modifiers(enable_if_t<is_copy_constructible<typename Vector::value_t
// If the type has an operator[] that doesn't return a reference (most notably std::vector<bool>), // If the type has an operator[] that doesn't return a reference (most notably std::vector<bool>),
// we have to access by copying; otherwise we return by reference. // we have to access by copying; otherwise we return by reference.
template <typename Vector> using vector_needs_copy = negation< template <typename Vector>
std::is_same<decltype(std::declval<Vector>()[typename Vector::size_type()]), typename Vector::value_type &>>; using vector_needs_copy
= negation<std::is_same<decltype(std::declval<Vector>()[typename Vector::size_type()]),
typename Vector::value_type &>>;
// The usual case: access and iterate by reference // The usual case: access and iterate by reference
template <typename Vector, typename Class_> template <typename Vector, typename Class_>
@ -293,31 +313,34 @@ void vector_accessor(enable_if_t<!vector_needs_copy<Vector>::value, Class_> &cl)
using T = typename Vector::value_type; using T = typename Vector::value_type;
using SizeType = typename Vector::size_type; using SizeType = typename Vector::size_type;
using DiffType = typename Vector::difference_type; using DiffType = typename Vector::difference_type;
using ItType = typename Vector::iterator; using ItType = typename Vector::iterator;
auto wrap_i = [](DiffType i, SizeType n) { auto wrap_i = [](DiffType i, SizeType n) {
if (i < 0) if (i < 0) {
i += n; i += n;
if (i < 0 || (SizeType)i >= n) }
if (i < 0 || (SizeType) i >= n) {
throw index_error(); throw index_error();
}
return i; return i;
}; };
cl.def("__getitem__", cl.def(
"__getitem__",
[wrap_i](Vector &v, DiffType i) -> T & { [wrap_i](Vector &v, DiffType i) -> T & {
i = wrap_i(i, v.size()); i = wrap_i(i, v.size());
return v[(SizeType)i]; return v[(SizeType) i];
}, },
return_value_policy::reference_internal // ref + keepalive return_value_policy::reference_internal // ref + keepalive
); );
cl.def("__iter__", cl.def(
[](Vector &v) { "__iter__",
return make_iterator< [](Vector &v) {
return_value_policy::reference_internal, ItType, ItType, T&>( return make_iterator<return_value_policy::reference_internal, ItType, ItType, T &>(
v.begin(), v.end()); v.begin(), v.end());
}, },
keep_alive<0, 1>() /* Essential: keep list alive while iterator exists */ keep_alive<0, 1>() /* Essential: keep list alive while iterator exists */
); );
} }
@ -327,53 +350,60 @@ void vector_accessor(enable_if_t<vector_needs_copy<Vector>::value, Class_> &cl)
using T = typename Vector::value_type; using T = typename Vector::value_type;
using SizeType = typename Vector::size_type; using SizeType = typename Vector::size_type;
using DiffType = typename Vector::difference_type; using DiffType = typename Vector::difference_type;
using ItType = typename Vector::iterator; using ItType = typename Vector::iterator;
cl.def("__getitem__", cl.def("__getitem__", [](const Vector &v, DiffType i) -> T {
[](const Vector &v, DiffType i) -> T { if (i < 0 && (i += v.size()) < 0) {
if (i < 0 && (i += v.size()) < 0) throw index_error();
throw index_error();
if ((SizeType)i >= v.size())
throw index_error();
return v[(SizeType)i];
} }
); if ((SizeType) i >= v.size()) {
throw index_error();
}
return v[(SizeType) i];
});
cl.def("__iter__", cl.def(
[](Vector &v) { "__iter__",
return make_iterator< [](Vector &v) {
return_value_policy::copy, ItType, ItType, T>( return make_iterator<return_value_policy::copy, ItType, ItType, T>(v.begin(), v.end());
v.begin(), v.end()); },
}, keep_alive<0, 1>() /* Essential: keep list alive while iterator exists */
keep_alive<0, 1>() /* Essential: keep list alive while iterator exists */
); );
} }
template <typename Vector, typename Class_> auto vector_if_insertion_operator(Class_ &cl, std::string const &name) template <typename Vector, typename Class_>
-> decltype(std::declval<std::ostream&>() << std::declval<typename Vector::value_type>(), void()) { auto vector_if_insertion_operator(Class_ &cl, std::string const &name)
-> decltype(std::declval<std::ostream &>() << std::declval<typename Vector::value_type>(),
void()) {
using size_type = typename Vector::size_type; using size_type = typename Vector::size_type;
cl.def("__repr__", cl.def(
[name](Vector &v) { "__repr__",
[name](Vector &v) {
std::ostringstream s; std::ostringstream s;
s << name << '['; s << name << '[';
for (size_type i=0; i < v.size(); ++i) { for (size_type i = 0; i < v.size(); ++i) {
s << v[i]; s << v[i];
if (i != v.size() - 1) if (i != v.size() - 1) {
s << ", "; s << ", ";
}
} }
s << ']'; s << ']';
return s.str(); return s.str();
}, },
"Return the canonical string representation of this list." "Return the canonical string representation of this list.");
);
} }
// Provide the buffer interface for vectors if we have data() and we have a format for it // Provide the buffer interface for vectors if we have data() and we have a format for it
// GCC seems to have "void std::vector<bool>::data()" - doing SFINAE on the existence of data() is insufficient, we need to check it returns an appropriate pointer // GCC seems to have "void std::vector<bool>::data()" - doing SFINAE on the existence of data()
// is insufficient, we need to check it returns an appropriate pointer
template <typename Vector, typename = void> template <typename Vector, typename = void>
struct vector_has_data_and_format : std::false_type {}; struct vector_has_data_and_format : std::false_type {};
template <typename Vector> template <typename Vector>
struct vector_has_data_and_format<Vector, enable_if_t<std::is_same<decltype(format_descriptor<typename Vector::value_type>::format(), std::declval<Vector>().data()), typename Vector::value_type*>::value>> : std::true_type {}; struct vector_has_data_and_format<
Vector,
enable_if_t<std::is_same<decltype(format_descriptor<typename Vector::value_type>::format(),
std::declval<Vector>().data()),
typename Vector::value_type *>::value>> : std::true_type {};
// [workaround(intel)] Separate function required here // [workaround(intel)] Separate function required here
// Workaround as the Intel compiler does not compile the enable_if_t part below // Workaround as the Intel compiler does not compile the enable_if_t part below
@ -388,26 +418,37 @@ constexpr bool args_any_are_buffer() {
// Add the buffer interface to a vector // Add the buffer interface to a vector
template <typename Vector, typename Class_, typename... Args> template <typename Vector, typename Class_, typename... Args>
void vector_buffer_impl(Class_& cl, std::true_type) { void vector_buffer_impl(Class_ &cl, std::true_type) {
using T = typename Vector::value_type; using T = typename Vector::value_type;
static_assert(vector_has_data_and_format<Vector>::value, "There is not an appropriate format descriptor for this vector"); static_assert(vector_has_data_and_format<Vector>::value,
"There is not an appropriate format descriptor for this vector");
// numpy.h declares this for arbitrary types, but it may raise an exception and crash hard at runtime if PYBIND11_NUMPY_DTYPE hasn't been called, so check here // numpy.h declares this for arbitrary types, but it may raise an exception and crash hard
// at runtime if PYBIND11_NUMPY_DTYPE hasn't been called, so check here
format_descriptor<T>::format(); format_descriptor<T>::format();
cl.def_buffer([](Vector& v) -> buffer_info { cl.def_buffer([](Vector &v) -> buffer_info {
return buffer_info(v.data(), static_cast<ssize_t>(sizeof(T)), format_descriptor<T>::format(), 1, {v.size()}, {sizeof(T)}); return buffer_info(v.data(),
static_cast<ssize_t>(sizeof(T)),
format_descriptor<T>::format(),
1,
{v.size()},
{sizeof(T)});
}); });
cl.def(init([](const buffer &buf) { cl.def(init([](const buffer &buf) {
auto info = buf.request(); auto info = buf.request();
if (info.ndim != 1 || info.strides[0] % static_cast<ssize_t>(sizeof(T))) if (info.ndim != 1 || info.strides[0] % static_cast<ssize_t>(sizeof(T))) {
throw type_error("Only valid 1D buffers can be copied to a vector"); throw type_error("Only valid 1D buffers can be copied to a vector");
if (!detail::compare_buffer_info<T>::compare(info) || (ssize_t) sizeof(T) != info.itemsize) }
throw type_error("Format mismatch (Python: " + info.format + " C++: " + format_descriptor<T>::format() + ")"); if (!detail::compare_buffer_info<T>::compare(info)
|| (ssize_t) sizeof(T) != info.itemsize) {
throw type_error("Format mismatch (Python: " + info.format
+ " C++: " + format_descriptor<T>::format() + ")");
}
T *p = static_cast<T*>(info.ptr); T *p = static_cast<T *>(info.ptr);
ssize_t step = info.strides[0] / static_cast<ssize_t>(sizeof(T)); ssize_t step = info.strides[0] / static_cast<ssize_t>(sizeof(T));
T *end = p + info.shape[0] * step; T *end = p + info.shape[0] * step;
if (step == 1) { if (step == 1) {
@ -415,21 +456,22 @@ void vector_buffer_impl(Class_& cl, std::true_type) {
} }
Vector vec; Vector vec;
vec.reserve((size_t) info.shape[0]); vec.reserve((size_t) info.shape[0]);
for (; p != end; p += step) for (; p != end; p += step) {
vec.push_back(*p); vec.push_back(*p);
}
return vec; return vec;
})); }));
return; return;
} }
template <typename Vector, typename Class_, typename... Args> template <typename Vector, typename Class_, typename... Args>
void vector_buffer_impl(Class_&, std::false_type) {} void vector_buffer_impl(Class_ &, std::false_type) {}
template <typename Vector, typename Class_, typename... Args> template <typename Vector, typename Class_, typename... Args>
void vector_buffer(Class_& cl) { void vector_buffer(Class_ &cl) {
vector_buffer_impl<Vector, Class_, Args...>(cl, detail::any_of<std::is_same<Args, buffer_protocol>...>{}); vector_buffer_impl<Vector, Class_, Args...>(
cl, detail::any_of<std::is_same<Args, buffer_protocol>...>{});
} }
PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(detail)
@ -438,13 +480,13 @@ PYBIND11_NAMESPACE_END(detail)
// std::vector // std::vector
// //
template <typename Vector, typename holder_type = std::unique_ptr<Vector>, typename... Args> template <typename Vector, typename holder_type = std::unique_ptr<Vector>, typename... Args>
class_<Vector, holder_type> bind_vector(handle scope, std::string const &name, Args&&... args) { class_<Vector, holder_type> bind_vector(handle scope, std::string const &name, Args &&...args) {
using Class_ = class_<Vector, holder_type>; using Class_ = class_<Vector, holder_type>;
// If the value_type is unregistered (e.g. a converting type) or is itself registered // If the value_type is unregistered (e.g. a converting type) or is itself registered
// module-local then make the vector binding module-local as well: // module-local then make the vector binding module-local as well:
using vtype = typename Vector::value_type; using vtype = typename Vector::value_type;
auto vtype_info = detail::get_type_info(typeid(vtype)); auto *vtype_info = detail::get_type_info(typeid(vtype));
bool local = !vtype_info || vtype_info->module_local; bool local = !vtype_info || vtype_info->module_local;
Class_ cl(scope, name.c_str(), pybind11::module_local(local), std::forward<Args>(args)...); Class_ cl(scope, name.c_str(), pybind11::module_local(local), std::forward<Args>(args)...);
@ -469,18 +511,13 @@ class_<Vector, holder_type> bind_vector(handle scope, std::string const &name, A
// Accessor and iterator; return by value if copyable, otherwise we return by ref + keep-alive // Accessor and iterator; return by value if copyable, otherwise we return by ref + keep-alive
detail::vector_accessor<Vector, Class_>(cl); detail::vector_accessor<Vector, Class_>(cl);
cl.def("__bool__", cl.def(
[](const Vector &v) -> bool { "__bool__",
return !v.empty(); [](const Vector &v) -> bool { return !v.empty(); },
}, "Check whether the list is nonempty");
"Check whether the list is nonempty"
);
cl.def("__len__", &Vector::size); cl.def("__len__", &Vector::size);
#if 0 #if 0
// C++ style functions deprecated, leaving it here as an example // C++ style functions deprecated, leaving it here as an example
cl.def(init<size_type>()); cl.def(init<size_type>());
@ -524,8 +561,6 @@ class_<Vector, holder_type> bind_vector(handle scope, std::string const &name, A
return cl; return cl;
} }
// //
// std::map, std::unordered_map // std::map, std::unordered_map
// //
@ -533,90 +568,93 @@ class_<Vector, holder_type> bind_vector(handle scope, std::string const &name, A
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
/* Fallback functions */ /* Fallback functions */
template <typename, typename, typename... Args> void map_if_insertion_operator(const Args &...) { } template <typename, typename, typename... Args>
template <typename, typename, typename... Args> void map_assignment(const Args &...) { } void map_if_insertion_operator(const Args &...) {}
template <typename, typename, typename... Args>
void map_assignment(const Args &...) {}
// Map assignment when copy-assignable: just copy the value // Map assignment when copy-assignable: just copy the value
template <typename Map, typename Class_> template <typename Map, typename Class_>
void map_assignment(enable_if_t<is_copy_assignable<typename Map::mapped_type>::value, Class_> &cl) { void map_assignment(
enable_if_t<is_copy_assignable<typename Map::mapped_type>::value, Class_> &cl) {
using KeyType = typename Map::key_type; using KeyType = typename Map::key_type;
using MappedType = typename Map::mapped_type; using MappedType = typename Map::mapped_type;
cl.def("__setitem__", cl.def("__setitem__", [](Map &m, const KeyType &k, const MappedType &v) {
[](Map &m, const KeyType &k, const MappedType &v) { auto it = m.find(k);
auto it = m.find(k); if (it != m.end()) {
if (it != m.end()) it->second = v; it->second = v;
else m.emplace(k, v); } else {
} m.emplace(k, v);
); }
});
} }
// Not copy-assignable, but still copy-constructible: we can update the value by erasing and reinserting // Not copy-assignable, but still copy-constructible: we can update the value by erasing and
template<typename Map, typename Class_> // reinserting
void map_assignment(enable_if_t< template <typename Map, typename Class_>
!is_copy_assignable<typename Map::mapped_type>::value && void map_assignment(enable_if_t<!is_copy_assignable<typename Map::mapped_type>::value
is_copy_constructible<typename Map::mapped_type>::value, && is_copy_constructible<typename Map::mapped_type>::value,
Class_> &cl) { Class_> &cl) {
using KeyType = typename Map::key_type; using KeyType = typename Map::key_type;
using MappedType = typename Map::mapped_type; using MappedType = typename Map::mapped_type;
cl.def("__setitem__", cl.def("__setitem__", [](Map &m, const KeyType &k, const MappedType &v) {
[](Map &m, const KeyType &k, const MappedType &v) { // We can't use m[k] = v; because value type might not be default constructable
// We can't use m[k] = v; because value type might not be default constructable auto r = m.emplace(k, v);
auto r = m.emplace(k, v); if (!r.second) {
if (!r.second) { // value type is not copy assignable so the only way to insert it is to erase it
// value type is not copy assignable so the only way to insert it is to erase it first... // first...
m.erase(r.first); m.erase(r.first);
m.emplace(k, v); m.emplace(k, v);
} }
} });
);
} }
template <typename Map, typename Class_>
auto map_if_insertion_operator(Class_ &cl, std::string const &name)
-> decltype(std::declval<std::ostream &>() << std::declval<typename Map::key_type>()
<< std::declval<typename Map::mapped_type>(),
void()) {
template <typename Map, typename Class_> auto map_if_insertion_operator(Class_ &cl, std::string const &name) cl.def(
-> decltype(std::declval<std::ostream&>() << std::declval<typename Map::key_type>() << std::declval<typename Map::mapped_type>(), void()) { "__repr__",
[name](Map &m) {
cl.def("__repr__",
[name](Map &m) {
std::ostringstream s; std::ostringstream s;
s << name << '{'; s << name << '{';
bool f = false; bool f = false;
for (auto const &kv : m) { for (auto const &kv : m) {
if (f) if (f) {
s << ", "; s << ", ";
}
s << kv.first << ": " << kv.second; s << kv.first << ": " << kv.second;
f = true; f = true;
} }
s << '}'; s << '}';
return s.str(); return s.str();
}, },
"Return the canonical string representation of this map." "Return the canonical string representation of this map.");
);
} }
template<typename Map> template <typename Map>
struct keys_view struct keys_view {
{
Map &map; Map &map;
}; };
template<typename Map> template <typename Map>
struct values_view struct values_view {
{
Map &map; Map &map;
}; };
template<typename Map> template <typename Map>
struct items_view struct items_view {
{
Map &map; Map &map;
}; };
PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(detail)
template <typename Map, typename holder_type = std::unique_ptr<Map>, typename... Args> template <typename Map, typename holder_type = std::unique_ptr<Map>, typename... Args>
class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args&&... args) { class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args &&...args) {
using KeyType = typename Map::key_type; using KeyType = typename Map::key_type;
using MappedType = typename Map::mapped_type; using MappedType = typename Map::mapped_type;
using KeysView = detail::keys_view<Map>; using KeysView = detail::keys_view<Map>;
@ -627,7 +665,7 @@ class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args&&.
// If either type is a non-module-local bound type then make the map binding non-local as well; // If either type is a non-module-local bound type then make the map binding non-local as well;
// otherwise (e.g. both types are either module-local or converting) the map will be // otherwise (e.g. both types are either module-local or converting) the map will be
// module-local. // module-local.
auto tinfo = detail::get_type_info(typeid(MappedType)); auto *tinfo = detail::get_type_info(typeid(MappedType));
bool local = !tinfo || tinfo->module_local; bool local = !tinfo || tinfo->module_local;
if (local) { if (local) {
tinfo = detail::get_type_info(typeid(KeyType)); tinfo = detail::get_type_info(typeid(KeyType));
@ -647,97 +685,97 @@ class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args&&.
// Register stream insertion operator (if possible) // Register stream insertion operator (if possible)
detail::map_if_insertion_operator<Map, Class_>(cl, name); detail::map_if_insertion_operator<Map, Class_>(cl, name);
cl.def("__bool__", cl.def(
"__bool__",
[](const Map &m) -> bool { return !m.empty(); }, [](const Map &m) -> bool { return !m.empty(); },
"Check whether the map is nonempty" "Check whether the map is nonempty");
cl.def(
"__iter__",
[](Map &m) { return make_key_iterator(m.begin(), m.end()); },
keep_alive<0, 1>() /* Essential: keep map alive while iterator exists */
); );
cl.def("__iter__", cl.def(
[](Map &m) { return make_key_iterator(m.begin(), m.end()); }, "keys",
keep_alive<0, 1>() /* Essential: keep map alive while iterator exists */ [](Map &m) { return KeysView{m}; },
keep_alive<0, 1>() /* Essential: keep map alive while view exists */
); );
cl.def("keys", cl.def(
[](Map &m) { return KeysView{m}; }, "values",
keep_alive<0, 1>() /* Essential: keep map alive while view exists */ [](Map &m) { return ValuesView{m}; },
keep_alive<0, 1>() /* Essential: keep map alive while view exists */
); );
cl.def("values", cl.def(
[](Map &m) { return ValuesView{m}; }, "items",
keep_alive<0, 1>() /* Essential: keep map alive while view exists */ [](Map &m) { return ItemsView{m}; },
keep_alive<0, 1>() /* Essential: keep map alive while view exists */
); );
cl.def("items", cl.def(
[](Map &m) { return ItemsView{m}; }, "__getitem__",
keep_alive<0, 1>() /* Essential: keep map alive while view exists */
);
cl.def("__getitem__",
[](Map &m, const KeyType &k) -> MappedType & { [](Map &m, const KeyType &k) -> MappedType & {
auto it = m.find(k); auto it = m.find(k);
if (it == m.end()) if (it == m.end()) {
throw key_error(); throw key_error();
return it->second; }
return it->second;
}, },
return_value_policy::reference_internal // ref + keepalive return_value_policy::reference_internal // ref + keepalive
); );
cl.def("__contains__", cl.def("__contains__", [](Map &m, const KeyType &k) -> bool {
[](Map &m, const KeyType &k) -> bool { auto it = m.find(k);
auto it = m.find(k); if (it == m.end()) {
if (it == m.end()) return false;
return false;
return true;
} }
); return true;
});
// Fallback for when the object is not of the key type // Fallback for when the object is not of the key type
cl.def("__contains__", [](Map &, const object &) -> bool { return false; }); cl.def("__contains__", [](Map &, const object &) -> bool { return false; });
// Assignment provided only if the type is copyable // Assignment provided only if the type is copyable
detail::map_assignment<Map, Class_>(cl); detail::map_assignment<Map, Class_>(cl);
cl.def("__delitem__", cl.def("__delitem__", [](Map &m, const KeyType &k) {
[](Map &m, const KeyType &k) { auto it = m.find(k);
auto it = m.find(k); if (it == m.end()) {
if (it == m.end()) throw key_error();
throw key_error(); }
m.erase(it); m.erase(it);
} });
);
cl.def("__len__", &Map::size); cl.def("__len__", &Map::size);
keys_view.def("__len__", [](KeysView &view) { return view.map.size(); }); keys_view.def("__len__", [](KeysView &view) { return view.map.size(); });
keys_view.def("__iter__", keys_view.def(
[](KeysView &view) { "__iter__",
return make_key_iterator(view.map.begin(), view.map.end()); [](KeysView &view) { return make_key_iterator(view.map.begin(), view.map.end()); },
},
keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */ keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */
); );
keys_view.def("__contains__", keys_view.def("__contains__", [](KeysView &view, const KeyType &k) -> bool {
[](KeysView &view, const KeyType &k) -> bool { auto it = view.map.find(k);
auto it = view.map.find(k); if (it == view.map.end()) {
if (it == view.map.end()) return false;
return false;
return true;
} }
); return true;
});
// Fallback for when the object is not of the key type // Fallback for when the object is not of the key type
keys_view.def("__contains__", [](KeysView &, const object &) -> bool { return false; }); keys_view.def("__contains__", [](KeysView &, const object &) -> bool { return false; });
values_view.def("__len__", [](ValuesView &view) { return view.map.size(); }); values_view.def("__len__", [](ValuesView &view) { return view.map.size(); });
values_view.def("__iter__", values_view.def(
[](ValuesView &view) { "__iter__",
return make_value_iterator(view.map.begin(), view.map.end()); [](ValuesView &view) { return make_value_iterator(view.map.begin(), view.map.end()); },
},
keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */ keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */
); );
items_view.def("__len__", [](ItemsView &view) { return view.map.size(); }); items_view.def("__len__", [](ItemsView &view) { return view.map.size(); });
items_view.def("__iter__", items_view.def(
[](ItemsView &view) { "__iter__",
return make_iterator(view.map.begin(), view.map.end()); [](ItemsView &view) { return make_iterator(view.map.begin(), view.map.end()); },
},
keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */ keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */
); );

View File

@ -1,8 +1,14 @@
import os
import nox import nox
nox.needs_version = ">=2022.1.7"
nox.options.sessions = ["lint", "tests", "tests_packaging"] nox.options.sessions = ["lint", "tests", "tests_packaging"]
PYTHON_VERISONS = ["2.7", "3.5", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11"] PYTHON_VERISONS = ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "pypy3.7", "pypy3.8"]
if os.environ.get("CI", None):
nox.options.error_on_missing_interpreters = True
@nox.session(reuse_venv=True) @nox.session(reuse_venv=True)
@ -24,14 +30,12 @@ def tests(session: nox.Session) -> None:
session.install("-r", "tests/requirements.txt") session.install("-r", "tests/requirements.txt")
session.run( session.run(
"cmake", "cmake",
"-S", "-S.",
".", f"-B{tmpdir}",
"-B",
tmpdir,
"-DPYBIND11_WERROR=ON", "-DPYBIND11_WERROR=ON",
"-DDOWNLOAD_CATCH=ON", "-DDOWNLOAD_CATCH=ON",
"-DDOWNLOAD_EIGEN=ON", "-DDOWNLOAD_EIGEN=ON",
*session.posargs *session.posargs,
) )
session.run("cmake", "--build", tmpdir) session.run("cmake", "--build", tmpdir)
session.run("cmake", "--build", tmpdir, "--config=Release", "--target", "check") session.run("cmake", "--build", tmpdir, "--config=Release", "--target", "check")
@ -57,10 +61,10 @@ def docs(session: nox.Session) -> None:
session.chdir("docs") session.chdir("docs")
if "pdf" in session.posargs: if "pdf" in session.posargs:
session.run("sphinx-build", "-b", "latexpdf", ".", "_build") session.run("sphinx-build", "-M", "latexpdf", ".", "_build")
return return
session.run("sphinx-build", "-b", "html", ".", "_build") session.run("sphinx-build", "-M", "html", ".", "_build")
if "serve" in session.posargs: if "serve" in session.posargs:
session.log("Launching docs at http://localhost:8000/ - use Ctrl-C to quit") session.log("Launching docs at http://localhost:8000/ - use Ctrl-C to quit")

View File

@ -1,4 +1,9 @@
# -*- coding: utf-8 -*- import sys
if sys.version_info < (3, 6):
msg = "pybind11 does not support Python < 3.6. 2.9 was the last release supporting Python 2.7 and 3.5."
raise ImportError(msg)
from ._version import __version__, version_info from ._version import __version__, version_info
from .commands import get_cmake_dir, get_include from .commands import get_cmake_dir, get_include

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # pylint: disable=missing-function-docstring
from __future__ import print_function
import argparse import argparse
import sys import sys
@ -8,8 +7,7 @@ import sysconfig
from .commands import get_cmake_dir, get_include from .commands import get_cmake_dir, get_include
def print_includes(): def print_includes() -> None:
# type: () -> None
dirs = [ dirs = [
sysconfig.get_path("include"), sysconfig.get_path("include"),
sysconfig.get_path("platinclude"), sysconfig.get_path("platinclude"),
@ -25,8 +23,7 @@ def print_includes():
print(" ".join("-I" + d for d in unique_dirs)) print(" ".join("-I" + d for d in unique_dirs))
def main(): def main() -> None:
# type: () -> None
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument( parser.add_argument(

View File

@ -1,12 +1,12 @@
# -*- coding: utf-8 -*- from typing import Union
def _to_int(s): def _to_int(s: str) -> Union[int, str]:
try: try:
return int(s) return int(s)
except ValueError: except ValueError:
return s return s
__version__ = "2.9.1" __version__ = "2.10.0"
version_info = tuple(_to_int(s) for s in __version__.split(".")) version_info = tuple(_to_int(s) for s in __version__.split("."))

View File

@ -1,6 +0,0 @@
from typing import Tuple, Union
def _to_int(s: str) -> Union[int, str]: ...
__version__: str
version_info: Tuple[Union[int, str], ...]

View File

@ -1,21 +1,25 @@
# -*- coding: utf-8 -*-
import os import os
DIR = os.path.abspath(os.path.dirname(__file__)) DIR = os.path.abspath(os.path.dirname(__file__))
def get_include(user=False): def get_include(user: bool = False) -> str: # pylint: disable=unused-argument
# type: (bool) -> str """
Return the path to the pybind11 include directory. The historical "user"
argument is unused, and may be removed.
"""
installed_path = os.path.join(DIR, "include") installed_path = os.path.join(DIR, "include")
source_path = os.path.join(os.path.dirname(DIR), "include") source_path = os.path.join(os.path.dirname(DIR), "include")
return installed_path if os.path.exists(installed_path) else source_path return installed_path if os.path.exists(installed_path) else source_path
def get_cmake_dir(): def get_cmake_dir() -> str:
# type: () -> str """
Return the path to the pybind11 CMake module directory.
"""
cmake_installed_path = os.path.join(DIR, "share", "cmake", "pybind11") cmake_installed_path = os.path.join(DIR, "share", "cmake", "pybind11")
if os.path.exists(cmake_installed_path): if os.path.exists(cmake_installed_path):
return cmake_installed_path return cmake_installed_path
else:
msg = "pybind11 not installed, installation required to access the CMake files" msg = "pybind11 not installed, installation required to access the CMake files"
raise ImportError(msg) raise ImportError(msg)

View File

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
""" """
This module provides helpers for C++11+ projects using pybind11. This module provides helpers for C++11+ projects using pybind11.
@ -49,6 +47,20 @@ import sysconfig
import tempfile import tempfile
import threading import threading
import warnings import warnings
from functools import lru_cache
from pathlib import Path
from typing import (
Any,
Callable,
Dict,
Iterable,
Iterator,
List,
Optional,
Tuple,
TypeVar,
Union,
)
try: try:
from setuptools import Extension as _Extension from setuptools import Extension as _Extension
@ -61,7 +73,6 @@ import distutils.ccompiler
import distutils.errors import distutils.errors
WIN = sys.platform.startswith("win32") and "mingw" not in sysconfig.get_platform() WIN = sys.platform.startswith("win32") and "mingw" not in sysconfig.get_platform()
PY2 = sys.version_info[0] < 3
MACOS = sys.platform.startswith("darwin") MACOS = sys.platform.startswith("darwin")
STD_TMPL = "/std:c++{}" if WIN else "-std=c++{}" STD_TMPL = "/std:c++{}" if WIN else "-std=c++{}"
@ -73,7 +84,7 @@ STD_TMPL = "/std:c++{}" if WIN else "-std=c++{}"
# directory into your path if it sits beside your setup.py. # directory into your path if it sits beside your setup.py.
class Pybind11Extension(_Extension): class Pybind11Extension(_Extension): # type: ignore[misc]
""" """
Build a C++11+ Extension module with pybind11. This automatically adds the Build a C++11+ Extension module with pybind11. This automatically adds the
recommended flags when you init the extension and assumes C++ sources - you recommended flags when you init the extension and assumes C++ sources - you
@ -95,21 +106,18 @@ class Pybind11Extension(_Extension):
If you want to add pybind11 headers manually, for example for an exact If you want to add pybind11 headers manually, for example for an exact
git checkout, then set ``include_pybind11=False``. git checkout, then set ``include_pybind11=False``.
Warning: do not use property-based access to the instance on Python 2 -
this is an ugly old-style class due to Distutils.
""" """
# flags are prepended, so that they can be further overridden, e.g. by # flags are prepended, so that they can be further overridden, e.g. by
# ``extra_compile_args=["-g"]``. # ``extra_compile_args=["-g"]``.
def _add_cflags(self, flags): def _add_cflags(self, flags: List[str]) -> None:
self.extra_compile_args[:0] = flags self.extra_compile_args[:0] = flags
def _add_ldflags(self, flags): def _add_ldflags(self, flags: List[str]) -> None:
self.extra_link_args[:0] = flags self.extra_link_args[:0] = flags
def __init__(self, *args, **kwargs): def __init__(self, *args: Any, **kwargs: Any) -> None:
self._cxx_level = 0 self._cxx_level = 0
cxx_std = kwargs.pop("cxx_std", 0) cxx_std = kwargs.pop("cxx_std", 0)
@ -119,9 +127,7 @@ class Pybind11Extension(_Extension):
include_pybind11 = kwargs.pop("include_pybind11", True) include_pybind11 = kwargs.pop("include_pybind11", True)
# Can't use super here because distutils has old-style classes in super().__init__(*args, **kwargs)
# Python 2!
_Extension.__init__(self, *args, **kwargs)
# Include the installed package pybind11 headers # Include the installed package pybind11 headers
if include_pybind11: if include_pybind11:
@ -133,11 +139,10 @@ class Pybind11Extension(_Extension):
if pyinc not in self.include_dirs: if pyinc not in self.include_dirs:
self.include_dirs.append(pyinc) self.include_dirs.append(pyinc)
except ImportError: except ModuleNotFoundError:
pass pass
# Have to use the accessor manually to support Python 2 distutils self.cxx_std = cxx_std
Pybind11Extension.cxx_std.__set__(self, cxx_std)
cflags = [] cflags = []
ldflags = [] ldflags = []
@ -157,18 +162,18 @@ class Pybind11Extension(_Extension):
self._add_ldflags(ldflags) self._add_ldflags(ldflags)
@property @property
def cxx_std(self): def cxx_std(self) -> int:
""" """
The CXX standard level. If set, will add the required flags. If left The CXX standard level. If set, will add the required flags. If left at
at 0, it will trigger an automatic search when pybind11's build_ext 0, it will trigger an automatic search when pybind11's build_ext is
is used. If None, will have no effect. Besides just the flags, this used. If None, will have no effect. Besides just the flags, this may
may add a register warning/error fix for Python 2 or macos-min 10.9 add a macos-min 10.9 or 10.14 flag if MACOSX_DEPLOYMENT_TARGET is
or 10.14. unset.
""" """
return self._cxx_level return self._cxx_level
@cxx_std.setter @cxx_std.setter
def cxx_std(self, level): def cxx_std(self, level: int) -> None:
if self._cxx_level: if self._cxx_level:
warnings.warn("You cannot safely change the cxx_level after setting it!") warnings.warn("You cannot safely change the cxx_level after setting it!")
@ -195,31 +200,20 @@ class Pybind11Extension(_Extension):
current_macos = tuple(int(x) for x in platform.mac_ver()[0].split(".")[:2]) current_macos = tuple(int(x) for x in platform.mac_ver()[0].split(".")[:2])
desired_macos = (10, 9) if level < 17 else (10, 14) desired_macos = (10, 9) if level < 17 else (10, 14)
macos_string = ".".join(str(x) for x in min(current_macos, desired_macos)) macos_string = ".".join(str(x) for x in min(current_macos, desired_macos))
macosx_min = "-mmacosx-version-min=" + macos_string macosx_min = f"-mmacosx-version-min={macos_string}"
cflags += [macosx_min] cflags += [macosx_min]
ldflags += [macosx_min] ldflags += [macosx_min]
if PY2:
if WIN:
# Will be ignored on MSVC 2015, where C++17 is not supported so
# this flag is not valid.
cflags += ["/wd5033"]
elif level >= 17:
cflags += ["-Wno-register"]
elif level >= 14:
cflags += ["-Wno-deprecated-register"]
self._add_cflags(cflags) self._add_cflags(cflags)
self._add_ldflags(ldflags) self._add_ldflags(ldflags)
# Just in case someone clever tries to multithread # Just in case someone clever tries to multithread
tmp_chdir_lock = threading.Lock() tmp_chdir_lock = threading.Lock()
cpp_cache_lock = threading.Lock()
@contextlib.contextmanager @contextlib.contextmanager
def tmp_chdir(): def tmp_chdir() -> Iterator[str]:
"Prepare and enter a temporary directory, cleanup when done" "Prepare and enter a temporary directory, cleanup when done"
# Threadsafe # Threadsafe
@ -235,7 +229,7 @@ def tmp_chdir():
# cf http://bugs.python.org/issue26689 # cf http://bugs.python.org/issue26689
def has_flag(compiler, flag): def has_flag(compiler: Any, flag: str) -> bool:
""" """
Return the flag if a flag name is supported on the Return the flag if a flag name is supported on the
specified compiler, otherwise None (can be used as a boolean). specified compiler, otherwise None (can be used as a boolean).
@ -243,13 +237,12 @@ def has_flag(compiler, flag):
""" """
with tmp_chdir(): with tmp_chdir():
fname = "flagcheck.cpp" fname = Path("flagcheck.cpp")
with open(fname, "w") as f: # Don't trigger -Wunused-parameter.
# Don't trigger -Wunused-parameter. fname.write_text("int main (int, char **) { return 0; }", encoding="utf-8")
f.write("int main (int, char **) { return 0; }")
try: try:
compiler.compile([fname], extra_postargs=[flag]) compiler.compile([str(fname)], extra_postargs=[flag])
except distutils.errors.CompileError: except distutils.errors.CompileError:
return False return False
return True return True
@ -259,7 +252,8 @@ def has_flag(compiler, flag):
cpp_flag_cache = None cpp_flag_cache = None
def auto_cpp_level(compiler): @lru_cache()
def auto_cpp_level(compiler: Any) -> Union[str, int]:
""" """
Return the max supported C++ std level (17, 14, or 11). Returns latest on Windows. Return the max supported C++ std level (17, 14, or 11). Returns latest on Windows.
""" """
@ -267,48 +261,38 @@ def auto_cpp_level(compiler):
if WIN: if WIN:
return "latest" return "latest"
global cpp_flag_cache
# If this has been previously calculated with the same args, return that
with cpp_cache_lock:
if cpp_flag_cache:
return cpp_flag_cache
levels = [17, 14, 11] levels = [17, 14, 11]
for level in levels: for level in levels:
if has_flag(compiler, STD_TMPL.format(level)): if has_flag(compiler, STD_TMPL.format(level)):
with cpp_cache_lock:
cpp_flag_cache = level
return level return level
msg = "Unsupported compiler -- at least C++11 support is needed!" msg = "Unsupported compiler -- at least C++11 support is needed!"
raise RuntimeError(msg) raise RuntimeError(msg)
class build_ext(_build_ext): # noqa: N801 class build_ext(_build_ext): # type: ignore[misc] # noqa: N801
""" """
Customized build_ext that allows an auto-search for the highest supported Customized build_ext that allows an auto-search for the highest supported
C++ level for Pybind11Extension. This is only needed for the auto-search C++ level for Pybind11Extension. This is only needed for the auto-search
for now, and is completely optional otherwise. for now, and is completely optional otherwise.
""" """
def build_extensions(self): def build_extensions(self) -> None:
""" """
Build extensions, injecting C++ std for Pybind11Extension if needed. Build extensions, injecting C++ std for Pybind11Extension if needed.
""" """
for ext in self.extensions: for ext in self.extensions:
if hasattr(ext, "_cxx_level") and ext._cxx_level == 0: if hasattr(ext, "_cxx_level") and ext._cxx_level == 0:
# Python 2 syntax - old-style distutils class ext.cxx_std = auto_cpp_level(self.compiler)
ext.__class__.cxx_std.__set__(ext, auto_cpp_level(self.compiler))
# Python 2 doesn't allow super here, since distutils uses old-style super().build_extensions()
# classes!
_build_ext.build_extensions(self)
def intree_extensions(paths, package_dir=None): def intree_extensions(
paths: Iterable[str], package_dir: Optional[Dict[str, str]] = None
) -> List[Pybind11Extension]:
""" """
Generate Pybind11Extensions from source files directly located in a Python Generate Pybind11Extensions from source files directly located in a Python
source tree. source tree.
@ -318,33 +302,37 @@ def intree_extensions(paths, package_dir=None):
not contain an ``__init__.py`` file. not contain an ``__init__.py`` file.
""" """
exts = [] exts = []
for path in paths:
if package_dir is None: if package_dir is None:
for path in paths:
parent, _ = os.path.split(path) parent, _ = os.path.split(path)
while os.path.exists(os.path.join(parent, "__init__.py")): while os.path.exists(os.path.join(parent, "__init__.py")):
parent, _ = os.path.split(parent) parent, _ = os.path.split(parent)
relname, _ = os.path.splitext(os.path.relpath(path, parent)) relname, _ = os.path.splitext(os.path.relpath(path, parent))
qualified_name = relname.replace(os.path.sep, ".") qualified_name = relname.replace(os.path.sep, ".")
exts.append(Pybind11Extension(qualified_name, [path])) exts.append(Pybind11Extension(qualified_name, [path]))
return exts
for path in paths:
for prefix, parent in package_dir.items():
if path.startswith(parent):
relname, _ = os.path.splitext(os.path.relpath(path, parent))
qualified_name = relname.replace(os.path.sep, ".")
if prefix:
qualified_name = prefix + "." + qualified_name
exts.append(Pybind11Extension(qualified_name, [path]))
break
else: else:
found = False msg = (
for prefix, parent in package_dir.items(): f"path {path} is not a child of any of the directories listed "
if path.startswith(parent): f"in 'package_dir' ({package_dir})"
found = True )
relname, _ = os.path.splitext(os.path.relpath(path, parent)) raise ValueError(msg)
qualified_name = relname.replace(os.path.sep, ".")
if prefix:
qualified_name = prefix + "." + qualified_name
exts.append(Pybind11Extension(qualified_name, [path]))
if not found:
raise ValueError(
"path {} is not a child of any of the directories listed "
"in 'package_dir' ({})".format(path, package_dir)
)
return exts return exts
def naive_recompile(obj, src): def naive_recompile(obj: str, src: str) -> bool:
""" """
This will recompile only if the source file changes. It does not check This will recompile only if the source file changes. It does not check
header files, so a more advanced function or Ccache is better if you have header files, so a more advanced function or Ccache is better if you have
@ -353,7 +341,7 @@ def naive_recompile(obj, src):
return os.stat(obj).st_mtime < os.stat(src).st_mtime return os.stat(obj).st_mtime < os.stat(src).st_mtime
def no_recompile(obg, src): def no_recompile(obg: str, src: str) -> bool: # pylint: disable=unused-argument
""" """
This is the safest but slowest choice (and is the default) - will always This is the safest but slowest choice (and is the default) - will always
recompile sources. recompile sources.
@ -361,15 +349,33 @@ def no_recompile(obg, src):
return True return True
S = TypeVar("S", bound="ParallelCompile")
CCompilerMethod = Callable[
[
distutils.ccompiler.CCompiler,
List[str],
Optional[str],
Optional[Union[Tuple[str], Tuple[str, Optional[str]]]],
Optional[List[str]],
bool,
Optional[List[str]],
Optional[List[str]],
Optional[List[str]],
],
List[str],
]
# Optional parallel compile utility # Optional parallel compile utility
# inspired by: http://stackoverflow.com/questions/11013851/speeding-up-build-process-with-distutils # inspired by: http://stackoverflow.com/questions/11013851/speeding-up-build-process-with-distutils
# and: https://github.com/tbenthompson/cppimport/blob/stable/cppimport/build_module.py # and: https://github.com/tbenthompson/cppimport/blob/stable/cppimport/build_module.py
# and NumPy's parallel distutils module: # and NumPy's parallel distutils module:
# https://github.com/numpy/numpy/blob/master/numpy/distutils/ccompiler.py # https://github.com/numpy/numpy/blob/master/numpy/distutils/ccompiler.py
class ParallelCompile(object): class ParallelCompile:
""" """
Make a parallel compile function. Inspired by Make a parallel compile function. Inspired by
numpy.distutils.ccompiler.CCompiler_compile and cppimport. numpy.distutils.ccompiler.CCompiler.compile and cppimport.
This takes several arguments that allow you to customize the compile This takes several arguments that allow you to customize the compile
function created: function created:
@ -404,35 +410,41 @@ class ParallelCompile(object):
__slots__ = ("envvar", "default", "max", "_old", "needs_recompile") __slots__ = ("envvar", "default", "max", "_old", "needs_recompile")
def __init__(self, envvar=None, default=0, max=0, needs_recompile=no_recompile): def __init__(
self,
envvar: Optional[str] = None,
default: int = 0,
max: int = 0, # pylint: disable=redefined-builtin
needs_recompile: Callable[[str, str], bool] = no_recompile,
) -> None:
self.envvar = envvar self.envvar = envvar
self.default = default self.default = default
self.max = max self.max = max
self.needs_recompile = needs_recompile self.needs_recompile = needs_recompile
self._old = [] self._old: List[CCompilerMethod] = []
def function(self): def function(self) -> CCompilerMethod:
""" """
Builds a function object usable as distutils.ccompiler.CCompiler.compile. Builds a function object usable as distutils.ccompiler.CCompiler.compile.
""" """
def compile_function( def compile_function(
compiler, compiler: distutils.ccompiler.CCompiler,
sources, sources: List[str],
output_dir=None, output_dir: Optional[str] = None,
macros=None, macros: Optional[Union[Tuple[str], Tuple[str, Optional[str]]]] = None,
include_dirs=None, include_dirs: Optional[List[str]] = None,
debug=0, debug: bool = False,
extra_preargs=None, extra_preargs: Optional[List[str]] = None,
extra_postargs=None, extra_postargs: Optional[List[str]] = None,
depends=None, depends: Optional[List[str]] = None,
): ) -> Any:
# These lines are directly from distutils.ccompiler.CCompiler # These lines are directly from distutils.ccompiler.CCompiler
macros, objects, extra_postargs, pp_opts, build = compiler._setup_compile( macros, objects, extra_postargs, pp_opts, build = compiler._setup_compile( # type: ignore[attr-defined]
output_dir, macros, include_dirs, sources, depends, extra_postargs output_dir, macros, include_dirs, sources, depends, extra_postargs
) )
cc_args = compiler._get_cc_args(pp_opts, debug, extra_preargs) cc_args = compiler._get_cc_args(pp_opts, debug, extra_preargs) # type: ignore[attr-defined]
# The number of threads; start with default. # The number of threads; start with default.
threads = self.default threads = self.default
@ -441,14 +453,14 @@ class ParallelCompile(object):
if self.envvar is not None: if self.envvar is not None:
threads = int(os.environ.get(self.envvar, self.default)) threads = int(os.environ.get(self.envvar, self.default))
def _single_compile(obj): def _single_compile(obj: Any) -> None:
try: try:
src, ext = build[obj] src, ext = build[obj]
except KeyError: except KeyError:
return return
if not os.path.exists(obj) or self.needs_recompile(obj, src): if not os.path.exists(obj) or self.needs_recompile(obj, src):
compiler._compile(obj, src, ext, cc_args, extra_postargs, pp_opts) compiler._compile(obj, src, ext, cc_args, extra_postargs, pp_opts) # type: ignore[attr-defined]
try: try:
# Importing .synchronize checks for platforms that have some multiprocessing # Importing .synchronize checks for platforms that have some multiprocessing
@ -466,14 +478,9 @@ class ParallelCompile(object):
threads = 1 threads = 1
if threads > 1: if threads > 1:
pool = ThreadPool(threads) with ThreadPool(threads) as pool:
# In Python 2, ThreadPool can't be used as a context manager.
# Once we are no longer supporting it, this can be 'with pool:'
try:
for _ in pool.imap_unordered(_single_compile, objects): for _ in pool.imap_unordered(_single_compile, objects):
pass pass
finally:
pool.terminate()
else: else:
for ob in objects: for ob in objects:
_single_compile(ob) _single_compile(ob)
@ -482,13 +489,16 @@ class ParallelCompile(object):
return compile_function return compile_function
def install(self): def install(self: S) -> S:
distutils.ccompiler.CCompiler.compile = self.function() """
Installs the compile function into distutils.ccompiler.CCompiler.compile.
"""
distutils.ccompiler.CCompiler.compile = self.function() # type: ignore[assignment]
return self return self
def __enter__(self): def __enter__(self: S) -> S:
self._old.append(distutils.ccompiler.CCompiler.compile) self._old.append(distutils.ccompiler.CCompiler.compile)
return self.install() return self.install()
def __exit__(self, *args): def __exit__(self, *args: Any) -> None:
distutils.ccompiler.CCompiler.compile = self._old.pop() distutils.ccompiler.CCompiler.compile = self._old.pop() # type: ignore[assignment]

View File

@ -1,63 +0,0 @@
# IMPORTANT: Should stay in sync with setup_helpers.py (mostly checked by CI /
# pre-commit).
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
from types import TracebackType
from typing import Any, Callable, Dict, Iterator, List, Optional, Type, TypeVar, Union
WIN: bool
PY2: bool
MACOS: bool
STD_TMPL: str
class Pybind11Extension(_Extension):
def _add_cflags(self, *flags: str) -> None: ...
def _add_lflags(self, *flags: str) -> None: ...
def __init__(
self, *args: Any, cxx_std: int = 0, language: str = "c++", **kwargs: Any
) -> None: ...
@property
def cxx_std(self) -> int: ...
@cxx_std.setter
def cxx_std(self, level: int) -> None: ...
@contextlib.contextmanager
def tmp_chdir() -> Iterator[str]: ...
def has_flag(compiler: distutils.ccompiler.CCompiler, flag: str) -> bool: ...
def auto_cpp_level(compiler: distutils.ccompiler.CCompiler) -> Union[int, str]: ...
class build_ext(_build_ext): # type: ignore
def build_extensions(self) -> None: ...
def intree_extensions(
paths: Iterator[str], package_dir: Optional[Dict[str, str]] = None
) -> List[Pybind11Extension]: ...
def no_recompile(obj: str, src: str) -> bool: ...
def naive_recompile(obj: str, src: str) -> bool: ...
T = TypeVar("T", bound="ParallelCompile")
class ParallelCompile:
envvar: Optional[str]
default: int
max: int
needs_recompile: Callable[[str, str], bool]
def __init__(
self,
envvar: Optional[str] = None,
default: int = 0,
max: int = 0,
needs_recompile: Callable[[str, str], bool] = no_recompile,
) -> None: ...
def function(self) -> Any: ...
def install(self: T) -> T: ...
def __enter__(self: T) -> T: ...
def __exit__(
self,
exc_type: Optional[Type[BaseException]],
exc_value: Optional[BaseException],
traceback: Optional[TracebackType],
) -> None: ...

View File

@ -1,5 +1,5 @@
[build-system] [build-system]
requires = ["setuptools>=42", "wheel", "cmake>=3.18", "ninja"] requires = ["setuptools>=42", "cmake>=3.18", "ninja"]
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"
[tool.check-manifest] [tool.check-manifest]
@ -22,20 +22,40 @@ known_first_party = "env,pybind11_cross_module_tests,pybind11_tests,"
profile = "black" profile = "black"
[tool.mypy] [tool.mypy]
files = "pybind11" files = ["pybind11"]
python_version = "2.7" python_version = "3.6"
warn_unused_configs = true strict = true
show_error_codes = true
enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"]
warn_unreachable = true
disallow_any_generics = true [[tool.mypy.overrides]]
disallow_subclassing_any = true module = ["ghapi.*", "setuptools.*"]
disallow_untyped_calls = true ignore_missing_imports = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
check_untyped_defs = true [tool.pytest.ini_options]
disallow_untyped_decorators = true minversion = "6.0"
no_implicit_optional = true addopts = ["-ra", "--showlocals", "--strict-markers", "--strict-config"]
warn_redundant_casts = true xfail_strict = true
warn_unused_ignores = true filterwarnings = ["error"]
warn_return_any = true log_cli_level = "info"
no_implicit_reexport = true testpaths = [
strict_equality = true "tests",
]
timeout=300
[tool.pylint]
master.py-version = "3.6"
reports.output-format = "colorized"
messages_control.disable = [
"design",
"fixme",
"imports",
"line-too-long",
"imports",
"invalid-name",
"protected-access",
"missing-module-docstring",
]

View File

@ -13,14 +13,13 @@ classifiers =
Topic :: Software Development :: Libraries :: Python Modules Topic :: Software Development :: Libraries :: Python Modules
Topic :: Utilities Topic :: Utilities
Programming Language :: C++ Programming Language :: C++
Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: 3
Programming Language :: Python :: 3.5
Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
License :: OSI Approved :: BSD License License :: OSI Approved :: BSD License
Programming Language :: Python :: Implementation :: PyPy Programming Language :: Python :: Implementation :: PyPy
Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: CPython
@ -39,25 +38,13 @@ project_urls =
Chat = https://gitter.im/pybind/Lobby Chat = https://gitter.im/pybind/Lobby
[options] [options]
python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* python_requires = >=3.6
zip_safe = False zip_safe = False
[bdist_wheel]
universal=1
[flake8] [flake8]
max-line-length = 99 max-line-length = 120
show_source = True show_source = True
exclude = .git, __pycache__, build, dist, docs, tools, venv exclude = .git, __pycache__, build, dist, docs, tools, venv
ignore = extend-ignore = E203, E722, B950
# required for pretty matrix formatting: multiple spaces after `,` and `[` extend-select = B9
E201, E241, W504,
# camelcase 'cPickle' imported as lowercase 'pickle'
N813
# Black conflict
W503, E203
[tool:pytest]
timeout = 300

View File

@ -1,53 +1,50 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Setup script for PyPI; use CMakeFile.txt to build extension modules # Setup script for PyPI; use CMakeFile.txt to build extension modules
import contextlib import contextlib
import io
import os import os
import re import re
import shutil import shutil
import string import string
import subprocess import subprocess
import sys import sys
import tempfile from pathlib import Path
from tempfile import TemporaryDirectory
from typing import Dict, Iterator, List, Union
import setuptools.command.sdist import setuptools.command.sdist
DIR = os.path.abspath(os.path.dirname(__file__)) DIR = Path(__file__).parent.absolute()
VERSION_REGEX = re.compile( VERSION_REGEX = re.compile(
r"^\s*#\s*define\s+PYBIND11_VERSION_([A-Z]+)\s+(.*)$", re.MULTILINE r"^\s*#\s*define\s+PYBIND11_VERSION_([A-Z]+)\s+(.*)$", re.MULTILINE
) )
VERSION_FILE = Path("pybind11/_version.py")
COMMON_FILE = Path("include/pybind11/detail/common.h")
def build_expected_version_hex(matches): def build_expected_version_hex(matches: Dict[str, str]) -> str:
patch_level_serial = matches["PATCH"] patch_level_serial = matches["PATCH"]
serial = None serial = None
try: major = int(matches["MAJOR"])
major = int(matches["MAJOR"]) minor = int(matches["MINOR"])
minor = int(matches["MINOR"]) flds = patch_level_serial.split(".")
flds = patch_level_serial.split(".") if flds:
if flds: patch = int(flds[0])
patch = int(flds[0]) if len(flds) == 1:
level = None level = "0"
if len(flds) == 1: serial = 0
level = "0" elif len(flds) == 2:
serial = 0 level_serial = flds[1]
elif len(flds) == 2: for level in ("a", "b", "c", "dev"):
level_serial = flds[1] if level_serial.startswith(level):
for level in ("a", "b", "c", "dev"): serial = int(level_serial[len(level) :])
if level_serial.startswith(level): break
serial = int(level_serial[len(level) :])
break
except ValueError:
pass
if serial is None: if serial is None:
msg = 'Invalid PYBIND11_VERSION_PATCH: "{}"'.format(patch_level_serial) msg = f'Invalid PYBIND11_VERSION_PATCH: "{patch_level_serial}"'
raise RuntimeError(msg) raise RuntimeError(msg)
return "0x{:02x}{:02x}{:02x}{}{:x}".format( version_hex_str = f"{major:02x}{minor:02x}{patch:02x}{level[:1]}{serial:x}"
major, minor, patch, level[:1].upper(), serial return f"0x{version_hex_str.upper()}"
)
# PYBIND11_GLOBAL_SDIST will build a different sdist, with the python-headers # PYBIND11_GLOBAL_SDIST will build a different sdist, with the python-headers
@ -55,82 +52,67 @@ def build_expected_version_hex(matches):
global_sdist = os.environ.get("PYBIND11_GLOBAL_SDIST", False) global_sdist = os.environ.get("PYBIND11_GLOBAL_SDIST", False)
setup_py = "tools/setup_global.py.in" if global_sdist else "tools/setup_main.py.in" setup_py = Path(
"tools/setup_global.py.in" if global_sdist else "tools/setup_main.py.in"
)
extra_cmd = 'cmdclass["sdist"] = SDist\n' extra_cmd = 'cmdclass["sdist"] = SDist\n'
to_src = ( to_src = (
("pyproject.toml", "tools/pyproject.toml"), (Path("pyproject.toml"), Path("tools/pyproject.toml")),
("setup.py", setup_py), (Path("setup.py"), setup_py),
) )
# Read the listed version # Read the listed version
with open("pybind11/_version.py") as f: loc: Dict[str, str] = {}
code = compile(f.read(), "pybind11/_version.py", "exec") code = compile(VERSION_FILE.read_text(encoding="utf-8"), "pybind11/_version.py", "exec")
loc = {}
exec(code, loc) exec(code, loc)
version = loc["__version__"] version = loc["__version__"]
# Verify that the version matches the one in C++ # Verify that the version matches the one in C++
with io.open("include/pybind11/detail/common.h", encoding="utf8") as f: matches = dict(VERSION_REGEX.findall(COMMON_FILE.read_text(encoding="utf8")))
matches = dict(VERSION_REGEX.findall(f.read()))
cpp_version = "{MAJOR}.{MINOR}.{PATCH}".format(**matches) cpp_version = "{MAJOR}.{MINOR}.{PATCH}".format(**matches)
if version != cpp_version: if version != cpp_version:
msg = "Python version {} does not match C++ version {}!".format( msg = f"Python version {version} does not match C++ version {cpp_version}!"
version, cpp_version
)
raise RuntimeError(msg) raise RuntimeError(msg)
version_hex = matches.get("HEX", "MISSING") version_hex = matches.get("HEX", "MISSING")
expected_version_hex = build_expected_version_hex(matches) exp_version_hex = build_expected_version_hex(matches)
if version_hex != expected_version_hex: if version_hex != exp_version_hex:
msg = "PYBIND11_VERSION_HEX {} does not match expected value {}!".format( msg = f"PYBIND11_VERSION_HEX {version_hex} does not match expected value {exp_version_hex}!"
version_hex,
expected_version_hex,
)
raise RuntimeError(msg) raise RuntimeError(msg)
def get_and_replace(filename, binary=False, **opts): # TODO: use literals & overload (typing extensions or Python 3.8)
with open(filename, "rb" if binary else "r") as f: def get_and_replace(
contents = f.read() filename: Path, binary: bool = False, **opts: str
# Replacement has to be done on text in Python 3 (both work in Python 2) ) -> Union[bytes, str]:
if binary: if binary:
contents = filename.read_bytes()
return string.Template(contents.decode()).substitute(opts).encode() return string.Template(contents.decode()).substitute(opts).encode()
else:
return string.Template(contents).substitute(opts) return string.Template(filename.read_text()).substitute(opts)
# Use our input files instead when making the SDist (and anything that depends # Use our input files instead when making the SDist (and anything that depends
# on it, like a wheel) # on it, like a wheel)
class SDist(setuptools.command.sdist.sdist): class SDist(setuptools.command.sdist.sdist): # type: ignore[misc]
def make_release_tree(self, base_dir, files): def make_release_tree(self, base_dir: str, files: List[str]) -> None:
setuptools.command.sdist.sdist.make_release_tree(self, base_dir, files) super().make_release_tree(base_dir, files)
for to, src in to_src: for to, src in to_src:
txt = get_and_replace(src, binary=True, version=version, extra_cmd="") txt = get_and_replace(src, binary=True, version=version, extra_cmd="")
dest = os.path.join(base_dir, to) dest = Path(base_dir) / to
# This is normally linked, so unlink before writing! # This is normally linked, so unlink before writing!
os.unlink(dest) dest.unlink()
with open(dest, "wb") as f: dest.write_bytes(txt) # type: ignore[arg-type]
f.write(txt)
# Backport from Python 3
@contextlib.contextmanager
def TemporaryDirectory(): # noqa: N802
"Prepare a temporary directory, cleanup when done"
try:
tmpdir = tempfile.mkdtemp()
yield tmpdir
finally:
shutil.rmtree(tmpdir)
# Remove the CMake install directory when done # Remove the CMake install directory when done
@contextlib.contextmanager @contextlib.contextmanager
def remove_output(*sources): def remove_output(*sources: str) -> Iterator[None]:
try: try:
yield yield
finally: finally:
@ -153,9 +135,14 @@ with remove_output("pybind11/include", "pybind11/share"):
if "DCMAKE_INSTALL_PREFIX" not in c if "DCMAKE_INSTALL_PREFIX" not in c
] ]
cmd += fcommand cmd += fcommand
cmake_opts = dict(cwd=DIR, stdout=sys.stdout, stderr=sys.stderr) subprocess.run(cmd, check=True, cwd=DIR, stdout=sys.stdout, stderr=sys.stderr)
subprocess.check_call(cmd, **cmake_opts) subprocess.run(
subprocess.check_call(["cmake", "--install", tmpdir], **cmake_opts) ["cmake", "--install", tmpdir],
check=True,
cwd=DIR,
stdout=sys.stdout,
stderr=sys.stderr,
)
txt = get_and_replace(setup_py, version=version, extra_cmd=extra_cmd) txt = get_and_replace(setup_py, version=version, extra_cmd=extra_cmd)
code = compile(txt, setup_py, "exec") code = compile(txt, setup_py, "exec")

View File

@ -179,11 +179,6 @@ if(PYBIND11_TEST_FILTER)
pybind11_filter_tests(PYBIND11_TEST_FILES ${PYBIND11_TEST_FILTER}) pybind11_filter_tests(PYBIND11_TEST_FILES ${PYBIND11_TEST_FILTER})
endif() endif()
if(PYTHON_VERSION VERSION_LESS 3.5)
pybind11_filter_tests(PYBIND11_TEST_FILES test_async.cpp MESSAGE
"Skipping test_async on Python 2")
endif()
# Skip tests for CUDA check: # Skip tests for CUDA check:
# /pybind11/tests/test_constants_and_functions.cpp(125): # /pybind11/tests/test_constants_and_functions.cpp(125):
# error: incompatible exception specifications # error: incompatible exception specifications
@ -220,6 +215,7 @@ tests_extra_targets("test_exceptions.py;test_local_bindings.py;test_stl.py;test_
"pybind11_cross_module_tests") "pybind11_cross_module_tests")
# And add additional targets for other tests. # And add additional targets for other tests.
tests_extra_targets("test_exceptions.py" "cross_module_interleaved_error_already_set")
tests_extra_targets("test_gil_scoped.py" "cross_module_gil_utils") tests_extra_targets("test_gil_scoped.py" "cross_module_gil_utils")
set(PYBIND11_EIGEN_REPO set(PYBIND11_EIGEN_REPO
@ -356,7 +352,7 @@ endif()
# Compile with compiler warnings turned on # Compile with compiler warnings turned on
function(pybind11_enable_warnings target_name) function(pybind11_enable_warnings target_name)
if(MSVC) if(MSVC)
target_compile_options(${target_name} PRIVATE /W4) target_compile_options(${target_name} PRIVATE /W4 /wd4189)
elseif(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Intel|Clang)" AND NOT PYBIND11_CUDA_TESTS) elseif(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Intel|Clang)" AND NOT PYBIND11_CUDA_TESTS)
target_compile_options( target_compile_options(
${target_name} ${target_name}
@ -388,17 +384,6 @@ function(pybind11_enable_warnings target_name)
-diag-disable 11074,11076) -diag-disable 11074,11076)
endif() endif()
endif() endif()
# Needs to be re-added since the ordering requires these to be after the ones above
if(CMAKE_CXX_STANDARD
AND CMAKE_CXX_COMPILER_ID MATCHES "Clang"
AND PYTHON_VERSION VERSION_LESS 3.0)
if(CMAKE_CXX_STANDARD LESS 17)
target_compile_options(${target_name} PUBLIC -Wno-deprecated-register)
else()
target_compile_options(${target_name} PUBLIC -Wno-register)
endif()
endif()
endfunction() endfunction()
set(test_targets pybind11_tests) set(test_targets pybind11_tests)

View File

@ -1,8 +1,7 @@
# -*- coding: utf-8 -*-
"""pytest configuration """pytest configuration
Extends output capture as needed by pybind11: ignore constructors, optional unordered lines. Extends output capture as needed by pybind11: ignore constructors, optional unordered lines.
Adds docstring and exceptions message sanitizers: ignore Python 2 vs 3 differences. Adds docstring and exceptions message sanitizers.
""" """
import contextlib import contextlib
@ -13,19 +12,14 @@ import textwrap
import pytest import pytest
import env
# Early diagnostic for failed imports # Early diagnostic for failed imports
import pybind11_tests # noqa: F401 import pybind11_tests
_unicode_marker = re.compile(r"u(\'[^\']*\')")
_long_marker = re.compile(r"([0-9])L") _long_marker = re.compile(r"([0-9])L")
_hexadecimal = re.compile(r"0x[0-9a-fA-F]+") _hexadecimal = re.compile(r"0x[0-9a-fA-F]+")
# Avoid collecting Python3 only files # Avoid collecting Python3 only files
collect_ignore = [] collect_ignore = []
if env.PY2:
collect_ignore.append("test_async.py")
def _strip_and_dedent(s): def _strip_and_dedent(s):
@ -45,7 +39,7 @@ def _make_explanation(a, b):
] ]
class Output(object): class Output:
"""Basic output post-processing and comparison""" """Basic output post-processing and comparison"""
def __init__(self, string): def __init__(self, string):
@ -83,7 +77,7 @@ class Unordered(Output):
return False return False
class Capture(object): class Capture:
def __init__(self, capfd): def __init__(self, capfd):
self.capfd = capfd self.capfd = capfd
self.out = "" self.out = ""
@ -126,7 +120,7 @@ def capture(capsys):
return Capture(capsys) return Capture(capsys)
class SanitizedString(object): class SanitizedString:
def __init__(self, sanitizer): def __init__(self, sanitizer):
self.sanitizer = sanitizer self.sanitizer = sanitizer
self.string = "" self.string = ""
@ -149,9 +143,7 @@ class SanitizedString(object):
def _sanitize_general(s): def _sanitize_general(s):
s = s.strip() s = s.strip()
s = s.replace("pybind11_tests.", "m.") s = s.replace("pybind11_tests.", "m.")
s = s.replace("unicode", "str")
s = _long_marker.sub(r"\1", s) s = _long_marker.sub(r"\1", s)
s = _unicode_marker.sub(r"\1", s)
return s return s
@ -206,3 +198,16 @@ def gc_collect():
def pytest_configure(): def pytest_configure():
pytest.suppress = suppress pytest.suppress = suppress
pytest.gc_collect = gc_collect pytest.gc_collect = gc_collect
def pytest_report_header(config):
del config # Unused.
assert (
pybind11_tests.compiler_info is not None
), "Please update pybind11_tests.cpp if this assert fails."
return (
"C++ Info:"
f" {pybind11_tests.compiler_info}"
f" {pybind11_tests.cpp_std}"
f" {pybind11_tests.PYBIND11_INTERNALS_ID}"
)

View File

@ -56,7 +56,8 @@ from the ConstructorStats instance `.values()` method.
In some cases, when you need to track instances of a C++ class not registered with pybind11, you In some cases, when you need to track instances of a C++ class not registered with pybind11, you
need to add a function returning the ConstructorStats for the C++ class; this can be done with: need to add a function returning the ConstructorStats for the C++ class; this can be done with:
m.def("get_special_cstats", &ConstructorStats::get<SpecialClass>, py::return_value_policy::reference) m.def("get_special_cstats", &ConstructorStats::get<SpecialClass>,
py::return_value_policy::reference)
Finally, you can suppress the output messages, but keep the constructor tracking (for Finally, you can suppress the output messages, but keep the constructor tracking (for
inspection/testing in python) by using the functions with `print_` replaced with `track_` (e.g. inspection/testing in python) by using the functions with `print_` replaced with `track_` (e.g.
@ -65,15 +66,18 @@ inspection/testing in python) by using the functions with `print_` replaced with
*/ */
#include "pybind11_tests.h" #include "pybind11_tests.h"
#include <unordered_map>
#include <list> #include <list>
#include <typeindex>
#include <sstream> #include <sstream>
#include <typeindex>
#include <unordered_map>
class ConstructorStats { class ConstructorStats {
protected: protected:
std::unordered_map<void*, int> _instances; // Need a map rather than set because members can shared address with parents std::unordered_map<void *, int> _instances; // Need a map rather than set because members can
std::list<std::string> _values; // Used to track values (e.g. of value constructors) // shared address with parents
std::list<std::string> _values; // Used to track values
// (e.g. of value constructors)
public: public:
int default_constructions = 0; int default_constructions = 0;
int copy_constructions = 0; int copy_constructions = 0;
@ -96,26 +100,26 @@ public:
default_constructions++; default_constructions++;
} }
void created(void *inst) { void created(void *inst) { ++_instances[inst]; }
++_instances[inst];
}
void destroyed(void *inst) { void destroyed(void *inst) {
if (--_instances[inst] < 0) if (--_instances[inst] < 0) {
throw std::runtime_error("cstats.destroyed() called with unknown " throw std::runtime_error("cstats.destroyed() called with unknown "
"instance; potential double-destruction " "instance; potential double-destruction "
"or a missing cstats.created()"); "or a missing cstats.created()");
}
} }
static void gc() { static void gc() {
// Force garbage collection to ensure any pending destructors are invoked: // Force garbage collection to ensure any pending destructors are invoked:
#if defined(PYPY_VERSION) #if defined(PYPY_VERSION)
PyObject *globals = PyEval_GetGlobals(); PyObject *globals = PyEval_GetGlobals();
PyObject *result = PyRun_String( PyObject *result = PyRun_String("import gc\n"
"import gc\n" "for i in range(2):"
"for i in range(2):" " gc.collect()\n",
" gc.collect()\n", Py_file_input,
Py_file_input, globals, globals); globals,
globals);
if (result == nullptr) if (result == nullptr)
throw py::error_already_set(); throw py::error_already_set();
Py_DECREF(result); Py_DECREF(result);
@ -127,15 +131,18 @@ public:
int alive() { int alive() {
gc(); gc();
int total = 0; int total = 0;
for (const auto &p : _instances) for (const auto &p : _instances) {
if (p.second > 0) if (p.second > 0) {
total += p.second; total += p.second;
}
}
return total; return total;
} }
void value() {} // Recursion terminator void value() {} // Recursion terminator
// Takes one or more values, converts them to strings, then stores them. // Takes one or more values, converts them to strings, then stores them.
template <typename T, typename... Tmore> void value(const T &v, Tmore &&...args) { template <typename T, typename... Tmore>
void value(const T &v, Tmore &&...args) {
std::ostringstream oss; std::ostringstream oss;
oss << v; oss << v;
_values.push_back(oss.str()); _values.push_back(oss.str());
@ -145,19 +152,22 @@ public:
// Move out stored values // Move out stored values
py::list values() { py::list values() {
py::list l; py::list l;
for (const auto &v : _values) l.append(py::cast(v)); for (const auto &v : _values) {
l.append(py::cast(v));
}
_values.clear(); _values.clear();
return l; return l;
} }
// Gets constructor stats from a C++ type index // Gets constructor stats from a C++ type index
static ConstructorStats& get(std::type_index type) { static ConstructorStats &get(std::type_index type) {
static std::unordered_map<std::type_index, ConstructorStats> all_cstats; static std::unordered_map<std::type_index, ConstructorStats> all_cstats;
return all_cstats[type]; return all_cstats[type];
} }
// Gets constructor stats from a C++ type // Gets constructor stats from a C++ type
template <typename T> static ConstructorStats& get() { template <typename T>
static ConstructorStats &get() {
#if defined(PYPY_VERSION) #if defined(PYPY_VERSION)
gc(); gc();
#endif #endif
@ -165,11 +175,12 @@ public:
} }
// Gets constructor stats from a Python class // Gets constructor stats from a Python class
static ConstructorStats& get(py::object class_) { static ConstructorStats &get(py::object class_) {
auto &internals = py::detail::get_internals(); auto &internals = py::detail::get_internals();
const std::type_index *t1 = nullptr, *t2 = nullptr; const std::type_index *t1 = nullptr, *t2 = nullptr;
try { try {
auto *type_info = internals.registered_types_py.at((PyTypeObject *) class_.ptr()).at(0); auto *type_info
= internals.registered_types_py.at((PyTypeObject *) class_.ptr()).at(0);
for (auto &p : internals.registered_types_cpp) { for (auto &p : internals.registered_types_cpp) {
if (p.second == type_info) { if (p.second == type_info) {
if (t1) { if (t1) {
@ -179,17 +190,23 @@ public:
t1 = &p.first; t1 = &p.first;
} }
} }
} catch (const std::out_of_range &) {
}
if (!t1) {
throw std::runtime_error("Unknown class passed to ConstructorStats::get()");
} }
catch (const std::out_of_range&) {}
if (!t1) throw std::runtime_error("Unknown class passed to ConstructorStats::get()");
auto &cs1 = get(*t1); auto &cs1 = get(*t1);
// If we have both a t1 and t2 match, one is probably the trampoline class; return whichever // If we have both a t1 and t2 match, one is probably the trampoline class; return
// has more constructions (typically one or the other will be 0) // whichever has more constructions (typically one or the other will be 0)
if (t2) { if (t2) {
auto &cs2 = get(*t2); auto &cs2 = get(*t2);
int cs1_total = cs1.default_constructions + cs1.copy_constructions + cs1.move_constructions + (int) cs1._values.size(); int cs1_total = cs1.default_constructions + cs1.copy_constructions
int cs2_total = cs2.default_constructions + cs2.copy_constructions + cs2.move_constructions + (int) cs2._values.size(); + cs1.move_constructions + (int) cs1._values.size();
if (cs2_total > cs1_total) return cs2; int cs2_total = cs2.default_constructions + cs2.copy_constructions
+ cs2.move_constructions + (int) cs2._values.size();
if (cs2_total > cs1_total) {
return cs2;
}
} }
return cs1; return cs1;
} }
@ -198,78 +215,108 @@ public:
// To track construction/destruction, you need to call these methods from the various // To track construction/destruction, you need to call these methods from the various
// constructors/operators. The ones that take extra values record the given values in the // constructors/operators. The ones that take extra values record the given values in the
// constructor stats values for later inspection. // constructor stats values for later inspection.
template <class T> void track_copy_created(T *inst) { ConstructorStats::get<T>().copy_created(inst); } template <class T>
template <class T> void track_move_created(T *inst) { ConstructorStats::get<T>().move_created(inst); } void track_copy_created(T *inst) {
template <class T, typename... Values> void track_copy_assigned(T *, Values &&...values) { ConstructorStats::get<T>().copy_created(inst);
}
template <class T>
void track_move_created(T *inst) {
ConstructorStats::get<T>().move_created(inst);
}
template <class T, typename... Values>
void track_copy_assigned(T *, Values &&...values) {
auto &cst = ConstructorStats::get<T>(); auto &cst = ConstructorStats::get<T>();
cst.copy_assignments++; cst.copy_assignments++;
cst.value(std::forward<Values>(values)...); cst.value(std::forward<Values>(values)...);
} }
template <class T, typename... Values> void track_move_assigned(T *, Values &&...values) { template <class T, typename... Values>
void track_move_assigned(T *, Values &&...values) {
auto &cst = ConstructorStats::get<T>(); auto &cst = ConstructorStats::get<T>();
cst.move_assignments++; cst.move_assignments++;
cst.value(std::forward<Values>(values)...); cst.value(std::forward<Values>(values)...);
} }
template <class T, typename... Values> void track_default_created(T *inst, Values &&...values) { template <class T, typename... Values>
void track_default_created(T *inst, Values &&...values) {
auto &cst = ConstructorStats::get<T>(); auto &cst = ConstructorStats::get<T>();
cst.default_created(inst); cst.default_created(inst);
cst.value(std::forward<Values>(values)...); cst.value(std::forward<Values>(values)...);
} }
template <class T, typename... Values> void track_created(T *inst, Values &&...values) { template <class T, typename... Values>
void track_created(T *inst, Values &&...values) {
auto &cst = ConstructorStats::get<T>(); auto &cst = ConstructorStats::get<T>();
cst.created(inst); cst.created(inst);
cst.value(std::forward<Values>(values)...); cst.value(std::forward<Values>(values)...);
} }
template <class T, typename... Values> void track_destroyed(T *inst) { template <class T, typename... Values>
void track_destroyed(T *inst) {
ConstructorStats::get<T>().destroyed(inst); ConstructorStats::get<T>().destroyed(inst);
} }
template <class T, typename... Values> void track_values(T *, Values &&...values) { template <class T, typename... Values>
void track_values(T *, Values &&...values) {
ConstructorStats::get<T>().value(std::forward<Values>(values)...); ConstructorStats::get<T>().value(std::forward<Values>(values)...);
} }
/// Don't cast pointers to Python, print them as strings /// Don't cast pointers to Python, print them as strings
inline const char *format_ptrs(const char *p) { return p; } inline const char *format_ptrs(const char *p) { return p; }
template <typename T> template <typename T>
py::str format_ptrs(T *p) { return "{:#x}"_s.format(reinterpret_cast<std::uintptr_t>(p)); } py::str format_ptrs(T *p) {
return "{:#x}"_s.format(reinterpret_cast<std::uintptr_t>(p));
}
template <typename T> template <typename T>
auto format_ptrs(T &&x) -> decltype(std::forward<T>(x)) { return std::forward<T>(x); } auto format_ptrs(T &&x) -> decltype(std::forward<T>(x)) {
return std::forward<T>(x);
}
template <class T, typename... Output> template <class T, typename... Output>
void print_constr_details(T *inst, const std::string &action, Output &&...output) { void print_constr_details(T *inst, const std::string &action, Output &&...output) {
py::print("###", py::type_id<T>(), "@", format_ptrs(inst), action, py::print("###",
py::type_id<T>(),
"@",
format_ptrs(inst),
action,
format_ptrs(std::forward<Output>(output))...); format_ptrs(std::forward<Output>(output))...);
} }
// Verbose versions of the above: // Verbose versions of the above:
template <class T, typename... Values> void print_copy_created(T *inst, Values &&...values) { // NB: this prints, but doesn't store, given values template <class T, typename... Values>
void print_copy_created(T *inst,
Values &&...values) { // NB: this prints, but doesn't store, given values
print_constr_details(inst, "created via copy constructor", values...); print_constr_details(inst, "created via copy constructor", values...);
track_copy_created(inst); track_copy_created(inst);
} }
template <class T, typename... Values> void print_move_created(T *inst, Values &&...values) { // NB: this prints, but doesn't store, given values template <class T, typename... Values>
void print_move_created(T *inst,
Values &&...values) { // NB: this prints, but doesn't store, given values
print_constr_details(inst, "created via move constructor", values...); print_constr_details(inst, "created via move constructor", values...);
track_move_created(inst); track_move_created(inst);
} }
template <class T, typename... Values> void print_copy_assigned(T *inst, Values &&...values) { template <class T, typename... Values>
void print_copy_assigned(T *inst, Values &&...values) {
print_constr_details(inst, "assigned via copy assignment", values...); print_constr_details(inst, "assigned via copy assignment", values...);
track_copy_assigned(inst, values...); track_copy_assigned(inst, values...);
} }
template <class T, typename... Values> void print_move_assigned(T *inst, Values &&...values) { template <class T, typename... Values>
void print_move_assigned(T *inst, Values &&...values) {
print_constr_details(inst, "assigned via move assignment", values...); print_constr_details(inst, "assigned via move assignment", values...);
track_move_assigned(inst, values...); track_move_assigned(inst, values...);
} }
template <class T, typename... Values> void print_default_created(T *inst, Values &&...values) { template <class T, typename... Values>
void print_default_created(T *inst, Values &&...values) {
print_constr_details(inst, "created via default constructor", values...); print_constr_details(inst, "created via default constructor", values...);
track_default_created(inst, values...); track_default_created(inst, values...);
} }
template <class T, typename... Values> void print_created(T *inst, Values &&...values) { template <class T, typename... Values>
void print_created(T *inst, Values &&...values) {
print_constr_details(inst, "created", values...); print_constr_details(inst, "created", values...);
track_created(inst, values...); track_created(inst, values...);
} }
template <class T, typename... Values> void print_destroyed(T *inst, Values &&...values) { // Prints but doesn't store given values template <class T, typename... Values>
void print_destroyed(T *inst, Values &&...values) { // Prints but doesn't store given values
print_constr_details(inst, "destroyed", values...); print_constr_details(inst, "destroyed", values...);
track_destroyed(inst); track_destroyed(inst);
} }
template <class T, typename... Values> void print_values(T *inst, Values &&...values) { template <class T, typename... Values>
void print_values(T *inst, Values &&...values) {
print_constr_details(inst, ":", values...); print_constr_details(inst, ":", values...);
track_values(inst, values...); track_values(inst, values...);
} }

View File

@ -7,6 +7,7 @@
BSD-style license that can be found in the LICENSE file. BSD-style license that can be found in the LICENSE file.
*/ */
#include <pybind11/pybind11.h> #include <pybind11/pybind11.h>
#include <cstdint> #include <cstdint>
// This file mimics a DSO that makes pybind11 calls but does not define a // This file mimics a DSO that makes pybind11 calls but does not define a
@ -24,50 +25,21 @@ void gil_acquire() { py::gil_scoped_acquire gil; }
constexpr char kModuleName[] = "cross_module_gil_utils"; constexpr char kModuleName[] = "cross_module_gil_utils";
#if PY_MAJOR_VERSION >= 3
struct PyModuleDef moduledef = { struct PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT, PyModuleDef_HEAD_INIT, kModuleName, nullptr, 0, nullptr, nullptr, nullptr, nullptr, nullptr};
kModuleName,
NULL,
0,
NULL,
NULL,
NULL,
NULL,
NULL
};
#else
PyMethodDef module_methods[] = {
{NULL, NULL, 0, NULL}
};
#endif
} // namespace } // namespace
extern "C" PYBIND11_EXPORT extern "C" PYBIND11_EXPORT PyObject *PyInit_cross_module_gil_utils() {
#if PY_MAJOR_VERSION >= 3
PyObject* PyInit_cross_module_gil_utils()
#else
void initcross_module_gil_utils()
#endif
{
PyObject* m = PyObject *m = PyModule_Create(&moduledef);
#if PY_MAJOR_VERSION >= 3
PyModule_Create(&moduledef);
#else
Py_InitModule(kModuleName, module_methods);
#endif
if (m != NULL) { if (m != nullptr) {
static_assert( static_assert(sizeof(&gil_acquire) == sizeof(void *),
sizeof(&gil_acquire) == sizeof(void*), "Function pointer must have the same size as void*");
"Function pointer must have the same size as void*"); PyModule_AddObject(
PyModule_AddObject(m, "gil_acquire_funcaddr", m, "gil_acquire_funcaddr", PyLong_FromVoidPtr(reinterpret_cast<void *>(&gil_acquire)));
PyLong_FromVoidPtr(reinterpret_cast<void*>(&gil_acquire)));
} }
#if PY_MAJOR_VERSION >= 3
return m; return m;
#endif
} }

View File

@ -0,0 +1,51 @@
/*
Copyright (c) 2022 Google LLC
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include <pybind11/pybind11.h>
// This file mimics a DSO that makes pybind11 calls but does not define a PYBIND11_MODULE,
// so that the first call of cross_module_error_already_set() triggers the first call of
// pybind11::detail::get_internals().
namespace {
namespace py = pybind11;
void interleaved_error_already_set() {
PyErr_SetString(PyExc_RuntimeError, "1st error.");
try {
throw py::error_already_set();
} catch (const py::error_already_set &) {
// The 2nd error could be conditional in a real application.
PyErr_SetString(PyExc_RuntimeError, "2nd error.");
} // Here the 1st error is destroyed before the 2nd error is fetched.
// The error_already_set dtor triggers a pybind11::detail::get_internals()
// call via pybind11::gil_scoped_acquire.
if (PyErr_Occurred()) {
throw py::error_already_set();
}
}
constexpr char kModuleName[] = "cross_module_interleaved_error_already_set";
struct PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT, kModuleName, nullptr, 0, nullptr, nullptr, nullptr, nullptr, nullptr};
} // namespace
extern "C" PYBIND11_EXPORT PyObject *PyInit_cross_module_interleaved_error_already_set() {
PyObject *m = PyModule_Create(&moduledef);
if (m != nullptr) {
static_assert(sizeof(&interleaved_error_already_set) == sizeof(void *),
"Function pointer must have the same size as void *");
PyModule_AddObject(
m,
"funcaddr",
PyLong_FromVoidPtr(reinterpret_cast<void *>(&interleaved_error_already_set)));
}
return m;
}

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
import platform import platform
import sys import sys
@ -11,10 +10,6 @@ WIN = sys.platform.startswith("win32") or sys.platform.startswith("cygwin")
CPYTHON = platform.python_implementation() == "CPython" CPYTHON = platform.python_implementation() == "CPython"
PYPY = platform.python_implementation() == "PyPy" PYPY = platform.python_implementation() == "PyPy"
PY2 = sys.version_info.major == 2
PY = sys.version_info
def deprecated_call(): def deprecated_call():
""" """

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
import contextlib import contextlib
import os import os
import string import string
@ -64,11 +63,9 @@ py_files = {
"__init__.py", "__init__.py",
"__main__.py", "__main__.py",
"_version.py", "_version.py",
"_version.pyi",
"commands.py", "commands.py",
"py.typed", "py.typed",
"setup_helpers.py", "setup_helpers.py",
"setup_helpers.pyi",
} }
headers = main_headers | detail_headers | stl_headers headers = main_headers | detail_headers | stl_headers
@ -111,19 +108,19 @@ def test_build_sdist(monkeypatch, tmpdir):
out = subprocess.check_output( out = subprocess.check_output(
[ [
sys.executable, sys.executable,
"setup.py", "-m",
"sdist", "build",
"--formats=tar", "--sdist",
"--dist-dir", "--outdir",
str(tmpdir), str(tmpdir),
] ]
) )
if hasattr(out, "decode"): if hasattr(out, "decode"):
out = out.decode() out = out.decode()
(sdist,) = tmpdir.visit("*.tar") (sdist,) = tmpdir.visit("*.tar.gz")
with tarfile.open(str(sdist)) as tar: with tarfile.open(str(sdist), "r:gz") as tar:
start = tar.getnames()[0] + "/" start = tar.getnames()[0] + "/"
version = start[9:-1] version = start[9:-1]
simpler = {n.split("/", 1)[-1] for n in tar.getnames()[1:]} simpler = {n.split("/", 1)[-1] for n in tar.getnames()[1:]}
@ -148,9 +145,9 @@ def test_build_sdist(monkeypatch, tmpdir):
contents = f.read().decode("utf8") contents = f.read().decode("utf8")
assert 'set(pybind11_INCLUDE_DIR "${PACKAGE_PREFIX_DIR}/include")' in contents assert 'set(pybind11_INCLUDE_DIR "${PACKAGE_PREFIX_DIR}/include")' in contents
files = {"pybind11/{}".format(n) for n in all_files} files = {f"pybind11/{n}" for n in all_files}
files |= sdist_files files |= sdist_files
files |= {"pybind11{}".format(n) for n in local_sdist_files} files |= {f"pybind11{n}" for n in local_sdist_files}
files.add("pybind11.egg-info/entry_points.txt") files.add("pybind11.egg-info/entry_points.txt")
files.add("pybind11.egg-info/requires.txt") files.add("pybind11.egg-info/requires.txt")
assert simpler == files assert simpler == files
@ -172,23 +169,23 @@ def test_build_global_dist(monkeypatch, tmpdir):
monkeypatch.chdir(MAIN_DIR) monkeypatch.chdir(MAIN_DIR)
monkeypatch.setenv("PYBIND11_GLOBAL_SDIST", "1") monkeypatch.setenv("PYBIND11_GLOBAL_SDIST", "1")
out = subprocess.check_output( out = subprocess.check_output(
[ [
sys.executable, sys.executable,
"setup.py", "-m",
"sdist", "build",
"--formats=tar", "--sdist",
"--dist-dir", "--outdir",
str(tmpdir), str(tmpdir),
] ]
) )
if hasattr(out, "decode"): if hasattr(out, "decode"):
out = out.decode() out = out.decode()
(sdist,) = tmpdir.visit("*.tar") (sdist,) = tmpdir.visit("*.tar.gz")
with tarfile.open(str(sdist)) as tar: with tarfile.open(str(sdist), "r:gz") as tar:
start = tar.getnames()[0] + "/" start = tar.getnames()[0] + "/"
version = start[16:-1] version = start[16:-1]
simpler = {n.split("/", 1)[-1] for n in tar.getnames()[1:]} simpler = {n.split("/", 1)[-1] for n in tar.getnames()[1:]}
@ -203,9 +200,9 @@ def test_build_global_dist(monkeypatch, tmpdir):
) as f: ) as f:
pyproject_toml = f.read() pyproject_toml = f.read()
files = {"pybind11/{}".format(n) for n in all_files} files = {f"pybind11/{n}" for n in all_files}
files |= sdist_files files |= sdist_files
files |= {"pybind11_global{}".format(n) for n in local_sdist_files} files |= {f"pybind11_global{n}" for n in local_sdist_files}
assert simpler == files assert simpler == files
with open(os.path.join(MAIN_DIR, "tools", "setup_global.py.in"), "rb") as f: with open(os.path.join(MAIN_DIR, "tools", "setup_global.py.in"), "rb") as f:
@ -230,7 +227,7 @@ def tests_build_wheel(monkeypatch, tmpdir):
(wheel,) = tmpdir.visit("*.whl") (wheel,) = tmpdir.visit("*.whl")
files = {"pybind11/{}".format(n) for n in all_files} files = {f"pybind11/{n}" for n in all_files}
files |= { files |= {
"dist-info/LICENSE", "dist-info/LICENSE",
"dist-info/METADATA", "dist-info/METADATA",
@ -244,9 +241,7 @@ def tests_build_wheel(monkeypatch, tmpdir):
names = z.namelist() names = z.namelist()
trimmed = {n for n in names if "dist-info" not in n} trimmed = {n for n in names if "dist-info" not in n}
trimmed |= { trimmed |= {f"dist-info/{n.split('/', 1)[-1]}" for n in names if "dist-info" in n}
"dist-info/{}".format(n.split("/", 1)[-1]) for n in names if "dist-info" in n
}
assert files == trimmed assert files == trimmed
@ -260,8 +255,8 @@ def tests_build_global_wheel(monkeypatch, tmpdir):
(wheel,) = tmpdir.visit("*.whl") (wheel,) = tmpdir.visit("*.whl")
files = {"data/data/{}".format(n) for n in src_files} files = {f"data/data/{n}" for n in src_files}
files |= {"data/headers/{}".format(n[8:]) for n in headers} files |= {f"data/headers/{n[8:]}" for n in headers}
files |= { files |= {
"dist-info/LICENSE", "dist-info/LICENSE",
"dist-info/METADATA", "dist-info/METADATA",

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
import os import os
import subprocess import subprocess
import sys import sys
@ -19,7 +18,7 @@ def test_simple_setup_py(monkeypatch, tmpdir, parallel, std):
(tmpdir / "setup.py").write_text( (tmpdir / "setup.py").write_text(
dedent( dedent(
u"""\ f"""\
import sys import sys
sys.path.append({MAIN_DIR!r}) sys.path.append({MAIN_DIR!r})
@ -52,13 +51,13 @@ def test_simple_setup_py(monkeypatch, tmpdir, parallel, std):
ext_modules=ext_modules, ext_modules=ext_modules,
) )
""" """
).format(MAIN_DIR=MAIN_DIR, std=std, parallel=parallel), ),
encoding="ascii", encoding="ascii",
) )
(tmpdir / "main.cpp").write_text( (tmpdir / "main.cpp").write_text(
dedent( dedent(
u"""\ """\
#include <pybind11/pybind11.h> #include <pybind11/pybind11.h>
int f(int x) { int f(int x) {
@ -96,7 +95,7 @@ def test_simple_setup_py(monkeypatch, tmpdir, parallel, std):
(tmpdir / "test.py").write_text( (tmpdir / "test.py").write_text(
dedent( dedent(
u"""\ """\
import simple_setup import simple_setup
assert simple_setup.f(3) == 9 assert simple_setup.f(3) == 9
""" """
@ -121,10 +120,11 @@ def test_intree_extensions(monkeypatch, tmpdir):
subdir.ensure_dir() subdir.ensure_dir()
src = subdir / "ext.cpp" src = subdir / "ext.cpp"
src.ensure() src.ensure()
(ext,) = intree_extensions([src.relto(tmpdir)]) relpath = src.relto(tmpdir)
(ext,) = intree_extensions([relpath])
assert ext.name == "ext" assert ext.name == "ext"
subdir.ensure("__init__.py") subdir.ensure("__init__.py")
(ext,) = intree_extensions([src.relto(tmpdir)]) (ext,) = intree_extensions([relpath])
assert ext.name == "dir.ext" assert ext.name == "dir.ext"

View File

@ -1,12 +1,13 @@
#pragma once #pragma once
#include <utility>
#include "pybind11_tests.h" #include "pybind11_tests.h"
#include <utility>
/// Simple class used to test py::local: /// Simple class used to test py::local:
template <int> class LocalBase { template <int>
class LocalBase {
public: public:
explicit LocalBase(int i) : i(i) { } explicit LocalBase(int i) : i(i) {}
int i = -1; int i = -1;
}; };
@ -35,12 +36,12 @@ using NonLocalVec2 = std::vector<NonLocal2>;
using NonLocalMap = std::unordered_map<std::string, NonLocalType>; using NonLocalMap = std::unordered_map<std::string, NonLocalType>;
using NonLocalMap2 = std::unordered_map<std::string, uint8_t>; using NonLocalMap2 = std::unordered_map<std::string, uint8_t>;
// Exception that will be caught via the module local translator. // Exception that will be caught via the module local translator.
class LocalException : public std::exception { class LocalException : public std::exception {
public: public:
explicit LocalException(const char * m) : message{m} {} explicit LocalException(const char *m) : message{m} {}
const char * what() const noexcept override {return message.c_str();} const char *what() const noexcept override { return message.c_str(); }
private: private:
std::string message = ""; std::string message = "";
}; };
@ -48,8 +49,9 @@ private:
// Exception that will be registered with register_local_exception_translator // Exception that will be registered with register_local_exception_translator
class LocalSimpleException : public std::exception { class LocalSimpleException : public std::exception {
public: public:
explicit LocalSimpleException(const char * m) : message{m} {} explicit LocalSimpleException(const char *m) : message{m} {}
const char * what() const noexcept override {return message.c_str();} const char *what() const noexcept override { return message.c_str(); }
private: private:
std::string message = ""; std::string message = "";
}; };
@ -58,17 +60,16 @@ PYBIND11_MAKE_OPAQUE(LocalVec);
PYBIND11_MAKE_OPAQUE(LocalVec2); PYBIND11_MAKE_OPAQUE(LocalVec2);
PYBIND11_MAKE_OPAQUE(LocalMap); PYBIND11_MAKE_OPAQUE(LocalMap);
PYBIND11_MAKE_OPAQUE(NonLocalVec); PYBIND11_MAKE_OPAQUE(NonLocalVec);
//PYBIND11_MAKE_OPAQUE(NonLocalVec2); // same type as LocalVec2 // PYBIND11_MAKE_OPAQUE(NonLocalVec2); // same type as LocalVec2
PYBIND11_MAKE_OPAQUE(NonLocalMap); PYBIND11_MAKE_OPAQUE(NonLocalMap);
PYBIND11_MAKE_OPAQUE(NonLocalMap2); PYBIND11_MAKE_OPAQUE(NonLocalMap2);
// Simple bindings (used with the above): // Simple bindings (used with the above):
template <typename T, int Adjust = 0, typename... Args> template <typename T, int Adjust = 0, typename... Args>
py::class_<T> bind_local(Args && ...args) { py::class_<T> bind_local(Args &&...args) {
return py::class_<T>(std::forward<Args>(args)...) return py::class_<T>(std::forward<Args>(args)...).def(py::init<int>()).def("get", [](T &i) {
.def(py::init<int>()) return i.i + Adjust;
.def("get", [](T &i) { return i.i + Adjust; }); });
}; };
// Simulate a foreign library base class (to match the example in the docs): // Simulate a foreign library base class (to match the example in the docs):
@ -81,5 +82,11 @@ public:
}; };
} // namespace pets } // namespace pets
struct MixGL { int i; explicit MixGL(int i) : i{i} {} }; struct MixGL {
struct MixGL2 { int i; explicit MixGL2(int i) : i{i} {} }; int i;
explicit MixGL(int i) : i{i} {}
};
struct MixGL2 {
int i;
explicit MixGL2(int i) : i{i} {}
};

View File

@ -1,8 +1,9 @@
#if !defined(__OBJECT_H) #if !defined(__OBJECT_H)
#define __OBJECT_H # define __OBJECT_H
#include <atomic> # include "constructor_stats.h"
#include "constructor_stats.h"
# include <atomic>
/// Reference counted object base class /// Reference counted object base class
class Object { class Object {
@ -27,20 +28,23 @@ public:
*/ */
void decRef(bool dealloc = true) const { void decRef(bool dealloc = true) const {
--m_refCount; --m_refCount;
if (m_refCount == 0 && dealloc) if (m_refCount == 0 && dealloc) {
delete this; delete this;
else if (m_refCount < 0) } else if (m_refCount < 0) {
throw std::runtime_error("Internal error: reference count < 0!"); throw std::runtime_error("Internal error: reference count < 0!");
}
} }
virtual std::string toString() const = 0; virtual std::string toString() const = 0;
protected: protected:
/** \brief Virtual protected deconstructor. /** \brief Virtual protected deconstructor.
* (Will only be called by \ref ref) * (Will only be called by \ref ref)
*/ */
virtual ~Object() { print_destroyed(this); } virtual ~Object() { print_destroyed(this); }
private: private:
mutable std::atomic<int> m_refCount { 0 }; mutable std::atomic<int> m_refCount{0};
}; };
// Tag class used to track constructions of ref objects. When we track constructors, below, we // Tag class used to track constructions of ref objects. When we track constructors, below, we
@ -59,84 +63,105 @@ class ref_tag {};
* *
* \ingroup libcore * \ingroup libcore
*/ */
template <typename T> class ref { template <typename T>
class ref {
public: public:
/// Create a nullptr reference /// Create a nullptr reference
ref() : m_ptr(nullptr) { print_default_created(this); track_default_created((ref_tag*) this); } ref() : m_ptr(nullptr) {
print_default_created(this);
track_default_created((ref_tag *) this);
}
/// Construct a reference from a pointer /// Construct a reference from a pointer
explicit ref(T *ptr) : m_ptr(ptr) { explicit ref(T *ptr) : m_ptr(ptr) {
if (m_ptr) ((Object *) m_ptr)->incRef(); if (m_ptr) {
((Object *) m_ptr)->incRef();
print_created(this, "from pointer", m_ptr); track_created((ref_tag*) this, "from pointer"); }
print_created(this, "from pointer", m_ptr);
track_created((ref_tag *) this, "from pointer");
} }
/// Copy constructor /// Copy constructor
ref(const ref &r) : m_ptr(r.m_ptr) { ref(const ref &r) : m_ptr(r.m_ptr) {
if (m_ptr) if (m_ptr) {
((Object *) m_ptr)->incRef(); ((Object *) m_ptr)->incRef();
}
print_copy_created(this, "with pointer", m_ptr); track_copy_created((ref_tag*) this); print_copy_created(this, "with pointer", m_ptr);
track_copy_created((ref_tag *) this);
} }
/// Move constructor /// Move constructor
ref(ref &&r) noexcept : m_ptr(r.m_ptr) { ref(ref &&r) noexcept : m_ptr(r.m_ptr) {
r.m_ptr = nullptr; r.m_ptr = nullptr;
print_move_created(this, "with pointer", m_ptr); track_move_created((ref_tag*) this); print_move_created(this, "with pointer", m_ptr);
track_move_created((ref_tag *) this);
} }
/// Destroy this reference /// Destroy this reference
~ref() { ~ref() {
if (m_ptr) if (m_ptr) {
((Object *) m_ptr)->decRef(); ((Object *) m_ptr)->decRef();
}
print_destroyed(this); track_destroyed((ref_tag*) this); print_destroyed(this);
track_destroyed((ref_tag *) this);
} }
/// Move another reference into the current one /// Move another reference into the current one
ref &operator=(ref &&r) noexcept { ref &operator=(ref &&r) noexcept {
print_move_assigned(this, "pointer", r.m_ptr); track_move_assigned((ref_tag*) this); print_move_assigned(this, "pointer", r.m_ptr);
track_move_assigned((ref_tag *) this);
if (*this == r) if (*this == r) {
return *this; return *this;
if (m_ptr) }
if (m_ptr) {
((Object *) m_ptr)->decRef(); ((Object *) m_ptr)->decRef();
}
m_ptr = r.m_ptr; m_ptr = r.m_ptr;
r.m_ptr = nullptr; r.m_ptr = nullptr;
return *this; return *this;
} }
/// Overwrite this reference with another reference /// Overwrite this reference with another reference
ref& operator=(const ref& r) { ref &operator=(const ref &r) {
if (this == &r) { if (this == &r) {
return *this; return *this;
} }
print_copy_assigned(this, "pointer", r.m_ptr); print_copy_assigned(this, "pointer", r.m_ptr);
track_copy_assigned((ref_tag *) this); track_copy_assigned((ref_tag *) this);
if (m_ptr == r.m_ptr) if (m_ptr == r.m_ptr) {
return *this; return *this;
if (m_ptr) }
if (m_ptr) {
((Object *) m_ptr)->decRef(); ((Object *) m_ptr)->decRef();
}
m_ptr = r.m_ptr; m_ptr = r.m_ptr;
if (m_ptr) if (m_ptr) {
((Object *) m_ptr)->incRef(); ((Object *) m_ptr)->incRef();
}
return *this; return *this;
} }
/// Overwrite this reference with a pointer to another object /// Overwrite this reference with a pointer to another object
ref& operator=(T *ptr) { ref &operator=(T *ptr) {
print_values(this, "assigned pointer"); track_values((ref_tag*) this, "assigned pointer"); print_values(this, "assigned pointer");
track_values((ref_tag *) this, "assigned pointer");
if (m_ptr == ptr) if (m_ptr == ptr) {
return *this; return *this;
if (m_ptr) }
if (m_ptr) {
((Object *) m_ptr)->decRef(); ((Object *) m_ptr)->decRef();
}
m_ptr = ptr; m_ptr = ptr;
if (m_ptr) if (m_ptr) {
((Object *) m_ptr)->incRef(); ((Object *) m_ptr)->incRef();
}
return *this; return *this;
} }
@ -147,31 +172,32 @@ public:
bool operator!=(const ref &r) const { return m_ptr != r.m_ptr; } bool operator!=(const ref &r) const { return m_ptr != r.m_ptr; }
/// Compare this reference with a pointer /// Compare this reference with a pointer
bool operator==(const T* ptr) const { return m_ptr == ptr; } bool operator==(const T *ptr) const { return m_ptr == ptr; }
/// Compare this reference with a pointer /// Compare this reference with a pointer
bool operator!=(const T* ptr) const { return m_ptr != ptr; } bool operator!=(const T *ptr) const { return m_ptr != ptr; }
/// Access the object referenced by this reference /// Access the object referenced by this reference
T* operator->() { return m_ptr; } T *operator->() { return m_ptr; }
/// Access the object referenced by this reference /// Access the object referenced by this reference
const T* operator->() const { return m_ptr; } const T *operator->() const { return m_ptr; }
/// Return a C++ reference to the referenced object /// Return a C++ reference to the referenced object
T& operator*() { return *m_ptr; } T &operator*() { return *m_ptr; }
/// Return a const C++ reference to the referenced object /// Return a const C++ reference to the referenced object
const T& operator*() const { return *m_ptr; } const T &operator*() const { return *m_ptr; }
/// Return a pointer to the referenced object /// Return a pointer to the referenced object
explicit operator T* () { return m_ptr; } explicit operator T *() { return m_ptr; }
/// Return a const pointer to the referenced object /// Return a const pointer to the referenced object
T* get_ptr() { return m_ptr; } T *get_ptr() { return m_ptr; }
/// Return a pointer to the referenced object /// Return a pointer to the referenced object
const T* get_ptr() const { return m_ptr; } const T *get_ptr() const { return m_ptr; }
private: private:
T *m_ptr; T *m_ptr;
}; };

View File

@ -7,12 +7,12 @@
BSD-style license that can be found in the LICENSE file. BSD-style license that can be found in the LICENSE file.
*/ */
#include "pybind11_tests.h"
#include "local_bindings.h"
#include "test_exceptions.h"
#include <pybind11/stl_bind.h> #include <pybind11/stl_bind.h>
#include "local_bindings.h"
#include "pybind11_tests.h"
#include "test_exceptions.h"
#include <numeric> #include <numeric>
#include <utility> #include <utility>
@ -30,37 +30,45 @@ PYBIND11_MODULE(pybind11_cross_module_tests, m) {
// test_exceptions.py // test_exceptions.py
py::register_local_exception<LocalSimpleException>(m, "LocalSimpleException"); py::register_local_exception<LocalSimpleException>(m, "LocalSimpleException");
m.def("raise_runtime_error", []() { PyErr_SetString(PyExc_RuntimeError, "My runtime error"); throw py::error_already_set(); }); m.def("raise_runtime_error", []() {
m.def("raise_value_error", []() { PyErr_SetString(PyExc_ValueError, "My value error"); throw py::error_already_set(); }); PyErr_SetString(PyExc_RuntimeError, "My runtime error");
throw py::error_already_set();
});
m.def("raise_value_error", []() {
PyErr_SetString(PyExc_ValueError, "My value error");
throw py::error_already_set();
});
m.def("throw_pybind_value_error", []() { throw py::value_error("pybind11 value error"); }); m.def("throw_pybind_value_error", []() { throw py::value_error("pybind11 value error"); });
m.def("throw_pybind_type_error", []() { throw py::type_error("pybind11 type error"); }); m.def("throw_pybind_type_error", []() { throw py::type_error("pybind11 type error"); });
m.def("throw_stop_iteration", []() { throw py::stop_iteration(); }); m.def("throw_stop_iteration", []() { throw py::stop_iteration(); });
m.def("throw_local_error", []() { throw LocalException("just local"); }); m.def("throw_local_error", []() { throw LocalException("just local"); });
m.def("throw_local_simple_error", []() { throw LocalSimpleException("external mod"); }); m.def("throw_local_simple_error", []() { throw LocalSimpleException("external mod"); });
py::register_exception_translator([](std::exception_ptr p) { py::register_exception_translator([](std::exception_ptr p) {
try { try {
if (p) std::rethrow_exception(p); if (p) {
} catch (const shared_exception &e) { std::rethrow_exception(p);
PyErr_SetString(PyExc_KeyError, e.what()); }
} } catch (const shared_exception &e) {
PyErr_SetString(PyExc_KeyError, e.what());
}
}); });
// translate the local exception into a key error but only in this module // translate the local exception into a key error but only in this module
py::register_local_exception_translator([](std::exception_ptr p) { py::register_local_exception_translator([](std::exception_ptr p) {
try { try {
if (p) { if (p) {
std::rethrow_exception(p); std::rethrow_exception(p);
} }
} catch (const LocalException &e) { } catch (const LocalException &e) {
PyErr_SetString(PyExc_KeyError, e.what()); PyErr_SetString(PyExc_KeyError, e.what());
} }
}); });
// test_local_bindings.py // test_local_bindings.py
// Local to both: // Local to both:
bind_local<LocalType, 1>(m, "LocalType", py::module_local()) bind_local<LocalType, 1>(m, "LocalType", py::module_local()).def("get2", [](LocalType &t) {
.def("get2", [](LocalType &t) { return t.i + 2; }) return t.i + 2;
; });
// Can only be called with our python type: // Can only be called with our python type:
m.def("local_value", [](LocalType &l) { return l.i; }); m.def("local_value", [](LocalType &l) { return l.i; });
@ -68,9 +76,7 @@ PYBIND11_MODULE(pybind11_cross_module_tests, m) {
// test_nonlocal_failure // test_nonlocal_failure
// This registration will fail (global registration when LocalFail is already registered // This registration will fail (global registration when LocalFail is already registered
// globally in the main test module): // globally in the main test module):
m.def("register_nonlocal", [m]() { m.def("register_nonlocal", [m]() { bind_local<NonLocalType, 0>(m, "NonLocalType"); });
bind_local<NonLocalType, 0>(m, "NonLocalType");
});
// test_stl_bind_local // test_stl_bind_local
// stl_bind.h binders defaults to py::module_local if the types are local or converting: // stl_bind.h binders defaults to py::module_local if the types are local or converting:
@ -80,27 +86,21 @@ PYBIND11_MODULE(pybind11_cross_module_tests, m) {
// test_stl_bind_global // test_stl_bind_global
// and global if the type (or one of the types, for the map) is global (so these will fail, // and global if the type (or one of the types, for the map) is global (so these will fail,
// assuming pybind11_tests is already loaded): // assuming pybind11_tests is already loaded):
m.def("register_nonlocal_vec", [m]() { m.def("register_nonlocal_vec", [m]() { py::bind_vector<NonLocalVec>(m, "NonLocalVec"); });
py::bind_vector<NonLocalVec>(m, "NonLocalVec"); m.def("register_nonlocal_map", [m]() { py::bind_map<NonLocalMap>(m, "NonLocalMap"); });
});
m.def("register_nonlocal_map", [m]() {
py::bind_map<NonLocalMap>(m, "NonLocalMap");
});
// The default can, however, be overridden to global using `py::module_local()` or // The default can, however, be overridden to global using `py::module_local()` or
// `py::module_local(false)`. // `py::module_local(false)`.
// Explicitly made local: // Explicitly made local:
py::bind_vector<NonLocalVec2>(m, "NonLocalVec2", py::module_local()); py::bind_vector<NonLocalVec2>(m, "NonLocalVec2", py::module_local());
// Explicitly made global (and so will fail to bind): // Explicitly made global (and so will fail to bind):
m.def("register_nonlocal_map2", [m]() { m.def("register_nonlocal_map2",
py::bind_map<NonLocalMap2>(m, "NonLocalMap2", py::module_local(false)); [m]() { py::bind_map<NonLocalMap2>(m, "NonLocalMap2", py::module_local(false)); });
});
// test_mixed_local_global // test_mixed_local_global
// We try this both with the global type registered first and vice versa (the order shouldn't // We try this both with the global type registered first and vice versa (the order shouldn't
// matter). // matter).
m.def("register_mixed_global_local", [m]() { m.def("register_mixed_global_local",
bind_local<MixedGlobalLocal, 200>(m, "MixedGlobalLocal", py::module_local()); [m]() { bind_local<MixedGlobalLocal, 200>(m, "MixedGlobalLocal", py::module_local()); });
});
m.def("register_mixed_local_global", [m]() { m.def("register_mixed_local_global", [m]() {
bind_local<MixedLocalGlobal, 2000>(m, "MixedLocalGlobal", py::module_local(false)); bind_local<MixedLocalGlobal, 2000>(m, "MixedLocalGlobal", py::module_local(false));
}); });
@ -108,14 +108,14 @@ PYBIND11_MODULE(pybind11_cross_module_tests, m) {
m.def("get_mixed_lg", [](int i) { return MixedLocalGlobal(i); }); m.def("get_mixed_lg", [](int i) { return MixedLocalGlobal(i); });
// test_internal_locals_differ // test_internal_locals_differ
m.def("local_cpp_types_addr", []() { return (uintptr_t) &py::detail::get_local_internals().registered_types_cpp; }); m.def("local_cpp_types_addr",
[]() { return (uintptr_t) &py::detail::get_local_internals().registered_types_cpp; });
// test_stl_caster_vs_stl_bind // test_stl_caster_vs_stl_bind
py::bind_vector<std::vector<int>>(m, "VectorInt"); py::bind_vector<std::vector<int>>(m, "VectorInt");
m.def("load_vector_via_binding", [](std::vector<int> &v) { m.def("load_vector_via_binding",
return std::accumulate(v.begin(), v.end(), 0); [](std::vector<int> &v) { return std::accumulate(v.begin(), v.end(), 0); });
});
// test_cross_module_calls // test_cross_module_calls
m.def("return_self", [](LocalVec *v) { return v; }); m.def("return_self", [](LocalVec *v) { return v; });
@ -125,11 +125,9 @@ PYBIND11_MODULE(pybind11_cross_module_tests, m) {
public: public:
explicit Dog(std::string name) : Pet(std::move(name)) {} explicit Dog(std::string name) : Pet(std::move(name)) {}
}; };
py::class_<pets::Pet>(m, "Pet", py::module_local()) py::class_<pets::Pet>(m, "Pet", py::module_local()).def("name", &pets::Pet::name);
.def("name", &pets::Pet::name);
// Binding for local extending class: // Binding for local extending class:
py::class_<Dog, pets::Pet>(m, "Dog") py::class_<Dog, pets::Pet>(m, "Dog").def(py::init<std::string>());
.def(py::init<std::string>());
m.def("pet_name", [](pets::Pet &p) { return p.name(); }); m.def("pet_name", [](pets::Pet &p) { return p.name(); });
py::class_<MixGL>(m, "MixGL", py::module_local()).def(py::init<int>()); py::class_<MixGL>(m, "MixGL", py::module_local()).def(py::init<int>());

View File

@ -8,6 +8,7 @@
*/ */
#include "pybind11_tests.h" #include "pybind11_tests.h"
#include "constructor_stats.h" #include "constructor_stats.h"
#include <functional> #include <functional>
@ -31,9 +32,7 @@ std::list<std::function<void(py::module_ &)>> &initializers() {
return inits; return inits;
} }
test_initializer::test_initializer(Initializer init) { test_initializer::test_initializer(Initializer init) { initializers().emplace_back(init); }
initializers().emplace_back(init);
}
test_initializer::test_initializer(const char *submodule_name, Initializer init) { test_initializer::test_initializer(const char *submodule_name, Initializer init) {
initializers().emplace_back([=](py::module_ &parent) { initializers().emplace_back([=](py::module_ &parent) {
@ -51,26 +50,52 @@ void bind_ConstructorStats(py::module_ &m) {
.def_readwrite("move_assignments", &ConstructorStats::move_assignments) .def_readwrite("move_assignments", &ConstructorStats::move_assignments)
.def_readwrite("copy_constructions", &ConstructorStats::copy_constructions) .def_readwrite("copy_constructions", &ConstructorStats::copy_constructions)
.def_readwrite("move_constructions", &ConstructorStats::move_constructions) .def_readwrite("move_constructions", &ConstructorStats::move_constructions)
.def_static("get", (ConstructorStats &(*)(py::object)) &ConstructorStats::get, py::return_value_policy::reference_internal) .def_static("get",
(ConstructorStats & (*) (py::object)) & ConstructorStats::get,
py::return_value_policy::reference_internal)
// Not exactly ConstructorStats, but related: expose the internal pybind number of registered instances // Not exactly ConstructorStats, but related: expose the internal pybind number of
// to allow instance cleanup checks (invokes a GC first) // registered instances to allow instance cleanup checks (invokes a GC first)
.def_static("detail_reg_inst", []() { .def_static("detail_reg_inst", []() {
ConstructorStats::gc(); ConstructorStats::gc();
return py::detail::get_internals().registered_instances.size(); return py::detail::get_internals().registered_instances.size();
}) });
; }
const char *cpp_std() {
return
#if defined(PYBIND11_CPP20)
"C++20";
#elif defined(PYBIND11_CPP17)
"C++17";
#elif defined(PYBIND11_CPP14)
"C++14";
#else
"C++11";
#endif
} }
PYBIND11_MODULE(pybind11_tests, m) { PYBIND11_MODULE(pybind11_tests, m) {
m.doc() = "pybind11 test module"; m.doc() = "pybind11 test module";
// Intentionally kept minimal to not create a maintenance chore
// ("just enough" to be conclusive).
#if defined(_MSC_FULL_VER)
m.attr("compiler_info") = "MSVC " PYBIND11_TOSTRING(_MSC_FULL_VER);
#elif defined(__VERSION__)
m.attr("compiler_info") = __VERSION__;
#else
m.attr("compiler_info") = py::none();
#endif
m.attr("cpp_std") = cpp_std();
m.attr("PYBIND11_INTERNALS_ID") = PYBIND11_INTERNALS_ID;
bind_ConstructorStats(m); bind_ConstructorStats(m);
#if !defined(NDEBUG) #if defined(PYBIND11_DETAILED_ERROR_MESSAGES)
m.attr("debug_enabled") = true; m.attr("detailed_error_messages_enabled") = true;
#else #else
m.attr("debug_enabled") = false; m.attr("detailed_error_messages_enabled") = false;
#endif #endif
py::class_<UserType>(m, "UserType", "A `py::class_` type for testing") py::class_<UserType>(m, "UserType", "A `py::class_` type for testing")
@ -79,13 +104,14 @@ PYBIND11_MODULE(pybind11_tests, m) {
.def("get_value", &UserType::value, "Get value using a method") .def("get_value", &UserType::value, "Get value using a method")
.def("set_value", &UserType::set, "Set value using a method") .def("set_value", &UserType::set, "Set value using a method")
.def_property("value", &UserType::value, &UserType::set, "Get/set value using a property") .def_property("value", &UserType::value, &UserType::set, "Get/set value using a property")
.def("__repr__", [](const UserType& u) { return "UserType({})"_s.format(u.value()); }); .def("__repr__", [](const UserType &u) { return "UserType({})"_s.format(u.value()); });
py::class_<IncType, UserType>(m, "IncType") py::class_<IncType, UserType>(m, "IncType")
.def(py::init<>()) .def(py::init<>())
.def(py::init<int>()) .def(py::init<int>())
.def("__repr__", [](const IncType& u) { return "IncType({})"_s.format(u.value()); }); .def("__repr__", [](const IncType &u) { return "IncType({})"_s.format(u.value()); });
for (const auto &initializer : initializers()) for (const auto &initializer : initializers()) {
initializer(m); initializer(m);
}
} }

View File

@ -1,13 +1,7 @@
#pragma once #pragma once
#include <pybind11/pybind11.h>
#include <pybind11/eval.h> #include <pybind11/eval.h>
#include <pybind11/pybind11.h>
#if defined(_MSC_VER) && _MSC_VER < 1910
// We get some really long type names here which causes MSVC 2015 to emit warnings
# pragma warning( \
disable : 4503) // warning C4503: decorated name length exceeded, name was truncated
#endif
namespace py = pybind11; namespace py = pybind11;
using namespace pybind11::literals; using namespace pybind11::literals;
@ -26,13 +20,13 @@ public:
void test_submodule_##name(py::module_ &(variable)) void test_submodule_##name(py::module_ &(variable))
/// Dummy type which is not exported anywhere -- something to trigger a conversion error /// Dummy type which is not exported anywhere -- something to trigger a conversion error
struct UnregisteredType { }; struct UnregisteredType {};
/// A user-defined type which is exported and can be used by any test /// A user-defined type which is exported and can be used by any test
class UserType { class UserType {
public: public:
UserType() = default; UserType() = default;
explicit UserType(int i) : i(i) { } explicit UserType(int i) : i(i) {}
int value() const { return i; } int value() const { return i; }
void set(int set) { i = set; } void set(int set) { i = set; }
@ -46,7 +40,7 @@ class IncType : public UserType {
public: public:
using UserType::UserType; using UserType::UserType;
IncType() = default; IncType() = default;
IncType(const IncType &other) : IncType(other.value() + 1) { } IncType(const IncType &other) : IncType(other.value() + 1) {}
IncType(IncType &&) = delete; IncType(IncType &&) = delete;
IncType &operator=(const IncType &) = delete; IncType &operator=(const IncType &) = delete;
IncType &operator=(IncType &&) = delete; IncType &operator=(IncType &&) = delete;
@ -58,16 +52,21 @@ union IntFloat {
float f; float f;
}; };
/// Custom cast-only type that casts to a string "rvalue" or "lvalue" depending on the cast context. /// Custom cast-only type that casts to a string "rvalue" or "lvalue" depending on the cast
/// Used to test recursive casters (e.g. std::tuple, stl containers). /// context. Used to test recursive casters (e.g. std::tuple, stl containers).
struct RValueCaster {}; struct RValueCaster {};
PYBIND11_NAMESPACE_BEGIN(pybind11) PYBIND11_NAMESPACE_BEGIN(pybind11)
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
template<> class type_caster<RValueCaster> { template <>
class type_caster<RValueCaster> {
public: public:
PYBIND11_TYPE_CASTER(RValueCaster, const_name("RValueCaster")); PYBIND11_TYPE_CASTER(RValueCaster, const_name("RValueCaster"));
static handle cast(RValueCaster &&, return_value_policy, handle) { return py::str("rvalue").release(); } static handle cast(RValueCaster &&, return_value_policy, handle) {
static handle cast(const RValueCaster &, return_value_policy, handle) { return py::str("lvalue").release(); } return py::str("rvalue").release();
}
static handle cast(const RValueCaster &, return_value_policy, handle) {
return py::str("lvalue").release();
}
}; };
PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(pybind11) PYBIND11_NAMESPACE_END(pybind11)
@ -81,5 +80,6 @@ void ignoreOldStyleInitWarnings(F &&body) {
with warnings.catch_warnings(): with warnings.catch_warnings():
warnings.filterwarnings("ignore", message=message, category=FutureWarning) warnings.filterwarnings("ignore", message=message, category=FutureWarning)
body() body()
)", py::dict(py::arg("body") = py::cpp_function(body))); )",
py::dict(py::arg("body") = py::cpp_function(body)));
} }

View File

@ -1,12 +1,15 @@
[pytest] [pytest]
minversion = 3.1 minversion = 3.10
norecursedirs = test_* extra_* norecursedirs = test_* extra_*
xfail_strict = True xfail_strict = True
addopts = addopts =
# show summary of skipped tests # show summary of tests
-rs -ra
# capture only Python print and C++ py::print, but not C output (low-level Python errors) # capture only Python print and C++ py::print, but not C output (low-level Python errors)
--capture=sys --capture=sys
# Show local info when a failure occurs
--showlocals
log_cli_level = info
filterwarnings = filterwarnings =
# make warnings into errors but ignore certain third-party extension issues # make warnings into errors but ignore certain third-party extension issues
error error

View File

@ -1,12 +1,9 @@
numpy==1.16.6; python_version<"3.6" and sys_platform!="win32" and platform_python_implementation!="PyPy" build==0.8.0
numpy==1.19.0; platform_python_implementation=="PyPy" and sys_platform=="linux" and python_version=="3.6" numpy==1.21.5; platform_python_implementation=="PyPy" and sys_platform=="linux" and python_version=="3.7"
numpy==1.20.0; platform_python_implementation=="PyPy" and sys_platform=="linux" and python_version=="3.7"
numpy==1.19.3; platform_python_implementation!="PyPy" and python_version=="3.6" numpy==1.19.3; platform_python_implementation!="PyPy" and python_version=="3.6"
numpy==1.21.3; platform_python_implementation!="PyPy" and python_version>="3.7" and python_version<"3.11" numpy==1.21.5; platform_python_implementation!="PyPy" and python_version>="3.7" and python_version<"3.10"
py @ git+https://github.com/pytest-dev/py; python_version>="3.11" numpy==1.22.2; platform_python_implementation!="PyPy" and python_version>="3.10" and python_version<"3.11"
pytest==4.6.9; python_version<"3.5" pytest==7.0.0
pytest==6.1.2; python_version=="3.5"
pytest==6.2.4; python_version>="3.6"
pytest-timeout pytest-timeout
scipy==1.2.3; platform_python_implementation!="PyPy" and python_version<"3.6" scipy==1.5.4; platform_python_implementation!="PyPy" and python_version<"3.10"
scipy==1.5.4; platform_python_implementation!="PyPy" and python_version>="3.6" and python_version<"3.10" scipy==1.8.0; platform_python_implementation!="PyPy" and python_version=="3.10"

View File

@ -11,12 +11,11 @@
TEST_SUBMODULE(async_module, m) { TEST_SUBMODULE(async_module, m) {
struct DoesNotSupportAsync {}; struct DoesNotSupportAsync {};
py::class_<DoesNotSupportAsync>(m, "DoesNotSupportAsync") py::class_<DoesNotSupportAsync>(m, "DoesNotSupportAsync").def(py::init<>());
.def(py::init<>());
struct SupportsAsync {}; struct SupportsAsync {};
py::class_<SupportsAsync>(m, "SupportsAsync") py::class_<SupportsAsync>(m, "SupportsAsync")
.def(py::init<>()) .def(py::init<>())
.def("__await__", [](const SupportsAsync& self) -> py::object { .def("__await__", [](const SupportsAsync &self) -> py::object {
static_cast<void>(self); static_cast<void>(self);
py::object loop = py::module_::import("asyncio.events").attr("get_event_loop")(); py::object loop = py::module_::import("asyncio.events").attr("get_event_loop")();
py::object f = loop.attr("create_future")(); py::object f = loop.attr("create_future")();

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
import pytest import pytest
asyncio = pytest.importorskip("asyncio") asyncio = pytest.importorskip("asyncio")

View File

@ -7,22 +7,26 @@
BSD-style license that can be found in the LICENSE file. BSD-style license that can be found in the LICENSE file.
*/ */
#include "pybind11_tests.h"
#include "constructor_stats.h"
#include <pybind11/stl.h> #include <pybind11/stl.h>
#include "constructor_stats.h"
#include "pybind11_tests.h"
TEST_SUBMODULE(buffers, m) { TEST_SUBMODULE(buffers, m) {
// test_from_python / test_to_python: // test_from_python / test_to_python:
class Matrix { class Matrix {
public: public:
Matrix(py::ssize_t rows, py::ssize_t cols) : m_rows(rows), m_cols(cols) { Matrix(py::ssize_t rows, py::ssize_t cols) : m_rows(rows), m_cols(cols) {
print_created(this, std::to_string(m_rows) + "x" + std::to_string(m_cols) + " matrix"); print_created(this, std::to_string(m_rows) + "x" + std::to_string(m_cols) + " matrix");
m_data = new float[(size_t) (rows*cols)]; // NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
m_data = new float[(size_t) (rows * cols)];
memset(m_data, 0, sizeof(float) * (size_t) (rows * cols)); memset(m_data, 0, sizeof(float) * (size_t) (rows * cols));
} }
Matrix(const Matrix &s) : m_rows(s.m_rows), m_cols(s.m_cols) { Matrix(const Matrix &s) : m_rows(s.m_rows), m_cols(s.m_cols) {
print_copy_created(this, std::to_string(m_rows) + "x" + std::to_string(m_cols) + " matrix"); print_copy_created(this,
std::to_string(m_rows) + "x" + std::to_string(m_cols) + " matrix");
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
m_data = new float[(size_t) (m_rows * m_cols)]; m_data = new float[(size_t) (m_rows * m_cols)];
memcpy(m_data, s.m_data, sizeof(float) * (size_t) (m_rows * m_cols)); memcpy(m_data, s.m_data, sizeof(float) * (size_t) (m_rows * m_cols));
} }
@ -35,7 +39,8 @@ TEST_SUBMODULE(buffers, m) {
} }
~Matrix() { ~Matrix() {
print_destroyed(this, std::to_string(m_rows) + "x" + std::to_string(m_cols) + " matrix"); print_destroyed(this,
std::to_string(m_rows) + "x" + std::to_string(m_cols) + " matrix");
delete[] m_data; delete[] m_data;
} }
@ -54,27 +59,33 @@ TEST_SUBMODULE(buffers, m) {
} }
Matrix &operator=(Matrix &&s) noexcept { Matrix &operator=(Matrix &&s) noexcept {
print_move_assigned(this, std::to_string(m_rows) + "x" + std::to_string(m_cols) + " matrix"); print_move_assigned(this,
std::to_string(m_rows) + "x" + std::to_string(m_cols) + " matrix");
if (&s != this) { if (&s != this) {
delete[] m_data; delete[] m_data;
m_rows = s.m_rows; m_cols = s.m_cols; m_data = s.m_data; m_rows = s.m_rows;
s.m_rows = 0; s.m_cols = 0; s.m_data = nullptr; m_cols = s.m_cols;
m_data = s.m_data;
s.m_rows = 0;
s.m_cols = 0;
s.m_data = nullptr;
} }
return *this; return *this;
} }
float operator()(py::ssize_t i, py::ssize_t j) const { float operator()(py::ssize_t i, py::ssize_t j) const {
return m_data[(size_t) (i*m_cols + j)]; return m_data[(size_t) (i * m_cols + j)];
} }
float &operator()(py::ssize_t i, py::ssize_t j) { float &operator()(py::ssize_t i, py::ssize_t j) {
return m_data[(size_t) (i*m_cols + j)]; return m_data[(size_t) (i * m_cols + j)];
} }
float *data() { return m_data; } float *data() { return m_data; }
py::ssize_t rows() const { return m_rows; } py::ssize_t rows() const { return m_rows; }
py::ssize_t cols() const { return m_cols; } py::ssize_t cols() const { return m_cols; }
private: private:
py::ssize_t m_rows; py::ssize_t m_rows;
py::ssize_t m_cols; py::ssize_t m_cols;
@ -85,10 +96,11 @@ TEST_SUBMODULE(buffers, m) {
/// Construct from a buffer /// Construct from a buffer
.def(py::init([](const py::buffer &b) { .def(py::init([](const py::buffer &b) {
py::buffer_info info = b.request(); py::buffer_info info = b.request();
if (info.format != py::format_descriptor<float>::format() || info.ndim != 2) if (info.format != py::format_descriptor<float>::format() || info.ndim != 2) {
throw std::runtime_error("Incompatible buffer format!"); throw std::runtime_error("Incompatible buffer format!");
}
auto v = new Matrix(info.shape[0], info.shape[1]); auto *v = new Matrix(info.shape[0], info.shape[1]);
memcpy(v->data(), info.ptr, sizeof(float) * (size_t) (v->rows() * v->cols())); memcpy(v->data(), info.ptr, sizeof(float) * (size_t) (v->rows() * v->cols()));
return v; return v;
})) }))
@ -99,24 +111,25 @@ TEST_SUBMODULE(buffers, m) {
/// Bare bones interface /// Bare bones interface
.def("__getitem__", .def("__getitem__",
[](const Matrix &m, std::pair<py::ssize_t, py::ssize_t> i) { [](const Matrix &m, std::pair<py::ssize_t, py::ssize_t> i) {
if (i.first >= m.rows() || i.second >= m.cols()) if (i.first >= m.rows() || i.second >= m.cols()) {
throw py::index_error(); throw py::index_error();
}
return m(i.first, i.second); return m(i.first, i.second);
}) })
.def("__setitem__", .def("__setitem__",
[](Matrix &m, std::pair<py::ssize_t, py::ssize_t> i, float v) { [](Matrix &m, std::pair<py::ssize_t, py::ssize_t> i, float v) {
if (i.first >= m.rows() || i.second >= m.cols()) if (i.first >= m.rows() || i.second >= m.cols()) {
throw py::index_error(); throw py::index_error();
}
m(i.first, i.second) = v; m(i.first, i.second) = v;
}) })
/// Provide buffer access /// Provide buffer access
.def_buffer([](Matrix &m) -> py::buffer_info { .def_buffer([](Matrix &m) -> py::buffer_info {
return py::buffer_info( return py::buffer_info(
m.data(), /* Pointer to buffer */ m.data(), /* Pointer to buffer */
{ m.rows(), m.cols() }, /* Buffer dimensions */ {m.rows(), m.cols()}, /* Buffer dimensions */
{ sizeof(float) * size_t(m.cols()), /* Strides (in bytes) for each index */ {sizeof(float) * size_t(m.cols()), /* Strides (in bytes) for each index */
sizeof(float) } sizeof(float)});
);
}); });
// test_inherited_protocol // test_inherited_protocol
@ -125,9 +138,7 @@ TEST_SUBMODULE(buffers, m) {
explicit SquareMatrix(py::ssize_t n) : Matrix(n, n) {} explicit SquareMatrix(py::ssize_t n) : Matrix(n, n) {}
}; };
// Derived classes inherit the buffer protocol and the buffer access function // Derived classes inherit the buffer protocol and the buffer access function
py::class_<SquareMatrix, Matrix>(m, "SquareMatrix") py::class_<SquareMatrix, Matrix>(m, "SquareMatrix").def(py::init<py::ssize_t>());
.def(py::init<py::ssize_t>());
// test_pointer_to_member_fn // test_pointer_to_member_fn
// Tests that passing a pointer to member to the base class works in // Tests that passing a pointer to member to the base class works in
@ -136,8 +147,8 @@ TEST_SUBMODULE(buffers, m) {
int32_t value = 0; int32_t value = 0;
py::buffer_info get_buffer_info() { py::buffer_info get_buffer_info() {
return py::buffer_info(&value, sizeof(value), return py::buffer_info(
py::format_descriptor<int32_t>::format(), 1); &value, sizeof(value), py::format_descriptor<int32_t>::format(), 1);
} }
}; };
py::class_<Buffer>(m, "Buffer", py::buffer_protocol()) py::class_<Buffer>(m, "Buffer", py::buffer_protocol())
@ -145,7 +156,6 @@ TEST_SUBMODULE(buffers, m) {
.def_readwrite("value", &Buffer::value) .def_readwrite("value", &Buffer::value)
.def_buffer(&Buffer::get_buffer_info); .def_buffer(&Buffer::get_buffer_info);
class ConstBuffer { class ConstBuffer {
std::unique_ptr<int32_t> value; std::unique_ptr<int32_t> value;
@ -154,8 +164,8 @@ TEST_SUBMODULE(buffers, m) {
void set_value(int32_t v) { *value = v; } void set_value(int32_t v) { *value = v; }
py::buffer_info get_buffer_info() const { py::buffer_info get_buffer_info() const {
return py::buffer_info(value.get(), sizeof(*value), return py::buffer_info(
py::format_descriptor<int32_t>::format(), 1); value.get(), sizeof(*value), py::format_descriptor<int32_t>::format(), 1);
} }
ConstBuffer() : value(new int32_t{0}) {} ConstBuffer() : value(new int32_t{0}) {}
@ -165,7 +175,7 @@ TEST_SUBMODULE(buffers, m) {
.def_property("value", &ConstBuffer::get_value, &ConstBuffer::set_value) .def_property("value", &ConstBuffer::get_value, &ConstBuffer::set_value)
.def_buffer(&ConstBuffer::get_buffer_info); .def_buffer(&ConstBuffer::get_buffer_info);
struct DerivedBuffer : public Buffer { }; struct DerivedBuffer : public Buffer {};
py::class_<DerivedBuffer>(m, "DerivedBuffer", py::buffer_protocol()) py::class_<DerivedBuffer>(m, "DerivedBuffer", py::buffer_protocol())
.def(py::init<>()) .def(py::init<>())
.def_readwrite("value", (int32_t DerivedBuffer::*) &DerivedBuffer::value) .def_readwrite("value", (int32_t DerivedBuffer::*) &DerivedBuffer::value)
@ -175,9 +185,7 @@ TEST_SUBMODULE(buffers, m) {
const uint8_t value = 0; const uint8_t value = 0;
explicit BufferReadOnly(uint8_t value) : value(value) {} explicit BufferReadOnly(uint8_t value) : value(value) {}
py::buffer_info get_buffer_info() { py::buffer_info get_buffer_info() { return py::buffer_info(&value, 1); }
return py::buffer_info(&value, 1);
}
}; };
py::class_<BufferReadOnly>(m, "BufferReadOnly", py::buffer_protocol()) py::class_<BufferReadOnly>(m, "BufferReadOnly", py::buffer_protocol())
.def(py::init<uint8_t>()) .def(py::init<uint8_t>())
@ -187,9 +195,7 @@ TEST_SUBMODULE(buffers, m) {
uint8_t value = 0; uint8_t value = 0;
bool readonly = false; bool readonly = false;
py::buffer_info get_buffer_info() { py::buffer_info get_buffer_info() { return py::buffer_info(&value, 1, readonly); }
return py::buffer_info(&value, 1, readonly);
}
}; };
py::class_<BufferReadOnlySelect>(m, "BufferReadOnlySelect", py::buffer_protocol()) py::class_<BufferReadOnlySelect>(m, "BufferReadOnlySelect", py::buffer_protocol())
.def(py::init<>()) .def(py::init<>())
@ -208,9 +214,11 @@ TEST_SUBMODULE(buffers, m) {
.def_readonly("strides", &py::buffer_info::strides) .def_readonly("strides", &py::buffer_info::strides)
.def_readonly("readonly", &py::buffer_info::readonly) .def_readonly("readonly", &py::buffer_info::readonly)
.def("__repr__", [](py::handle self) { .def("__repr__", [](py::handle self) {
return py::str("itemsize={0.itemsize!r}, size={0.size!r}, format={0.format!r}, ndim={0.ndim!r}, shape={0.shape!r}, strides={0.strides!r}, readonly={0.readonly!r}").format(self); return py::str("itemsize={0.itemsize!r}, size={0.size!r}, format={0.format!r}, "
}) "ndim={0.ndim!r}, shape={0.shape!r}, strides={0.strides!r}, "
; "readonly={0.readonly!r}")
.format(self);
});
m.def("get_buffer_info", [](const py::buffer &buffer) { return buffer.request(); }); m.def("get_buffer_info", [](const py::buffer &buffer) { return buffer.request(); });
} }

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
import ctypes import ctypes
import io import io
import struct import struct
@ -93,16 +92,16 @@ def test_pointer_to_member_fn():
def test_readonly_buffer(): def test_readonly_buffer():
buf = m.BufferReadOnly(0x64) buf = m.BufferReadOnly(0x64)
view = memoryview(buf) view = memoryview(buf)
assert view[0] == b"d" if env.PY2 else 0x64 assert view[0] == 0x64
assert view.readonly assert view.readonly
with pytest.raises(TypeError): with pytest.raises(TypeError):
view[0] = b"\0" if env.PY2 else 0 view[0] = 0
def test_selective_readonly_buffer(): def test_selective_readonly_buffer():
buf = m.BufferReadOnlySelect() buf = m.BufferReadOnlySelect()
memoryview(buf)[0] = b"d" if env.PY2 else 0x64 memoryview(buf)[0] = 0x64
assert buf.value == 0x64 assert buf.value == 0x64
io.BytesIO(b"A").readinto(buf) io.BytesIO(b"A").readinto(buf)
@ -110,7 +109,7 @@ def test_selective_readonly_buffer():
buf.readonly = True buf.readonly = True
with pytest.raises(TypeError): with pytest.raises(TypeError):
memoryview(buf)[0] = b"\0" if env.PY2 else 0 memoryview(buf)[0] = 0
with pytest.raises(TypeError): with pytest.raises(TypeError):
io.BytesIO(b"1").readinto(buf) io.BytesIO(b"1").readinto(buf)
@ -145,9 +144,6 @@ def test_ctypes_array_2d():
assert not info.readonly assert not info.readonly
@pytest.mark.skipif(
"env.PYPY and env.PY2", reason="PyPy2 bytes buffer not reported as readonly"
)
def test_ctypes_from_buffer(): def test_ctypes_from_buffer():
test_pystr = b"0123456789" test_pystr = b"0123456789"
for pyarray in (test_pystr, bytearray(test_pystr)): for pyarray in (test_pystr, bytearray(test_pystr)):

View File

@ -7,64 +7,67 @@
BSD-style license that can be found in the LICENSE file. BSD-style license that can be found in the LICENSE file.
*/ */
#include "pybind11_tests.h"
#include <pybind11/complex.h> #include <pybind11/complex.h>
#include "pybind11_tests.h"
struct ConstRefCasted { struct ConstRefCasted {
int tag; int tag;
}; };
PYBIND11_NAMESPACE_BEGIN(pybind11) PYBIND11_NAMESPACE_BEGIN(pybind11)
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
template <> template <>
class type_caster<ConstRefCasted> { class type_caster<ConstRefCasted> {
public: public:
static constexpr auto name = const_name<ConstRefCasted>(); static constexpr auto name = const_name<ConstRefCasted>();
// Input is unimportant, a new value will always be constructed based on the // Input is unimportant, a new value will always be constructed based on the
// cast operator. // cast operator.
bool load(handle, bool) { return true; } bool load(handle, bool) { return true; }
explicit operator ConstRefCasted &&() { explicit operator ConstRefCasted &&() {
value = {1}; value = {1};
// NOLINTNEXTLINE(performance-move-const-arg) // NOLINTNEXTLINE(performance-move-const-arg)
return std::move(value); return std::move(value);
} }
explicit operator ConstRefCasted &() { explicit operator ConstRefCasted &() {
value = {2}; value = {2};
return value; return value;
} }
explicit operator ConstRefCasted *() { explicit operator ConstRefCasted *() {
value = {3}; value = {3};
return &value; return &value;
} }
explicit operator const ConstRefCasted &() { explicit operator const ConstRefCasted &() {
value = {4}; value = {4};
return value; return value;
} }
explicit operator const ConstRefCasted *() { explicit operator const ConstRefCasted *() {
value = {5}; value = {5};
return &value; return &value;
} }
// custom cast_op to explicitly propagate types to the conversion operators. // custom cast_op to explicitly propagate types to the conversion operators.
template <typename T_> template <typename T_>
using cast_op_type = using cast_op_type =
/// const /// const
conditional_t< conditional_t<
std::is_same<remove_reference_t<T_>, const ConstRefCasted*>::value, const ConstRefCasted*, std::is_same<remove_reference_t<T_>, const ConstRefCasted *>::value,
conditional_t< const ConstRefCasted *,
std::is_same<T_, const ConstRefCasted&>::value, const ConstRefCasted&, conditional_t<
/// non-const std::is_same<T_, const ConstRefCasted &>::value,
conditional_t< const ConstRefCasted &,
std::is_same<remove_reference_t<T_>, ConstRefCasted*>::value, ConstRefCasted*, /// non-const
conditional_t< conditional_t<std::is_same<remove_reference_t<T_>, ConstRefCasted *>::value,
std::is_same<T_, ConstRefCasted&>::value, ConstRefCasted&, ConstRefCasted *,
/* else */ConstRefCasted&&>>>>; conditional_t<std::is_same<T_, ConstRefCasted &>::value,
ConstRefCasted &,
/* else */ ConstRefCasted &&>>>>;
private: private:
ConstRefCasted value = {0}; ConstRefCasted value = {0};
}; };
PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(pybind11) PYBIND11_NAMESPACE_END(pybind11)
@ -74,28 +77,47 @@ TEST_SUBMODULE(builtin_casters, m) {
m.def("string_roundtrip", [](const char *s) { return s; }); m.def("string_roundtrip", [](const char *s) { return s; });
// test_unicode_conversion // test_unicode_conversion
// Some test characters in utf16 and utf32 encodings. The last one (the 𝐀) contains a null byte // Some test characters in utf16 and utf32 encodings. The last one (the 𝐀) contains a null
char32_t a32 = 0x61 /*a*/, z32 = 0x7a /*z*/, ib32 = 0x203d /*‽*/, cake32 = 0x1f382 /*🎂*/, mathbfA32 = 0x1d400 /*𝐀*/; // byte
char16_t b16 = 0x62 /*b*/, z16 = 0x7a, ib16 = 0x203d, cake16_1 = 0xd83c, cake16_2 = 0xdf82, mathbfA16_1 = 0xd835, mathbfA16_2 = 0xdc00; char32_t a32 = 0x61 /*a*/, z32 = 0x7a /*z*/, ib32 = 0x203d /*‽*/, cake32 = 0x1f382 /*🎂*/,
mathbfA32 = 0x1d400 /*𝐀*/;
char16_t b16 = 0x62 /*b*/, z16 = 0x7a, ib16 = 0x203d, cake16_1 = 0xd83c, cake16_2 = 0xdf82,
mathbfA16_1 = 0xd835, mathbfA16_2 = 0xdc00;
std::wstring wstr; std::wstring wstr;
wstr.push_back(0x61); // a wstr.push_back(0x61); // a
wstr.push_back(0x2e18); // ⸘ wstr.push_back(0x2e18); // ⸘
if (PYBIND11_SILENCE_MSVC_C4127(sizeof(wchar_t) == 2)) { wstr.push_back(mathbfA16_1); wstr.push_back(mathbfA16_2); } // 𝐀, utf16 if (PYBIND11_SILENCE_MSVC_C4127(sizeof(wchar_t) == 2)) {
else { wstr.push_back((wchar_t) mathbfA32); } // 𝐀, utf32 wstr.push_back(mathbfA16_1);
wstr.push_back(mathbfA16_2);
} // 𝐀, utf16
else {
wstr.push_back((wchar_t) mathbfA32);
} // 𝐀, utf32
wstr.push_back(0x7a); // z wstr.push_back(0x7a); // z
m.def("good_utf8_string", []() { return std::string((const char*)u8"Say utf8\u203d \U0001f382 \U0001d400"); }); // Say utf8‽ 🎂 𝐀 m.def("good_utf8_string", []() {
m.def("good_utf16_string", [=]() { return std::u16string({ b16, ib16, cake16_1, cake16_2, mathbfA16_1, mathbfA16_2, z16 }); }); // b‽🎂𝐀z return std::string((const char *) u8"Say utf8\u203d \U0001f382 \U0001d400");
m.def("good_utf32_string", [=]() { return std::u32string({ a32, mathbfA32, cake32, ib32, z32 }); }); // a𝐀🎂‽z }); // Say utf8‽ 🎂 𝐀
m.def("good_utf16_string", [=]() {
return std::u16string({b16, ib16, cake16_1, cake16_2, mathbfA16_1, mathbfA16_2, z16});
}); // b‽🎂𝐀z
m.def("good_utf32_string", [=]() {
return std::u32string({a32, mathbfA32, cake32, ib32, z32});
}); // a𝐀🎂‽z
m.def("good_wchar_string", [=]() { return wstr; }); // a‽𝐀z m.def("good_wchar_string", [=]() { return wstr; }); // a‽𝐀z
m.def("bad_utf8_string", []() { return std::string("abc\xd0" "def"); }); m.def("bad_utf8_string", []() {
m.def("bad_utf16_string", [=]() { return std::u16string({ b16, char16_t(0xd800), z16 }); }); return std::string("abc\xd0"
#if PY_MAJOR_VERSION >= 3 "def");
// Under Python 2.7, invalid unicode UTF-32 characters don't appear to trigger UnicodeDecodeError });
m.def("bad_utf32_string", [=]() { return std::u32string({ a32, char32_t(0xd800), z32 }); }); m.def("bad_utf16_string", [=]() { return std::u16string({b16, char16_t(0xd800), z16}); });
if (PYBIND11_SILENCE_MSVC_C4127(sizeof(wchar_t) == 2)) // Under Python 2.7, invalid unicode UTF-32 characters didn't appear to trigger
m.def("bad_wchar_string", [=]() { return std::wstring({ wchar_t(0x61), wchar_t(0xd800) }); }); // UnicodeDecodeError
#endif m.def("bad_utf32_string", [=]() { return std::u32string({a32, char32_t(0xd800), z32}); });
if (PYBIND11_SILENCE_MSVC_C4127(sizeof(wchar_t) == 2)) {
m.def("bad_wchar_string", [=]() {
return std::wstring({wchar_t(0x61), wchar_t(0xd800)});
});
}
m.def("u8_Z", []() -> char { return 'Z'; }); m.def("u8_Z", []() -> char { return 'Z'; });
m.def("u8_eacute", []() -> char { return '\xe9'; }); m.def("u8_eacute", []() -> char { return '\xe9'; });
m.def("u16_ibang", [=]() -> char16_t { return ib16; }); m.def("u16_ibang", [=]() -> char16_t { return ib16; });
@ -117,8 +139,13 @@ TEST_SUBMODULE(builtin_casters, m) {
#ifdef PYBIND11_HAS_U8STRING #ifdef PYBIND11_HAS_U8STRING
m.attr("has_u8string") = true; m.attr("has_u8string") = true;
m.def("good_utf8_u8string", []() { return std::u8string(u8"Say utf8\u203d \U0001f382 \U0001d400"); }); // Say utf8‽ 🎂 𝐀 m.def("good_utf8_u8string", []() {
m.def("bad_utf8_u8string", []() { return std::u8string((const char8_t*)"abc\xd0" "def"); }); return std::u8string(u8"Say utf8\u203d \U0001f382 \U0001d400");
}); // Say utf8‽ 🎂 𝐀
m.def("bad_utf8_u8string", []() {
return std::u8string((const char8_t *) "abc\xd0"
"def");
});
m.def("u8_char8_Z", []() -> char8_t { return u8'Z'; }); m.def("u8_char8_Z", []() -> char8_t { return u8'Z'; });
@ -130,34 +157,61 @@ TEST_SUBMODULE(builtin_casters, m) {
// test_string_view // test_string_view
#ifdef PYBIND11_HAS_STRING_VIEW #ifdef PYBIND11_HAS_STRING_VIEW
m.attr("has_string_view") = true; m.attr("has_string_view") = true;
m.def("string_view_print", [](std::string_view s) { py::print(s, s.size()); }); m.def("string_view_print", [](std::string_view s) { py::print(s, s.size()); });
m.def("string_view16_print", [](std::u16string_view s) { py::print(s, s.size()); }); m.def("string_view16_print", [](std::u16string_view s) { py::print(s, s.size()); });
m.def("string_view32_print", [](std::u32string_view s) { py::print(s, s.size()); }); m.def("string_view32_print", [](std::u32string_view s) { py::print(s, s.size()); });
m.def("string_view_chars", [](std::string_view s) { py::list l; for (auto c : s) l.append((std::uint8_t) c); return l; }); m.def("string_view_chars", [](std::string_view s) {
m.def("string_view16_chars", [](std::u16string_view s) { py::list l; for (auto c : s) l.append((int) c); return l; }); py::list l;
m.def("string_view32_chars", [](std::u32string_view s) { py::list l; for (auto c : s) l.append((int) c); return l; }); for (auto c : s) {
m.def("string_view_return", []() { return std::string_view((const char*)u8"utf8 secret \U0001f382"); }); l.append((std::uint8_t) c);
m.def("string_view16_return", []() { return std::u16string_view(u"utf16 secret \U0001f382"); }); }
m.def("string_view32_return", []() { return std::u32string_view(U"utf32 secret \U0001f382"); }); return l;
});
m.def("string_view16_chars", [](std::u16string_view s) {
py::list l;
for (auto c : s) {
l.append((int) c);
}
return l;
});
m.def("string_view32_chars", [](std::u32string_view s) {
py::list l;
for (auto c : s) {
l.append((int) c);
}
return l;
});
m.def("string_view_return",
[]() { return std::string_view((const char *) u8"utf8 secret \U0001f382"); });
m.def("string_view16_return",
[]() { return std::u16string_view(u"utf16 secret \U0001f382"); });
m.def("string_view32_return",
[]() { return std::u32string_view(U"utf32 secret \U0001f382"); });
// The inner lambdas here are to also test implicit conversion // The inner lambdas here are to also test implicit conversion
using namespace std::literals; using namespace std::literals;
m.def("string_view_bytes", []() { return [](py::bytes b) { return b; }("abc \x80\x80 def"sv); }); m.def("string_view_bytes",
m.def("string_view_str", []() { return [](py::str s) { return s; }("abc \342\200\275 def"sv); }); []() { return [](py::bytes b) { return b; }("abc \x80\x80 def"sv); });
m.def("string_view_from_bytes", [](const py::bytes &b) { return [](std::string_view s) { return s; }(b); }); m.def("string_view_str",
#if PY_MAJOR_VERSION >= 3 []() { return [](py::str s) { return s; }("abc \342\200\275 def"sv); });
m.def("string_view_from_bytes",
[](const py::bytes &b) { return [](std::string_view s) { return s; }(b); });
m.def("string_view_memoryview", []() { m.def("string_view_memoryview", []() {
static constexpr auto val = "Have some \360\237\216\202"sv; static constexpr auto val = "Have some \360\237\216\202"sv;
return py::memoryview::from_memory(val); return py::memoryview::from_memory(val);
}); });
#endif
# ifdef PYBIND11_HAS_U8STRING # ifdef PYBIND11_HAS_U8STRING
m.def("string_view8_print", [](std::u8string_view s) { py::print(s, s.size()); }); m.def("string_view8_print", [](std::u8string_view s) { py::print(s, s.size()); });
m.def("string_view8_chars", [](std::u8string_view s) { py::list l; for (auto c : s) l.append((std::uint8_t) c); return l; }); m.def("string_view8_chars", [](std::u8string_view s) {
py::list l;
for (auto c : s)
l.append((std::uint8_t) c);
return l;
});
m.def("string_view8_return", []() { return std::u8string_view(u8"utf8 secret \U0001f382"); }); m.def("string_view8_return", []() { return std::u8string_view(u8"utf8 secret \U0001f382"); });
m.def("string_view8_str", []() { return py::str{std::u8string_view{u8"abc ‽ def"}}; }); m.def("string_view8_str", []() { return py::str{std::u8string_view{u8"abc ‽ def"}}; });
# endif # endif
struct TypeWithBothOperatorStringAndStringView { struct TypeWithBothOperatorStringAndStringView {
// NOLINTNEXTLINE(google-explicit-constructor) // NOLINTNEXTLINE(google-explicit-constructor)
@ -179,7 +233,8 @@ TEST_SUBMODULE(builtin_casters, m) {
// test_int_convert // test_int_convert
m.def("int_passthrough", [](int arg) { return arg; }); m.def("int_passthrough", [](int arg) { return arg; });
m.def("int_passthrough_noconvert", [](int arg) { return arg; }, py::arg{}.noconvert()); m.def(
"int_passthrough_noconvert", [](int arg) { return arg; }, py::arg{}.noconvert());
// test_tuple // test_tuple
m.def( m.def(
@ -188,31 +243,40 @@ TEST_SUBMODULE(builtin_casters, m) {
return std::make_pair(input.second, input.first); return std::make_pair(input.second, input.first);
}, },
"Return a pair in reversed order"); "Return a pair in reversed order");
m.def("tuple_passthrough", [](std::tuple<bool, std::string, int> input) { m.def(
return std::make_tuple(std::get<2>(input), std::get<1>(input), std::get<0>(input)); "tuple_passthrough",
}, "Return a triple in reversed order"); [](std::tuple<bool, std::string, int> input) {
return std::make_tuple(std::get<2>(input), std::get<1>(input), std::get<0>(input));
},
"Return a triple in reversed order");
m.def("empty_tuple", []() { return std::tuple<>(); }); m.def("empty_tuple", []() { return std::tuple<>(); });
static std::pair<RValueCaster, RValueCaster> lvpair; static std::pair<RValueCaster, RValueCaster> lvpair;
static std::tuple<RValueCaster, RValueCaster, RValueCaster> lvtuple; static std::tuple<RValueCaster, RValueCaster, RValueCaster> lvtuple;
static std::pair<RValueCaster, std::tuple<RValueCaster, std::pair<RValueCaster, RValueCaster>>> lvnested; static std::pair<RValueCaster, std::tuple<RValueCaster, std::pair<RValueCaster, RValueCaster>>>
lvnested;
m.def("rvalue_pair", []() { return std::make_pair(RValueCaster{}, RValueCaster{}); }); m.def("rvalue_pair", []() { return std::make_pair(RValueCaster{}, RValueCaster{}); });
m.def("lvalue_pair", []() -> const decltype(lvpair) & { return lvpair; }); m.def("lvalue_pair", []() -> const decltype(lvpair) & { return lvpair; });
m.def("rvalue_tuple", []() { return std::make_tuple(RValueCaster{}, RValueCaster{}, RValueCaster{}); }); m.def("rvalue_tuple",
[]() { return std::make_tuple(RValueCaster{}, RValueCaster{}, RValueCaster{}); });
m.def("lvalue_tuple", []() -> const decltype(lvtuple) & { return lvtuple; }); m.def("lvalue_tuple", []() -> const decltype(lvtuple) & { return lvtuple; });
m.def("rvalue_nested", []() { m.def("rvalue_nested", []() {
return std::make_pair(RValueCaster{}, std::make_tuple(RValueCaster{}, std::make_pair(RValueCaster{}, RValueCaster{}))); }); return std::make_pair(
RValueCaster{},
std::make_tuple(RValueCaster{}, std::make_pair(RValueCaster{}, RValueCaster{})));
});
m.def("lvalue_nested", []() -> const decltype(lvnested) & { return lvnested; }); m.def("lvalue_nested", []() -> const decltype(lvnested) & { return lvnested; });
static std::pair<int, std::string> int_string_pair{2, "items"}; static std::pair<int, std::string> int_string_pair{2, "items"};
m.def("int_string_pair", []() { return &int_string_pair; }); m.def(
"int_string_pair", []() { return &int_string_pair; }, py::return_value_policy::reference);
// test_builtins_cast_return_none // test_builtins_cast_return_none
m.def("return_none_string", []() -> std::string * { return nullptr; }); m.def("return_none_string", []() -> std::string * { return nullptr; });
m.def("return_none_char", []() -> const char * { return nullptr; }); m.def("return_none_char", []() -> const char * { return nullptr; });
m.def("return_none_bool", []() -> bool * { return nullptr; }); m.def("return_none_bool", []() -> bool * { return nullptr; });
m.def("return_none_int", []() -> int * { return nullptr; }); m.def("return_none_int", []() -> int * { return nullptr; });
m.def("return_none_float", []() -> float * { return nullptr; }); m.def("return_none_float", []() -> float * { return nullptr; });
m.def("return_none_pair", []() -> std::pair<int,int> * { return nullptr; }); m.def("return_none_pair", []() -> std::pair<int, int> * { return nullptr; });
// test_none_deferred // test_none_deferred
m.def("defer_none_cstring", [](char *) { return false; }); m.def("defer_none_cstring", [](char *) { return false; });
@ -230,7 +294,8 @@ TEST_SUBMODULE(builtin_casters, m) {
// test_bool_caster // test_bool_caster
m.def("bool_passthrough", [](bool arg) { return arg; }); m.def("bool_passthrough", [](bool arg) { return arg; });
m.def("bool_passthrough_noconvert", [](bool arg) { return arg; }, py::arg{}.noconvert()); m.def(
"bool_passthrough_noconvert", [](bool arg) { return arg; }, py::arg{}.noconvert());
// TODO: This should be disabled and fixed in future Intel compilers // TODO: This should be disabled and fixed in future Intel compilers
#if !defined(__INTEL_COMPILER) #if !defined(__INTEL_COMPILER)
@ -238,13 +303,15 @@ TEST_SUBMODULE(builtin_casters, m) {
// When compiled with the Intel compiler, this results in segmentation faults when importing // When compiled with the Intel compiler, this results in segmentation faults when importing
// the module. Tested with icc (ICC) 2021.1 Beta 20200827, this should be tested again when // the module. Tested with icc (ICC) 2021.1 Beta 20200827, this should be tested again when
// a newer version of icc is available. // a newer version of icc is available.
m.def("bool_passthrough_noconvert2", [](bool arg) { return arg; }, py::arg().noconvert()); m.def(
"bool_passthrough_noconvert2", [](bool arg) { return arg; }, py::arg().noconvert());
#endif #endif
// test_reference_wrapper // test_reference_wrapper
m.def("refwrap_builtin", [](std::reference_wrapper<int> p) { return 10 * p.get(); }); m.def("refwrap_builtin", [](std::reference_wrapper<int> p) { return 10 * p.get(); });
m.def("refwrap_usertype", [](std::reference_wrapper<UserType> p) { return p.get().value(); }); m.def("refwrap_usertype", [](std::reference_wrapper<UserType> p) { return p.get().value(); });
m.def("refwrap_usertype_const", [](std::reference_wrapper<const UserType> p) { return p.get().value(); }); m.def("refwrap_usertype_const",
[](std::reference_wrapper<const UserType> p) { return p.get().value(); });
m.def("refwrap_lvalue", []() -> std::reference_wrapper<UserType> { m.def("refwrap_lvalue", []() -> std::reference_wrapper<UserType> {
static UserType x(1); static UserType x(1);
@ -257,17 +324,20 @@ TEST_SUBMODULE(builtin_casters, m) {
// Not currently supported (std::pair caster has return-by-value cast operator); // Not currently supported (std::pair caster has return-by-value cast operator);
// triggers static_assert failure. // triggers static_assert failure.
//m.def("refwrap_pair", [](std::reference_wrapper<std::pair<int, int>>) { }); // m.def("refwrap_pair", [](std::reference_wrapper<std::pair<int, int>>) { });
m.def("refwrap_list", [](bool copy) { m.def(
static IncType x1(1), x2(2); "refwrap_list",
py::list l; [](bool copy) {
for (auto &f : {std::ref(x1), std::ref(x2)}) { static IncType x1(1), x2(2);
l.append(py::cast(f, copy ? py::return_value_policy::copy py::list l;
: py::return_value_policy::reference)); for (const auto &f : {std::ref(x1), std::ref(x2)}) {
} l.append(py::cast(
return l; f, copy ? py::return_value_policy::copy : py::return_value_policy::reference));
}, "copy"_a); }
return l;
},
"copy"_a);
m.def("refwrap_iiw", [](const IncType &w) { return w.value(); }); m.def("refwrap_iiw", [](const IncType &w) { return w.value(); });
m.def("refwrap_call_iiw", [](IncType &w, const py::function &f) { m.def("refwrap_call_iiw", [](IncType &w, const py::function &f) {
@ -284,12 +354,13 @@ TEST_SUBMODULE(builtin_casters, m) {
// test_complex // test_complex
m.def("complex_cast", [](float x) { return "{}"_s.format(x); }); m.def("complex_cast", [](float x) { return "{}"_s.format(x); });
m.def("complex_cast", [](std::complex<float> x) { return "({}, {})"_s.format(x.real(), x.imag()); }); m.def("complex_cast",
[](std::complex<float> x) { return "({}, {})"_s.format(x.real(), x.imag()); });
// test int vs. long (Python 2) // test int vs. long (Python 2)
m.def("int_cast", []() {return (int) 42;}); m.def("int_cast", []() { return (int) 42; });
m.def("long_cast", []() {return (long) 42;}); m.def("long_cast", []() { return (long) 42; });
m.def("longlong_cast", []() {return ULLONG_MAX;}); m.def("longlong_cast", []() { return ULLONG_MAX; });
/// test void* cast operator /// test void* cast operator
m.def("test_void_caster", []() -> bool { m.def("test_void_caster", []() -> bool {
@ -300,11 +371,12 @@ TEST_SUBMODULE(builtin_casters, m) {
// Tests const/non-const propagation in cast_op. // Tests const/non-const propagation in cast_op.
m.def("takes", [](ConstRefCasted x) { return x.tag; }); m.def("takes", [](ConstRefCasted x) { return x.tag; });
m.def("takes_move", [](ConstRefCasted&& x) { return x.tag; }); m.def("takes_move", [](ConstRefCasted &&x) { return x.tag; });
m.def("takes_ptr", [](ConstRefCasted* x) { return x->tag; }); m.def("takes_ptr", [](ConstRefCasted *x) { return x->tag; });
m.def("takes_ref", [](ConstRefCasted& x) { return x.tag; }); m.def("takes_ref", [](ConstRefCasted &x) { return x.tag; });
m.def("takes_ref_wrap", [](std::reference_wrapper<ConstRefCasted> x) { return x.get().tag; }); m.def("takes_ref_wrap", [](std::reference_wrapper<ConstRefCasted> x) { return x.get().tag; });
m.def("takes_const_ptr", [](const ConstRefCasted* x) { return x->tag; }); m.def("takes_const_ptr", [](const ConstRefCasted *x) { return x->tag; });
m.def("takes_const_ref", [](const ConstRefCasted& x) { return x.tag; }); m.def("takes_const_ref", [](const ConstRefCasted &x) { return x.tag; });
m.def("takes_const_ref_wrap", [](std::reference_wrapper<const ConstRefCasted> x) { return x.get().tag; }); m.def("takes_const_ref_wrap",
[](std::reference_wrapper<const ConstRefCasted> x) { return x.get().tag; });
} }

View File

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- import sys
import pytest import pytest
import env import env
@ -12,12 +13,12 @@ def test_simple_string():
def test_unicode_conversion(): def test_unicode_conversion():
"""Tests unicode conversion and error reporting.""" """Tests unicode conversion and error reporting."""
assert m.good_utf8_string() == u"Say utf8‽ 🎂 𝐀" assert m.good_utf8_string() == "Say utf8‽ 🎂 𝐀"
assert m.good_utf16_string() == u"b‽🎂𝐀z" assert m.good_utf16_string() == "b‽🎂𝐀z"
assert m.good_utf32_string() == u"a𝐀🎂‽z" assert m.good_utf32_string() == "a𝐀🎂‽z"
assert m.good_wchar_string() == u"a⸘𝐀z" assert m.good_wchar_string() == "a⸘𝐀z"
if hasattr(m, "has_u8string"): if hasattr(m, "has_u8string"):
assert m.good_utf8_u8string() == u"Say utf8‽ 🎂 𝐀" assert m.good_utf8_u8string() == "Say utf8‽ 🎂 𝐀"
with pytest.raises(UnicodeDecodeError): with pytest.raises(UnicodeDecodeError):
m.bad_utf8_string() m.bad_utf8_string()
@ -25,7 +26,7 @@ def test_unicode_conversion():
with pytest.raises(UnicodeDecodeError): with pytest.raises(UnicodeDecodeError):
m.bad_utf16_string() m.bad_utf16_string()
# These are provided only if they actually fail (they don't when 32-bit and under Python 2.7) # These are provided only if they actually fail (they don't when 32-bit)
if hasattr(m, "bad_utf32_string"): if hasattr(m, "bad_utf32_string"):
with pytest.raises(UnicodeDecodeError): with pytest.raises(UnicodeDecodeError):
m.bad_utf32_string() m.bad_utf32_string()
@ -37,10 +38,10 @@ def test_unicode_conversion():
m.bad_utf8_u8string() m.bad_utf8_u8string()
assert m.u8_Z() == "Z" assert m.u8_Z() == "Z"
assert m.u8_eacute() == u"é" assert m.u8_eacute() == "é"
assert m.u16_ibang() == u"" assert m.u16_ibang() == ""
assert m.u32_mathbfA() == u"𝐀" assert m.u32_mathbfA() == "𝐀"
assert m.wchar_heart() == u"" assert m.wchar_heart() == ""
if hasattr(m, "has_u8string"): if hasattr(m, "has_u8string"):
assert m.u8_char8_Z() == "Z" assert m.u8_char8_Z() == "Z"
@ -49,72 +50,72 @@ def test_single_char_arguments():
"""Tests failures for passing invalid inputs to char-accepting functions""" """Tests failures for passing invalid inputs to char-accepting functions"""
def toobig_message(r): def toobig_message(r):
return "Character code point not in range({:#x})".format(r) return f"Character code point not in range({r:#x})"
toolong_message = "Expected a character, but multi-character string found" toolong_message = "Expected a character, but multi-character string found"
assert m.ord_char(u"a") == 0x61 # simple ASCII assert m.ord_char("a") == 0x61 # simple ASCII
assert m.ord_char_lv(u"b") == 0x62 assert m.ord_char_lv("b") == 0x62
assert ( assert (
m.ord_char(u"é") == 0xE9 m.ord_char("é") == 0xE9
) # requires 2 bytes in utf-8, but can be stuffed in a char ) # requires 2 bytes in utf-8, but can be stuffed in a char
with pytest.raises(ValueError) as excinfo: with pytest.raises(ValueError) as excinfo:
assert m.ord_char(u"Ā") == 0x100 # requires 2 bytes, doesn't fit in a char assert m.ord_char("Ā") == 0x100 # requires 2 bytes, doesn't fit in a char
assert str(excinfo.value) == toobig_message(0x100) assert str(excinfo.value) == toobig_message(0x100)
with pytest.raises(ValueError) as excinfo: with pytest.raises(ValueError) as excinfo:
assert m.ord_char(u"ab") assert m.ord_char("ab")
assert str(excinfo.value) == toolong_message assert str(excinfo.value) == toolong_message
assert m.ord_char16(u"a") == 0x61 assert m.ord_char16("a") == 0x61
assert m.ord_char16(u"é") == 0xE9 assert m.ord_char16("é") == 0xE9
assert m.ord_char16_lv(u"ê") == 0xEA assert m.ord_char16_lv("ê") == 0xEA
assert m.ord_char16(u"Ā") == 0x100 assert m.ord_char16("Ā") == 0x100
assert m.ord_char16(u"") == 0x203D assert m.ord_char16("") == 0x203D
assert m.ord_char16(u"") == 0x2665 assert m.ord_char16("") == 0x2665
assert m.ord_char16_lv(u"") == 0x2661 assert m.ord_char16_lv("") == 0x2661
with pytest.raises(ValueError) as excinfo: with pytest.raises(ValueError) as excinfo:
assert m.ord_char16(u"🎂") == 0x1F382 # requires surrogate pair assert m.ord_char16("🎂") == 0x1F382 # requires surrogate pair
assert str(excinfo.value) == toobig_message(0x10000) assert str(excinfo.value) == toobig_message(0x10000)
with pytest.raises(ValueError) as excinfo: with pytest.raises(ValueError) as excinfo:
assert m.ord_char16(u"aa") assert m.ord_char16("aa")
assert str(excinfo.value) == toolong_message assert str(excinfo.value) == toolong_message
assert m.ord_char32(u"a") == 0x61 assert m.ord_char32("a") == 0x61
assert m.ord_char32(u"é") == 0xE9 assert m.ord_char32("é") == 0xE9
assert m.ord_char32(u"Ā") == 0x100 assert m.ord_char32("Ā") == 0x100
assert m.ord_char32(u"") == 0x203D assert m.ord_char32("") == 0x203D
assert m.ord_char32(u"") == 0x2665 assert m.ord_char32("") == 0x2665
assert m.ord_char32(u"🎂") == 0x1F382 assert m.ord_char32("🎂") == 0x1F382
with pytest.raises(ValueError) as excinfo: with pytest.raises(ValueError) as excinfo:
assert m.ord_char32(u"aa") assert m.ord_char32("aa")
assert str(excinfo.value) == toolong_message assert str(excinfo.value) == toolong_message
assert m.ord_wchar(u"a") == 0x61 assert m.ord_wchar("a") == 0x61
assert m.ord_wchar(u"é") == 0xE9 assert m.ord_wchar("é") == 0xE9
assert m.ord_wchar(u"Ā") == 0x100 assert m.ord_wchar("Ā") == 0x100
assert m.ord_wchar(u"") == 0x203D assert m.ord_wchar("") == 0x203D
assert m.ord_wchar(u"") == 0x2665 assert m.ord_wchar("") == 0x2665
if m.wchar_size == 2: if m.wchar_size == 2:
with pytest.raises(ValueError) as excinfo: with pytest.raises(ValueError) as excinfo:
assert m.ord_wchar(u"🎂") == 0x1F382 # requires surrogate pair assert m.ord_wchar("🎂") == 0x1F382 # requires surrogate pair
assert str(excinfo.value) == toobig_message(0x10000) assert str(excinfo.value) == toobig_message(0x10000)
else: else:
assert m.ord_wchar(u"🎂") == 0x1F382 assert m.ord_wchar("🎂") == 0x1F382
with pytest.raises(ValueError) as excinfo: with pytest.raises(ValueError) as excinfo:
assert m.ord_wchar(u"aa") assert m.ord_wchar("aa")
assert str(excinfo.value) == toolong_message assert str(excinfo.value) == toolong_message
if hasattr(m, "has_u8string"): if hasattr(m, "has_u8string"):
assert m.ord_char8(u"a") == 0x61 # simple ASCII assert m.ord_char8("a") == 0x61 # simple ASCII
assert m.ord_char8_lv(u"b") == 0x62 assert m.ord_char8_lv("b") == 0x62
assert ( assert (
m.ord_char8(u"é") == 0xE9 m.ord_char8("é") == 0xE9
) # requires 2 bytes in utf-8, but can be stuffed in a char ) # requires 2 bytes in utf-8, but can be stuffed in a char
with pytest.raises(ValueError) as excinfo: with pytest.raises(ValueError) as excinfo:
assert m.ord_char8(u"Ā") == 0x100 # requires 2 bytes, doesn't fit in a char assert m.ord_char8("Ā") == 0x100 # requires 2 bytes, doesn't fit in a char
assert str(excinfo.value) == toobig_message(0x100) assert str(excinfo.value) == toobig_message(0x100)
with pytest.raises(ValueError) as excinfo: with pytest.raises(ValueError) as excinfo:
assert m.ord_char8(u"ab") assert m.ord_char8("ab")
assert str(excinfo.value) == toolong_message assert str(excinfo.value) == toolong_message
@ -123,18 +124,22 @@ def test_bytes_to_string():
one-way: the only way to return bytes to Python is via the pybind11::bytes class.""" one-way: the only way to return bytes to Python is via the pybind11::bytes class."""
# Issue #816 # Issue #816
def to_bytes(s): assert m.strlen(b"hi") == 2
b = s if env.PY2 else s.encode("utf8") assert m.string_length(b"world") == 5
assert isinstance(b, bytes) assert m.string_length("a\x00b".encode()) == 3
return b assert m.strlen("a\x00b".encode()) == 1 # C-string limitation
assert m.strlen(to_bytes("hi")) == 2
assert m.string_length(to_bytes("world")) == 5
assert m.string_length(to_bytes("a\x00b")) == 3
assert m.strlen(to_bytes("a\x00b")) == 1 # C-string limitation
# passing in a utf8 encoded string should work # passing in a utf8 encoded string should work
assert m.string_length(u"💩".encode("utf8")) == 4 assert m.string_length("💩".encode()) == 4
def test_bytearray_to_string():
"""Tests the ability to pass bytearray to C++ string-accepting functions"""
assert m.string_length(bytearray(b"Hi")) == 2
assert m.strlen(bytearray(b"bytearray")) == 9
assert m.string_length(bytearray()) == 0
assert m.string_length(bytearray("🦜", "utf-8", "strict")) == 4
assert m.string_length(bytearray(b"\x80")) == 1
@pytest.mark.skipif(not hasattr(m, "has_string_view"), reason="no <string_view>") @pytest.mark.skipif(not hasattr(m, "has_string_view"), reason="no <string_view>")
@ -142,26 +147,26 @@ def test_string_view(capture):
"""Tests support for C++17 string_view arguments and return values""" """Tests support for C++17 string_view arguments and return values"""
assert m.string_view_chars("Hi") == [72, 105] assert m.string_view_chars("Hi") == [72, 105]
assert m.string_view_chars("Hi 🎂") == [72, 105, 32, 0xF0, 0x9F, 0x8E, 0x82] assert m.string_view_chars("Hi 🎂") == [72, 105, 32, 0xF0, 0x9F, 0x8E, 0x82]
assert m.string_view16_chars(u"Hi 🎂") == [72, 105, 32, 0xD83C, 0xDF82] assert m.string_view16_chars("Hi 🎂") == [72, 105, 32, 0xD83C, 0xDF82]
assert m.string_view32_chars(u"Hi 🎂") == [72, 105, 32, 127874] assert m.string_view32_chars("Hi 🎂") == [72, 105, 32, 127874]
if hasattr(m, "has_u8string"): if hasattr(m, "has_u8string"):
assert m.string_view8_chars("Hi") == [72, 105] assert m.string_view8_chars("Hi") == [72, 105]
assert m.string_view8_chars(u"Hi 🎂") == [72, 105, 32, 0xF0, 0x9F, 0x8E, 0x82] assert m.string_view8_chars("Hi 🎂") == [72, 105, 32, 0xF0, 0x9F, 0x8E, 0x82]
assert m.string_view_return() == u"utf8 secret 🎂" assert m.string_view_return() == "utf8 secret 🎂"
assert m.string_view16_return() == u"utf16 secret 🎂" assert m.string_view16_return() == "utf16 secret 🎂"
assert m.string_view32_return() == u"utf32 secret 🎂" assert m.string_view32_return() == "utf32 secret 🎂"
if hasattr(m, "has_u8string"): if hasattr(m, "has_u8string"):
assert m.string_view8_return() == u"utf8 secret 🎂" assert m.string_view8_return() == "utf8 secret 🎂"
with capture: with capture:
m.string_view_print("Hi") m.string_view_print("Hi")
m.string_view_print("utf8 🎂") m.string_view_print("utf8 🎂")
m.string_view16_print(u"utf16 🎂") m.string_view16_print("utf16 🎂")
m.string_view32_print(u"utf32 🎂") m.string_view32_print("utf32 🎂")
assert ( assert (
capture capture
== u""" == """
Hi 2 Hi 2
utf8 🎂 9 utf8 🎂 9
utf16 🎂 8 utf16 🎂 8
@ -171,10 +176,10 @@ def test_string_view(capture):
if hasattr(m, "has_u8string"): if hasattr(m, "has_u8string"):
with capture: with capture:
m.string_view8_print("Hi") m.string_view8_print("Hi")
m.string_view8_print(u"utf8 🎂") m.string_view8_print("utf8 🎂")
assert ( assert (
capture capture
== u""" == """
Hi 2 Hi 2
utf8 🎂 9 utf8 🎂 9
""" """
@ -183,11 +188,11 @@ def test_string_view(capture):
with capture: with capture:
m.string_view_print("Hi, ascii") m.string_view_print("Hi, ascii")
m.string_view_print("Hi, utf8 🎂") m.string_view_print("Hi, utf8 🎂")
m.string_view16_print(u"Hi, utf16 🎂") m.string_view16_print("Hi, utf16 🎂")
m.string_view32_print(u"Hi, utf32 🎂") m.string_view32_print("Hi, utf32 🎂")
assert ( assert (
capture capture
== u""" == """
Hi, ascii 9 Hi, ascii 9
Hi, utf8 🎂 13 Hi, utf8 🎂 13
Hi, utf16 🎂 12 Hi, utf16 🎂 12
@ -197,22 +202,21 @@ def test_string_view(capture):
if hasattr(m, "has_u8string"): if hasattr(m, "has_u8string"):
with capture: with capture:
m.string_view8_print("Hi, ascii") m.string_view8_print("Hi, ascii")
m.string_view8_print(u"Hi, utf8 🎂") m.string_view8_print("Hi, utf8 🎂")
assert ( assert (
capture capture
== u""" == """
Hi, ascii 9 Hi, ascii 9
Hi, utf8 🎂 13 Hi, utf8 🎂 13
""" """
) )
assert m.string_view_bytes() == b"abc \x80\x80 def" assert m.string_view_bytes() == b"abc \x80\x80 def"
assert m.string_view_str() == u"abc ‽ def" assert m.string_view_str() == "abc ‽ def"
assert m.string_view_from_bytes(u"abc ‽ def".encode("utf-8")) == u"abc ‽ def" assert m.string_view_from_bytes("abc ‽ def".encode()) == "abc ‽ def"
if hasattr(m, "has_u8string"): if hasattr(m, "has_u8string"):
assert m.string_view8_str() == u"abc ‽ def" assert m.string_view8_str() == "abc ‽ def"
if not env.PY2: assert m.string_view_memoryview() == "Have some 🎂".encode()
assert m.string_view_memoryview() == "Have some 🎂".encode()
assert m.bytes_from_type_with_both_operator_string_and_string_view() == b"success" assert m.bytes_from_type_with_both_operator_string_and_string_view() == b"success"
assert m.str_from_type_with_both_operator_string_and_string_view() == "success" assert m.str_from_type_with_both_operator_string_and_string_view() == "success"
@ -224,20 +228,8 @@ def test_integer_casting():
assert m.i64_str(-1) == "-1" assert m.i64_str(-1) == "-1"
assert m.i32_str(2000000000) == "2000000000" assert m.i32_str(2000000000) == "2000000000"
assert m.u32_str(2000000000) == "2000000000" assert m.u32_str(2000000000) == "2000000000"
if env.PY2: assert m.i64_str(-999999999999) == "-999999999999"
assert m.i32_str(long(-1)) == "-1" # noqa: F821 undefined name 'long' assert m.u64_str(999999999999) == "999999999999"
assert m.i64_str(long(-1)) == "-1" # noqa: F821 undefined name 'long'
assert (
m.i64_str(long(-999999999999)) # noqa: F821 undefined name 'long'
== "-999999999999"
)
assert (
m.u64_str(long(999999999999)) # noqa: F821 undefined name 'long'
== "999999999999"
)
else:
assert m.i64_str(-999999999999) == "-999999999999"
assert m.u64_str(999999999999) == "999999999999"
with pytest.raises(TypeError) as excinfo: with pytest.raises(TypeError) as excinfo:
m.u32_str(-1) m.u32_str(-1)
@ -252,46 +244,38 @@ def test_integer_casting():
m.i32_str(3000000000) m.i32_str(3000000000)
assert "incompatible function arguments" in str(excinfo.value) assert "incompatible function arguments" in str(excinfo.value)
if env.PY2:
with pytest.raises(TypeError) as excinfo:
m.u32_str(long(-1)) # noqa: F821 undefined name 'long'
assert "incompatible function arguments" in str(excinfo.value)
with pytest.raises(TypeError) as excinfo:
m.u64_str(long(-1)) # noqa: F821 undefined name 'long'
assert "incompatible function arguments" in str(excinfo.value)
def test_int_convert(): def test_int_convert():
class Int(object): class Int:
def __int__(self): def __int__(self):
return 42 return 42
class NotInt(object): class NotInt:
pass pass
class Float(object): class Float:
def __float__(self): def __float__(self):
return 41.99999 return 41.99999
class Index(object): class Index:
def __index__(self): def __index__(self):
return 42 return 42
class IntAndIndex(object): class IntAndIndex:
def __int__(self): def __int__(self):
return 42 return 42
def __index__(self): def __index__(self):
return 0 return 0
class RaisingTypeErrorOnIndex(object): class RaisingTypeErrorOnIndex:
def __index__(self): def __index__(self):
raise TypeError raise TypeError
def __int__(self): def __int__(self):
return 42 return 42
class RaisingValueErrorOnIndex(object): class RaisingValueErrorOnIndex:
def __index__(self): def __index__(self):
raise ValueError raise ValueError
@ -311,7 +295,7 @@ def test_int_convert():
cant_convert(3.14159) cant_convert(3.14159)
# TODO: Avoid DeprecationWarning in `PyLong_AsLong` (and similar) # TODO: Avoid DeprecationWarning in `PyLong_AsLong` (and similar)
# TODO: PyPy 3.8 does not behave like CPython 3.8 here yet (7.3.7) # TODO: PyPy 3.8 does not behave like CPython 3.8 here yet (7.3.7)
if (3, 8) <= env.PY < (3, 10) and env.CPYTHON: if (3, 8) <= sys.version_info < (3, 10) and env.CPYTHON:
with env.deprecated_call(): with env.deprecated_call():
assert convert(Int()) == 42 assert convert(Int()) == 42
else: else:
@ -348,7 +332,7 @@ def test_numpy_int_convert():
# TODO: Avoid DeprecationWarning in `PyLong_AsLong` (and similar) # TODO: Avoid DeprecationWarning in `PyLong_AsLong` (and similar)
# TODO: PyPy 3.8 does not behave like CPython 3.8 here yet (7.3.7) # TODO: PyPy 3.8 does not behave like CPython 3.8 here yet (7.3.7)
# https://github.com/pybind/pybind11/issues/3408 # https://github.com/pybind/pybind11/issues/3408
if (3, 8) <= env.PY < (3, 10) and env.CPYTHON: if (3, 8) <= sys.version_info < (3, 10) and env.CPYTHON:
with env.deprecated_call(): with env.deprecated_call():
assert convert(np.float32(3.14159)) == 3 assert convert(np.float32(3.14159)) == 3
else: else:
@ -475,7 +459,7 @@ def test_bool_caster():
require_implicit(None) require_implicit(None)
assert convert(None) is False assert convert(None) is False
class A(object): class A:
def __init__(self, x): def __init__(self, x):
self.x = x self.x = x
@ -485,7 +469,7 @@ def test_bool_caster():
def __bool__(self): def __bool__(self):
return self.x return self.x
class B(object): class B:
pass pass
# Arbitrary objects are not accepted # Arbitrary objects are not accepted
@ -515,17 +499,9 @@ def test_numpy_bool():
def test_int_long(): def test_int_long():
"""In Python 2, a C++ int should return a Python int rather than long
if possible: longs are not always accepted where ints are used (such
as the argument to sys.exit()). A C++ long long is always a Python
long."""
import sys
must_be_long = type(getattr(sys, "maxint", 1) + 1)
assert isinstance(m.int_cast(), int) assert isinstance(m.int_cast(), int)
assert isinstance(m.long_cast(), int) assert isinstance(m.long_cast(), int)
assert isinstance(m.longlong_cast(), must_be_long) assert isinstance(m.longlong_cast(), int)
def test_void_caster_2(): def test_void_caster_2():

View File

@ -40,18 +40,17 @@ TEST_SUBMODULE(call_policies, m) {
Child(Child &&) = default; Child(Child &&) = default;
~Child() { py::print("Releasing child."); } ~Child() { py::print("Releasing child."); }
}; };
py::class_<Child>(m, "Child") py::class_<Child>(m, "Child").def(py::init<>());
.def(py::init<>());
class Parent { class Parent {
public: public:
Parent() { py::print("Allocating parent."); } Parent() { py::print("Allocating parent."); }
Parent(const Parent& parent) = default; Parent(const Parent &parent) = default;
~Parent() { py::print("Releasing parent."); } ~Parent() { py::print("Releasing parent."); }
void addChild(Child *) { } void addChild(Child *) {}
Child *returnChild() { return new Child(); } Child *returnChild() { return new Child(); }
Child *returnNullChild() { return nullptr; } Child *returnNullChild() { return nullptr; }
static Child *staticFunction(Parent*) { return new Child(); } static Child *staticFunction(Parent *) { return new Child(); }
}; };
py::class_<Parent>(m, "Parent") py::class_<Parent>(m, "Parent")
.def(py::init<>()) .def(py::init<>())
@ -62,11 +61,12 @@ TEST_SUBMODULE(call_policies, m) {
.def("returnChildKeepAlive", &Parent::returnChild, py::keep_alive<1, 0>()) .def("returnChildKeepAlive", &Parent::returnChild, py::keep_alive<1, 0>())
.def("returnNullChildKeepAliveChild", &Parent::returnNullChild, py::keep_alive<1, 0>()) .def("returnNullChildKeepAliveChild", &Parent::returnNullChild, py::keep_alive<1, 0>())
.def("returnNullChildKeepAliveParent", &Parent::returnNullChild, py::keep_alive<0, 1>()) .def("returnNullChildKeepAliveParent", &Parent::returnNullChild, py::keep_alive<0, 1>())
.def_static( .def_static("staticFunction", &Parent::staticFunction, py::keep_alive<1, 0>());
"staticFunction", &Parent::staticFunction, py::keep_alive<1, 0>());
m.def("free_function", [](Parent*, Child*) {}, py::keep_alive<1, 2>()); m.def(
m.def("invalid_arg_index", []{}, py::keep_alive<0, 1>()); "free_function", [](Parent *, Child *) {}, py::keep_alive<1, 2>());
m.def(
"invalid_arg_index", [] {}, py::keep_alive<0, 1>());
#if !defined(PYPY_VERSION) #if !defined(PYPY_VERSION)
// test_alive_gc // test_alive_gc
@ -74,29 +74,37 @@ TEST_SUBMODULE(call_policies, m) {
public: public:
using Parent::Parent; using Parent::Parent;
}; };
py::class_<ParentGC, Parent>(m, "ParentGC", py::dynamic_attr()) py::class_<ParentGC, Parent>(m, "ParentGC", py::dynamic_attr()).def(py::init<>());
.def(py::init<>());
#endif #endif
// test_call_guard // test_call_guard
m.def("unguarded_call", &CustomGuard::report_status); m.def("unguarded_call", &CustomGuard::report_status);
m.def("guarded_call", &CustomGuard::report_status, py::call_guard<CustomGuard>()); m.def("guarded_call", &CustomGuard::report_status, py::call_guard<CustomGuard>());
m.def("multiple_guards_correct_order", []() { m.def(
return CustomGuard::report_status() + std::string(" & ") + DependentGuard::report_status(); "multiple_guards_correct_order",
}, py::call_guard<CustomGuard, DependentGuard>()); []() {
return CustomGuard::report_status() + std::string(" & ")
+ DependentGuard::report_status();
},
py::call_guard<CustomGuard, DependentGuard>());
m.def("multiple_guards_wrong_order", []() { m.def(
return DependentGuard::report_status() + std::string(" & ") + CustomGuard::report_status(); "multiple_guards_wrong_order",
}, py::call_guard<DependentGuard, CustomGuard>()); []() {
return DependentGuard::report_status() + std::string(" & ")
+ CustomGuard::report_status();
},
py::call_guard<DependentGuard, CustomGuard>());
#if defined(WITH_THREAD) && !defined(PYPY_VERSION) #if defined(WITH_THREAD) && !defined(PYPY_VERSION)
// `py::call_guard<py::gil_scoped_release>()` should work in PyPy as well, // `py::call_guard<py::gil_scoped_release>()` should work in PyPy as well,
// but it's unclear how to test it without `PyGILState_GetThisThreadState`. // but it's unclear how to test it without `PyGILState_GetThisThreadState`.
auto report_gil_status = []() { auto report_gil_status = []() {
auto is_gil_held = false; auto is_gil_held = false;
if (auto tstate = py::detail::get_thread_state_unchecked()) if (auto *tstate = py::detail::get_thread_state_unchecked()) {
is_gil_held = (tstate == PyGILState_GetThisThreadState()); is_gil_held = (tstate == PyGILState_GetThisThreadState());
}
return is_gil_held ? "GIL held" : "GIL released"; return is_gil_held ? "GIL held" : "GIL released";
}; };

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
import pytest import pytest
import env # noqa: F401 import env # noqa: F401

View File

@ -7,11 +7,12 @@
BSD-style license that can be found in the LICENSE file. BSD-style license that can be found in the LICENSE file.
*/ */
#include "pybind11_tests.h"
#include "constructor_stats.h"
#include <pybind11/functional.h> #include <pybind11/functional.h>
#include <thread>
#include "constructor_stats.h"
#include "pybind11_tests.h"
#include <thread>
int dummy_function(int i) { return i + 1; } int dummy_function(int i) { return i + 1; }
@ -20,11 +21,12 @@ TEST_SUBMODULE(callbacks, m) {
m.def("test_callback1", [](const py::object &func) { return func(); }); m.def("test_callback1", [](const py::object &func) { return func(); });
m.def("test_callback2", [](const py::object &func) { return func("Hello", 'x', true, 5); }); m.def("test_callback2", [](const py::object &func) { return func("Hello", 'x', true, 5); });
m.def("test_callback3", [](const std::function<int(int)> &func) { m.def("test_callback3", [](const std::function<int(int)> &func) {
return "func(43) = " + std::to_string(func(43)); }); return "func(43) = " + std::to_string(func(43));
m.def("test_callback4", []() -> std::function<int(int)> { return [](int i) { return i+1; }; });
m.def("test_callback5", []() {
return py::cpp_function([](int i) { return i+1; }, py::arg("number"));
}); });
m.def("test_callback4",
[]() -> std::function<int(int)> { return [](int i) { return i + 1; }; });
m.def("test_callback5",
[]() { return py::cpp_function([](int i) { return i + 1; }, py::arg("number")); });
// test_keyword_args_and_generalized_unpacking // test_keyword_args_and_generalized_unpacking
m.def("test_tuple_unpacking", [](const py::function &f) { m.def("test_tuple_unpacking", [](const py::function &f) {
@ -34,9 +36,9 @@ TEST_SUBMODULE(callbacks, m) {
}); });
m.def("test_dict_unpacking", [](const py::function &f) { m.def("test_dict_unpacking", [](const py::function &f) {
auto d1 = py::dict("key"_a="value", "a"_a=1); auto d1 = py::dict("key"_a = "value", "a"_a = 1);
auto d2 = py::dict(); auto d2 = py::dict();
auto d3 = py::dict("b"_a=2); auto d3 = py::dict("b"_a = 2);
return f("positional", 1, **d1, **d2, **d3); return f("positional", 1, **d1, **d2, **d3);
}); });
@ -44,32 +46,40 @@ TEST_SUBMODULE(callbacks, m) {
m.def("test_unpacking_and_keywords1", [](const py::function &f) { m.def("test_unpacking_and_keywords1", [](const py::function &f) {
auto args = py::make_tuple(2); auto args = py::make_tuple(2);
auto kwargs = py::dict("d"_a=4); auto kwargs = py::dict("d"_a = 4);
return f(1, *args, "c"_a=3, **kwargs); return f(1, *args, "c"_a = 3, **kwargs);
}); });
m.def("test_unpacking_and_keywords2", [](const py::function &f) { m.def("test_unpacking_and_keywords2", [](const py::function &f) {
auto kwargs1 = py::dict("a"_a=1); auto kwargs1 = py::dict("a"_a = 1);
auto kwargs2 = py::dict("c"_a=3, "d"_a=4); auto kwargs2 = py::dict("c"_a = 3, "d"_a = 4);
return f("positional", *py::make_tuple(1), 2, *py::make_tuple(3, 4), 5, return f("positional",
"key"_a="value", **kwargs1, "b"_a=2, **kwargs2, "e"_a=5); *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", [](const py::function &f) { m.def("test_unpacking_error1", [](const py::function &f) {
auto kwargs = py::dict("x"_a=3); auto kwargs = py::dict("x"_a = 3);
return f("x"_a=1, "y"_a=2, **kwargs); // duplicate ** after keyword return f("x"_a = 1, "y"_a = 2, **kwargs); // duplicate ** after keyword
}); });
m.def("test_unpacking_error2", [](const py::function &f) { m.def("test_unpacking_error2", [](const py::function &f) {
auto kwargs = py::dict("x"_a=3); auto kwargs = py::dict("x"_a = 3);
return f(**kwargs, "x"_a=1); // duplicate keyword after ** return f(**kwargs, "x"_a = 1); // duplicate keyword after **
}); });
m.def("test_arg_conversion_error1", m.def("test_arg_conversion_error1",
[](const py::function &f) { f(234, UnregisteredType(), "kw"_a = 567); }); [](const py::function &f) { f(234, UnregisteredType(), "kw"_a = 567); });
m.def("test_arg_conversion_error2", [](const py::function &f) { m.def("test_arg_conversion_error2", [](const py::function &f) {
f(234, "expected_name"_a=UnregisteredType(), "kw"_a=567); f(234, "expected_name"_a = UnregisteredType(), "kw"_a = 567);
}); });
// test_lambda_closure_cleanup // test_lambda_closure_cleanup
@ -136,14 +146,19 @@ TEST_SUBMODULE(callbacks, m) {
m.def("dummy_function_overloaded", [](int i, int j) { return i + j; }); m.def("dummy_function_overloaded", [](int i, int j) { return i + j; });
m.def("dummy_function_overloaded", &dummy_function); m.def("dummy_function_overloaded", &dummy_function);
m.def("dummy_function2", [](int i, int j) { return i + j; }); m.def("dummy_function2", [](int i, int j) { return i + j; });
m.def("roundtrip", [](std::function<int(int)> f, bool expect_none = false) { m.def(
if (expect_none && f) "roundtrip",
throw std::runtime_error("Expected None to be converted to empty std::function"); [](std::function<int(int)> f, bool expect_none = false) {
return f; if (expect_none && f) {
}, py::arg("f"), py::arg("expect_none")=false); throw std::runtime_error("Expected None to be converted to empty std::function");
}
return f;
},
py::arg("f"),
py::arg("expect_none") = false);
m.def("test_dummy_function", [](const std::function<int(int)> &f) -> std::string { m.def("test_dummy_function", [](const std::function<int(int)> &f) -> std::string {
using fn_type = int (*)(int); using fn_type = int (*)(int);
auto result = f.target<fn_type>(); const auto *result = f.target<fn_type>();
if (!result) { if (!result) {
auto r = f(1); auto r = f(1);
return "can't convert to function pointer: eval(1) = " + std::to_string(r); return "can't convert to function pointer: eval(1) = " + std::to_string(r);
@ -153,7 +168,6 @@ TEST_SUBMODULE(callbacks, m) {
return "matches dummy_function: eval(1) = " + std::to_string(r); return "matches dummy_function: eval(1) = " + std::to_string(r);
} }
return "argument does NOT match dummy_function. This should never happen!"; return "argument does NOT match dummy_function. This should never happen!";
}); });
class AbstractBase { class AbstractBase {
@ -185,7 +199,7 @@ TEST_SUBMODULE(callbacks, m) {
// test_movable_object // test_movable_object
m.def("callback_with_movable", [](const std::function<void(MovableObject &)> &f) { m.def("callback_with_movable", [](const std::function<void(MovableObject &)> &f) {
auto x = MovableObject(); auto x = MovableObject();
f(x); // lvalue reference shouldn't move out object f(x); // lvalue reference shouldn't move out object
return x.valid; // must still return `true` return x.valid; // must still return `true`
}); });
@ -197,9 +211,10 @@ TEST_SUBMODULE(callbacks, m) {
// This checks that builtin functions can be passed as callbacks // This checks that builtin functions can be passed as callbacks
// rather than throwing RuntimeError due to trying to extract as capsule // rather than throwing RuntimeError due to trying to extract as capsule
m.def("test_sum_builtin", [](const std::function<double(py::iterable)> &sum_builtin, const py::iterable &i) { m.def("test_sum_builtin",
return sum_builtin(i); [](const std::function<double(py::iterable)> &sum_builtin, const py::iterable &i) {
}); return sum_builtin(i);
});
// test async Python callbacks // test async Python callbacks
using callback_f = std::function<void(int)>; using callback_f = std::function<void(int)>;
@ -215,8 +230,9 @@ TEST_SUBMODULE(callbacks, m) {
}; };
// spawn worker threads // spawn worker threads
for (auto i : work) for (auto i : work) {
start_f(py::cast<int>(i)); start_f(py::cast<int>(i));
}
}); });
m.def("callback_num_times", [](const py::function &f, std::size_t num) { m.def("callback_num_times", [](const py::function &f, std::size_t num) {

Some files were not shown because too many files have changed in this diff Show More