Package: numba-passthru
Pass-through type for NumbaTools to ferry arbitrary Python objects throughnopythonmode. This is a stand-alone version of the Numba internal
implementationPR 3640.This has two typical use-cases:ferrying data structures not currently supported by Numba intoobjmodeblocks viaPassThruContainercreating extension types that are simplified representations of the Python class and keep a link to the
Python objectIt's not unlikely both can be avoided completely when starting from scratch but potentially require extensive
refactoring when moving Numba into an existing code base.Ferrying objects intoobjmodeusingPassThruContainerPassThruContainercan be used to make Python objects not supported bynopythonavailable insideobjmodeblocks. Outsideobjmodeblocks the only supported operation onPassThruContaineris==. Two instances are equal if the wrapped objects are identical, iea.obj is b.obj.In the following example an object unsupported innopythonis ferried into anobjmodeblock and
mutated there.from__future__importannotationsfromnumbaimportjit,objmodefromnumba_passthruimportPassThruContainerclassTestee:def__init__(self,value:int,invert=False):self.value=valueself.numba_will_not_like_this=compile('pass','N/A','single')def__gt__(self,other):# will be used in next examplereturnself.value>other.valuetestee=Testee(1)container=PassThruContainer(testee)@jit(nopython=True)defdo_something(container):withobjmode(value='int64'):setattr(container.obj,'value_2',2)value=container.obj.valuereturncontainer,valueresult,value=do_something(container)assertcontainerisresultassertvalue==1assertresult.obj.value_2==2There will be no speed-up for the code inside theobjmodeblock andcontaineris (un)boxed twice adding further
overhead. Hence, this only makes sense in rarely visited code-path and if refactoring into a more Numba friendly
form is not an option.Note that the example above already contains the most common pattern that is pretty unpleasant to refactor into a
Numba friendly form in requiring object identity being preserved throughnopython(assert container is result).
Obviously, this is a highly artificial requirement in this toy example but might get more real if the pass-through
object is part of conditional branches.Creating custom passthrough typesPassThroughContainerdoes not allow attribute access on the wrapped object innopythonand there is no way
to dispatch on the type of the wrapped object. To get both you can create a Numba extension type usingpass_thru_type.pass_thru_typeholds a pointer to thePyObjectand manages references.pass_thru_typecan be used like any
mem-managed member on anextension type. (Some
familiarity with Numba extension types is expected for the following.)Continuing the example above let's try to get the following code working innopython(another toy
example, no speed-up expected):deffind_max(testees:List[Testee])->Testee:result=testees[0]# testees must not be emptyfortesteeintestees[1:]:iftestee>result:result=testeereturnresultPassThroughContainerwill not help here as there would be no way to dispatch>toTestee.__gt__and there would
be no way to access.valuefromnopythoninsideTestee.__gt__. Still, sinceTestee.valueis the only attribute
being accessed fromnopythonthere is a realistic chance to get this working. Indeed, assuming we already had the
(un)boxer this is a straight forward Numba extension type:fromnumbaimportjit,typesfromnumba.extendingimportoverloadfromnumba.typing.typeofimporttypeof_implimportoperatorclassTesteeType(PassThruType):def__init__(self):super(TesteeType,self).__init__(name='Testee')testee_type=TesteeType()@typeof_impl.register(Testee)deftype_testee(val,context):returntestee_type@overload(operator.gt)deftestee_gt(self,other):ifselfistestee_typeandotheristestee_type:returnTestee.__gt__find_max=jit(nopython=True)(find_max)Trying to implement the (un)boxer to somehow pass the.numba_will_not_like_thisattribute aroundnopython(sharing
a dict between boxer/unboxer etc) is not straight forward to get working forfind_maxalone and it is impossible
to get the reference counting right in the general case. The clean approach is to have the Numba runtime manage the
references by putting a NRT managed reference to the original Python object onto the extension type's data model.pass_thru_typehelps with the boiler-plate of boxing/unboxing the requiredMemInfoPointer. ThePyObjectpassed into the unboxer can be unboxed directly into apass_thru_type. On the way out the originalPyObjectis
recovered  by boxing thepass_thru_type.fromnumbaimportcgutilsfromnumba.datamodelimportmodelsfromnumba.extendingimportmake_attribute_wrapper,overload,overload_method,register_modelfromnumba.pythonapiimportNativeValue,unbox,boxfromnumba.targets.hashingimport_Py_hash_tfromnumba_passthruimportpass_thru_type@register_model(TesteeType)classTesteeModel(models.StructModel):def__init__(self,dmm,fe_typ):members=[('parent',pass_thru_type),('value',types.intp),]super(TesteeModel,self).__init__(dmm,fe_typ,members)make_attribute_wrapper(TesteeType,'value','value')@unbox(TesteeType)defunbox_testee(typ,obj,context):testee=cgutils.create_struct_proxy(typ)(context.context,context.builder)testee.parent=context.unbox(pass_thru_type,obj).valuevalue=context.pyapi.object_getattr_string(obj,"value")native_value=context.unbox(types.intp,value)context.pyapi.decref(value)testee.value=native_value.valueis_error=cgutils.is_not_null(context.builder,context.pyapi.err_occurred())returnNativeValue(testee._getvalue(),is_error=is_error)@box(TesteeType)defbox_testee(typ,val,context):val=cgutils.create_struct_proxy(typ)(context.context,context.builder,value=val)obj=context.box(pass_thru_type,val.parent)returnobjGiven the implementation aboveTesteeTypeis immutable fromnopython(make_attribute_wrappercreates read-only
attributes). If you made a pass-through type mutable fromnopythonyou had to make sure to reflect changes back
to the Python object in the boxer. However, given theexperience with reflected lists and setsthere are good reasons to be careful about this.Upward compatibility noticeThis is a stand-alone version of NumbaPR 3640. Import ofPassThruType,pass_thru_type,PassThruContainer,pass_thru_container_typefromnumbais attempted first
hence you will get the Numba internal implementations once the PR has landed.This package contains an overload ofint(Opaque)(essentiallyptrtoint) that might break future Numba versions
if Numba created diverging implementations.This was considered too unlikely to put a version constraint on the Numba dependency (which would require a new release
ofnumba-passthruevery time a new Numba versions is released)