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,6 +117,7 @@ class MatlabWrapper(CheckMixin, FormatMixin):
is_instantiated_class = isinstance(collector_function[1], is_instantiated_class = isinstance(collector_function[1],
instantiator.InstantiatedClass) instantiator.InstantiatedClass)
if function_name is None:
if is_instantiated_class: if is_instantiated_class:
function_name = collector_function[0] + \ function_name = collector_function[0] + \
collector_function[1].name + '_' + collector_function[2] collector_function[1].name + '_' + collector_function[2]
@ -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,19 +339,8 @@ class MatlabWrapper(CheckMixin, FormatMixin):
return check_statement return check_statement
def _wrapper_unwrap_arguments(self, args, arg_id=0, constructor=False): def _unwrap_argument(self, arg, arg_id=0, constructor=False):
"""Format the interface_parser.Arguments. ctype_camel = self._format_type_name(arg.ctype.typename, separator='')
Examples:
((a), unsigned char a = unwrap< unsigned char >(in[1]);),
((a), Test& t = *unwrap_shared_ptr< Test >(in[1], "ptr_Test");),
((a), std::shared_ptr<Test> p1 = unwrap_shared_ptr< Test >(in[1], "ptr_Test");)
"""
body_args = ''
for arg in args.list():
ctype_camel = self._format_type_name(arg.ctype.typename,
separator='')
ctype_sep = self._format_type_name(arg.ctype.typename) ctype_sep = self._format_type_name(arg.ctype.typename)
if self.is_ref(arg.ctype): # and not constructor: if self.is_ref(arg.ctype): # and not constructor:
@ -361,7 +357,6 @@ class MatlabWrapper(CheckMixin, FormatMixin):
elif (self.is_shared_ptr(arg.ctype) or self.can_be_pointer(arg.ctype)) and \ 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.ctype.typename.name not in self.ignore_namespace:
call_type = arg.ctype.is_shared_ptr
arg_type = "{std_boost}::shared_ptr<{ctype_sep}>".format( arg_type = "{std_boost}::shared_ptr<{ctype_sep}>".format(
std_boost='boost' if constructor else 'boost', std_boost='boost' if constructor else 'boost',
@ -374,6 +369,21 @@ class MatlabWrapper(CheckMixin, FormatMixin):
unwrap = 'unwrap< {ctype} >(in[{id}]);'.format( unwrap = 'unwrap< {ctype} >(in[{id}]);'.format(
ctype=arg.ctype.typename.name, id=arg_id) ctype=arg.ctype.typename.name, id=arg_id)
return arg_type, unwrap
def _wrapper_unwrap_arguments(self, args, arg_id=0, constructor=False):
"""Format the interface_parser.Arguments.
Examples:
((a), unsigned char a = unwrap< unsigned char >(in[1]);),
((a), Test& t = *unwrap_shared_ptr< Test >(in[1], "ptr_Test");),
((a), std::shared_ptr<Test> p1 = unwrap_shared_ptr< Test >(in[1], "ptr_Test");)
"""
body_args = ''
for arg in args.list():
arg_type, unwrap = self._unwrap_argument(arg, arg_id, constructor)
body_args += textwrap.indent(textwrap.dedent('''\ body_args += textwrap.indent(textwrap.dedent('''\
{arg_type} {name} = {unwrap} {arg_type} {name} = {unwrap}
'''.format(arg_type=arg_type, name=arg.name, '''.format(arg_type=arg_type, name=arg.name,
@ -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 properties
ptr_{} = 0 ptr_gtsamISAM2Params = 0
relinearizeSkip
end end
''').format(class_name) ```
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}
end
''').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,15 +1085,26 @@ 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
''') ''')
return file_name + '.m', content_text return file_name + '.m', content_text
@ -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,12 +1,17 @@
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,
cppcoreguidelines-init-variables,
cppcoreguidelines-prefer-member-initializer,
cppcoreguidelines-pro-type-static-cast-downcast,
cppcoreguidelines-slicing,
google-explicit-constructor, google-explicit-constructor,
llvm-namespace-comment, llvm-namespace-comment,
misc-definitions-in-headers,
misc-misplaced-const, misc-misplaced-const,
misc-non-copyable-objects, misc-non-copyable-objects,
misc-static-assert, misc-static-assert,
@ -14,6 +19,7 @@ misc-throw-by-value-catch-by-reference,
misc-uniqueptr-reset-release, misc-uniqueptr-reset-release,
misc-unused-parameters, misc-unused-parameters,
modernize-avoid-bind, modernize-avoid-bind,
modernize-loop-convert,
modernize-make-shared, modernize-make-shared,
modernize-redundant-void-arg, modernize-redundant-void-arg,
modernize-replace-auto-ptr, modernize-replace-auto-ptr,
@ -22,23 +28,26 @@ modernize-replace-random-shuffle,
modernize-shrink-to-fit, modernize-shrink-to-fit,
modernize-use-auto, modernize-use-auto,
modernize-use-bool-literals, modernize-use-bool-literals,
modernize-use-default-member-init,
modernize-use-emplace,
modernize-use-equals-default, modernize-use-equals-default,
modernize-use-equals-delete, modernize-use-equals-delete,
modernize-use-default-member-init,
modernize-use-noexcept, modernize-use-noexcept,
modernize-use-emplace, modernize-use-nullptr,
modernize-use-override, modernize-use-override,
modernize-use-using, modernize-use-using,
*performance*,
readability-avoid-const-params-in-decls, readability-avoid-const-params-in-decls,
readability-braces-around-statements,
readability-const-return-type, readability-const-return-type,
readability-container-size-empty, readability-container-size-empty,
readability-delete-null-pointer, readability-delete-null-pointer,
readability-else-after-return, readability-else-after-return,
readability-implicit-bool-conversion, readability-implicit-bool-conversion,
readability-inconsistent-declaration-parameter-name,
readability-make-member-function-const, readability-make-member-function-const,
readability-misplaced-array-index, readability-misplaced-array-index,
readability-non-const-parameter, readability-non-const-parameter,
readability-qualified-auto,
readability-redundant-function-ptr-dereference, readability-redundant-function-ptr-dereference,
readability-redundant-smartptr-get, readability-redundant-smartptr-get,
readability-redundant-string-cstr, readability-redundant-string-cstr,
@ -48,19 +57,19 @@ readability-static-definition-in-anonymous-namespace,
readability-string-compare, readability-string-compare,
readability-suspicious-call-argument, readability-suspicious-call-argument,
readability-uniqueptr-delete-release, readability-uniqueptr-delete-release,
-bugprone-easily-swappable-parameters,
-bugprone-exception-escape, -bugprone-exception-escape,
-bugprone-reserved-identifier, -bugprone-reserved-identifier,
-bugprone-unused-raii, -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 cPickle as pickle # Use cPickle on Python 2.7
except ImportError:
import pickle 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"

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)
if(NOT MSVC AND NOT ${CMAKE_BUILD_TYPE} MATCHES Debug|RelWithDebInfo)
# Strip unnecessary sections of the binary on Linux/macOS
pybind11_strip(example) 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,7 +21,8 @@ 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) {}
}; };
@ -31,34 +33,41 @@ struct is_operator { };
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 {};
@ -74,8 +83,7 @@ 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) {}
@ -99,7 +107,8 @@ 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) {}
}; };
@ -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
@ -169,12 +183,13 @@ struct argument_record {
: 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.. */
@ -311,35 +326,38 @@ struct type_record {
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);
} }
@ -353,9 +371,11 @@ 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 *) {}
@ -364,63 +384,84 @@ template <typename T> struct process_attribute_default {
}; };
/// 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 <>
@ -551,19 +609,26 @@ struct process_attribute<call_guard<Ts...>> : process_attribute_default<call_gua
* 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>
struct process_attributes {
static void init(const Args &...args, function_record *r) { 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);

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,29 +44,52 @@ 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,
const std::string &format,
ssize_t ndim,
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), : ptr(ptr), itemsize(itemsize), size(1), format(format), ndim(ndim),
shape(std::move(shape_in)), strides(std::move(strides_in)), readonly(readonly) { shape(std::move(shape_in)), strides(std::move(strides_in)), readonly(readonly) {
if (ndim != (ssize_t) shape.size() || ndim != (ssize_t) strides.size()) 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,
ssize_t itemsize,
const std::string &format,
ssize_t size,
bool readonly = false)
: buffer_info(ptr, itemsize, format, 1, {size}, {itemsize}, readonly) {} : buffer_info(ptr, itemsize, format, 1, {size}, {itemsize}, readonly) {}
template <typename T> template <typename T>
@ -72,10 +98,15 @@ struct buffer_info {
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.
@ -84,7 +115,9 @@ struct buffer_info {
? 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;
} }
@ -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: private:
struct private_ctr_tag {}; struct private_ctr_tag {};
buffer_info(private_ctr_tag, void *ptr, ssize_t itemsize, const std::string &format, ssize_t ndim, buffer_info(private_ctr_tag,
detail::any_container<ssize_t> &&shape_in, detail::any_container<ssize_t> &&strides_in, bool readonly) void *ptr,
: buffer_info(ptr, itemsize, format, ndim, std::move(shape_in), std::move(strides_in), readonly) { } 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>
struct compare_buffer_info {
static bool compare(const buffer_info &b) { 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>
struct compare_buffer_info<T, detail::enable_if_t<std::is_integral<T>::value>> {
static bool compare(const buffer_info &b) { static bool compare(const buffer_info &b) {
return (size_t) b.itemsize == sizeof(T) && (b.format == format_descriptor<T>::value || return (size_t) b.itemsize == sizeof(T)
((sizeof(T) == sizeof(long)) && b.format == (std::is_unsigned<T>::value ? "L" : "l")) || && (b.format == format_descriptor<T>::value
((sizeof(T) == sizeof(size_t)) && b.format == (std::is_unsigned<T>::value ? "N" : "n"))); || ((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,38 +15,32 @@
#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>>(
@ -57,19 +51,23 @@ public:
} }
// 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,16 +116,21 @@ 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;
@ -156,35 +162,43 @@ public:
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,6 +10,7 @@
#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
@ -19,7 +20,8 @@
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); }
@ -27,25 +29,31 @@ template <typename T> struct format_descriptor<std::complex<T>, detail::enable_i
#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);
@ -105,8 +108,10 @@ inline PyTypeObject *make_static_property_type() {
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);
@ -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,28 +205,29 @@ 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;
} }
@ -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.
inline void traverse_offset_bases(void *valueptr,
const detail::type_info *tinfo,
instance *self,
bool (*f)(void * /*parentptr*/, 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,33 +404,40 @@ 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`
/// to destroy the C++ object itself, while the rest is Python bookkeeping. /// to destroy the C++ object itself, while the rest is Python bookkeeping.
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;
#if PY_VERSION_HEX < 0x030B0000
type->tp_dictoffset = type->tp_basicsize; // place dict at the end type->tp_dictoffset = type->tp_basicsize; // place dict at the end
type->tp_basicsize += (ssize_t) sizeof(PyObject *); // and allocate enough space for it 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;
@ -598,23 +631,20 @@ inline PyObject* make_new_python_type(const type_record &rec) {
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
@ -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);

View File

@ -10,19 +10,19 @@
#pragma once #pragma once
#define PYBIND11_VERSION_MAJOR 2 #define PYBIND11_VERSION_MAJOR 2
#define PYBIND11_VERSION_MINOR 9 #define PYBIND11_VERSION_MINOR 10
#define PYBIND11_VERSION_PATCH 1 #define PYBIND11_VERSION_PATCH 0
// Similar to Python's convention: https://docs.python.org/3/c-api/apiabiversion.html // Similar to Python's convention: https://docs.python.org/3/c-api/apiabiversion.html
// Additional convention: 0xD = dev // Additional convention: 0xD = dev
#define PYBIND11_VERSION_HEX 0x02090100 #define PYBIND11_VERSION_HEX 0x020A0000
#define PYBIND11_NAMESPACE_BEGIN(name) namespace name { #define PYBIND11_NAMESPACE_BEGIN(name) namespace name {
#define PYBIND11_NAMESPACE_END(name) } #define PYBIND11_NAMESPACE_END(name) }
// Robust support for some features and loading modules compiled against different pybind versions // Robust support for some features and loading modules compiled against different pybind versions
// requires forcing hidden visibility on pybind code, so we enforce this by setting the attribute on // requires forcing hidden visibility on pybind code, so we enforce this by setting the attribute
// the main `pybind11` namespace. // on the main `pybind11` namespace.
#if !defined(PYBIND11_NAMESPACE) #if !defined(PYBIND11_NAMESPACE)
# ifdef __GNUG__ # ifdef __GNUG__
# define PYBIND11_NAMESPACE pybind11 __attribute__((visibility("hidden"))) # define PYBIND11_NAMESPACE pybind11 __attribute__((visibility("hidden")))
@ -38,15 +38,17 @@
# define PYBIND11_CPP17 # define PYBIND11_CPP17
# if __cplusplus >= 202002L # if __cplusplus >= 202002L
# define PYBIND11_CPP20 # define PYBIND11_CPP20
// Please update tests/pybind11_tests.cpp `cpp_std()` when adding a macro here.
# endif # endif
# endif # endif
# endif # endif
#elif defined(_MSC_VER) && __cplusplus == 199711L #elif defined(_MSC_VER) && __cplusplus == 199711L
// MSVC sets _MSVC_LANG rather than __cplusplus (supposedly until the standard is fully implemented) // MSVC sets _MSVC_LANG rather than __cplusplus (supposedly until the standard is fully
// Unless you use the /Zc:__cplusplus flag on Visual Studio 2017 15.7 Preview 3 or newer // implemented). Unless you use the /Zc:__cplusplus flag on Visual Studio 2017 15.7 Preview 3
// or newer.
# if _MSVC_LANG >= 201402L # if _MSVC_LANG >= 201402L
# define PYBIND11_CPP14 # define PYBIND11_CPP14
# if _MSVC_LANG > 201402L && _MSC_VER >= 1910 # if _MSVC_LANG > 201402L
# define PYBIND11_CPP17 # define PYBIND11_CPP17
# if _MSVC_LANG >= 202002L # if _MSVC_LANG >= 202002L
# define PYBIND11_CPP20 # define PYBIND11_CPP20
@ -80,10 +82,8 @@
# error pybind11 requires gcc 4.8 or newer # error pybind11 requires gcc 4.8 or newer
# endif # endif
#elif defined(_MSC_VER) #elif defined(_MSC_VER)
// Pybind hits various compiler bugs in 2015u2 and earlier, and also makes use of some stl features # if _MSC_VER < 1910
// (e.g. std::negation) added in 2015u3: # error pybind11 2.10+ requires MSVC 2017 or newer
# if _MSC_FULL_VER < 190024210
# error pybind11 requires MSVC 2015 update 3 or newer
# endif # endif
#endif #endif
@ -98,7 +98,8 @@
#if !defined(PYBIND11_EXPORT_EXCEPTION) #if !defined(PYBIND11_EXPORT_EXCEPTION)
# ifdef __MINGW32__ # ifdef __MINGW32__
// workaround for: // workaround for:
// error: 'dllexport' implies default visibility, but xxx has already been declared with a different visibility // error: 'dllexport' implies default visibility, but xxx has already been declared with a
// different visibility
# define PYBIND11_EXPORT_EXCEPTION # define PYBIND11_EXPORT_EXCEPTION
# else # else
# define PYBIND11_EXPORT_EXCEPTION PYBIND11_EXPORT # define PYBIND11_EXPORT_EXCEPTION PYBIND11_EXPORT
@ -111,8 +112,8 @@
// However, the measured shared-library size saving when using noinline are only // However, the measured shared-library size saving when using noinline are only
// 1.7% for CUDA, -0.2% for GCC7, and 0.0% for GCC8 (using -DCMAKE_BUILD_TYPE=MinSizeRel, // 1.7% for CUDA, -0.2% for GCC7, and 0.0% for GCC8 (using -DCMAKE_BUILD_TYPE=MinSizeRel,
// the default under pybind11/tests). // the default under pybind11/tests).
#if !defined(PYBIND11_NOINLINE_FORCED) && \ #if !defined(PYBIND11_NOINLINE_FORCED) \
(defined(__CUDACC__) || (defined(__GNUC__) && (__GNUC__ == 7 || __GNUC__ == 8))) && (defined(__CUDACC__) || (defined(__GNUC__) && (__GNUC__ == 7 || __GNUC__ == 8)))
# define PYBIND11_NOINLINE_DISABLED # define PYBIND11_NOINLINE_DISABLED
#endif #endif
@ -147,15 +148,12 @@
/* Don't let Python.h #define (v)snprintf as macro because they are implemented /* Don't let Python.h #define (v)snprintf as macro because they are implemented
properly in Visual Studio since 2015. */ properly in Visual Studio since 2015. */
#if defined(_MSC_VER) && _MSC_VER >= 1900 #if defined(_MSC_VER)
# define HAVE_SNPRINTF 1 # define HAVE_SNPRINTF 1
#endif #endif
/// Include Python header, disable linking to pythonX_d.lib on Windows in debug mode /// Include Python header, disable linking to pythonX_d.lib on Windows in debug mode
#if defined(_MSC_VER) #if defined(_MSC_VER)
# if (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION < 4)
# define HAVE_ROUND 1
# endif
# pragma warning(push) # pragma warning(push)
// C4505: 'PySlice_GetIndicesEx': unreferenced local function has been removed (PyPy only) // C4505: 'PySlice_GetIndicesEx': unreferenced local function has been removed (PyPy only)
# pragma warning(disable : 4505) # pragma warning(disable : 4505)
@ -211,8 +209,10 @@
# define PYBIND11_HAS_U8STRING # define PYBIND11_HAS_U8STRING
#endif #endif
#include <Python.h> #include <Python.h>
#if PY_VERSION_HEX < 0x03060000
# error "PYTHON < 3.6 IS UNSUPPORTED. pybind11 v2.9 was the last to support Python 2 and 3.5."
#endif
#include <frameobject.h> #include <frameobject.h>
#include <pythread.h> #include <pythread.h>
@ -243,16 +243,16 @@
#include <cstddef> #include <cstddef>
#include <cstring> #include <cstring>
#include <forward_list>
#include <vector>
#include <string>
#include <stdexcept>
#include <exception> #include <exception>
#include <unordered_set> #include <forward_list>
#include <unordered_map>
#include <memory> #include <memory>
#include <typeindex> #include <stdexcept>
#include <string>
#include <type_traits> #include <type_traits>
#include <typeindex>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#if defined(__has_include) #if defined(__has_include)
# if __has_include(<version>) # if __has_include(<version>)
# include <version> # include <version>
@ -268,11 +268,11 @@
// If UNDEFINED, pybind11::str can only hold PyUnicodeObject, and // If UNDEFINED, pybind11::str can only hold PyUnicodeObject, and
// pybind11::isinstance<str>() is true only for pybind11::str. // pybind11::isinstance<str>() is true only for pybind11::str.
// However, for Python 2 only (!), the pybind11::str caster // However, for Python 2 only (!), the pybind11::str caster
// implicitly decodes bytes to PyUnicodeObject. This is to ease // implicitly decoded bytes to PyUnicodeObject. This was to ease
// the transition from the legacy behavior to the non-permissive // the transition from the legacy behavior to the non-permissive
// behavior. // behavior.
#if PY_MAJOR_VERSION >= 3 /// Compatibility macros for various Python versions /// Compatibility macros for Python 2 / Python 3 versions TODO: remove
#define PYBIND11_INSTANCE_METHOD_NEW(ptr, class_) PyInstanceMethod_New(ptr) #define PYBIND11_INSTANCE_METHOD_NEW(ptr, class_) PyInstanceMethod_New(ptr)
#define PYBIND11_INSTANCE_METHOD_CHECK PyInstanceMethod_Check #define PYBIND11_INSTANCE_METHOD_CHECK PyInstanceMethod_Check
#define PYBIND11_INSTANCE_METHOD_GET_FUNCTION PyInstanceMethod_GET_FUNCTION #define PYBIND11_INSTANCE_METHOD_GET_FUNCTION PyInstanceMethod_GET_FUNCTION
@ -300,57 +300,16 @@
extern "C" PYBIND11_MAYBE_UNUSED PYBIND11_EXPORT PyObject *PyInit_##name(); \ extern "C" PYBIND11_MAYBE_UNUSED PYBIND11_EXPORT PyObject *PyInit_##name(); \
extern "C" PYBIND11_EXPORT PyObject *PyInit_##name() extern "C" PYBIND11_EXPORT PyObject *PyInit_##name()
#else
#define PYBIND11_INSTANCE_METHOD_NEW(ptr, class_) PyMethod_New(ptr, nullptr, class_)
#define PYBIND11_INSTANCE_METHOD_CHECK PyMethod_Check
#define PYBIND11_INSTANCE_METHOD_GET_FUNCTION PyMethod_GET_FUNCTION
#define PYBIND11_BYTES_CHECK PyString_Check
#define PYBIND11_BYTES_FROM_STRING PyString_FromString
#define PYBIND11_BYTES_FROM_STRING_AND_SIZE PyString_FromStringAndSize
#define PYBIND11_BYTES_AS_STRING_AND_SIZE PyString_AsStringAndSize
#define PYBIND11_BYTES_AS_STRING PyString_AsString
#define PYBIND11_BYTES_SIZE PyString_Size
#define PYBIND11_LONG_CHECK(o) (PyInt_Check(o) || PyLong_Check(o))
#define PYBIND11_LONG_AS_LONGLONG(o) (PyInt_Check(o) ? (long long) PyLong_AsLong(o) : PyLong_AsLongLong(o))
#define PYBIND11_LONG_FROM_SIGNED(o) PyInt_FromSsize_t((ssize_t) o) // Returns long if needed.
#define PYBIND11_LONG_FROM_UNSIGNED(o) PyInt_FromSize_t((size_t) o) // Returns long if needed.
#define PYBIND11_BYTES_NAME "str"
#define PYBIND11_STRING_NAME "unicode"
#define PYBIND11_SLICE_OBJECT PySliceObject
#define PYBIND11_FROM_STRING PyString_FromString
#define PYBIND11_STR_TYPE ::pybind11::bytes
#define PYBIND11_BOOL_ATTR "__nonzero__"
#define PYBIND11_NB_BOOL(ptr) ((ptr)->nb_nonzero)
#define PYBIND11_BUILTINS_MODULE "__builtin__"
// Providing a separate PyInit decl to make Clang's -Wmissing-prototypes happy.
// See comment for PYBIND11_MODULE below for why this is marked "maybe unused".
#define PYBIND11_PLUGIN_IMPL(name) \
static PyObject *pybind11_init_wrapper(); \
extern "C" PYBIND11_MAYBE_UNUSED PYBIND11_EXPORT void init##name(); \
extern "C" PYBIND11_EXPORT void init##name() { \
(void)pybind11_init_wrapper(); \
} \
PyObject *pybind11_init_wrapper()
#endif
#if PY_VERSION_HEX >= 0x03050000 && PY_VERSION_HEX < 0x03050200
extern "C" {
struct _Py_atomic_address { void *value; };
PyAPI_DATA(_Py_atomic_address) _PyThreadState_Current;
}
#endif
#define PYBIND11_TRY_NEXT_OVERLOAD ((PyObject *) 1) // special failure return code #define PYBIND11_TRY_NEXT_OVERLOAD ((PyObject *) 1) // special failure return code
#define PYBIND11_STRINGIFY(x) #x #define PYBIND11_STRINGIFY(x) #x
#define PYBIND11_TOSTRING(x) PYBIND11_STRINGIFY(x) #define PYBIND11_TOSTRING(x) PYBIND11_STRINGIFY(x)
#define PYBIND11_CONCAT(first, second) first##second #define PYBIND11_CONCAT(first, second) first##second
#define PYBIND11_ENSURE_INTERNALS_READY \ #define PYBIND11_ENSURE_INTERNALS_READY pybind11::detail::get_internals();
pybind11::detail::get_internals();
#define PYBIND11_CHECK_PYTHON_VERSION \ #define PYBIND11_CHECK_PYTHON_VERSION \
{ \ { \
const char *compiled_ver = PYBIND11_TOSTRING(PY_MAJOR_VERSION) \ const char *compiled_ver \
"." PYBIND11_TOSTRING(PY_MINOR_VERSION); \ = PYBIND11_TOSTRING(PY_MAJOR_VERSION) "." PYBIND11_TOSTRING(PY_MINOR_VERSION); \
const char *runtime_ver = Py_GetVersion(); \ const char *runtime_ver = Py_GetVersion(); \
size_t len = std::strlen(compiled_ver); \ size_t len = std::strlen(compiled_ver); \
if (std::strncmp(runtime_ver, compiled_ver, len) != 0 \ if (std::strncmp(runtime_ver, compiled_ver, len) != 0 \
@ -358,34 +317,21 @@ extern "C" {
PyErr_Format(PyExc_ImportError, \ PyErr_Format(PyExc_ImportError, \
"Python version mismatch: module was compiled for Python %s, " \ "Python version mismatch: module was compiled for Python %s, " \
"but the interpreter version is incompatible: %s.", \ "but the interpreter version is incompatible: %s.", \
compiled_ver, runtime_ver); \ compiled_ver, \
runtime_ver); \
return nullptr; \ return nullptr; \
} \ } \
} }
#if PY_VERSION_HEX >= 0x03030000
#define PYBIND11_CATCH_INIT_EXCEPTIONS \ #define PYBIND11_CATCH_INIT_EXCEPTIONS \
catch (pybind11::error_already_set & e) { \ catch (pybind11::error_already_set & e) { \
pybind11::raise_from(e, PyExc_ImportError, "initialization failed"); \ pybind11::raise_from(e, PyExc_ImportError, "initialization failed"); \
return nullptr; \ return nullptr; \
} catch (const std::exception &e) { \
PyErr_SetString(PyExc_ImportError, e.what()); \
return nullptr; \
} \ } \
catch (const std::exception &e) { \
#else
#define PYBIND11_CATCH_INIT_EXCEPTIONS \
catch (pybind11::error_already_set &e) { \
PyErr_SetString(PyExc_ImportError, e.what()); \ PyErr_SetString(PyExc_ImportError, e.what()); \
return nullptr; \ return nullptr; \
} catch (const std::exception &e) { \ }
PyErr_SetString(PyExc_ImportError, e.what()); \
return nullptr; \
} \
#endif
/** \rst /** \rst
***Deprecated in favor of PYBIND11_MODULE*** ***Deprecated in favor of PYBIND11_MODULE***
@ -410,7 +356,8 @@ extern "C" {
PYBIND11_ENSURE_INTERNALS_READY \ PYBIND11_ENSURE_INTERNALS_READY \
try { \ try { \
return pybind11_init(); \ return pybind11_init(); \
} PYBIND11_CATCH_INIT_EXCEPTIONS \ } \
PYBIND11_CATCH_INIT_EXCEPTIONS \
} \ } \
PyObject *pybind11_init() PyObject *pybind11_init()
@ -481,7 +428,7 @@ enum class return_value_policy : uint8_t {
/** Reference an existing object (i.e. do not create a new copy) and take /** Reference an existing object (i.e. do not create a new copy) and take
ownership. Python will call the destructor and delete operator when the ownership. Python will call the destructor and delete operator when the
objects reference count reaches zero. Undefined behavior ensues when object's reference count reaches zero. Undefined behavior ensues when
the C++ side does the same.. */ the C++ side does the same.. */
take_ownership, take_ownership,
@ -497,7 +444,7 @@ enum class return_value_policy : uint8_t {
move, move,
/** Reference an existing object, but do not take ownership. The C++ side /** Reference an existing object, but do not take ownership. The C++ side
is responsible for managing the objects lifetime and deallocating it is responsible for managing the object's lifetime and deallocating it
when it is no longer used. Warning: undefined behavior will ensue when when it is no longer used. Warning: undefined behavior will ensue when
the C++ side deletes an object that is still referenced and used by the C++ side deletes an object that is still referenced and used by
Python. */ Python. */
@ -506,7 +453,7 @@ enum class return_value_policy : uint8_t {
/** This policy only applies to methods and properties. It references the /** This policy only applies to methods and properties. It references the
object without taking ownership similar to the above object without taking ownership similar to the above
return_value_policy::reference policy. In contrast to that policy, the return_value_policy::reference policy. In contrast to that policy, the
function or propertys implicit this argument (called the parent) is function or property's implicit this argument (called the parent) is
considered to be the the owner of the return value (the child). considered to be the the owner of the return value (the child).
pybind11 then couples the lifetime of the parent to the child via a pybind11 then couples the lifetime of the parent to the child via a
reference relationship that ensures that the parent cannot be garbage reference relationship that ensures that the parent cannot be garbage
@ -518,10 +465,14 @@ enum class return_value_policy : uint8_t {
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
inline static constexpr int log2(size_t n, int k = 0) { return (n <= 1) ? k : log2(n >> 1, k + 1); } inline static constexpr int log2(size_t n, int k = 0) {
return (n <= 1) ? k : log2(n >> 1, k + 1);
}
// Returns the size as a multiple of sizeof(void *), rounded up. // Returns the size as a multiple of sizeof(void *), rounded up.
inline static constexpr size_t size_in_ptrs(size_t s) { return 1 + ((s - 1) >> log2(sizeof(void *))); } inline static constexpr size_t size_in_ptrs(size_t s) {
return 1 + ((s - 1) >> log2(sizeof(void *)));
}
/** /**
* The space to allocate for simple layout instance holders (see below) in multiple of the size of * The space to allocate for simple layout instance holders (see below) in multiple of the size of
@ -559,21 +510,21 @@ struct instance {
/** /**
* An instance has two possible value/holder layouts. * An instance has two possible value/holder layouts.
* *
* Simple layout (when this flag is true), means the `simple_value_holder` is set with a pointer * Simple layout (when this flag is true), means the `simple_value_holder` is set with a
* and the holder object governing that pointer, i.e. [val1*][holder]. This layout is applied * pointer and the holder object governing that pointer, i.e. [val1*][holder]. This layout is
* whenever there is no python-side multiple inheritance of bound C++ types *and* the type's * applied whenever there is no python-side multiple inheritance of bound C++ types *and* the
* holder will fit in the default space (which is large enough to hold either a std::unique_ptr * type's holder will fit in the default space (which is large enough to hold either a
* or std::shared_ptr). * std::unique_ptr or std::shared_ptr).
* *
* Non-simple layout applies when using custom holders that require more space than `shared_ptr` * Non-simple layout applies when using custom holders that require more space than
* (which is typically the size of two pointers), or when multiple inheritance is used on the * `shared_ptr` (which is typically the size of two pointers), or when multiple inheritance is
* python side. Non-simple layout allocates the required amount of memory to have multiple * used on the python side. Non-simple layout allocates the required amount of memory to have
* bound C++ classes as parents. Under this layout, `nonsimple.values_and_holders` is set to a * multiple bound C++ classes as parents. Under this layout, `nonsimple.values_and_holders` is
* pointer to allocated space of the required space to hold a sequence of value pointers and * set to a pointer to allocated space of the required space to hold a sequence of value
* holders followed `status`, a set of bit flags (1 byte each), i.e. * pointers and holders followed `status`, a set of bit flags (1 byte each), i.e.
* [val1*][holder1][val2*][holder2]...[bb...] where each [block] is rounded up to a multiple of * [val1*][holder1][val2*][holder2]...[bb...] where each [block] is rounded up to a multiple
* `sizeof(void *)`. `nonsimple.status` is, for convenience, a pointer to the * of `sizeof(void *)`. `nonsimple.status` is, for convenience, a pointer to the beginning of
* beginning of the [bb...] block (but not independently allocated). * the [bb...] block (but not independently allocated).
* *
* Status bits indicate whether the associated holder is constructed (& * Status bits indicate whether the associated holder is constructed (&
* status_holder_constructed) and whether the value pointer is registered (& * status_holder_constructed) and whether the value pointer is registered (&
@ -587,7 +538,8 @@ struct instance {
/// If true, get_internals().patients has an entry for this object /// If true, get_internals().patients has an entry for this object
bool has_patients : 1; bool has_patients : 1;
/// Initializes all of the above type/values/holders data (but not the instance values themselves) /// Initializes all of the above type/values/holders data (but not the instance values
/// themselves)
void allocate_layout(); void allocate_layout();
/// Destroys/deallocates all of the above /// Destroys/deallocates all of the above
@ -596,26 +548,32 @@ struct instance {
/// Returns the value_and_holder wrapper for the given type (or the first, if `find_type` /// Returns the value_and_holder wrapper for the given type (or the first, if `find_type`
/// omitted). Returns a default-constructed (with `.inst = nullptr`) object on failure if /// omitted). Returns a default-constructed (with `.inst = nullptr`) object on failure if
/// `throw_if_missing` is false. /// `throw_if_missing` is false.
value_and_holder get_value_and_holder(const type_info *find_type = nullptr, bool throw_if_missing = true); value_and_holder get_value_and_holder(const type_info *find_type = nullptr,
bool throw_if_missing = true);
/// Bit values for the non-simple status flags /// Bit values for the non-simple status flags
static constexpr uint8_t status_holder_constructed = 1; static constexpr uint8_t status_holder_constructed = 1;
static constexpr uint8_t status_instance_registered = 2; static constexpr uint8_t status_instance_registered = 2;
}; };
static_assert(std::is_standard_layout<instance>::value, "Internal error: `pybind11::detail::instance` is not standard layout!"); static_assert(std::is_standard_layout<instance>::value,
"Internal error: `pybind11::detail::instance` is not standard layout!");
/// from __cpp_future__ import (convenient aliases from C++14/17) /// from __cpp_future__ import (convenient aliases from C++14/17)
#if defined(PYBIND11_CPP14) && (!defined(_MSC_VER) || _MSC_VER >= 1910) #if defined(PYBIND11_CPP14)
using std::enable_if_t;
using std::conditional_t; using std::conditional_t;
using std::enable_if_t;
using std::remove_cv_t; using std::remove_cv_t;
using std::remove_reference_t; using std::remove_reference_t;
#else #else
template <bool B, typename T = void> using enable_if_t = typename std::enable_if<B, T>::type; template <bool B, typename T = void>
template <bool B, typename T, typename F> using conditional_t = typename std::conditional<B, T, F>::type; using enable_if_t = typename std::enable_if<B, T>::type;
template <typename T> using remove_cv_t = typename std::remove_cv<T>::type; template <bool B, typename T, typename F>
template <typename T> using remove_reference_t = typename std::remove_reference<T>::type; using conditional_t = typename std::conditional<B, T, F>::type;
template <typename T>
using remove_cv_t = typename std::remove_cv<T>::type;
template <typename T>
using remove_reference_t = typename std::remove_reference<T>::type;
#endif #endif
#if defined(PYBIND11_CPP20) #if defined(PYBIND11_CPP20)
@ -635,110 +593,185 @@ using remove_cvref_t = typename remove_cvref<T>::type;
using std::index_sequence; using std::index_sequence;
using std::make_index_sequence; using std::make_index_sequence;
#else #else
template<size_t ...> struct index_sequence { }; template <size_t...>
template<size_t N, size_t ...S> struct make_index_sequence_impl : make_index_sequence_impl <N - 1, N - 1, S...> { }; struct index_sequence {};
template<size_t ...S> struct make_index_sequence_impl <0, S...> { using type = index_sequence<S...>; }; template <size_t N, size_t... S>
template<size_t N> using make_index_sequence = typename make_index_sequence_impl<N>::type; struct make_index_sequence_impl : make_index_sequence_impl<N - 1, N - 1, S...> {};
template <size_t... S>
struct make_index_sequence_impl<0, S...> {
using type = index_sequence<S...>;
};
template <size_t N>
using make_index_sequence = typename make_index_sequence_impl<N>::type;
#endif #endif
/// Make an index sequence of the indices of true arguments /// Make an index sequence of the indices of true arguments
template <typename ISeq, size_t, bool...> struct select_indices_impl { using type = ISeq; }; template <typename ISeq, size_t, bool...>
template <size_t... IPrev, size_t I, bool B, bool... Bs> struct select_indices_impl<index_sequence<IPrev...>, I, B, Bs...> struct select_indices_impl {
: select_indices_impl<conditional_t<B, index_sequence<IPrev..., I>, index_sequence<IPrev...>>, I + 1, Bs...> {}; using type = ISeq;
template <bool... Bs> using select_indices = typename select_indices_impl<index_sequence<>, 0, Bs...>::type; };
template <size_t... IPrev, size_t I, bool B, bool... Bs>
struct select_indices_impl<index_sequence<IPrev...>, I, B, Bs...>
: select_indices_impl<conditional_t<B, index_sequence<IPrev..., I>, index_sequence<IPrev...>>,
I + 1,
Bs...> {};
template <bool... Bs>
using select_indices = typename select_indices_impl<index_sequence<>, 0, Bs...>::type;
/// Backports of std::bool_constant and std::negation to accommodate older compilers /// Backports of std::bool_constant and std::negation to accommodate older compilers
template <bool B> using bool_constant = std::integral_constant<bool, B>; template <bool B>
template <typename T> struct negation : bool_constant<!T::value> { }; using bool_constant = std::integral_constant<bool, B>;
template <typename T>
struct negation : bool_constant<!T::value> {};
// PGI/Intel cannot detect operator delete with the "compatible" void_t impl, so // PGI/Intel cannot detect operator delete with the "compatible" void_t impl, so
// using the new one (C++14 defect, so generally works on newer compilers, even // using the new one (C++14 defect, so generally works on newer compilers, even
// if not in C++17 mode) // if not in C++17 mode)
#if defined(__PGIC__) || defined(__INTEL_COMPILER) #if defined(__PGIC__) || defined(__INTEL_COMPILER)
template<typename... > using void_t = void; template <typename...>
using void_t = void;
#else #else
template <typename...> struct void_t_impl { using type = void; }; template <typename...>
template <typename... Ts> using void_t = typename void_t_impl<Ts...>::type; struct void_t_impl {
using type = void;
};
template <typename... Ts>
using void_t = typename void_t_impl<Ts...>::type;
#endif #endif
/// Compile-time all/any/none of that check the boolean value of all template types /// Compile-time all/any/none of that check the boolean value of all template types
#if defined(__cpp_fold_expressions) && !(defined(_MSC_VER) && (_MSC_VER < 1916)) #if defined(__cpp_fold_expressions) && !(defined(_MSC_VER) && (_MSC_VER < 1916))
template <class... Ts> using all_of = bool_constant<(Ts::value && ...)>; template <class... Ts>
template <class... Ts> using any_of = bool_constant<(Ts::value || ...)>; using all_of = bool_constant<(Ts::value && ...)>;
template <class... Ts>
using any_of = bool_constant<(Ts::value || ...)>;
#elif !defined(_MSC_VER) #elif !defined(_MSC_VER)
template <bool...> struct bools {}; template <bool...>
template <class... Ts> using all_of = std::is_same< struct bools {};
bools<Ts::value..., true>, template <class... Ts>
bools<true, Ts::value...>>; using all_of = std::is_same<bools<Ts::value..., true>, bools<true, Ts::value...>>;
template <class... Ts> using any_of = negation<all_of<negation<Ts>...>>; template <class... Ts>
using any_of = negation<all_of<negation<Ts>...>>;
#else #else
// MSVC has trouble with the above, but supports std::conjunction, which we can use instead (albeit // MSVC has trouble with the above, but supports std::conjunction, which we can use instead (albeit
// at a slight loss of compilation efficiency). // at a slight loss of compilation efficiency).
template <class... Ts> using all_of = std::conjunction<Ts...>; template <class... Ts>
template <class... Ts> using any_of = std::disjunction<Ts...>; using all_of = std::conjunction<Ts...>;
template <class... Ts>
using any_of = std::disjunction<Ts...>;
#endif #endif
template <class... Ts> using none_of = negation<any_of<Ts...>>; template <class... Ts>
using none_of = negation<any_of<Ts...>>;
template <class T, template<class> class... Predicates> using satisfies_all_of = all_of<Predicates<T>...>; template <class T, template <class> class... Predicates>
template <class T, template<class> class... Predicates> using satisfies_any_of = any_of<Predicates<T>...>; using satisfies_all_of = all_of<Predicates<T>...>;
template <class T, template<class> class... Predicates> using satisfies_none_of = none_of<Predicates<T>...>; template <class T, template <class> class... Predicates>
using satisfies_any_of = any_of<Predicates<T>...>;
template <class T, template <class> class... Predicates>
using satisfies_none_of = none_of<Predicates<T>...>;
/// Strip the class from a method type /// Strip the class from a method type
template <typename T> struct remove_class { }; template <typename T>
template <typename C, typename R, typename... A> struct remove_class<R (C::*)(A...)> { using type = R (A...); }; struct remove_class {};
template <typename C, typename R, typename... A> struct remove_class<R (C::*)(A...) const> { using type = R (A...); }; template <typename C, typename R, typename... A>
struct remove_class<R (C::*)(A...)> {
using type = R(A...);
};
template <typename C, typename R, typename... A>
struct remove_class<R (C::*)(A...) const> {
using type = R(A...);
};
/// Helper template to strip away type modifiers /// Helper template to strip away type modifiers
template <typename T> struct intrinsic_type { using type = T; }; template <typename T>
template <typename T> struct intrinsic_type<const T> { using type = typename intrinsic_type<T>::type; }; struct intrinsic_type {
template <typename T> struct intrinsic_type<T*> { using type = typename intrinsic_type<T>::type; }; using type = T;
template <typename T> struct intrinsic_type<T&> { using type = typename intrinsic_type<T>::type; }; };
template <typename T> struct intrinsic_type<T&&> { using type = typename intrinsic_type<T>::type; }; template <typename T>
template <typename T, size_t N> struct intrinsic_type<const T[N]> { using type = typename intrinsic_type<T>::type; }; struct intrinsic_type<const T> {
template <typename T, size_t N> struct intrinsic_type<T[N]> { using type = typename intrinsic_type<T>::type; }; using type = typename intrinsic_type<T>::type;
template <typename T> using intrinsic_t = typename intrinsic_type<T>::type; };
template <typename T>
struct intrinsic_type<T *> {
using type = typename intrinsic_type<T>::type;
};
template <typename T>
struct intrinsic_type<T &> {
using type = typename intrinsic_type<T>::type;
};
template <typename T>
struct intrinsic_type<T &&> {
using type = typename intrinsic_type<T>::type;
};
template <typename T, size_t N>
struct intrinsic_type<const T[N]> {
using type = typename intrinsic_type<T>::type;
};
template <typename T, size_t N>
struct intrinsic_type<T[N]> {
using type = typename intrinsic_type<T>::type;
};
template <typename T>
using intrinsic_t = typename intrinsic_type<T>::type;
/// Helper type to replace 'void' in some expressions /// Helper type to replace 'void' in some expressions
struct void_type {}; struct void_type {};
/// Helper template which holds a list of types /// Helper template which holds a list of types
template <typename...> struct type_list { }; template <typename...>
struct type_list {};
/// Compile-time integer sum /// Compile-time integer sum
#ifdef __cpp_fold_expressions #ifdef __cpp_fold_expressions
template <typename... Ts> constexpr size_t constexpr_sum(Ts... ns) { return (0 + ... + size_t{ns}); } template <typename... Ts>
constexpr size_t constexpr_sum(Ts... ns) {
return (0 + ... + size_t{ns});
}
#else #else
constexpr size_t constexpr_sum() { return 0; } constexpr size_t constexpr_sum() { return 0; }
template <typename T, typename... Ts> template <typename T, typename... Ts>
constexpr size_t constexpr_sum(T n, Ts... ns) { return size_t{n} + constexpr_sum(ns...); } constexpr size_t constexpr_sum(T n, Ts... ns) {
return size_t{n} + constexpr_sum(ns...);
}
#endif #endif
PYBIND11_NAMESPACE_BEGIN(constexpr_impl) PYBIND11_NAMESPACE_BEGIN(constexpr_impl)
/// Implementation details for constexpr functions /// Implementation details for constexpr functions
constexpr int first(int i) { return i; } constexpr int first(int i) { return i; }
template <typename T, typename... Ts> template <typename T, typename... Ts>
constexpr int first(int i, T v, Ts... vs) { return v ? i : first(i + 1, vs...); } constexpr int first(int i, T v, Ts... vs) {
return v ? i : first(i + 1, vs...);
}
constexpr int last(int /*i*/, int result) { return result; } constexpr int last(int /*i*/, int result) { return result; }
template <typename T, typename... Ts> template <typename T, typename... Ts>
constexpr int last(int i, int result, T v, Ts... vs) { return last(i + 1, v ? i : result, vs...); } constexpr int last(int i, int result, T v, Ts... vs) {
return last(i + 1, v ? i : result, vs...);
}
PYBIND11_NAMESPACE_END(constexpr_impl) PYBIND11_NAMESPACE_END(constexpr_impl)
/// Return the index of the first type in Ts which satisfies Predicate<T>. Returns sizeof...(Ts) if /// Return the index of the first type in Ts which satisfies Predicate<T>.
/// none match. /// Returns sizeof...(Ts) if none match.
template <template <typename> class Predicate, typename... Ts> template <template <typename> class Predicate, typename... Ts>
constexpr int constexpr_first() { return constexpr_impl::first(0, Predicate<Ts>::value...); } constexpr int constexpr_first() {
return constexpr_impl::first(0, Predicate<Ts>::value...);
}
/// Return the index of the last type in Ts which satisfies Predicate<T>, or -1 if none match. /// Return the index of the last type in Ts which satisfies Predicate<T>, or -1 if none match.
template <template <typename> class Predicate, typename... Ts> template <template <typename> class Predicate, typename... Ts>
constexpr int constexpr_last() { return constexpr_impl::last(0, -1, Predicate<Ts>::value...); } constexpr int constexpr_last() {
return constexpr_impl::last(0, -1, Predicate<Ts>::value...);
}
/// Return the Nth element from the parameter pack /// Return the Nth element from the parameter pack
template <size_t N, typename T, typename... Ts> template <size_t N, typename T, typename... Ts>
struct pack_element { using type = typename pack_element<N - 1, Ts...>::type; }; struct pack_element {
using type = typename pack_element<N - 1, Ts...>::type;
};
template <typename T, typename... Ts> template <typename T, typename... Ts>
struct pack_element<0, T, Ts...> { using type = T; }; struct pack_element<0, T, Ts...> {
using type = T;
};
/// Return the one and only type which matches the predicate, or Default if none match. /// Return the one and only type which matches the predicate, or Default if none match.
/// If more than one type matches the predicate, fail at compile-time. /// If more than one type matches the predicate, fail at compile-time.
@ -751,39 +784,54 @@ struct exactly_one {
using type = conditional_t<found, typename pack_element<index, Ts...>::type, Default>; using type = conditional_t<found, typename pack_element<index, Ts...>::type, Default>;
}; };
template <template <typename> class P, typename Default> template <template <typename> class P, typename Default>
struct exactly_one<P, Default> { using type = Default; }; struct exactly_one<P, Default> {
using type = Default;
};
template <template <typename> class Predicate, typename Default, typename... Ts> template <template <typename> class Predicate, typename Default, typename... Ts>
using exactly_one_t = typename exactly_one<Predicate, Default, Ts...>::type; using exactly_one_t = typename exactly_one<Predicate, Default, Ts...>::type;
/// Defer the evaluation of type T until types Us are instantiated /// Defer the evaluation of type T until types Us are instantiated
template <typename T, typename... /*Us*/> struct deferred_type { using type = T; }; template <typename T, typename... /*Us*/>
template <typename T, typename... Us> using deferred_t = typename deferred_type<T, Us...>::type; struct deferred_type {
using type = T;
};
template <typename T, typename... Us>
using deferred_t = typename deferred_type<T, Us...>::type;
/// Like is_base_of, but requires a strict base (i.e. `is_strict_base_of<T, T>::value == false`, /// Like is_base_of, but requires a strict base (i.e. `is_strict_base_of<T, T>::value == false`,
/// unlike `std::is_base_of`) /// unlike `std::is_base_of`)
template <typename Base, typename Derived> using is_strict_base_of = bool_constant< template <typename Base, typename Derived>
std::is_base_of<Base, Derived>::value && !std::is_same<Base, Derived>::value>; using is_strict_base_of
= bool_constant<std::is_base_of<Base, Derived>::value && !std::is_same<Base, Derived>::value>;
/// Like is_base_of, but also requires that the base type is accessible (i.e. that a Derived pointer /// Like is_base_of, but also requires that the base type is accessible (i.e. that a Derived
/// can be converted to a Base pointer) /// pointer can be converted to a Base pointer) For unions, `is_base_of<T, T>::value` is False, so
/// For unions, `is_base_of<T, T>::value` is False, so we need to check `is_same` as well. /// we need to check `is_same` as well.
template <typename Base, typename Derived> using is_accessible_base_of = bool_constant< template <typename Base, typename Derived>
(std::is_same<Base, Derived>::value || std::is_base_of<Base, Derived>::value) && std::is_convertible<Derived *, Base *>::value>; using is_accessible_base_of
= bool_constant<(std::is_same<Base, Derived>::value || std::is_base_of<Base, Derived>::value)
&& std::is_convertible<Derived *, Base *>::value>;
template <template <typename...> class Base> template <template <typename...> class Base>
struct is_template_base_of_impl { struct is_template_base_of_impl {
template <typename... Us> static std::true_type check(Base<Us...> *); template <typename... Us>
static std::true_type check(Base<Us...> *);
static std::false_type check(...); static std::false_type check(...);
}; };
/// Check if a template is the base of a type. For example: /// Check if a template is the base of a type. For example:
/// `is_template_base_of<Base, T>` is true if `struct T : Base<U> {}` where U can be anything /// `is_template_base_of<Base, T>` is true if `struct T : Base<U> {}` where U can be anything
template <template <typename...> class Base, typename T> template <template <typename...> class Base, typename T>
// Sadly, all MSVC versions incl. 2022 need the workaround, even in C++20 mode.
// See also: https://github.com/pybind/pybind11/pull/3741
#if !defined(_MSC_VER) #if !defined(_MSC_VER)
using is_template_base_of = decltype(is_template_base_of_impl<Base>::check((intrinsic_t<T>*)nullptr)); using is_template_base_of
#else // MSVC2015 has trouble with decltype in template aliases = decltype(is_template_base_of_impl<Base>::check((intrinsic_t<T> *) nullptr));
struct is_template_base_of : decltype(is_template_base_of_impl<Base>::check((intrinsic_t<T>*)nullptr)) { }; #else
struct is_template_base_of
: decltype(is_template_base_of_impl<Base>::check((intrinsic_t<T> *) nullptr)) {
};
#endif #endif
/// Check if T is an instantiation of the template `Class`. For example: /// Check if T is an instantiation of the template `Class`. For example:
@ -794,18 +842,24 @@ template <template<typename...> class Class, typename... Us>
struct is_instantiation<Class, Class<Us...>> : std::true_type {}; struct is_instantiation<Class, Class<Us...>> : std::true_type {};
/// Check if T is std::shared_ptr<U> where U can be anything /// Check if T is std::shared_ptr<U> where U can be anything
template <typename T> using is_shared_ptr = is_instantiation<std::shared_ptr, T>; template <typename T>
using is_shared_ptr = is_instantiation<std::shared_ptr, T>;
/// Check if T looks like an input iterator /// Check if T looks like an input iterator
template <typename T, typename = void> struct is_input_iterator : std::false_type {}; template <typename T, typename = void>
struct is_input_iterator : std::false_type {};
template <typename T> template <typename T>
struct is_input_iterator<T, void_t<decltype(*std::declval<T &>()), decltype(++std::declval<T &>())>> struct is_input_iterator<T,
void_t<decltype(*std::declval<T &>()), decltype(++std::declval<T &>())>>
: std::true_type {}; : std::true_type {};
template <typename T> using is_function_pointer = bool_constant< template <typename T>
std::is_pointer<T>::value && std::is_function<typename std::remove_pointer<T>::type>::value>; using is_function_pointer
= bool_constant<std::is_pointer<T>::value
&& std::is_function<typename std::remove_pointer<T>::type>::value>;
template <typename F> struct strip_function_object { template <typename F>
struct strip_function_object {
// If you are encountering an // If you are encountering an
// 'error: name followed by "::" must be a class or namespace name' // 'error: name followed by "::" must be a class or namespace name'
// with the Intel compiler and a noexcept function here, // with the Intel compiler and a noexcept function here,
@ -818,18 +872,18 @@ template <typename Function, typename F = remove_reference_t<Function>>
using function_signature_t = conditional_t< using function_signature_t = conditional_t<
std::is_function<F>::value, std::is_function<F>::value,
F, F,
typename conditional_t< typename conditional_t<std::is_pointer<F>::value || std::is_member_pointer<F>::value,
std::is_pointer<F>::value || std::is_member_pointer<F>::value,
std::remove_pointer<F>, std::remove_pointer<F>,
strip_function_object<F> strip_function_object<F>>::type>;
>::type
>;
/// Returns true if the type looks like a lambda: that is, isn't a function, pointer or member /// Returns true if the type looks like a lambda: that is, isn't a function, pointer or member
/// pointer. Note that this can catch all sorts of other things, too; this is intended to be used /// pointer. Note that this can catch all sorts of other things, too; this is intended to be used
/// in a place where passing a lambda makes sense. /// in a place where passing a lambda makes sense.
template <typename T> using is_lambda = satisfies_none_of<remove_reference_t<T>, template <typename T>
std::is_function, std::is_pointer, std::is_member_pointer>; using is_lambda = satisfies_none_of<remove_reference_t<T>,
std::is_function,
std::is_pointer,
std::is_member_pointer>;
// [workaround(intel)] Internal error on fold expression // [workaround(intel)] Internal error on fold expression
/// Apply a function over each element of a parameter pack /// Apply a function over each element of a parameter pack
@ -838,14 +892,17 @@ template <typename T> using is_lambda = satisfies_none_of<remove_reference_t<T>,
# define PYBIND11_EXPAND_SIDE_EFFECTS(PATTERN) (((PATTERN), void()), ...) # define PYBIND11_EXPAND_SIDE_EFFECTS(PATTERN) (((PATTERN), void()), ...)
#else #else
using expand_side_effects = bool[]; using expand_side_effects = bool[];
#define PYBIND11_EXPAND_SIDE_EFFECTS(PATTERN) (void)pybind11::detail::expand_side_effects{ ((PATTERN), void(), false)..., false } # define PYBIND11_EXPAND_SIDE_EFFECTS(PATTERN) \
(void) pybind11::detail::expand_side_effects { ((PATTERN), void(), false)..., false }
#endif #endif
PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(detail)
#if defined(_MSC_VER) #if defined(_MSC_VER)
# pragma warning(push) # pragma warning(push)
# pragma warning(disable: 4275) // warning C4275: An exported class was derived from a class that wasn't exported. Can be ignored when derived from a STL class. # pragma warning(disable : 4275)
// warning C4275: An exported class was derived from a class that wasn't exported.
// Can be ignored when derived from a STL class.
#endif #endif
/// C++ bindings of builtin Python exceptions /// C++ bindings of builtin Python exceptions
class PYBIND11_EXPORT_EXCEPTION builtin_exception : public std::runtime_error { class PYBIND11_EXPORT_EXCEPTION builtin_exception : public std::runtime_error {
@ -859,7 +916,8 @@ public:
#endif #endif
#define PYBIND11_RUNTIME_EXCEPTION(name, type) \ #define PYBIND11_RUNTIME_EXCEPTION(name, type) \
class PYBIND11_EXPORT_EXCEPTION name : public builtin_exception { public: \ class PYBIND11_EXPORT_EXCEPTION name : public builtin_exception { \
public: \
using builtin_exception::builtin_exception; \ using builtin_exception::builtin_exception; \
name() : name("") {} \ name() : name("") {} \
void set_error() const override { PyErr_SetString(type, what()); } \ void set_error() const override { PyErr_SetString(type, what()); } \
@ -873,13 +931,22 @@ PYBIND11_RUNTIME_EXCEPTION(type_error, PyExc_TypeError)
PYBIND11_RUNTIME_EXCEPTION(buffer_error, PyExc_BufferError) PYBIND11_RUNTIME_EXCEPTION(buffer_error, PyExc_BufferError)
PYBIND11_RUNTIME_EXCEPTION(import_error, PyExc_ImportError) PYBIND11_RUNTIME_EXCEPTION(import_error, PyExc_ImportError)
PYBIND11_RUNTIME_EXCEPTION(attribute_error, PyExc_AttributeError) PYBIND11_RUNTIME_EXCEPTION(attribute_error, PyExc_AttributeError)
PYBIND11_RUNTIME_EXCEPTION(cast_error, PyExc_RuntimeError) /// Thrown when pybind11::cast or handle::call fail due to a type casting error PYBIND11_RUNTIME_EXCEPTION(cast_error, PyExc_RuntimeError) /// Thrown when pybind11::cast or
/// handle::call fail due to a type
/// casting error
PYBIND11_RUNTIME_EXCEPTION(reference_cast_error, PyExc_RuntimeError) /// Used internally PYBIND11_RUNTIME_EXCEPTION(reference_cast_error, PyExc_RuntimeError) /// Used internally
[[noreturn]] PYBIND11_NOINLINE void pybind11_fail(const char *reason) { throw std::runtime_error(reason); } [[noreturn]] PYBIND11_NOINLINE void pybind11_fail(const char *reason) {
[[noreturn]] PYBIND11_NOINLINE void pybind11_fail(const std::string &reason) { throw std::runtime_error(reason); } assert(!PyErr_Occurred());
throw std::runtime_error(reason);
}
[[noreturn]] PYBIND11_NOINLINE void pybind11_fail(const std::string &reason) {
assert(!PyErr_Occurred());
throw std::runtime_error(reason);
}
template <typename T, typename SFINAE = void> struct format_descriptor { }; template <typename T, typename SFINAE = void>
struct format_descriptor {};
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
// Returns the index of the given type in the type char array below, and in the list in numpy.h // Returns the index of the given type in the type char array below, and in the list in numpy.h
@ -887,16 +954,28 @@ PYBIND11_NAMESPACE_BEGIN(detail)
// complex float,double,long double. Note that the long double types only participate when long // complex float,double,long double. Note that the long double types only participate when long
// double is actually longer than double (it isn't under MSVC). // double is actually longer than double (it isn't under MSVC).
// NB: not only the string below but also complex.h and numpy.h rely on this order. // NB: not only the string below but also complex.h and numpy.h rely on this order.
template <typename T, typename SFINAE = void> struct is_fmt_numeric { static constexpr bool value = false; }; template <typename T, typename SFINAE = void>
template <typename T> struct is_fmt_numeric<T, enable_if_t<std::is_arithmetic<T>::value>> { struct is_fmt_numeric {
static constexpr bool value = false;
};
template <typename T>
struct is_fmt_numeric<T, enable_if_t<std::is_arithmetic<T>::value>> {
static constexpr bool value = true; static constexpr bool value = true;
static constexpr int index = std::is_same<T, bool>::value ? 0 : 1 + ( static constexpr int index
std::is_integral<T>::value ? detail::log2(sizeof(T))*2 + std::is_unsigned<T>::value : 8 + ( = std::is_same<T, bool>::value
std::is_same<T, double>::value ? 1 : std::is_same<T, long double>::value ? 2 : 0)); ? 0
: 1
+ (std::is_integral<T>::value
? detail::log2(sizeof(T)) * 2 + std::is_unsigned<T>::value
: 8
+ (std::is_same<T, double>::value ? 1
: std::is_same<T, long double>::value ? 2
: 0));
}; };
PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(detail)
template <typename T> struct format_descriptor<T, detail::enable_if_t<std::is_arithmetic<T>::value>> { template <typename T>
struct format_descriptor<T, detail::enable_if_t<std::is_arithmetic<T>::value>> {
static constexpr const char c = "?bBhHiIqQfdg"[detail::is_fmt_numeric<T>::index]; static constexpr const char c = "?bBhHiIqQfdg"[detail::is_fmt_numeric<T>::index];
static constexpr const char value[2] = {c, '\0'}; static constexpr const char value[2] = {c, '\0'};
static std::string format() { return std::string(1, c); } static std::string format() { return std::string(1, c); }
@ -904,8 +983,9 @@ template <typename T> struct format_descriptor<T, detail::enable_if_t<std::is_ar
#if !defined(PYBIND11_CPP17) #if !defined(PYBIND11_CPP17)
template <typename T> constexpr const char format_descriptor< template <typename T>
T, detail::enable_if_t<std::is_arithmetic<T>::value>>::value[2]; constexpr const char
format_descriptor<T, detail::enable_if_t<std::is_arithmetic<T>::value>>::value[2];
#endif #endif
@ -913,29 +993,36 @@ template <typename T> constexpr const char format_descriptor<
struct error_scope { struct error_scope {
PyObject *type, *value, *trace; PyObject *type, *value, *trace;
error_scope() { PyErr_Fetch(&type, &value, &trace); } error_scope() { PyErr_Fetch(&type, &value, &trace); }
error_scope(const error_scope &) = delete;
error_scope &operator=(const error_scope &) = delete;
~error_scope() { PyErr_Restore(type, value, trace); } ~error_scope() { PyErr_Restore(type, value, trace); }
}; };
/// Dummy destructor wrapper that can be used to expose classes with a private destructor /// Dummy destructor wrapper that can be used to expose classes with a private destructor
struct nodelete { template <typename T> void operator()(T*) { } }; struct nodelete {
template <typename T>
void operator()(T *) {}
};
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
template <typename... Args> template <typename... Args>
struct overload_cast_impl { struct overload_cast_impl {
// NOLINTNEXTLINE(modernize-use-equals-default): MSVC 2015 needs this
constexpr overload_cast_impl() {}
template <typename Return> template <typename Return>
constexpr auto operator()(Return (*pf)(Args...)) const noexcept constexpr auto operator()(Return (*pf)(Args...)) const noexcept -> decltype(pf) {
-> decltype(pf) { return pf; } return pf;
}
template <typename Return, typename Class> template <typename Return, typename Class>
constexpr auto operator()(Return (Class::*pmf)(Args...), std::false_type = {}) const noexcept constexpr auto operator()(Return (Class::*pmf)(Args...), std::false_type = {}) const noexcept
-> decltype(pmf) { return pmf; } -> decltype(pmf) {
return pmf;
}
template <typename Return, typename Class> template <typename Return, typename Class>
constexpr auto operator()(Return (Class::*pmf)(Args...) const, std::true_type) const noexcept constexpr auto operator()(Return (Class::*pmf)(Args...) const, std::true_type) const noexcept
-> decltype(pmf) { return pmf; } -> decltype(pmf) {
return pmf;
}
}; };
PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(detail)
@ -946,8 +1033,12 @@ PYBIND11_NAMESPACE_END(detail)
/// - regular: static_cast<Return (Class::*)(Arg0, Arg1, Arg2)>(&Class::func) /// - regular: static_cast<Return (Class::*)(Arg0, Arg1, Arg2)>(&Class::func)
/// - sweet: overload_cast<Arg0, Arg1, Arg2>(&Class::func) /// - sweet: overload_cast<Arg0, Arg1, Arg2>(&Class::func)
template <typename... Args> template <typename... Args>
# if (defined(_MSC_VER) && _MSC_VER < 1920) /* MSVC 2017 */ \
|| (defined(__clang__) && __clang_major__ == 5)
static constexpr detail::overload_cast_impl<Args...> overload_cast = {}; static constexpr detail::overload_cast_impl<Args...> overload_cast = {};
// MSVC 2015 only accepts this particular initialization syntax for this variable template. # else
static constexpr detail::overload_cast_impl<Args...> overload_cast;
# endif
#endif #endif
/// Const member function selector for overload_cast /// Const member function selector for overload_cast
@ -956,7 +1047,8 @@ static constexpr detail::overload_cast_impl<Args...> overload_cast = {};
static constexpr auto const_ = std::true_type{}; static constexpr auto const_ = std::true_type{};
#if !defined(PYBIND11_CPP14) // no overload_cast: providing something that static_assert-fails: #if !defined(PYBIND11_CPP14) // no overload_cast: providing something that static_assert-fails:
template <typename... Args> struct overload_cast { template <typename... Args>
struct overload_cast {
static_assert(detail::deferred_t<std::false_type, Args...>::value, static_assert(detail::deferred_t<std::false_type, Args...>::value,
"pybind11::overload_cast<...> requires compiling in C++14 mode"); "pybind11::overload_cast<...> requires compiling in C++14 mode");
}; };
@ -970,6 +1062,7 @@ PYBIND11_NAMESPACE_BEGIN(detail)
template <typename T> template <typename T>
class any_container { class any_container {
std::vector<T> v; std::vector<T> v;
public: public:
any_container() = default; any_container() = default;
@ -977,13 +1070,17 @@ public:
template <typename It, typename = enable_if_t<is_input_iterator<It>::value>> template <typename It, typename = enable_if_t<is_input_iterator<It>::value>>
any_container(It first, It last) : v(first, last) {} any_container(It first, It last) : v(first, last) {}
// Implicit conversion constructor from any arbitrary container type with values convertible to T // Implicit conversion constructor from any arbitrary container type
template <typename Container, typename = enable_if_t<std::is_convertible<decltype(*std::begin(std::declval<const Container &>())), T>::value>> // with values convertible to T
template <typename Container,
typename = enable_if_t<
std::is_convertible<decltype(*std::begin(std::declval<const Container &>())),
T>::value>>
// NOLINTNEXTLINE(google-explicit-constructor) // NOLINTNEXTLINE(google-explicit-constructor)
any_container(const Container &c) : any_container(std::begin(c), std::end(c)) {} any_container(const Container &c) : any_container(std::begin(c), std::end(c)) {}
// initializer_list's aren't deducible, so don't get matched by the above template; we need this // initializer_list's aren't deducible, so don't get matched by the above template;
// to explicitly allow implicit conversion from one: // we need this to explicitly allow implicit conversion from one:
template <typename TIn, typename = enable_if_t<std::is_convertible<TIn, T>::value>> template <typename TIn, typename = enable_if_t<std::is_convertible<TIn, T>::value>>
any_container(const std::initializer_list<TIn> &c) : any_container(c.begin(), c.end()) {} any_container(const std::initializer_list<TIn> &c) : any_container(c.begin(), c.end()) {}
@ -1008,7 +1105,8 @@ public:
std::string get_fully_qualified_tp_name(PyTypeObject *); std::string get_fully_qualified_tp_name(PyTypeObject *);
template <typename T> template <typename T>
inline static std::shared_ptr<T> try_get_shared_from_this(std::enable_shared_from_this<T> *holder_value_ptr) { inline static std::shared_ptr<T>
try_get_shared_from_this(std::enable_shared_from_this<T> *holder_value_ptr) {
// Pre C++17, this code path exploits undefined behavior, but is known to work on many platforms. // Pre C++17, this code path exploits undefined behavior, but is known to work on many platforms.
// Use at your own risk! // Use at your own risk!
// See also https://en.cppreference.com/w/cpp/memory/enable_shared_from_this, and in particular // See also https://en.cppreference.com/w/cpp/memory/enable_shared_from_this, and in particular
@ -1018,8 +1116,7 @@ inline static std::shared_ptr<T> try_get_shared_from_this(std::enable_shared_fro
#else #else
try { try {
return holder_value_ptr->shared_from_this(); return holder_value_ptr->shared_from_this();
} } catch (const std::bad_weak_ptr &) {
catch (const std::bad_weak_ptr &) {
return nullptr; return nullptr;
} }
#endif #endif
@ -1027,10 +1124,12 @@ inline static std::shared_ptr<T> try_get_shared_from_this(std::enable_shared_fro
// For silencing "unused" compiler warnings in special situations. // For silencing "unused" compiler warnings in special situations.
template <typename... Args> template <typename... Args>
#if defined(_MSC_VER) && _MSC_VER >= 1910 && _MSC_VER < 1920 // MSVC 2017 #if defined(_MSC_VER) && _MSC_VER < 1920 // MSVC 2017
constexpr constexpr
#endif #endif
inline void silence_unused_warnings(Args &&...) {} inline void
silence_unused_warnings(Args &&...) {
}
// MSVC warning C4100: Unreferenced formal parameter // MSVC warning C4100: Unreferenced formal parameter
#if defined(_MSC_VER) && _MSC_VER <= 1916 #if defined(_MSC_VER) && _MSC_VER <= 1916
@ -1059,5 +1158,12 @@ constexpr inline bool silence_msvc_c4127(bool cond) { return cond; }
# define PYBIND11_SILENCE_MSVC_C4127(...) __VA_ARGS__ # define PYBIND11_SILENCE_MSVC_C4127(...) __VA_ARGS__
#endif #endif
// Pybind offers detailed error messages by default for all builts that are debug (through the
// negation of ndebug). This can also be manually enabled by users, for any builds, through
// defining PYBIND11_DETAILED_ERROR_MESSAGES.
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) && !defined(NDEBUG)
# define PYBIND11_DETAILED_ERROR_MESSAGES
#endif
PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@ -42,23 +42,30 @@ 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]) {
return descr<N - 1>(text);
}
constexpr descr<0> const_name(char const (&)[1]) { return {}; } 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)...);
}; };
@ -74,16 +81,23 @@ constexpr enable_if_t<!B, descr<N2 - 1>> const_name(char const(&)[N1], char cons
} }
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.
@ -92,7 +106,9 @@ template <typename Type> constexpr descr<1, Type> const_name() { return {'%'}; }
#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]) {
return const_name<N>(text);
}
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>> _(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);
@ -102,21 +118,30 @@ constexpr enable_if_t<!B, descr<N2 - 1>> _(char const(&text1)[N1], char const(&t
return const_name<B, N1, N2>(text1, text2); 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>
constexpr descr<1, Type> _() {
return const_name<Type>();
}
#endif // #ifndef _ #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");
} }
@ -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);
@ -153,18 +174,20 @@ 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
// Alias instance (even if no the python-side inheritance is involved). The is intended for // Alias instance (even if no the python-side inheritance is involved). The is intended for
// 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));
} }
@ -174,47 +197,75 @@ 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(
"__init__",
[](value_and_holder &v_h, Args... args) {
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)...);
}, is_new_style_constructor(), extra...); },
is_new_style_constructor(),
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,
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(
if (Py_TYPE(v_h.inst) == v_h.type->type) "__init__",
v_h.value_ptr() = construct_or_initialize<Cpp<Class>>(std::forward<Args>(args)...); [](value_and_holder &v_h, Args... args) {
else if (Py_TYPE(v_h.inst) == v_h.type->type) {
v_h.value_ptr() = construct_or_initialize<Alias<Class>>(std::forward<Args>(args)...); v_h.value_ptr()
}, is_new_style_constructor(), extra...); = 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, 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,
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<Alias<Class>>(std::forward<Args>(args)...); "__init__",
}, is_new_style_constructor(), extra...); [](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,
typename... Extra,
enable_if_t<Class::has_alias && std::is_constructible<Alias<Class>, Args...>::value,
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<Alias<Class>>(std::forward<Args>(args)...); "__init__",
}, is_new_style_constructor(), extra...); [](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)
@ -233,21 +284,31 @@ struct factory<Func, void_type (*)(), Return(Args...)> {
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(
"__init__",
[func = std::move(class_factory)]
#else #else
auto &func = class_factory; auto &func = class_factory;
cl.def("__init__", [func] cl.def(
"__init__",
[func]
#endif #endif
(value_and_holder &v_h, Args... args) { (value_and_holder &v_h, Args... args) {
construct<Class>(v_h, func(std::forward<Args>(args)...), construct<Class>(
Py_TYPE(v_h.inst) != v_h.type->type); v_h, func(std::forward<Args>(args)...), Py_TYPE(v_h.inst) != v_h.type->type);
}, is_new_style_constructor(), extra...); },
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 "
@ -266,23 +327,31 @@ struct factory<CFunc, AFunc, CReturn(CArgs...), AReturn(AArgs...)> {
// 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,
"The two-argument version of `py::init()` can "
"only be used if the class has an alias"); "only be used if the class has an alias");
#if defined(PYBIND11_CPP14) #if defined(PYBIND11_CPP14)
cl.def("__init__", [class_func = std::move(class_factory), alias_func = std::move(alias_factory)] cl.def(
"__init__",
[class_func = std::move(class_factory), alias_func = std::move(alias_factory)]
#else #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(
"__init__",
[class_func, alias_func]
#endif #endif
(value_and_holder &v_h, CArgs... args) { (value_and_holder &v_h, CArgs... args) {
if (Py_TYPE(v_h.inst) == v_h.type->type) if (Py_TYPE(v_h.inst) == v_h.type->type) {
// If the instance type equals the registered type we don't have inheritance, so // If the instance type equals the registered type we don't have inheritance,
// don't need the alias and can construct using the class function: // so don't need the alias and can construct using the class function:
construct<Class>(v_h, class_func(std::forward<CArgs>(args)...), false); construct<Class>(v_h, class_func(std::forward<CArgs>(args)...), false);
else } else {
construct<Class>(v_h, alias_func(std::forward<CArgs>(args)...), true); construct<Class>(v_h, alias_func(std::forward<CArgs>(args)...), true);
}, is_new_style_constructor(), extra...); }
},
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;
} }
}; };
@ -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;
// PyTypeObject* -> base type_info(s)
std::unordered_map<PyTypeObject *, std::vector<type_info *>> registered_types_py;
std::unordered_multimap<const void *, instance *> registered_instances; // void * -> instance* std::unordered_multimap<const void *, instance *> registered_instances; // void * -> instance*
std::unordered_set<std::pair<const PyObject *, const char *>, override_hash> inactive_override_cache; 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;
@ -270,11 +277,15 @@ struct type_info {
# 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,8 +404,9 @@ 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.
@ -408,6 +415,7 @@ PYBIND11_NOINLINE internals &get_internals() {
~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)
@ -506,7 +516,6 @@ inline local_internals &get_local_internals() {
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

View File

@ -14,6 +14,7 @@
#include "descr.h" #include "descr.h"
#include "internals.h" #include "internals.h"
#include "typeid.h" #include "typeid.h"
#include <cstdint> #include <cstdint>
#include <iterator> #include <iterator>
#include <new> #include <new>
@ -62,19 +63,18 @@ private:
public: public:
/// A new patient frame is created when a function is entered /// A new patient frame is created when a function is entered
loader_life_support() { loader_life_support() : parent{get_stack_top()} { set_stack_top(this); }
parent = get_stack_top();
set_stack_top(this);
}
/// ... and destroyed after it returns /// ... and destroyed after it returns
~loader_life_support() { ~loader_life_support() {
if (get_stack_top() != this) if (get_stack_top() != this) {
pybind11_fail("loader_life_support: internal error"); pybind11_fail("loader_life_support: internal error");
}
set_stack_top(parent); set_stack_top(parent);
for (auto* item : keep_alive) for (auto *item : keep_alive) {
Py_DECREF(item); Py_DECREF(item);
} }
}
/// This can only be used inside a pybind11-bound function, either by `argument_loader` /// This can only be used inside a pybind11-bound function, either by `argument_loader`
/// at argument preparation time or by `py::cast()` at execution time. /// at argument preparation time or by `py::cast()` at execution time.
@ -90,47 +90,56 @@ public:
"of temporary values"); "of temporary values");
} }
if (frame->keep_alive.insert(h.ptr()).second) if (frame->keep_alive.insert(h.ptr()).second) {
Py_INCREF(h.ptr()); Py_INCREF(h.ptr());
} }
}
}; };
// Gets the cache entry for the given type, creating it if necessary. The return value is the pair // Gets the cache entry for the given type, creating it if necessary. The return value is the pair
// returned by emplace, i.e. an iterator for the entry and a bool set to `true` if the entry was // returned by emplace, i.e. an iterator for the entry and a bool set to `true` if the entry was
// just created. // just created.
inline std::pair<decltype(internals::registered_types_py)::iterator, bool> all_type_info_get_cache(PyTypeObject *type); inline std::pair<decltype(internals::registered_types_py)::iterator, bool>
all_type_info_get_cache(PyTypeObject *type);
// Populates a just-created cache entry. // Populates a just-created cache entry.
PYBIND11_NOINLINE void all_type_info_populate(PyTypeObject *t, std::vector<type_info *> &bases) { PYBIND11_NOINLINE void all_type_info_populate(PyTypeObject *t, std::vector<type_info *> &bases) {
std::vector<PyTypeObject *> check; std::vector<PyTypeObject *> check;
for (handle parent : reinterpret_borrow<tuple>(t->tp_bases)) for (handle parent : reinterpret_borrow<tuple>(t->tp_bases)) {
check.push_back((PyTypeObject *) parent.ptr()); check.push_back((PyTypeObject *) parent.ptr());
}
auto const &type_dict = get_internals().registered_types_py; auto const &type_dict = get_internals().registered_types_py;
for (size_t i = 0; i < check.size(); i++) { for (size_t i = 0; i < check.size(); i++) {
auto type = check[i]; auto *type = check[i];
// Ignore Python2 old-style class super type: // Ignore Python2 old-style class super type:
if (!PyType_Check((PyObject *) type)) continue; if (!PyType_Check((PyObject *) type)) {
continue;
}
// Check `type` in the current set of registered python types: // Check `type` in the current set of registered python types:
auto it = type_dict.find(type); auto it = type_dict.find(type);
if (it != type_dict.end()) { if (it != type_dict.end()) {
// We found a cache entry for it, so it's either pybind-registered or has pre-computed // We found a cache entry for it, so it's either pybind-registered or has pre-computed
// pybind bases, but we have to make sure we haven't already seen the type(s) before: we // pybind bases, but we have to make sure we haven't already seen the type(s) before:
// want to follow Python/virtual C++ rules that there should only be one instance of a // we want to follow Python/virtual C++ rules that there should only be one instance of
// common base. // a common base.
for (auto *tinfo : it->second) { for (auto *tinfo : it->second) {
// NB: Could use a second set here, rather than doing a linear search, but since // NB: Could use a second set here, rather than doing a linear search, but since
// having a large number of immediate pybind11-registered types seems fairly // having a large number of immediate pybind11-registered types seems fairly
// unlikely, that probably isn't worthwhile. // unlikely, that probably isn't worthwhile.
bool found = false; bool found = false;
for (auto *known : bases) { for (auto *known : bases) {
if (known == tinfo) { found = true; break; } if (known == tinfo) {
} found = true;
if (!found) bases.push_back(tinfo); break;
} }
} }
else if (type->tp_bases) { if (!found) {
bases.push_back(tinfo);
}
}
} else if (type->tp_bases) {
// It's some python type, so keep follow its bases classes to look for one or more // It's some python type, so keep follow its bases classes to look for one or more
// registered types // registered types
if (i + 1 == check.size()) { if (i + 1 == check.size()) {
@ -140,11 +149,12 @@ PYBIND11_NOINLINE void all_type_info_populate(PyTypeObject *t, std::vector<type_
check.pop_back(); check.pop_back();
i--; i--;
} }
for (handle parent : reinterpret_borrow<tuple>(type->tp_bases)) for (handle parent : reinterpret_borrow<tuple>(type->tp_bases)) {
check.push_back((PyTypeObject *) parent.ptr()); check.push_back((PyTypeObject *) parent.ptr());
} }
} }
} }
}
/** /**
* Extracts vector of type_info pointers of pybind-registered roots of the given Python type. Will * Extracts vector of type_info pointers of pybind-registered roots of the given Python type. Will
@ -158,9 +168,10 @@ PYBIND11_NOINLINE void all_type_info_populate(PyTypeObject *t, std::vector<type_
*/ */
inline const std::vector<detail::type_info *> &all_type_info(PyTypeObject *type) { inline const std::vector<detail::type_info *> &all_type_info(PyTypeObject *type) {
auto ins = all_type_info_get_cache(type); auto ins = all_type_info_get_cache(type);
if (ins.second) if (ins.second) {
// New cache entry: populate it // New cache entry: populate it
all_type_info_populate(type, ins.first->second); all_type_info_populate(type, ins.first->second);
}
return ins.first->second; return ins.first->second;
} }
@ -171,42 +182,51 @@ inline const std::vector<detail::type_info *> &all_type_info(PyTypeObject *type)
* `all_type_info` instead if you want to support multiple bases. * `all_type_info` instead if you want to support multiple bases.
*/ */
PYBIND11_NOINLINE detail::type_info *get_type_info(PyTypeObject *type) { PYBIND11_NOINLINE detail::type_info *get_type_info(PyTypeObject *type) {
auto &bases = all_type_info(type); const auto &bases = all_type_info(type);
if (bases.empty()) if (bases.empty()) {
return nullptr; return nullptr;
if (bases.size() > 1) }
pybind11_fail("pybind11::detail::get_type_info: type has multiple pybind11-registered bases"); if (bases.size() > 1) {
pybind11_fail(
"pybind11::detail::get_type_info: type has multiple pybind11-registered bases");
}
return bases.front(); return bases.front();
} }
inline detail::type_info *get_local_type_info(const std::type_index &tp) { inline detail::type_info *get_local_type_info(const std::type_index &tp) {
auto &locals = get_local_internals().registered_types_cpp; auto &locals = get_local_internals().registered_types_cpp;
auto it = locals.find(tp); auto it = locals.find(tp);
if (it != locals.end()) if (it != locals.end()) {
return it->second; return it->second;
}
return nullptr; return nullptr;
} }
inline detail::type_info *get_global_type_info(const std::type_index &tp) { inline detail::type_info *get_global_type_info(const std::type_index &tp) {
auto &types = get_internals().registered_types_cpp; auto &types = get_internals().registered_types_cpp;
auto it = types.find(tp); auto it = types.find(tp);
if (it != types.end()) if (it != types.end()) {
return it->second; return it->second;
}
return nullptr; return nullptr;
} }
/// Return the type info for a given C++ type; on lookup failure can either throw or return nullptr. /// Return the type info for a given C++ type; on lookup failure can either throw or return
/// nullptr.
PYBIND11_NOINLINE detail::type_info *get_type_info(const std::type_index &tp, PYBIND11_NOINLINE detail::type_info *get_type_info(const std::type_index &tp,
bool throw_if_missing = false) { bool throw_if_missing = false) {
if (auto ltype = get_local_type_info(tp)) if (auto *ltype = get_local_type_info(tp)) {
return ltype; return ltype;
if (auto gtype = get_global_type_info(tp)) }
if (auto *gtype = get_global_type_info(tp)) {
return gtype; return gtype;
}
if (throw_if_missing) { if (throw_if_missing) {
std::string tname = tp.name(); std::string tname = tp.name();
detail::clean_type_id(tname); detail::clean_type_id(tname);
pybind11_fail("pybind11::detail::get_type_info: unable to find type info for \"" + tname + "\""); pybind11_fail("pybind11::detail::get_type_info: unable to find type info for \""
+ std::move(tname) + '"');
} }
return nullptr; return nullptr;
} }
@ -221,11 +241,12 @@ PYBIND11_NOINLINE handle find_registered_python_instance(void *src,
const detail::type_info *tinfo) { const detail::type_info *tinfo) {
auto it_instances = get_internals().registered_instances.equal_range(src); auto it_instances = get_internals().registered_instances.equal_range(src);
for (auto it_i = it_instances.first; it_i != it_instances.second; ++it_i) { for (auto it_i = it_instances.first; it_i != it_instances.second; ++it_i) {
for (auto instance_type : detail::all_type_info(Py_TYPE(it_i->second))) { for (auto *instance_type : detail::all_type_info(Py_TYPE(it_i->second))) {
if (instance_type && same_type(*instance_type->cpptype, *tinfo->cpptype)) if (instance_type && same_type(*instance_type->cpptype, *tinfo->cpptype)) {
return handle((PyObject *) it_i->second).inc_ref(); return handle((PyObject *) it_i->second).inc_ref();
} }
} }
}
return handle(); return handle();
} }
@ -236,10 +257,10 @@ struct value_and_holder {
void **vh = nullptr; void **vh = nullptr;
// Main constructor for a found value/holder: // Main constructor for a found value/holder:
value_and_holder(instance *i, const detail::type_info *type, size_t vpos, size_t index) : value_and_holder(instance *i, const detail::type_info *type, size_t vpos, size_t index)
inst{i}, index{index}, type{type}, : inst{i}, index{index}, type{type}, vh{inst->simple_layout
vh{inst->simple_layout ? inst->simple_value_holder : &inst->nonsimple.values_and_holders[vpos]} ? inst->simple_value_holder
{} : &inst->nonsimple.values_and_holders[vpos]} {}
// Default constructor (used to signal a value-and-holder not found by get_value_and_holder()) // Default constructor (used to signal a value-and-holder not found by get_value_and_holder())
value_and_holder() = default; value_and_holder() = default;
@ -247,13 +268,15 @@ struct value_and_holder {
// Used for past-the-end iterator // Used for past-the-end iterator
explicit value_and_holder(size_t index) : index{index} {} explicit value_and_holder(size_t index) : index{index} {}
template <typename V = void> V *&value_ptr() const { template <typename V = void>
V *&value_ptr() const {
return reinterpret_cast<V *&>(vh[0]); return reinterpret_cast<V *&>(vh[0]);
} }
// True if this `value_and_holder` has a non-null value pointer // True if this `value_and_holder` has a non-null value pointer
explicit operator bool() const { return value_ptr() != nullptr; } explicit operator bool() const { return value_ptr() != nullptr; }
template <typename H> H &holder() const { template <typename H>
H &holder() const {
return reinterpret_cast<H &>(vh[1]); return reinterpret_cast<H &>(vh[1]);
} }
bool holder_constructed() const { bool holder_constructed() const {
@ -263,13 +286,14 @@ struct value_and_holder {
} }
// NOLINTNEXTLINE(readability-make-member-function-const) // NOLINTNEXTLINE(readability-make-member-function-const)
void set_holder_constructed(bool v = true) { void set_holder_constructed(bool v = true) {
if (inst->simple_layout) if (inst->simple_layout) {
inst->simple_holder_constructed = v; inst->simple_holder_constructed = v;
else if (v) } else if (v) {
inst->nonsimple.status[index] |= instance::status_holder_constructed; inst->nonsimple.status[index] |= instance::status_holder_constructed;
else } else {
inst->nonsimple.status[index] &= (std::uint8_t) ~instance::status_holder_constructed; inst->nonsimple.status[index] &= (std::uint8_t) ~instance::status_holder_constructed;
} }
}
bool instance_registered() const { bool instance_registered() const {
return inst->simple_layout return inst->simple_layout
? inst->simple_instance_registered ? inst->simple_instance_registered
@ -277,13 +301,14 @@ struct value_and_holder {
} }
// NOLINTNEXTLINE(readability-make-member-function-const) // NOLINTNEXTLINE(readability-make-member-function-const)
void set_instance_registered(bool v = true) { void set_instance_registered(bool v = true) {
if (inst->simple_layout) if (inst->simple_layout) {
inst->simple_instance_registered = v; inst->simple_instance_registered = v;
else if (v) } else if (v) {
inst->nonsimple.status[index] |= instance::status_instance_registered; inst->nonsimple.status[index] |= instance::status_instance_registered;
else } else {
inst->nonsimple.status[index] &= (std::uint8_t) ~instance::status_instance_registered; inst->nonsimple.status[index] &= (std::uint8_t) ~instance::status_instance_registered;
} }
}
}; };
// Container for accessing and iterating over an instance's values/holders // Container for accessing and iterating over an instance's values/holders
@ -308,8 +333,7 @@ public:
curr(inst /* instance */, curr(inst /* instance */,
types->empty() ? nullptr : (*types)[0] /* type info */, types->empty() ? nullptr : (*types)[0] /* type info */,
0, /* vpos: (non-simple types only): the first vptr comes first */ 0, /* vpos: (non-simple types only): the first vptr comes first */
0 /* index */) 0 /* index */) {}
{}
// Past-the-end iterator: // Past-the-end iterator:
explicit iterator(size_t end) : curr(end) {} explicit iterator(size_t end) : curr(end) {}
@ -317,8 +341,9 @@ public:
bool operator==(const iterator &other) const { return curr.index == other.curr.index; } bool operator==(const iterator &other) const { return curr.index == other.curr.index; }
bool operator!=(const iterator &other) const { return curr.index != other.curr.index; } bool operator!=(const iterator &other) const { return curr.index != other.curr.index; }
iterator &operator++() { iterator &operator++() {
if (!inst->simple_layout) if (!inst->simple_layout) {
curr.vh += 1 + (*types)[curr.index]->holder_size_in_ptrs; curr.vh += 1 + (*types)[curr.index]->holder_size_in_ptrs;
}
++curr.index; ++curr.index;
curr.type = curr.index < types->size() ? (*types)[curr.index] : nullptr; curr.type = curr.index < types->size() ? (*types)[curr.index] : nullptr;
return *this; return *this;
@ -332,7 +357,9 @@ public:
iterator find(const type_info *find_type) { iterator find(const type_info *find_type) {
auto it = begin(), endit = end(); auto it = begin(), endit = end();
while (it != endit && it->type != find_type) ++it; while (it != endit && it->type != find_type) {
++it;
}
return it; return it;
} }
@ -349,166 +376,116 @@ public:
* The returned object should be short-lived: in particular, it must not outlive the called-upon * The returned object should be short-lived: in particular, it must not outlive the called-upon
* instance. * instance.
*/ */
PYBIND11_NOINLINE value_and_holder instance::get_value_and_holder(const type_info *find_type /*= nullptr default in common.h*/, bool throw_if_missing /*= true in common.h*/) { PYBIND11_NOINLINE value_and_holder
instance::get_value_and_holder(const type_info *find_type /*= nullptr default in common.h*/,
bool throw_if_missing /*= true in common.h*/) {
// Optimize common case: // Optimize common case:
if (!find_type || Py_TYPE(this) == find_type->type) if (!find_type || Py_TYPE(this) == find_type->type) {
return value_and_holder(this, find_type, 0, 0); return value_and_holder(this, find_type, 0, 0);
}
detail::values_and_holders vhs(this); detail::values_and_holders vhs(this);
auto it = vhs.find(find_type); auto it = vhs.find(find_type);
if (it != vhs.end()) if (it != vhs.end()) {
return *it; return *it;
}
if (!throw_if_missing) if (!throw_if_missing) {
return value_and_holder(); return value_and_holder();
}
#if defined(NDEBUG) #if defined(PYBIND11_DETAILED_ERROR_MESSAGES)
pybind11_fail("pybind11::detail::instance::get_value_and_holder: " pybind11_fail("pybind11::detail::instance::get_value_and_holder: `"
"type is not a pybind11 base of the given instance " + get_fully_qualified_tp_name(find_type->type)
"(compile in debug mode for type details)"); + "' is not a pybind11 base of the given `"
+ get_fully_qualified_tp_name(Py_TYPE(this)) + "' instance");
#else #else
pybind11_fail("pybind11::detail::instance::get_value_and_holder: `" + pybind11_fail(
get_fully_qualified_tp_name(find_type->type) + "' is not a pybind11 base of the given `" + "pybind11::detail::instance::get_value_and_holder: "
get_fully_qualified_tp_name(Py_TYPE(this)) + "' instance"); "type is not a pybind11 base of the given instance "
"(#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for type details)");
#endif #endif
} }
PYBIND11_NOINLINE void instance::allocate_layout() { PYBIND11_NOINLINE void instance::allocate_layout() {
auto &tinfo = all_type_info(Py_TYPE(this)); const auto &tinfo = all_type_info(Py_TYPE(this));
const size_t n_types = tinfo.size(); const size_t n_types = tinfo.size();
if (n_types == 0) if (n_types == 0) {
pybind11_fail("instance allocation failed: new instance has no pybind11-registered base types"); pybind11_fail(
"instance allocation failed: new instance has no pybind11-registered base types");
}
simple_layout = simple_layout
n_types == 1 && tinfo.front()->holder_size_in_ptrs <= instance_simple_holder_in_ptrs(); = n_types == 1 && tinfo.front()->holder_size_in_ptrs <= instance_simple_holder_in_ptrs();
// Simple path: no python-side multiple inheritance, and a small-enough holder // Simple path: no python-side multiple inheritance, and a small-enough holder
if (simple_layout) { if (simple_layout) {
simple_value_holder[0] = nullptr; simple_value_holder[0] = nullptr;
simple_holder_constructed = false; simple_holder_constructed = false;
simple_instance_registered = false; simple_instance_registered = false;
} } else { // multiple base types or a too-large holder
else { // multiple base types or a too-large holder
// Allocate space to hold: [v1*][h1][v2*][h2]...[bb...] where [vN*] is a value pointer, // Allocate space to hold: [v1*][h1][v2*][h2]...[bb...] where [vN*] is a value pointer,
// [hN] is the (uninitialized) holder instance for value N, and [bb...] is a set of bool // [hN] is the (uninitialized) holder instance for value N, and [bb...] is a set of bool
// values that tracks whether each associated holder has been initialized. Each [block] is // values that tracks whether each associated holder has been initialized. Each [block] is
// padded, if necessary, to an integer multiple of sizeof(void *). // padded, if necessary, to an integer multiple of sizeof(void *).
size_t space = 0; size_t space = 0;
for (auto t : tinfo) { for (auto *t : tinfo) {
space += 1; // value pointer space += 1; // value pointer
space += t->holder_size_in_ptrs; // holder instance space += t->holder_size_in_ptrs; // holder instance
} }
size_t flags_at = space; size_t flags_at = space;
space += size_in_ptrs(n_types); // status bytes (holder_constructed and instance_registered) space += size_in_ptrs(n_types); // status bytes (holder_constructed and
// instance_registered)
// Allocate space for flags, values, and holders, and initialize it to 0 (flags and values, // Allocate space for flags, values, and holders, and initialize it to 0 (flags and values,
// in particular, need to be 0). Use Python's memory allocation functions: in Python 3.6 // in particular, need to be 0). Use Python's memory allocation
// they default to using pymalloc, which is designed to be efficient for small allocations // functions: Python is using pymalloc, which is designed to be
// like the one we're doing here; in earlier versions (and for larger allocations) they are // efficient for small allocations like the one we're doing here;
// just wrappers around malloc. // for larger allocations they are just wrappers around malloc.
#if PY_VERSION_HEX >= 0x03050000 // TODO: is this still true for pure Python 3.6?
nonsimple.values_and_holders = (void **) PyMem_Calloc(space, sizeof(void *)); nonsimple.values_and_holders = (void **) PyMem_Calloc(space, sizeof(void *));
if (!nonsimple.values_and_holders) throw std::bad_alloc(); if (!nonsimple.values_and_holders) {
#else throw std::bad_alloc();
nonsimple.values_and_holders = (void **) PyMem_New(void *, space); }
if (!nonsimple.values_and_holders) throw std::bad_alloc(); nonsimple.status
std::memset(nonsimple.values_and_holders, 0, space * sizeof(void *)); = reinterpret_cast<std::uint8_t *>(&nonsimple.values_and_holders[flags_at]);
#endif
nonsimple.status = reinterpret_cast<std::uint8_t *>(&nonsimple.values_and_holders[flags_at]);
} }
owned = true; owned = true;
} }
// NOLINTNEXTLINE(readability-make-member-function-const) // NOLINTNEXTLINE(readability-make-member-function-const)
PYBIND11_NOINLINE void instance::deallocate_layout() { PYBIND11_NOINLINE void instance::deallocate_layout() {
if (!simple_layout) if (!simple_layout) {
PyMem_Free(nonsimple.values_and_holders); PyMem_Free(nonsimple.values_and_holders);
} }
}
PYBIND11_NOINLINE bool isinstance_generic(handle obj, const std::type_info &tp) { PYBIND11_NOINLINE bool isinstance_generic(handle obj, const std::type_info &tp) {
handle type = detail::get_type_handle(tp, false); handle type = detail::get_type_handle(tp, false);
if (!type) if (!type) {
return false; return false;
}
return isinstance(obj, type); return isinstance(obj, type);
} }
PYBIND11_NOINLINE std::string error_string() {
if (!PyErr_Occurred()) {
PyErr_SetString(PyExc_RuntimeError, "Unknown internal error occurred");
return "Unknown internal error occurred";
}
error_scope scope; // Preserve error state
std::string errorString;
if (scope.type) {
errorString += handle(scope.type).attr("__name__").cast<std::string>();
errorString += ": ";
}
if (scope.value)
errorString += (std::string) str(scope.value);
PyErr_NormalizeException(&scope.type, &scope.value, &scope.trace);
#if PY_MAJOR_VERSION >= 3
if (scope.trace != nullptr)
PyException_SetTraceback(scope.value, scope.trace);
#endif
#if !defined(PYPY_VERSION)
if (scope.trace) {
auto *trace = (PyTracebackObject *) scope.trace;
/* Get the deepest trace possible */
while (trace->tb_next)
trace = trace->tb_next;
PyFrameObject *frame = trace->tb_frame;
errorString += "\n\nAt:\n";
while (frame) {
#if PY_VERSION_HEX >= 0x03090000
PyCodeObject *f_code = PyFrame_GetCode(frame);
#else
PyCodeObject *f_code = frame->f_code;
Py_INCREF(f_code);
#endif
int lineno = PyFrame_GetLineNumber(frame);
errorString +=
" " + handle(f_code->co_filename).cast<std::string>() +
"(" + std::to_string(lineno) + "): " +
handle(f_code->co_name).cast<std::string>() + "\n";
frame = frame->f_back;
Py_DECREF(f_code);
}
}
#endif
return errorString;
}
PYBIND11_NOINLINE handle get_object_handle(const void *ptr, const detail::type_info *type) { PYBIND11_NOINLINE handle get_object_handle(const void *ptr, const detail::type_info *type) {
auto &instances = get_internals().registered_instances; auto &instances = get_internals().registered_instances;
auto range = instances.equal_range(ptr); auto range = instances.equal_range(ptr);
for (auto it = range.first; it != range.second; ++it) { for (auto it = range.first; it != range.second; ++it) {
for (const auto &vh : values_and_holders(it->second)) { for (const auto &vh : values_and_holders(it->second)) {
if (vh.type == type) if (vh.type == type) {
return handle((PyObject *) it->second); return handle((PyObject *) it->second);
} }
} }
}
return handle(); return handle();
} }
inline PyThreadState *get_thread_state_unchecked() { inline PyThreadState *get_thread_state_unchecked() {
#if defined(PYPY_VERSION) #if defined(PYPY_VERSION)
return PyThreadState_GET(); return PyThreadState_GET();
#elif PY_VERSION_HEX < 0x03000000
return _PyThreadState_Current;
#elif PY_VERSION_HEX < 0x03050000
return (PyThreadState*) _Py_atomic_load_relaxed(&_PyThreadState_Current);
#elif PY_VERSION_HEX < 0x03050200
return (PyThreadState*) _PyThreadState_Current.value;
#else #else
return _PyThreadState_UncheckedGet(); return _PyThreadState_UncheckedGet();
#endif #endif
@ -526,27 +503,30 @@ public:
explicit type_caster_generic(const type_info *typeinfo) explicit type_caster_generic(const type_info *typeinfo)
: typeinfo(typeinfo), cpptype(typeinfo ? typeinfo->cpptype : nullptr) {} : typeinfo(typeinfo), cpptype(typeinfo ? typeinfo->cpptype : nullptr) {}
bool load(handle src, bool convert) { bool load(handle src, bool convert) { return load_impl<type_caster_generic>(src, convert); }
return load_impl<type_caster_generic>(src, convert);
}
PYBIND11_NOINLINE static handle cast(const void *_src, return_value_policy policy, handle parent, PYBIND11_NOINLINE static handle cast(const void *_src,
return_value_policy policy,
handle parent,
const detail::type_info *tinfo, const detail::type_info *tinfo,
void *(*copy_constructor)(const void *), void *(*copy_constructor)(const void *),
void *(*move_constructor)(const void *), void *(*move_constructor)(const void *),
const void *existing_holder = nullptr) { const void *existing_holder = nullptr) {
if (!tinfo) // no type info: error will be set already if (!tinfo) { // no type info: error will be set already
return handle(); return handle();
}
void *src = const_cast<void *>(_src); void *src = const_cast<void *>(_src);
if (src == nullptr) if (src == nullptr) {
return none().release(); return none().release();
}
if (handle registered_inst = find_registered_python_instance(src, tinfo)) if (handle registered_inst = find_registered_python_instance(src, tinfo)) {
return registered_inst; return registered_inst;
}
auto inst = reinterpret_steal<object>(make_new_instance(tinfo->type)); auto inst = reinterpret_steal<object>(make_new_instance(tinfo->type));
auto wrapper = reinterpret_cast<instance *>(inst.ptr()); auto *wrapper = reinterpret_cast<instance *>(inst.ptr());
wrapper->owned = false; wrapper->owned = false;
void *&valueptr = values_and_holders(wrapper).begin()->value_ptr(); void *&valueptr = values_and_holders(wrapper).begin()->value_ptr();
@ -564,37 +544,39 @@ public:
break; break;
case return_value_policy::copy: case return_value_policy::copy:
if (copy_constructor) if (copy_constructor) {
valueptr = copy_constructor(src); valueptr = copy_constructor(src);
else { } else {
#if defined(NDEBUG) #if defined(PYBIND11_DETAILED_ERROR_MESSAGES)
throw cast_error("return_value_policy = copy, but type is "
"non-copyable! (compile in debug mode for details)");
#else
std::string type_name(tinfo->cpptype->name()); std::string type_name(tinfo->cpptype->name());
detail::clean_type_id(type_name); detail::clean_type_id(type_name);
throw cast_error("return_value_policy = copy, but type " + throw cast_error("return_value_policy = copy, but type " + type_name
type_name + " is non-copyable!"); + " is non-copyable!");
#else
throw cast_error("return_value_policy = copy, but type is "
"non-copyable! (#define PYBIND11_DETAILED_ERROR_MESSAGES or "
"compile in debug mode for details)");
#endif #endif
} }
wrapper->owned = true; wrapper->owned = true;
break; break;
case return_value_policy::move: case return_value_policy::move:
if (move_constructor) if (move_constructor) {
valueptr = move_constructor(src); valueptr = move_constructor(src);
else if (copy_constructor) } else if (copy_constructor) {
valueptr = copy_constructor(src); valueptr = copy_constructor(src);
else { } else {
#if defined(NDEBUG) #if defined(PYBIND11_DETAILED_ERROR_MESSAGES)
throw cast_error("return_value_policy = move, but type is neither "
"movable nor copyable! "
"(compile in debug mode for details)");
#else
std::string type_name(tinfo->cpptype->name()); std::string type_name(tinfo->cpptype->name());
detail::clean_type_id(type_name); detail::clean_type_id(type_name);
throw cast_error("return_value_policy = move, but type " + throw cast_error("return_value_policy = move, but type " + type_name
type_name + " is neither movable nor copyable!"); + " is neither movable nor copyable!");
#else
throw cast_error("return_value_policy = move, but type is neither "
"movable nor copyable! "
"(#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in "
"debug mode for details)");
#endif #endif
} }
wrapper->owned = true; wrapper->owned = true;
@ -620,23 +602,25 @@ public:
auto *&vptr = v_h.value_ptr(); auto *&vptr = v_h.value_ptr();
// Lazy allocation for unallocated values: // Lazy allocation for unallocated values:
if (vptr == nullptr) { if (vptr == nullptr) {
auto *type = v_h.type ? v_h.type : typeinfo; const auto *type = v_h.type ? v_h.type : typeinfo;
if (type->operator_new) { if (type->operator_new) {
vptr = type->operator_new(type->type_size); vptr = type->operator_new(type->type_size);
} else { } else {
#if defined(__cpp_aligned_new) && (!defined(_MSC_VER) || _MSC_VER >= 1912) #if defined(__cpp_aligned_new) && (!defined(_MSC_VER) || _MSC_VER >= 1912)
if (type->type_align > __STDCPP_DEFAULT_NEW_ALIGNMENT__) if (type->type_align > __STDCPP_DEFAULT_NEW_ALIGNMENT__) {
vptr = ::operator new(type->type_size, vptr = ::operator new(type->type_size, std::align_val_t(type->type_align));
std::align_val_t(type->type_align)); } else {
else
#endif
vptr = ::operator new(type->type_size); vptr = ::operator new(type->type_size);
} }
#else
vptr = ::operator new(type->type_size);
#endif
}
} }
value = vptr; value = vptr;
} }
bool try_implicit_casts(handle src, bool convert) { bool try_implicit_casts(handle src, bool convert) {
for (auto &cast : typeinfo->implicit_casts) { for (const auto &cast : typeinfo->implicit_casts) {
type_caster_generic sub_caster(*cast.first); type_caster_generic sub_caster(*cast.first);
if (sub_caster.load(src, convert)) { if (sub_caster.load(src, convert)) {
value = cast.second(sub_caster.value); value = cast.second(sub_caster.value);
@ -647,17 +631,19 @@ public:
} }
bool try_direct_conversions(handle src) { bool try_direct_conversions(handle src) {
for (auto &converter : *typeinfo->direct_conversions) { for (auto &converter : *typeinfo->direct_conversions) {
if (converter(src.ptr(), value)) if (converter(src.ptr(), value)) {
return true; return true;
} }
}
return false; return false;
} }
void check_holder_compat() {} void check_holder_compat() {}
PYBIND11_NOINLINE static void *local_load(PyObject *src, const type_info *ti) { PYBIND11_NOINLINE static void *local_load(PyObject *src, const type_info *ti) {
auto caster = type_caster_generic(ti); auto caster = type_caster_generic(ti);
if (caster.load(src, false)) if (caster.load(src, false)) {
return caster.value; return caster.value;
}
return nullptr; return nullptr;
} }
@ -666,16 +652,19 @@ public:
PYBIND11_NOINLINE bool try_load_foreign_module_local(handle src) { PYBIND11_NOINLINE bool try_load_foreign_module_local(handle src) {
constexpr auto *local_key = PYBIND11_MODULE_LOCAL_ID; constexpr auto *local_key = PYBIND11_MODULE_LOCAL_ID;
const auto pytype = type::handle_of(src); const auto pytype = type::handle_of(src);
if (!hasattr(pytype, local_key)) if (!hasattr(pytype, local_key)) {
return false; return false;
}
type_info *foreign_typeinfo = reinterpret_borrow<capsule>(getattr(pytype, local_key)); type_info *foreign_typeinfo = reinterpret_borrow<capsule>(getattr(pytype, local_key));
// Only consider this foreign loader if actually foreign and is a loader of the correct cpp type // Only consider this foreign loader if actually foreign and is a loader of the correct cpp
// type
if (foreign_typeinfo->module_local_load == &local_load if (foreign_typeinfo->module_local_load == &local_load
|| (cpptype && !same_type(*cpptype, *foreign_typeinfo->cpptype))) || (cpptype && !same_type(*cpptype, *foreign_typeinfo->cpptype))) {
return false; return false;
}
if (auto result = foreign_typeinfo->module_local_load(src.ptr(), foreign_typeinfo)) { if (auto *result = foreign_typeinfo->module_local_load(src.ptr(), foreign_typeinfo)) {
value = result; value = result;
return true; return true;
} }
@ -687,8 +676,12 @@ public:
// logic (without having to resort to virtual inheritance). // logic (without having to resort to virtual inheritance).
template <typename ThisT> template <typename ThisT>
PYBIND11_NOINLINE bool load_impl(handle src, bool convert) { PYBIND11_NOINLINE bool load_impl(handle src, bool convert) {
if (!src) return false; if (!src) {
if (!typeinfo) return try_load_foreign_module_local(src); return false;
}
if (!typeinfo) {
return try_load_foreign_module_local(src);
}
auto &this_ = static_cast<ThisT &>(*this); auto &this_ = static_cast<ThisT &>(*this);
this_.check_holder_compat(); this_.check_holder_compat();
@ -703,7 +696,7 @@ public:
} }
// Case 2: We have a derived class // Case 2: We have a derived class
if (PyType_IsSubtype(srctype, typeinfo->type)) { if (PyType_IsSubtype(srctype, typeinfo->type)) {
auto &bases = all_type_info(srctype); const auto &bases = all_type_info(srctype);
bool no_cpp_mi = typeinfo->simple_type; bool no_cpp_mi = typeinfo->simple_type;
// Case 2a: the python type is a Python-inherited derived class that inherits from just // Case 2a: the python type is a Python-inherited derived class that inherits from just
@ -716,41 +709,45 @@ public:
this_.load_value(reinterpret_cast<instance *>(src.ptr())->get_value_and_holder()); this_.load_value(reinterpret_cast<instance *>(src.ptr())->get_value_and_holder());
return true; return true;
} }
// Case 2b: the python type inherits from multiple C++ bases. Check the bases to see if // Case 2b: the python type inherits from multiple C++ bases. Check the bases to see
// we can find an exact match (or, for a simple C++ type, an inherited match); if so, we // if we can find an exact match (or, for a simple C++ type, an inherited match); if
// can safely reinterpret_cast to the relevant pointer. // so, we can safely reinterpret_cast to the relevant pointer.
if (bases.size() > 1) { if (bases.size() > 1) {
for (auto base : bases) { for (auto *base : bases) {
if (no_cpp_mi ? PyType_IsSubtype(base->type, typeinfo->type) : base->type == typeinfo->type) { if (no_cpp_mi ? PyType_IsSubtype(base->type, typeinfo->type)
this_.load_value(reinterpret_cast<instance *>(src.ptr())->get_value_and_holder(base)); : base->type == typeinfo->type) {
this_.load_value(
reinterpret_cast<instance *>(src.ptr())->get_value_and_holder(base));
return true; return true;
} }
} }
} }
// Case 2c: C++ multiple inheritance is involved and we couldn't find an exact type match // Case 2c: C++ multiple inheritance is involved and we couldn't find an exact type
// in the registered bases, above, so try implicit casting (needed for proper C++ casting // match in the registered bases, above, so try implicit casting (needed for proper C++
// when MI is involved). // casting when MI is involved).
if (this_.try_implicit_casts(src, convert)) if (this_.try_implicit_casts(src, convert)) {
return true; return true;
} }
}
// Perform an implicit conversion // Perform an implicit conversion
if (convert) { if (convert) {
for (auto &converter : typeinfo->implicit_conversions) { for (const auto &converter : typeinfo->implicit_conversions) {
auto temp = reinterpret_steal<object>(converter(src.ptr(), typeinfo->type)); auto temp = reinterpret_steal<object>(converter(src.ptr(), typeinfo->type));
if (load_impl<ThisT>(temp, false)) { if (load_impl<ThisT>(temp, false)) {
loader_life_support::add_patient(temp); loader_life_support::add_patient(temp);
return true; return true;
} }
} }
if (this_.try_direct_conversions(src)) if (this_.try_direct_conversions(src)) {
return true; return true;
} }
}
// Failed to match local typeinfo. Try again with global. // Failed to match local typeinfo. Try again with global.
if (typeinfo->module_local) { if (typeinfo->module_local) {
if (auto gtype = get_global_type_info(*typeinfo->cpptype)) { if (auto *gtype = get_global_type_info(*typeinfo->cpptype)) {
typeinfo = gtype; typeinfo = gtype;
return load(src, false); return load(src, false);
} }
@ -764,7 +761,9 @@ public:
// Custom converters didn't take None, now we convert None to nullptr. // Custom converters didn't take None, now we convert None to nullptr.
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;
}
value = nullptr; value = nullptr;
return true; return true;
} }
@ -772,14 +771,16 @@ public:
return false; return false;
} }
// Called to do type lookup and wrap the pointer and type in a pair when a dynamic_cast // Called to do type lookup and wrap the pointer and type in a pair when a dynamic_cast
// isn't needed or can't be used. If the type is unknown, sets the error and returns a pair // isn't needed or can't be used. If the type is unknown, sets the error and returns a pair
// with .second = nullptr. (p.first = nullptr is not an error: it becomes None). // with .second = nullptr. (p.first = nullptr is not an error: it becomes None).
PYBIND11_NOINLINE static std::pair<const void *, const type_info *> src_and_type( PYBIND11_NOINLINE static std::pair<const void *, const type_info *>
const void *src, const std::type_info &cast_type, const std::type_info *rtti_type = nullptr) { src_and_type(const void *src,
if (auto *tpi = get_type_info(cast_type)) const std::type_info &cast_type,
const std::type_info *rtti_type = nullptr) {
if (auto *tpi = get_type_info(cast_type)) {
return {src, const_cast<const type_info *>(tpi)}; return {src, const_cast<const type_info *>(tpi)};
}
// Not found, set error: // Not found, set error:
std::string tname = rtti_type ? rtti_type->name() : cast_type.name(); std::string tname = rtti_type ? rtti_type->name() : cast_type.name();
@ -802,8 +803,7 @@ public:
* `movable_cast_op_type` instead. * `movable_cast_op_type` instead.
*/ */
template <typename T> template <typename T>
using cast_op_type = using cast_op_type = conditional_t<std::is_pointer<remove_reference_t<T>>::value,
conditional_t<std::is_pointer<remove_reference_t<T>>::value,
typename std::add_pointer<intrinsic_t<T>>::type, typename std::add_pointer<intrinsic_t<T>>::type,
typename std::add_lvalue_reference<intrinsic_t<T>>::type>; typename std::add_lvalue_reference<intrinsic_t<T>>::type>;
@ -815,8 +815,8 @@ using cast_op_type =
* These operator are automatically provided when using the PYBIND11_TYPE_CASTER macro. * These operator are automatically provided when using the PYBIND11_TYPE_CASTER macro.
*/ */
template <typename T> template <typename T>
using movable_cast_op_type = using movable_cast_op_type
conditional_t<std::is_pointer<typename std::remove_reference<T>::type>::value, = conditional_t<std::is_pointer<typename std::remove_reference<T>::type>::value,
typename std::add_pointer<intrinsic_t<T>>::type, typename std::add_pointer<intrinsic_t<T>>::type,
conditional_t<std::is_rvalue_reference<T>::value, conditional_t<std::is_rvalue_reference<T>::value,
typename std::add_rvalue_reference<intrinsic_t<T>>::type, typename std::add_rvalue_reference<intrinsic_t<T>>::type,
@ -824,31 +824,41 @@ using movable_cast_op_type =
// std::is_copy_constructible isn't quite enough: it lets std::vector<T> (and similar) through when // std::is_copy_constructible isn't quite enough: it lets std::vector<T> (and similar) through when
// T is non-copyable, but code containing such a copy constructor fails to actually compile. // T is non-copyable, but code containing such a copy constructor fails to actually compile.
template <typename T, typename SFINAE = void> struct is_copy_constructible : std::is_copy_constructible<T> {}; template <typename T, typename SFINAE = void>
struct is_copy_constructible : std::is_copy_constructible<T> {};
// Specialization for types that appear to be copy constructible but also look like stl containers // Specialization for types that appear to be copy constructible but also look like stl containers
// (we specifically check for: has `value_type` and `reference` with `reference = value_type&`): if // (we specifically check for: has `value_type` and `reference` with `reference = value_type&`): if
// so, copy constructability depends on whether the value_type is copy constructible. // so, copy constructability depends on whether the value_type is copy constructible.
template <typename Container> struct is_copy_constructible<Container, enable_if_t<all_of< template <typename Container>
std::is_copy_constructible<Container>, struct is_copy_constructible<
Container,
enable_if_t<
all_of<std::is_copy_constructible<Container>,
std::is_same<typename Container::value_type &, typename Container::reference>, std::is_same<typename Container::value_type &, typename Container::reference>,
// Avoid infinite recursion // Avoid infinite recursion
negation<std::is_same<Container, typename Container::value_type>> negation<std::is_same<Container, typename Container::value_type>>>::value>>
>::value>> : is_copy_constructible<typename Container::value_type> {}; : is_copy_constructible<typename Container::value_type> {};
// Likewise for std::pair // Likewise for std::pair
// (after C++17 it is mandatory that the copy constructor not exist when the two types aren't themselves // (after C++17 it is mandatory that the copy constructor not exist when the two types aren't
// copy constructible, but this can not be relied upon when T1 or T2 are themselves containers). // themselves copy constructible, but this can not be relied upon when T1 or T2 are themselves
template <typename T1, typename T2> struct is_copy_constructible<std::pair<T1, T2>> // containers).
template <typename T1, typename T2>
struct is_copy_constructible<std::pair<T1, T2>>
: all_of<is_copy_constructible<T1>, is_copy_constructible<T2>> {}; : all_of<is_copy_constructible<T1>, is_copy_constructible<T2>> {};
// The same problems arise with std::is_copy_assignable, so we use the same workaround. // The same problems arise with std::is_copy_assignable, so we use the same workaround.
template <typename T, typename SFINAE = void> struct is_copy_assignable : std::is_copy_assignable<T> {}; template <typename T, typename SFINAE = void>
template <typename Container> struct is_copy_assignable<Container, enable_if_t<all_of< struct is_copy_assignable : std::is_copy_assignable<T> {};
std::is_copy_assignable<Container>, template <typename Container>
std::is_same<typename Container::value_type &, typename Container::reference> struct is_copy_assignable<Container,
>::value>> : is_copy_assignable<typename Container::value_type> {}; enable_if_t<all_of<std::is_copy_assignable<Container>,
template <typename T1, typename T2> struct is_copy_assignable<std::pair<T1, T2>> std::is_same<typename Container::value_type &,
typename Container::reference>>::value>>
: is_copy_assignable<typename Container::value_type> {};
template <typename T1, typename T2>
struct is_copy_assignable<std::pair<T1, T2>>
: all_of<is_copy_assignable<T1>, is_copy_assignable<T2>> {}; : all_of<is_copy_assignable<T1>, is_copy_assignable<T2>> {};
PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(detail)
@ -875,13 +885,11 @@ PYBIND11_NAMESPACE_END(detail)
// std::enable_if. User provided specializations will always have higher priority than // std::enable_if. User provided specializations will always have higher priority than
// the default implementation and specialization provided in polymorphic_type_hook_base. // the default implementation and specialization provided in polymorphic_type_hook_base.
template <typename itype, typename SFINAE = void> template <typename itype, typename SFINAE = void>
struct polymorphic_type_hook_base struct polymorphic_type_hook_base {
{
static const void *get(const itype *src, const std::type_info *&) { return src; } static const void *get(const itype *src, const std::type_info *&) { return src; }
}; };
template <typename itype> template <typename itype>
struct polymorphic_type_hook_base<itype, detail::enable_if_t<std::is_polymorphic<itype>::value>> struct polymorphic_type_hook_base<itype, detail::enable_if_t<std::is_polymorphic<itype>::value>> {
{
static const void *get(const itype *src, const std::type_info *&type) { static const void *get(const itype *src, const std::type_info *&type) {
type = src ? &typeid(*src) : nullptr; type = src ? &typeid(*src) : nullptr;
return dynamic_cast<const void *>(src); return dynamic_cast<const void *>(src);
@ -893,7 +901,8 @@ struct polymorphic_type_hook : public polymorphic_type_hook_base<itype> {};
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
/// Generic type caster for objects stored on the heap /// Generic type caster for objects stored on the heap
template <typename type> class type_caster_base : public type_caster_generic { template <typename type>
class type_caster_base : public type_caster_generic {
using itype = intrinsic_t<type>; using itype = intrinsic_t<type>;
public: public:
@ -903,8 +912,10 @@ public:
explicit type_caster_base(const std::type_info &info) : type_caster_generic(info) {} explicit type_caster_base(const std::type_info &info) : type_caster_generic(info) {}
static handle cast(const itype &src, return_value_policy policy, handle parent) { static handle cast(const itype &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);
} }
@ -916,7 +927,7 @@ public:
// polymorphic type (using RTTI by default, but can be overridden by specializing // polymorphic type (using RTTI by default, but can be overridden by specializing
// polymorphic_type_hook). If the instance isn't derived, returns the base version. // polymorphic_type_hook). If the instance isn't derived, returns the base version.
static std::pair<const void *, const type_info *> src_and_type(const itype *src) { static std::pair<const void *, const type_info *> src_and_type(const itype *src) {
auto &cast_type = typeid(itype); const auto &cast_type = typeid(itype);
const std::type_info *instance_type = nullptr; const std::type_info *instance_type = nullptr;
const void *vsrc = polymorphic_type_hook<itype>::get(src, instance_type); const void *vsrc = polymorphic_type_hook<itype>::get(src, instance_type);
if (instance_type && !same_type(cast_type, *instance_type)) { if (instance_type && !same_type(cast_type, *instance_type)) {
@ -928,50 +939,64 @@ public:
// except via a user-provided specialization of polymorphic_type_hook, // except via a user-provided specialization of polymorphic_type_hook,
// and the user has promised that no this-pointer adjustment is // and the user has promised that no this-pointer adjustment is
// required in that case, so it's OK to use static_cast. // required in that case, so it's OK to use static_cast.
if (const auto *tpi = get_type_info(*instance_type)) if (const auto *tpi = get_type_info(*instance_type)) {
return {vsrc, tpi}; return {vsrc, tpi};
} }
// Otherwise we have either a nullptr, an `itype` pointer, or an unknown derived pointer, so }
// don't do a cast // Otherwise we have either a nullptr, an `itype` pointer, or an unknown derived pointer,
// so don't do a cast
return type_caster_generic::src_and_type(src, cast_type, instance_type); return type_caster_generic::src_and_type(src, cast_type, instance_type);
} }
static handle cast(const itype *src, return_value_policy policy, handle parent) { static handle cast(const itype *src, return_value_policy policy, handle parent) {
auto st = src_and_type(src); auto st = src_and_type(src);
return type_caster_generic::cast( return type_caster_generic::cast(st.first,
st.first, policy, parent, st.second, policy,
make_copy_constructor(src), make_move_constructor(src)); parent,
st.second,
make_copy_constructor(src),
make_move_constructor(src));
} }
static handle cast_holder(const itype *src, const void *holder) { static handle cast_holder(const itype *src, const void *holder) {
auto st = src_and_type(src); auto st = src_and_type(src);
return type_caster_generic::cast( return type_caster_generic::cast(st.first,
st.first, return_value_policy::take_ownership, {}, st.second, return_value_policy::take_ownership,
nullptr, nullptr, holder); {},
st.second,
nullptr,
nullptr,
holder);
} }
template <typename T> using cast_op_type = detail::cast_op_type<T>; template <typename T>
using cast_op_type = detail::cast_op_type<T>;
// NOLINTNEXTLINE(google-explicit-constructor) // NOLINTNEXTLINE(google-explicit-constructor)
operator itype *() { return (type *) value; } operator itype *() { return (type *) value; }
// NOLINTNEXTLINE(google-explicit-constructor) // NOLINTNEXTLINE(google-explicit-constructor)
operator itype&() { if (!value) throw reference_cast_error(); return *((itype *) value); } operator itype &() {
if (!value) {
throw reference_cast_error();
}
return *((itype *) value);
}
protected: protected:
using Constructor = void *(*) (const void *); using Constructor = void *(*) (const void *);
/* Only enabled when the types are {copy,move}-constructible *and* when the type /* Only enabled when the types are {copy,move}-constructible *and* when the type
does not have a private operator new implementation. A comma operator is used in the decltype does not have a private operator new implementation. A comma operator is used in the
argument to apply SFINAE to the public copy/move constructors.*/ decltype argument to apply SFINAE to the public copy/move constructors.*/
template <typename T, typename = enable_if_t<is_copy_constructible<T>::value>> template <typename T, typename = enable_if_t<is_copy_constructible<T>::value>>
static auto make_copy_constructor(const T *) -> decltype(new T(std::declval<const T>()), Constructor{}) { static auto make_copy_constructor(const T *)
return [](const void *arg) -> void * { -> decltype(new T(std::declval<const T>()), Constructor{}) {
return new T(*reinterpret_cast<const T *>(arg)); return [](const void *arg) -> void * { return new T(*reinterpret_cast<const T *>(arg)); };
};
} }
template <typename T, typename = enable_if_t<std::is_move_constructible<T>::value>> template <typename T, typename = enable_if_t<std::is_move_constructible<T>::value>>
static auto make_move_constructor(const T *) -> decltype(new T(std::declval<T&&>()), Constructor{}) { static auto make_move_constructor(const T *)
-> decltype(new T(std::declval<T &&>()), Constructor{}) {
return [](const void *arg) -> void * { return [](const void *arg) -> void * {
return new T(std::move(*const_cast<T *>(reinterpret_cast<const T *>(arg)))); return new T(std::move(*const_cast<T *>(reinterpret_cast<const T *>(arg))));
}; };

View File

@ -20,11 +20,14 @@
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());
} }
} }
@ -34,8 +37,9 @@ PYBIND11_NOINLINE void clean_type_id(std::string &name) {
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

@ -25,6 +25,8 @@
#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>
@ -37,14 +39,17 @@
// 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)
@ -59,21 +64,28 @@ using EigenMapSparseMatrix = Eigen::MappedSparseMatrix<Scalar, Flags, StorageInd
#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!
@ -82,94 +94,114 @@ template <bool EigenRowMajor> struct EigenConformable {
// 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,
cols = Type::ColsAtCompileTime,
size = Type::SizeAtCompileTime; size = Type::SizeAtCompileTime;
static constexpr bool static constexpr bool row_major = Type::IsRowMajor,
row_major = Type::IsRowMajor, vector
vector = Type::IsVectorAtCompileTime, // At least one dimension has fixed size 1 = Type::IsVectorAtCompileTime, // At least one dimension has fixed size 1
fixed_rows = rows != Eigen::Dynamic, fixed_rows = rows != Eigen::Dynamic, fixed_cols = cols != Eigen::Dynamic,
fixed_cols = cols != Eigen::Dynamic,
fixed = size != Eigen::Dynamic, // Fully-fixed size fixed = size != Eigen::Dynamic, // Fully-fixed size
dynamic = !fixed_rows && !fixed_cols; // Fully-dynamic 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>;
static constexpr EigenIndex inner_stride
= if_zero<StrideType::InnerStrideAtCompileTime, 1>::value,
outer_stride = if_zero < StrideType::OuterStrideAtCompileTime, outer_stride = if_zero < StrideType::OuterStrideAtCompileTime,
vector ? size : row_major ? cols : rows>::value; vector ? size
static constexpr bool dynamic_stride = inner_stride == Eigen::Dynamic && outer_stride == Eigen::Dynamic; : row_major ? cols
static constexpr bool requires_row_major = !dynamic_stride && !vector && (row_major ? inner_stride : outer_stride) == 1; : rows > ::value;
static constexpr bool requires_col_major = !dynamic_stride && !vector && (row_major ? outer_stride : inner_stride) == 1; 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_cols = a.shape(1),
np_rstride = a.strides(0) / static_cast<ssize_t>(sizeof(Scalar)), np_rstride = a.strides(0) / static_cast<ssize_t>(sizeof(Scalar)),
np_cstride = a.strides(1) / static_cast<ssize_t>(sizeof(Scalar)); np_cstride = a.strides(1) / static_cast<ssize_t>(sizeof(Scalar));
if ((PYBIND11_SILENCE_MSVC_C4127(fixed_rows) && np_rows != rows) || if ((PYBIND11_SILENCE_MSVC_C4127(fixed_rows) && np_rows != rows)
(PYBIND11_SILENCE_MSVC_C4127(fixed_cols) && np_cols != cols)) || (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 false;
}
return {n, 1, stride}; 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); });
@ -255,28 +298,35 @@ struct type_caster<Type, enable_if_t<is_eigen_dense_plain<Type>::value>> {
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
@ -350,25 +402,26 @@ public:
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,19 +516,27 @@ 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;
@ -476,44 +546,63 @@ public:
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,7 +632,8 @@ 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>
@ -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,14 +666,18 @@ 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;
} }
@ -587,20 +685,22 @@ struct type_caster<Type, enable_if_t<is_eigen_sparse<Type>::value>> {
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,
const_name<(Type::IsRowMajor) != 0>("scipy.sparse.csr_matrix[",
"scipy.sparse.csc_matrix[")
+ npy_format_descriptor<Scalar>::name + const_name("]")); + npy_format_descriptor<Scalar>::name + const_name("]"));
}; };

View File

@ -19,19 +19,9 @@
# 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() { \ extern "C" PyObject *pybind11_init_impl_##name() { return pybind11_init_wrapper_##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,9 +265,10 @@ 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:
bool is_valid = true; bool is_valid = true;

View File

@ -11,19 +11,19 @@
#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
@ -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)
@ -25,12 +26,15 @@ 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,9 +62,10 @@ 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);
@ -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) }
if (tstate->gilstate_counter < 0) {
pybind11_fail("scoped_acquire::dec_ref(): reference count underflow!"); pybind11_fail("scoped_acquire::dec_ref(): reference count underflow!");
}
# endif # 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); const auto dist = static_cast<size_t>(leading - rpptr);
size_t remainder = 0; 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
@ -105,22 +100,21 @@ private:
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)
@ -132,14 +126,11 @@ public:
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,11 +211,13 @@ 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() {
redirect_stdout.reset(); redirect_stdout.reset();

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 {
@ -40,44 +78,39 @@ struct undefined_t { };
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> \
struct op_impl<op_##id, op_l, B, L, R> { \
static char const *name() { return "__" #id "__"; } \ static char const *name() { return "__" #id "__"; } \
static auto execute(const L &l, const R &r) -> decltype(expr) { return (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); } \ 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> { \ template <typename B, typename L, typename R> \
struct op_impl<op_##id, op_r, B, L, R> { \
static char const *name() { return "__" #rid "__"; } \ static char const *name() { return "__" #rid "__"; } \
static auto execute(const R &r, const L &l) -> decltype(expr) { return (expr); } \ static auto execute(const R &r, const L &l) -> decltype(expr) { return (expr); } \
static B execute_cast(const R &r, const L &l) { return B(expr); } \ static B execute_cast(const R &r, const L &l) { return B(expr); } \
@ -85,25 +118,30 @@ template <typename B, typename L, typename R> struct op_impl<op_##id, op_r, B, L
inline op_<op_##id, op_l, self_t, self_t> op(const self_t &, const self_t &) { \ inline op_<op_##id, op_l, self_t, self_t> op(const self_t &, const self_t &) { \
return op_<op_##id, op_l, self_t, self_t>(); \ return op_<op_##id, op_l, self_t, self_t>(); \
} \ } \
template <typename T> op_<op_##id, op_l, self_t, T> op(const self_t &, const 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>(); \ 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 &) { \ 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>(); \ 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> \
struct op_impl<op_##id, op_l, B, L, R> { \
static char const *name() { return "__" #id "__"; } \ static char const *name() { return "__" #id "__"; } \
static auto execute(L &l, const R &r) -> decltype(expr) { return 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); } \ 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 &) { \ 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>(); \ 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> \
struct op_impl<op_##id, op_u, B, L, undefined_t> { \
static char const *name() { return "__" #id "__"; } \ static char const *name() { return "__" #id "__"; } \
static auto execute(const L &l) -> decltype(expr) { return expr; } \ static auto execute(const L &l) -> decltype(expr) { return expr; } \
static B execute_cast(const L &l) { return B(expr); } \ static B execute_cast(const L &l) { return B(expr); } \

View File

@ -15,7 +15,6 @@ 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()) {}
@ -24,23 +23,35 @@ public:
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; }
@ -48,10 +59,10 @@ public:
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,15 +9,16 @@
#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.
@ -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,35 +72,40 @@ 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();
} }
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>
struct map_caster {
using key_conv = make_caster<Key>; 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;
@ -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>
struct type_caster<std::array<Type, Size>>
: array_caster<std::array<Type, Size>, Type, false, 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>
struct type_caster<std::set<Key, Compare, Alloc>>
: set_caster<std::set<Key, Compare, Alloc>, Key> {}; : 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>
struct type_caster<std::unordered_set<Key, Hash, Equal, Alloc>>
: set_caster<std::unordered_set<Key, Hash, Equal, Alloc>, Key> {}; : 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>
struct type_caster<std::map<Key, Value, Compare, Alloc>>
: map_caster<std::map<Key, Value, Compare, Alloc>, Key, Value> {}; : 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>
struct type_caster<std::unordered_map<Key, Value, Hash, Equal, Alloc>>
: map_caster<std::unordered_map<Key, Value, Hash, Equal, Alloc>, Key, Value> {}; : 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);
} }
@ -266,8 +306,9 @@ template<typename Type, typename Value = typename Type::value_type> struct optio
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
@ -318,7 +361,8 @@ 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...>> {
@ -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,33 +4,39 @@
#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)
# if __has_include(<filesystem>) && \
PY_VERSION_HEX >= 0x03060000 PY_VERSION_HEX >= 0x03060000
# include <filesystem> # include <filesystem>
# define PYBIND11_HAS_FILESYSTEM 1 # define PYBIND11_HAS_FILESYSTEM 1
# elif __has_include(<experimental/filesystem>)
# include <experimental/filesystem>
# define PYBIND11_HAS_EXPERIMENTAL_FILESYSTEM 1
# endif
# 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) {
@ -38,8 +44,7 @@ private:
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
} }
@ -50,7 +55,8 @@ private:
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")
.attr("Path")(reinterpret_steal<object>(py_str))
.release(); .release();
} }
return nullptr; return nullptr;
@ -68,7 +74,7 @@ public:
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,7 +82,7 @@ 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,17 +19,28 @@ 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;
}; };
@ -40,30 +51,33 @@ 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) {
@ -77,53 +91,55 @@ void vector_if_equal_operator(enable_if_t<is_comparable<Vector>::value, Class_>
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(
"append",
[](Vector &v, const T &value) { v.push_back(value); }, [](Vector &v, const T &value) { v.push_back(value); },
arg("x"), arg("x"),
"Add an item to the end of the list"); "Add an item to the end of the list");
@ -131,25 +147,20 @@ void vector_modifiers(enable_if_t<is_copy_constructible<typename Vector::value_t
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,24 +222,22 @@ 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);
@ -239,13 +253,16 @@ 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];
@ -254,21 +271,22 @@ void vector_modifiers(enable_if_t<is_copy_constructible<typename Vector::value_t
}, },
"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_>
@ -296,14 +316,17 @@ void vector_accessor(enable_if_t<!vector_needs_copy<Vector>::value, Class_> &cl)
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];
@ -311,10 +334,10 @@ void vector_accessor(enable_if_t<!vector_needs_copy<Vector>::value, Class_> &cl)
return_value_policy::reference_internal // ref + keepalive return_value_policy::reference_internal // ref + keepalive
); );
cl.def("__iter__", cl.def(
"__iter__",
[](Vector &v) { [](Vector &v) {
return make_iterator< return make_iterator<return_value_policy::reference_internal, ItType, ItType, T &>(
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 */
@ -328,52 +351,59 @@ void vector_accessor(enable_if_t<vector_needs_copy<Vector>::value, Class_> &cl)
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(
"__iter__",
[](Vector &v) { [](Vector &v) {
return make_iterator< return make_iterator<return_value_policy::copy, ItType, ItType, T>(v.begin(), v.end());
return_value_policy::copy, ItType, ItType, T>(
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(
"__repr__",
[name](Vector &v) { [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
@ -391,21 +421,32 @@ 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));
@ -415,10 +456,10 @@ 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;
@ -429,7 +470,8 @@ 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)
@ -444,7 +486,7 @@ class_<Vector, holder_type> bind_vector(handle scope, std::string const &name, A
// 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,83 +568,86 @@ 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()) it->second = v; if (it != m.end()) {
else m.emplace(k, v); it->second = 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
// reinserting
template <typename Map, typename Class_> template <typename Map, typename Class_>
void map_assignment(enable_if_t< void map_assignment(enable_if_t<!is_copy_assignable<typename Map::mapped_type>::value
!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 first... // value type is not copy assignable so the only way to insert it is to erase it
// 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__",
cl.def("__repr__",
[name](Map &m) { [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;
}; };
@ -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__", cl.def(
"__iter__",
[](Map &m) { return make_key_iterator(m.begin(), m.end()); }, [](Map &m) { return make_key_iterator(m.begin(), m.end()); },
keep_alive<0, 1>() /* Essential: keep map alive while iterator exists */ keep_alive<0, 1>() /* Essential: keep map alive while iterator exists */
); );
cl.def("keys", cl.def(
"keys",
[](Map &m) { return KeysView{m}; }, [](Map &m) { return KeysView{m}; },
keep_alive<0, 1>() /* Essential: keep map alive while view exists */ keep_alive<0, 1>() /* Essential: keep map alive while view exists */
); );
cl.def("values", cl.def(
"values",
[](Map &m) { return ValuesView{m}; }, [](Map &m) { return ValuesView{m}; },
keep_alive<0, 1>() /* Essential: keep map alive while view exists */ keep_alive<0, 1>() /* Essential: keep map alive while view exists */
); );
cl.def("items", cl.def(
"items",
[](Map &m) { return ItemsView{m}; }, [](Map &m) { return ItemsView{m}; },
keep_alive<0, 1>() /* Essential: keep map alive while view exists */ keep_alive<0, 1>() /* Essential: keep map alive while view exists */
); );
cl.def("__getitem__", 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.
f.write("int main (int, char **) { return 0; }") fname.write_text("int main (int, char **) { return 0; }", encoding="utf-8")
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]))
else: return exts
found = False
for path in paths:
for prefix, parent in package_dir.items(): for prefix, parent in package_dir.items():
if path.startswith(parent): if path.startswith(parent):
found = True
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, ".")
if prefix: if prefix:
qualified_name = prefix + "." + qualified_name qualified_name = prefix + "." + qualified_name
exts.append(Pybind11Extension(qualified_name, [path])) exts.append(Pybind11Extension(qualified_name, [path]))
if not found: break
raise ValueError( else:
"path {} is not a child of any of the directories listed " msg = (
"in 'package_dir' ({})".format(path, package_dir) f"path {path} is not a child of any of the directories listed "
f"in 'package_dir' ({package_dir})"
) )
raise ValueError(msg)
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,36 +1,36 @@
#!/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])
level = None
if len(flds) == 1: if len(flds) == 1:
level = "0" level = "0"
serial = 0 serial = 0
@ -40,14 +40,11 @@ def build_expected_version_hex(matches):
if level_serial.startswith(level): if level_serial.startswith(level):
serial = int(level_serial[len(level) :]) serial = int(level_serial[len(level) :])
break 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, globals, globals); Py_file_input,
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,7 +152,9 @@ 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;
} }
@ -157,7 +166,8 @@ public:
} }
// 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
@ -169,7 +179,8 @@ public:
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(m, "gil_acquire_funcaddr", PyModule_AddObject(
PyLong_FromVoidPtr(reinterpret_cast<void*>(&gil_acquire))); m, "gil_acquire_funcaddr", 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,10 +1,11 @@
#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 = "";
}; };
@ -50,6 +51,7 @@ 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 = "";
}; };
@ -62,13 +64,12 @@ PYBIND11_MAKE_OPAQUE(NonLocalVec);
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,9 +1,10 @@
#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 {
public: public:
@ -27,18 +28,21 @@ 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};
}; };
@ -59,50 +63,64 @@ 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;
@ -116,27 +134,34 @@ public:
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;
} }
@ -172,6 +197,7 @@ public:
/// 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,8 +30,14 @@ 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(); });
@ -39,7 +45,9 @@ PYBIND11_MODULE(pybind11_cross_module_tests, m) {
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) {
std::rethrow_exception(p);
}
} catch (const shared_exception &e) { } catch (const shared_exception &e) {
PyErr_SetString(PyExc_KeyError, e.what()); PyErr_SetString(PyExc_KeyError, e.what());
} }
@ -58,9 +66,9 @@ PYBIND11_MODULE(pybind11_cross_module_tests, m) {
// 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")
@ -86,6 +111,7 @@ PYBIND11_MODULE(pybind11_tests, m) {
.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;
@ -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,8 +11,7 @@
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<>())

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");
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
m_data = new float[(size_t) (rows * cols)]; 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,11 +59,16 @@ 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;
} }
@ -75,6 +85,7 @@ TEST_SUBMODULE(buffers, m) {
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,14 +111,16 @@ 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
@ -115,8 +129,7 @@ TEST_SUBMODULE(buffers, m) {
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}) {}
@ -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,9 +7,10 @@
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;
}; };
@ -53,14 +54,16 @@ class type_caster<ConstRefCasted> {
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,
const ConstRefCasted *,
conditional_t< conditional_t<
std::is_same<T_, const ConstRefCasted&>::value, const ConstRefCasted&, std::is_same<T_, const ConstRefCasted &>::value,
const ConstRefCasted &,
/// non-const /// non-const
conditional_t< conditional_t<std::is_same<remove_reference_t<T_>, ConstRefCasted *>::value,
std::is_same<remove_reference_t<T_>, ConstRefCasted*>::value, ConstRefCasted*, ConstRefCasted *,
conditional_t< conditional_t<std::is_same<T_, ConstRefCasted &>::value,
std::is_same<T_, ConstRefCasted&>::value, ConstRefCasted&, ConstRefCasted &,
/* else */ ConstRefCasted &&>>>>; /* else */ ConstRefCasted &&>>>>;
private: private:
@ -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", []() {
return std::string("abc\xd0"
"def");
});
m.def("bad_utf16_string", [=]() { return std::u16string({b16, char16_t(0xd800), z16}); }); m.def("bad_utf16_string", [=]() { return std::u16string({b16, char16_t(0xd800), z16}); });
#if PY_MAJOR_VERSION >= 3 // Under Python 2.7, invalid unicode UTF-32 characters didn't appear to trigger
// Under Python 2.7, invalid unicode UTF-32 characters don't appear to trigger UnicodeDecodeError // UnicodeDecodeError
m.def("bad_utf32_string", [=]() { return std::u32string({a32, char32_t(0xd800), z32}); }); m.def("bad_utf32_string", [=]() { return std::u32string({a32, char32_t(0xd800), z32}); });
if (PYBIND11_SILENCE_MSVC_C4127(sizeof(wchar_t) == 2)) 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("bad_wchar_string", [=]() {
#endif 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'; });
@ -133,28 +160,55 @@ TEST_SUBMODULE(builtin_casters, m) {
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
@ -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,23 +243,32 @@ 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(
"tuple_passthrough",
[](std::tuple<bool, std::string, int> input) {
return std::make_tuple(std::get<2>(input), std::get<1>(input), std::get<0>(input)); return std::make_tuple(std::get<2>(input), std::get<1>(input), std::get<0>(input));
}, "Return a triple in reversed order"); },
"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; });
@ -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);
@ -259,15 +326,18 @@ TEST_SUBMODULE(builtin_casters, m) {
// 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(
"refwrap_list",
[](bool copy) {
static IncType x1(1), x2(2); static IncType x1(1), x2(2);
py::list l; py::list l;
for (auto &f : {std::ref(x1), std::ref(x2)}) { for (const auto &f : {std::ref(x1), std::ref(x2)}) {
l.append(py::cast(f, copy ? py::return_value_policy::copy l.append(py::cast(
: py::return_value_policy::reference)); f, copy ? py::return_value_policy::copy : py::return_value_policy::reference));
} }
return l; return l;
}, "copy"_a); },
"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,7 +354,8 @@ 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; });
@ -306,5 +377,6 @@ TEST_SUBMODULE(builtin_casters, m) {
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,21 +202,20 @@ 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"
@ -224,18 +228,6 @@ 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.i32_str(long(-1)) == "-1" # noqa: F821 undefined name 'long'
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.i64_str(-999999999999) == "-999999999999"
assert m.u64_str(999999999999) == "999999999999" assert m.u64_str(999999999999) == "999999999999"
@ -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,8 +40,7 @@ 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:
@ -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) {
@ -51,8 +53,16 @@ TEST_SUBMODULE(callbacks, m) {
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) {
@ -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",
[](std::function<int(int)> f, bool expect_none = false) {
if (expect_none && f) {
throw std::runtime_error("Expected None to be converted to empty std::function"); throw std::runtime_error("Expected None to be converted to empty std::function");
}
return f; return f;
}, py::arg("f"), py::arg("expect_none")=false); },
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 {
@ -197,7 +211,8 @@ 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",
[](const std::function<double(py::iterable)> &sum_builtin, const py::iterable &i) {
return sum_builtin(i); return sum_builtin(i);
}); });
@ -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