i2.util¶
Misc util objects
Functions
|
Context manager for file-like objects. |
|
The identity function: f(x) := x (takes only one argument, and returns it). |
|
Make a (shallow) copy of a function. |
|
Wrap |
|
Get |
|
Get stuff from a dict (or any Mapping), using dot_paths (i.e. 'foo.bar' instead of ['foo']['bar']). |
|
Ensure an iterable of identifiers |
|
Get the full path of a directory suitable for storing application-specific configs, (or data, or cache, or state or runtime) |
|
Get the body of a function as a (dedented) string, from its source code. |
The identity function: f(x) := x (takes only one argument, and returns it). |
|
|
Inject a method into an object instance (binding the function to it). |
|
Make several string-parametrized objects and insert them in a scope (e.g. locals()). |
|
Copy formatargspec from python 3.7 standard library. |
|
Whether |
|
Extract code of expression from lambda function. |
|
Creates and returns a new instance of a new class, suitable for usage as a "sentinel" since it is a kind of singleton (there can be only one instance of it.) |
|
Tries to find the (or "a") name for an object, even if |
|
Get items from a tree-structured object from a sequence of tree-traversal indices. |
|
Register an object (e.g. function, class) in the global registry. |
|
Return False, whatever the arguments. |
|
Return None, whatever the arguments. |
|
Return True, whatever the arguments. |
Classes
A read-only mapping with attribute access. |
|
A mutable mapping that provides both attribute and dictionary-style access. |
|
|
Context manager to catch exceptions of a certain type and instance condition. |
|
|
An immutable dict subtype that is hashable and can itself be used as a |
|
|
The FunctionBuilder type provides an interface for programmatically creating new functions, either based on existing functions or from scratch. |
|
An object to indicate that the value should be considered literally. |
Type of the |
|
|
Wraps a lambda function to make it picklable (through extracting its code) Also, provide it with a name, optionally. |
A dict whose mutating methods raise |
|
|
A descriptor implementation of lazyprop (cached property) from David Beazley's "Python Cookbook" book. |
Exceptions
Raised by |
|
Raised (and cached) when a |
|
Raised by |
|
Raise when a user is not allowed to overwrite a mapping's key |
- class i2.util.AttributeMapping[source]¶
Bases:
SimpleNamespace,Mapping[str,Any]A read-only mapping with attribute access.
Useful when you want mapping interface but don’t need mutation.
Examples
>>> ns = AttributeMapping(x=10, y=20) >>> ns.x 10 >>> ns['y'] 20 >>> list(ns) ['x', 'y']
- class i2.util.AttributeMutableMapping[source]¶
Bases:
AttributeMapping,MutableMapping[str,Any]A mutable mapping that provides both attribute and dictionary-style access.
Extends AttributeMapping with mutation capabilities, ensuring proper error handling and protocol compliance.
Examples
>>> ns = AttributeMutableMapping(apple=1, banana=2) >>> ns.apple 1 >>> ns['banana'] 2 >>> ns['cherry'] = 3 >>> ns.cherry 3 >>> list(ns) ['apple', 'banana', 'cherry'] >>> len(ns) 3 >>> 'apple' in ns True >>> del ns['banana'] >>> 'banana' in ns False
- class i2.util.ConditionalExceptionCatcher(exception_types, exception_condition=<function return_true>, handlers=<function asis>, *, prevent_propagation=True)[source]¶
Bases:
objectContext manager to catch exceptions of a certain type and instance condition.
- Parameters:
exception_types (
Union[BaseException,tuple[BaseException]]) – The type of exception to catch. Can be a single exception type or a tuple of exception types.exception_condition (
Callable[[BaseException],bool]) – A function that takes an exception instance and returns a “key” value indicating whether the exception should be caught. If the bool(key) is True, the exception is caught. If the bool(key) is False, the exception is not caught. The key can further be used to determine the handler to use, when the handlers argument is a mapping. The default is to catch all exceptions of the specified type(s).handlers (
Callable[[BaseException],None]) – Specification of how to handle the exceptions. Can be a single function to run on the exception object when an exception of the specified type is caught, or a mapping (e.g. dict) of handler functions, keyed by the key returned by the exception_condition function.prevent_propagation – Whether to prevent the exception from propagating. Defaults to
True.
Example
>>> exception_catcher = ConditionalExceptionCatcher( ... ValueError, lambda e: e.args[0] == 'foo', handlers=print ... ) >>> with exception_catcher: ... raise ValueError('foo') foo >>> with exception_catcher: ... raise TypeError('foo') Traceback (most recent call last): ... TypeError: foo >>> with exception_catcher: ... raise ValueError('bar') Traceback (most recent call last): ... ValueError: bar
- exception i2.util.ExistingArgument[source]¶
Bases:
ValueErrorRaised by
FunctionBuilder.add_argwhen the argument name is already taken.
- i2.util.FileLikeObject(file, *, io_cls=<class '_io.BytesIO'>, open_mode='rb')[source]¶
Context manager for file-like objects.
The purpose of this context manager is to be able to ensure we have a file-like object interface to work with, regardless of whether we are given a file path, bytes of a file, or an open file pointer.
- Parameters:
file – The file path, bytes of a file, or an open file pointer.
io_cls – Accepted for interface compatibility; not used by the current implementation (bytes are always wrapped in
io.BytesIO).open_mode – The mode
openis called with whenfileis a path.
- Yields:
A file-like object.
- class i2.util.FolderSpec(env_var, default_path)¶
Bases:
tuple- default_path¶
Alias for field number 1
- env_var¶
Alias for field number 0
- class i2.util.FrozenDict[source]¶
Bases:
dictAn immutable dict subtype that is hashable and can itself be used as a
dictkey orsetentry. Whatfrozensetis toset, FrozenDict is todict.There was once an attempt to introduce such a type to the standard library, but it was rejected: PEP 416.
Because FrozenDict is a
dictsubtype, it automatically works everywhere a dict would, including JSON serialization.- clear(*a, **kw)¶
raises a TypeError, because FrozenDicts are immutable
- classmethod fromkeys(keys, value=None)[source]¶
Create a new dictionary with keys from iterable and values set to value.
- pop(*a, **kw)¶
raises a TypeError, because FrozenDicts are immutable
- popitem(*a, **kw)¶
raises a TypeError, because FrozenDicts are immutable
- setdefault(*a, **kw)¶
raises a TypeError, because FrozenDicts are immutable
- update(*a, **kw)¶
raises a TypeError, because FrozenDicts are immutable
- updated(*a, **kw)[source]¶
Make a copy and add items from a dictionary or iterable (and/or keyword arguments), overwriting values under an existing key. See
dict.update()for more details.
- exception i2.util.FrozenHashError[source]¶
Bases:
TypeErrorRaised (and cached) when a
frozendictholds an unhashable value and is hashed.
- class i2.util.FunctionBuilder(name, **kw)[source]¶
Bases:
objectThe FunctionBuilder type provides an interface for programmatically creating new functions, either based on existing functions or from scratch.
Note
Based on https://boltons.readthedocs.io
Values are passed in at construction or set as attributes on the instance. For creating a new function based of an existing one, see the
from_func()classmethod. At any point,get_func()can be called to get a newly compiled function, based on the values configured.>>> fb = FunctionBuilder('return_five', doc='returns the integer 5', ... body='return 5') >>> f = fb.get_func() >>> f() 5 >>> fb.varkw = 'kw' >>> f_kw = fb.get_func() >>> f_kw(ignored_arg='ignored_val') 5Note that function signatures themselves changed quite a bit in Python 3, so several arguments are only applicable to FunctionBuilder in Python 3. Except for name, all arguments to the constructor are keyword arguments.
- Parameters:
name (str) – Name of the function.
module (str) – Name of the module from which this function was imported. Defaults to None.
body (str) – String version of the code representing the body of the function. Defaults to
'pass', which will result in a function which does nothing and returnsNone.args (list) – List of argument names, defaults to empty list, denoting no arguments.
varargs (str) – Name of the catch-all variable for positional arguments. E.g., “args” if the resultant function is to have
*argsin the signature. Defaults to None.varkw (str) – Name of the catch-all variable for keyword arguments. E.g., “kwargs” if the resultant function is to have
**kwargsin the signature. Defaults to None.defaults (tuple) – A tuple containing default argument values for those arguments that have defaults.
kwonlyargs (list) – Argument names which are only valid as keyword arguments. Python 3 only.
kwonlydefaults (dict) – A mapping, same as normal defaults, but only for the kwonlyargs. Python 3 only.
annotations (dict) – Mapping of type hints and so forth. Python 3 only.
filename (str) – The filename that will appear in tracebacks. Defaults to “boltons.funcutils.FunctionBuilder”.
indent (int) – Number of spaces with which to indent the function body. Values less than 1 will result in an error.
dict (dict) – Any other attributes which should be added to the functions compiled with this FunctionBuilder.
All of these arguments are also made available as attributes which can be mutated as necessary.
- add_arg(arg_name, default=Sentinel('NO_DEFAULT'), kwonly=False)[source]¶
Add an argument with optional default (defaults to
funcutils.NO_DEFAULT). Pass kwonly=True to add a keyword-only argument
- classmethod from_func(func)[source]¶
Create a new FunctionBuilder instance based on an existing function. The original function will not be stored or modified.
- get_defaults_dict()[source]¶
Get a dictionary of function arguments with defaults and the respective values.
- get_func(execdict=None, add_source=True, with_dict=True)[source]¶
Compile and return a new function based on the current values of the FunctionBuilder.
- Parameters:
execdict (dict) – The dictionary representing the scope in which the compilation should take place. Defaults to an empty dict.
add_source (bool) – Whether to add the source used to a special
__source__attribute on the resulting function. Defaults to True.with_dict (bool) – Add any custom attributes, if applicable. Defaults to True.
To see an example of usage, see the implementation of
wraps().
- get_sig_str(with_annotations=True)[source]¶
Return function signature as a string.
with_annotations is ignored on Python 2. On Python 3 signature will omit annotations if it is set to False.
- remove_arg(arg_name)[source]¶
Remove an argument from this FunctionBuilder’s argument list. The resulting function will have one less argument per call to this function.
- Parameters:
arg_name (str) – The name of the argument to remove.
Raises a
ValueErrorif the argument is not present.
- class i2.util.LiteralVal(val)[source]¶
Bases:
objectAn object to indicate that the value should be considered literally.
>>> t = LiteralVal(42) >>> t.get_val() 42 >>> t() 42- get_val()[source]¶
Get the value wrapped by Literal instance.
One might want to use
literal.get_val()insteadliteral()to get the value aLiteralis wrapping because.get_valis more explicit.That said, with a bit of hesitation, we allow the
literal()form as well since it is useful in situations where we need to use a callback function to get a value.
- exception i2.util.MissingArgument[source]¶
Bases:
ValueErrorRaised by
FunctionBuilder.remove_argwhen the argument is not in the function.
- class i2.util.NoDefault[source]¶
Bases:
objectType of the
no_defaultsentinel, marking the absence of a default value.
- exception i2.util.OverwritesForbidden[source]¶
Bases:
ValueErrorRaise when a user is not allowed to overwrite a mapping’s key
- class i2.util.PicklableLambda(func, name=None)[source]¶
Bases:
objectWraps a lambda function to make it picklable (through extracting its code) Also, provide it with a name, optionally.
>>> f = lambda x, y=0: x + y >>> ff = PicklableLambda(f) >>> import pickle >>> fff = pickle.loads(pickle.dumps(ff)) >>> assert fff(2, 3) == ff(2, 3) == f(2, 3)For lambda code-extraction see: https://stackoverflow.com/questions/73980648/how-to-transform-a-lambda-function-into-a-pickle-able-function
- i2.util.asis(x)[source]¶
The identity function: f(x) := x (takes only one argument, and returns it).
- Return type:
TypeVar(T)
>>> asis(3) 3
- i2.util.copy_func(func, *, copy_dict=True, code=None, globals_=None)[source]¶
Make a (shallow) copy of a function.
>>> f = lambda x, *, y=2: x * y >>> f.an_attr = 42 >>> f_copy = copy_func(f) >>> f_copy(3) == f(3) == 6 True >>> f_copy.an_attr == f.an_attr == 42 TrueVerify that making an attribute in one won’t create an attribute in the other:
>>> f.another_attr = 42 >>> hasattr(f_copy, 'another_attr') False >>> f_copy.yet_another_attr = 84 >>> hasattr(f, 'yet_another_attr') False- Parameters:
func (
Callable) – The function to be copied.copy_dict (
bool) – Indicates whether to copy the__dict__attribute of the function (any attributes set on the function instance). Defaults toTrue.code – The value to be used as the
__code__attribute of the copy.globals_ (
dict) – The value to be used as the__globals__attribute of the copy.
- Returns:
A shallow copy of the function.
Note that it should always work with proper functions and attempts to do the best job it can with other callables, but there are no guarantees on how
copy_funcwill behave with custom callables.If these custom callables don’t have a
__code__attribute, the copy will fail. Furthermore, if the custom callable doesn’t have__globals__, the empty dictionary will be used as the globals. We provide acodeandglobalsargument to allow the user to provide the__code__and__globals__attributes of the function to be copied.
- i2.util.deprecation_of(func, old_name)[source]¶
Wrap
funcso that each call emits a DeprecationWarning namingold_name.Bind the result to the old name to keep it importable while pointing users to
func.
- i2.util.dflt_idx_preprocessor(obj, idx)[source]¶
Get
idxfromobj: by item for ints, digit strings and Mappings, else by attribute.The default
getterofpath_extractor.>>> dflt_idx_preprocessor({"a": 1}, "a"), dflt_idx_preprocessor([10, 20], "1") (1, 20)- Raises:
KeyError – If
idxis neither an item nor an attribute ofobj.
- i2.util.dp_get(d, dot_path)[source]¶
Get stuff from a dict (or any Mapping), using dot_paths (i.e. ‘foo.bar’ instead of [‘foo’][‘bar’]).
>>> d = {'foo': {'bar': 2, 'alice': 'bob'}, 3: {'pi': 3.14}} >>> assert dp_get(d, 'foo') == {'bar': 2, 'alice': 'bob'} >>> assert dp_get(d, 'foo.bar') == 2 >>> assert dp_get(d, 'foo.alice') == 'bob'
- i2.util.ensure_identifiers(*objs, get_identfiers=<method 'split' of 'str' objects>, is_identifier=<method 'isidentifier' of 'str' objects>)[source]¶
Ensure an iterable of identifiers
>>> list(ensure_identifiers('these', 'are', 'valid', 'identifiers')) ['these', 'are', 'valid', 'identifiers']By default,
ensure_identifierswill applystr.splitto eachobjofobjs(assumed to be strings!) so that it can extract identifiers from space-separated strings of identifiers:>>> list(ensure_identifiers('these are valid identifiers')) ['these', 'are', 'valid', 'identifiers']You can control this functionality through the
get_identfiersargument, for example, disallowing such splitting, or enabling the extraction of identifiers from other objects than strings.>>> list(ensure_identifiers( ... {'this': 0, 'works': 1}, {'too': 2}, ... get_identfiers=list ... )) ['this', 'works', 'too']You can also control the
is_identifiervalidatation function:>>> def less_than_6_chars(s): return len(s) < 6 >>> list(ensure_identifiers('okay', 'too_long', is_identifier=less_than_6_chars)) Traceback (most recent call last): ... ValueError: too_long isn't an identifier according toless_than_6_chars
- i2.util.frozendict¶
alias of
FrozenDict
- i2.util.get_app_config_folder(*, folder_kind='config')¶
Get the full path of a directory suitable for storing application-specific configs, (or data, or cache, or state or runtime)
On Windows, this is typically %APPDATA%. On macOS, this is typically ~/.config. On Linux, this is typically ~/.config.
- Parameters:
folder_kind (str) – The kind of folder to get. One of ‘config’, ‘data’, ‘cache’, ‘state’, ‘runtime’. Defaults to ‘config’. Here are concise explanations for each folder kind: config: User preferences and settings files (e.g., API keys, theme preferences, editor settings). Files users might edit manually or that define how the app behaves. data: Essential user-created content and application state (e.g., databases, saved games, user documents, session files). Data that should be backed up and persists across updates. cache: Temporary, regeneratable files (e.g., downloaded images, compiled assets, web cache). Can be safely deleted to free space without losing user work. state: Application state and logs that persist between sessions but aren’t critical user data (e.g., command history, undo history, recently opened files, log files). Unlike cache, shouldn’t be auto-deleted. runtime: Temporary runtime files that only exist while the app runs (e.g., PID files, Unix sockets, lock files, named pipes). Typically cleared on logout/reboot. TL;DR: config = settings, data = user files, cache = disposable, state = logs/history, runtime = process files.
- Returns:
The full path of the app data folder.
- Return type:
- i2.util.get_app_data_folder(*, folder_kind='data')¶
Get the full path of a directory suitable for storing application-specific configs, (or data, or cache, or state or runtime)
On Windows, this is typically %APPDATA%. On macOS, this is typically ~/.config. On Linux, this is typically ~/.config.
- Parameters:
folder_kind (str) – The kind of folder to get. One of ‘config’, ‘data’, ‘cache’, ‘state’, ‘runtime’. Defaults to ‘config’. Here are concise explanations for each folder kind: config: User preferences and settings files (e.g., API keys, theme preferences, editor settings). Files users might edit manually or that define how the app behaves. data: Essential user-created content and application state (e.g., databases, saved games, user documents, session files). Data that should be backed up and persists across updates. cache: Temporary, regeneratable files (e.g., downloaded images, compiled assets, web cache). Can be safely deleted to free space without losing user work. state: Application state and logs that persist between sessions but aren’t critical user data (e.g., command history, undo history, recently opened files, log files). Unlike cache, shouldn’t be auto-deleted. runtime: Temporary runtime files that only exist while the app runs (e.g., PID files, Unix sockets, lock files, named pipes). Typically cleared on logout/reboot. TL;DR: config = settings, data = user files, cache = disposable, state = logs/history, runtime = process files.
- Returns:
The full path of the app data folder.
- Return type:
- i2.util.get_app_folder(folder_kind='config')[source]¶
Get the full path of a directory suitable for storing application-specific configs, (or data, or cache, or state or runtime)
On Windows, this is typically %APPDATA%. On macOS, this is typically ~/.config. On Linux, this is typically ~/.config.
- Parameters:
folder_kind (
Literal['config','data','cache','state','runtime']) – The kind of folder to get. One of ‘config’, ‘data’, ‘cache’, ‘state’, ‘runtime’. Defaults to ‘config’. Here are concise explanations for each folder kind: config: User preferences and settings files (e.g., API keys, theme preferences, editor settings). Files users might edit manually or that define how the app behaves. data: Essential user-created content and application state (e.g., databases, saved games, user documents, session files). Data that should be backed up and persists across updates. cache: Temporary, regeneratable files (e.g., downloaded images, compiled assets, web cache). Can be safely deleted to free space without losing user work. state: Application state and logs that persist between sessions but aren’t critical user data (e.g., command history, undo history, recently opened files, log files). Unlike cache, shouldn’t be auto-deleted. runtime: Temporary runtime files that only exist while the app runs (e.g., PID files, Unix sockets, lock files, named pipes). Typically cleared on logout/reboot. TL;DR: config = settings, data = user files, cache = disposable, state = logs/history, runtime = process files.- Returns:
The full path of the app data folder.
- Return type:
- i2.util.get_function_body(func)[source]¶
Get the body of a function as a (dedented) string, from its source code.
Decorator lines and the
defline(s) are dropped. Requires the source to be available throughinspect(not the case for functions defined in a REPL).>>> def f(x): ... y = x + 1 ... return y * 2 >>> print(get_function_body(f)) y = x + 1 return y * 2
- i2.util.ignore_exception(x)¶
The identity function: f(x) := x (takes only one argument, and returns it).
- Return type:
TypeVar(T)
>>> asis(3) 3
- class i2.util.imdict[source]¶
Bases:
dictA dict whose mutating methods raise
TypeError, hashable by identity.- clear() None. Remove all items from D.¶
- pop(k[, d]) v, remove specified key and return the corresponding value.¶
If the key is not found, return the default if given; otherwise, raise a KeyError.
- popitem(*args, **kws)¶
Remove and return a (key, value) pair as a 2-tuple.
Pairs are returned in LIFO (last-in, first-out) order. Raises KeyError if the dict is empty.
- setdefault(*args, **kws)¶
Insert key with a value of default if key is not in the dictionary.
Return the value for key if key is in the dictionary, else default.
- update([E, ]**F) None. Update D from mapping/iterable E and F.¶
If E is present and has a .keys() method, then does: for k in E.keys(): D[k] = E[k] If E is present and lacks a .keys() method, then does: for k, v in E: D[k] = v In either case, this is followed by: for k in F: D[k] = F[k]
- i2.util.inject_method(self, method_function, method_name=None)[source]¶
Inject a method into an object instance (binding the function to it).
method_functioncan be:a function (the method name is
method_name, or the function’s name)a
{method_name: function, ...}dict (for multiple injections)a list of functions or
(function, method_name)pairs
Returns the instance, mutated.
>>> class A: ... >>> a = A() >>> def greet(self, name): ... return f"hi {name} from {type(self).__name__}" >>> _ = inject_method(a, greet) >>> a.greet("bob") 'hi bob from A' >>> _ = inject_method(a, {"shout": lambda self, s: s.upper()}) >>> a.shout("x") 'X'
- i2.util.insert_name_based_objects_in_scope(*names, factory, scope, allow_overwrites=False)[source]¶
Make several string-parametrized objects and insert them in a scope (e.g. locals()).
This is useful when to avoid (error-prone) situations where we want the name we assign an object to, to be aligned with it’s internal name, such as:
foo = Factory('foo', ...) bar = Factory('bar', ...) baz = Factory('baz', ...)- Parameters:
names – Identifier (valid python variable name) strings. These are used both as arguments of the
factoryand as keys for thescopethe object the factory makes will be inserted under.factory (
Callable[[str],Any]) – A function that takes a (valid python identifier) string and returns an object parametrized by that string.scope (
MutableMapping) – TheMutableMappingwe want to insert the objects in.allow_overwrites (
bool) – Whether the objects we create can overwrite existing objects thescopemay already have. If we don’t allow overwrites and we try to write under an existing key, aOverwritesForbiddenerror will be raised. This also includes the situation where we have some duplicates innames.
- Returns:
None (this function has the side effect of inserting items in
scope.
One of the (controversal) uses of
insert_name_based_objects_in_scopeis to be able to make several string-parametrized>>> from collections import namedtuple >>> from functools import partial >>> >>> factory = partial(namedtuple, field_names='apple banana') >>> insert_namedtuples_in_locals = partial(insert_name_based_objects_in_scope, ... factory=factory, scope=locals(), allow_overwrites=True ... ) >>> insert_namedtuples_in_locals('foo bar', 'baz')And now
fooexists!>>> 'foo' in locals() True >>> foo(1,2) foo(apple=1, banana=2)And so does
barandbaz:>>> bar(3, banana=4) bar(apple=3, banana=4) >>> baz(apple=3, banana=4) baz(apple=3, banana=4)
- i2.util.inspect_formatargspec(args, varargs=None, varkw=None, defaults=None, kwonlyargs=(), kwonlydefaults={}, annotations={}, formatarg=<class 'str'>, formatvarargs=<function <lambda>>, formatvarkw=<function <lambda>>, formatvalue=<function <lambda>>, formatreturns=<function <lambda>>, formatannotation=<function formatannotation>)[source]¶
Copy formatargspec from python 3.7 standard library. Python 3 has deprecated formatargspec and requested that Signature be used instead, however this requires a full reimplementation of formatargspec() in terms of creating Parameter objects and such. Instead of introducing all the object-creation overhead and having to reinvent from scratch, just copy their compatibility routine.
- i2.util.lambda_code(lambda_func)[source]¶
Extract code of expression from lambda function. For lambda code-extraction see: https://stackoverflow.com/questions/73980648/how-to-transform-a-lambda-function-into-a-pickle-able-function
- Return type:
- class i2.util.lazyprop(func)[source]¶
Bases:
objectA descriptor implementation of lazyprop (cached property) from David Beazley’s “Python Cookbook” book. It’s
>>> class Test: ... def __init__(self, a): ... self.a = a ... @lazyprop ... def len(self): ... print('generating "len"') ... return len(self.a) >>> t = Test([0, 1, 2, 3, 4]) >>> t.__dict__ {'a': [0, 1, 2, 3, 4]} >>> t.len generating "len" 5 >>> t.__dict__ {'a': [0, 1, 2, 3, 4], 'len': 5} >>> t.len 5 >>> # But careful when using lazyprop that no one will change the value of a without deleting the property first >>> t.a = [0, 1, 2] # if we change a... >>> t.len # ... we still get the old cached value of len 5 >>> del t.len # if we delete the len prop >>> t.len # ... then len being recomputed again generating "len" 3
- i2.util.mk_sentinel(name, boolean_value=False, repr_=<function _default_sentinel_repr_method>, *, module=None)[source]¶
Creates and returns a new instance of a new class, suitable for usage as a “sentinel” since it is a kind of singleton (there can be only one instance of it.)
A frequent use case for sentinels are where we want to indicate that something is missing. Often, we use
Nonefor this, but sometimesNoneis a valid value in our context (see for example theinspect.Parameter.emptysentinel to indicate that an argument doesn’t have a default or annotation). Other times, we may want to distinguish different kinds of “nothing”.mk_sentinelcan help you create such sentinels, takes care of annoying details like pickability and allows you to control how to resolve your sentinel to a boolean.- Parameters:
name – The name of your sentinel. Will be used for
__name__attribute.boolean_value (
bool) – The boolean value that the sentinel instance should resolve to.repr_ (
str|Callable) – The method or string that should be used for the repr.module (
str|None) – The__module__to give the sentinel’s class (needed for pickling). By default it is taken from the calling frame’s__name__.
- Returns:
A sentinel instance
>>> Empty = mk_sentinel('Empty') >>> Empty Sentinel('Empty')By default, the boolean resolution of a sentinel is
False. Meaning:>>> Nothing = mk_sentinel('Nothing') >>> bool(Nothing) FalseThis is consistent with
None, so that you can check that an objectxis notNothingby doingif x: ...or idioms like:>>> x = Nothing >>> x = x or 'default' >>> x 'default'(Though note that in situations where other elements that cast to
Falseare valid values forx(like0,None, orFalseitself), it’s safer to useif x is not Nothing: ....)Anyway, I digress. Point is that in some situations, the semantics or usage of your sentinel is better align with True. You can control what the boolean resolution of your sentinel should be through the
boolean_valueargument:>>> Empty = mk_sentinel('Empty', boolean_value=True) >>> bool(Empty) TrueYou can also control what you see in the repr, specifying a string value;
>>> Empty = mk_sentinel('undefined', repr_='undefined') >>> Empty undefinedor a method;
>>> Empty = mk_sentinel('Empty', repr_=lambda self: f"<{self.__name__}>") >>> Empty <Empty>And yes, even though we used a lambda here, it’s still picklable:
>>> import pickle>>> Empty = mk_sentinel('Empty', repr_='Empty', module=__name__) >>> pickle.loads(pickle.dumps(Empty)) EmptyTalking about pickle, here’s some more info on that:
>>> unpickled_Empty = pickle.loads(pickle.dumps(Empty)) >>> # The unpickled version is "equal" to the original: >>> unpickled_Empty == Empty True >>> # the types are the same too: >>> type(unpickled_Empty) == type(Empty) True >>> >>>Note that though two sentinels might have the same name, they’re not equal:
>>> Empty = mk_sentinel('Empty') >>> AnotherEmptyWithSameName = mk_sentinel('Empty') >>> Empty Sentinel('Empty') >>> AnotherEmptyWithSameName Sentinel('Empty') >>> # but... >>> AnotherEmptyWithSameName == Empty False >>> # Note even the types are the same! >>> type(AnotherEmptyWithSameName) == type(Empty) FalseOne thing that makes the pickle work is that we took care of sticking in a
__module__for you.mk_sentinelfigures this out by some dark magic involving looking into the system’s “frames” etc. This may not always work since some systems (e.g.pypy) may use different “under-the-hood” methods.But if you want to control the value of
__module__yourself, you can, simply but indicating what the module of the sentinel is. Usually, you’ll just specify it asmodule=__name__, which will stick the name of the module you’re defining the sentinel in for you!>>> MySentinel = mk_sentinel('MySentinel', module=__name__)Thanks: Inspired greately from the
make_sentinelfunction ofboltons: See https://boltons.readthedocs.io/.
- i2.util.name_of_obj(o, *, base_name_of_obj=operator.attrgetter('__name__'), caught_exceptions=(<class 'AttributeError'>, ), default_factory=<function return_none>)[source]¶
Tries to find the (or “a”) name for an object, even if
__name__doesn’t exist.>>> name_of_obj(map) 'map' >>> name_of_obj([1, 2, 3]) 'list' >>> name_of_obj(print) 'print' >>> name_of_obj(lambda x: x) '<lambda>' >>> from functools import partial >>> name_of_obj(partial(print, sep=",")) 'print' >>> from functools import cached_property >>> class A: ... @property ... def prop(self): ... return 1.0 ... @cached_property ... def cached_prop(self): ... return 2.0 >>> name_of_obj(A.prop) 'prop' >>> name_of_obj(A.cached_prop) 'cached_prop'Note that
name_of_objuses the__name__attribute as its base way to get a name. You can customize this behavior though. For example, see that:>>> from inspect import Signature >>> name_of_obj(Signature.replace) 'replace'If you want to get the fully qualified name of an object, you can do:
>>> alt = partial(name_of_obj, base_name_of_obj=attrgetter('__qualname__')) >>> alt(Signature.replace) 'Signature.replace'
- i2.util.path_extractor(tree, path, getter=<function dflt_idx_preprocessor>, *, path_sep='.')[source]¶
Get items from a tree-structured object from a sequence of tree-traversal indices.
- Parameters:
tree – The object you want to extract values from: Can be any object you want, as long as the indices listed by path and how to get the items indexed are well specified by
pathandgetter.path – An iterable of indices that define how to traverse the tree to get to desired item(s). If this iterable is a string, the
path_separgument will be used to transform it into a tuple of string indices.getter – A
(tree, idx)function that specifies how to extract itemidxfrom thetreeobject.path_sep – The string separator to use if
pathis a string
- Returns:
The
treeitem(s) referenced bypath
>>> tree = {'a': {'b': [0, {'c': [1, 2, 3]}]}} >>> path_extractor(tree, path=['a']) {'b': [0, {'c': [1, 2, 3]}]} >>> path_extractor(tree, path=['a', 'b']) [0, {'c': [1, 2, 3]}] >>> path_extractor(tree, path=['a', 'b', 1]) {'c': [1, 2, 3]} >>> path_extractor(tree, path=['a', 'b', 1, 'c']) [1, 2, 3] >>> path_extractor(tree, path=('a', 'b', 1, 'c', 2)) 3You could do the same by specifying the path as a dot-separated string.
>>> path_extractor(tree, 'a.b.1.c.2') 3You can use any separation you want.
>>> path_extractor(tree, 'a/b/1/c/2', path_sep='/') 3You can also use
*to indicate that you want to keep all the nodes of a given level.>>> tree = {'a': [{'b': [1, 10]}, {'b': [2, 20]}, {'b': [3, 30]}]} >>> path_extractor(tree, 'a.*.b.1') [10, 20, 30]A generalization of
*is to specify a callable which will be intepreted as a filter function.>>> tree = {'a': [{'b': 1}, {'c': 2}, {'b': 3}, {'b': 4}]} >>> path_extractor(tree, ['a', lambda x: 'b' in x]) [{'b': 1}, {'b': 3}, {'b': 4}] >>> path_extractor(tree, ['a', lambda x: 'b' in x, 'b']) [1, 3, 4]
- i2.util.register_object(obj=None, name=None, *, registry)[source]¶
Register an object (e.g. function, class) in the global registry.
The raw use is to define a registry Mapping and then call this function with the registry and the object to register.
>>> registry = {} >>> def wet(): ... pass >>> register_object(wet, registry=registry) <function wet at 0x...> >>> registry {'wet': <function wet at 0x...>}>>> register_object(wet, name='custom_name', registry=registry) <function wet at 0x...> >>> registry {'wet': <function wet at 0x...>, 'custom_name': <function wet at 0x...>}The most common use of this function is to use it as a decorator with a fixed (but mutable!) registry:
>>> another_registry = {} >>> register_to_another = register_object(registry=another_registry) >>> @register_to_another ... def dry(): ... pass >>> another_registry {'dry': <function dry at 0x...>}>>> @register_to_another('DRY') ... def foo(): ... pass >>> another_registry {'dry': <function dry at 0x...>, 'DRY': <function foo at 0x...>}
- i2.util.return_false(*args, **kwargs)[source]¶
Return False, whatever the arguments.
>>> return_false(1, x=2) False