def _get_by_path(bits, _globals):
	c = None
	for i, bit in enumerate(bits):
		try:
			c = globals()[bit] if c is None else getattr(c, bit)
		except (AttributeError, KeyError):
			c = __import__('.'.join(bits[:i+1]), _globals,
				fromlist=[bits[i+1]] if i+1 < len(bits) else [])
	return c

def get_by_path(path: str, _globals=None):
	""" Returns an object by <path>, importing modules if necessary """
	if _globals is None: _globals = list()
	return _get_by_path(path.split('.'), _globals)

def set_by_path(path: str, value, _globals=None):
	""" Sets an object by <path> to <value>, importing modules if
	    necessary """
	if _globals is None: _globals = list()
	bits = path.split('.')
	c = _get_by_path(bits[:-1], _globals)
	if not c is None:
		setattr(c, bits[-1], value)
	else:
		globals()[bits[-1]] = value

def create_ondemand_singleton(target, constr, *args, **kwargs):
	""" Creates an on-demand singleton at path <target> with <constr> """ 
	inst = None
	def get_instance():
		nonlocal inst
		if inst is None:
			inst = constr(*args, **kwargs)
			set_by_path(target, inst)
		return inst
	class _OnDemandSingletonStub:
		def __getattr__(self, name):
			return getattr(get_instance(), name)
		def __setattr__(self, name, value):
			setattr(get_instance(), name, value)
		def __delattr__(self, name):
			delattr(get_instance(), name)
	set_by_path(target, _OnDemandSingletonStub())

