def get_getattr(self, typ, attr):
        """
        Get the getattr() implementation for the given type and attribute name.
        The return value is a callable with the signature
        (context, builder, typ, val, attr).
        """
        const_attr = (typ, attr) not in self.nonconst_module_attrs
        is_module = isinstance(typ, types.Module)
        if is_module and const_attr:
            # Implement getattr for module-level globals that we treat as
            # constants.
            # XXX We shouldn't have to retype this
            attrty = self.typing_context.resolve_module_constants(typ, attr)
            if attrty is None or isinstance(attrty, types.Dummy):
                # No implementation required for dummies (functions, modules...),
                # which are dealt with later
                return None
            else:
                pyval = getattr(typ.pymod, attr)
                def imp(context, builder, typ, val, attr):
                    llval = self.get_constant_generic(builder, attrty, pyval)
                    return impl_ret_borrowed(context, builder, attrty, llval)
                return imp

        # Lookup specific getattr implementation for this type and attribute
        overloads = self._getattrs[attr]
        try:
            return overloads.find((typ,))
        except errors.NumbaNotImplementedError:
            pass
        # Lookup generic getattr implementation for this type
        overloads = self._getattrs[None]
        try:
            return overloads.find((typ,))
        except errors.NumbaNotImplementedError:
            pass

        raise NotImplementedError("No definition for lowering %s.%s" % (typ, attr))

        Get the getattr() implementation for the given type and attribute name.
        The return value is a callable with the signature
        (context, builder, typ, val, attr).
        
numba/core/base.py