Metaprogramming: automating conversions between types - Printable Version +- Python Forum (https://python-forum.io) +-- Forum: General (https://python-forum.io/forum-1.html) +--- Forum: Code sharing (https://python-forum.io/forum-5.html) +--- Thread: Metaprogramming: automating conversions between types (/thread-37281.html) |
Metaprogramming: automating conversions between types - Gribouillis - May-24-2022 I tend to use more and more unary conversions between types in my programming, so here is a small utility module that I wrote today for composing automatically converter functions. Comments are welcomed! Edit: This new version allows circular conversion between types and has a richer API. # converter.py __version__ = '2022.05.23' from functools import partial, singledispatch import itertools as itt def _default_index(cls): raise TypeError('Cannot convert to/from type', cls) def _first_arg(i, *args): return i class LinearConverter: """Implements automatic composition of conversion functions This class implements composition of conversion functions between a sequence of n types T[0] -> T[1] -> T[2] -> ... -> T[n-1] assuming that we have n-1 or n unary conversion functions func[i]: T[i] -> T[i+1] then the converter offers a method to convert an object of type T[i] to type T[j] when i <= j by composing the successive conversion functions conv.convert(object, type) The converter uses generic functions to allow conversion of objects which type is a subtype of T[i]. The converter can also return a callable to convert one type into another fun = conv.converter(type_A, type_B) fun(obj) # converts an instance of type_A The constructor takes two arguments: LinearConverter(<seq of n types>, <seq of n-1 or n functions>) If the constructor is passed n unary conversion functions, the last one is supposed to convert type T[n-1] into T[0]. In this case the linear converter is said to be cyclic and it can also convert type T[i] to T[j] when j < i. The converter can indicate which ancestor type is actually used when passing an object of the given type tp = conv.used_type(type) This used type is one of the types passed to the converter's ctor. """ def __init__(self, iclass, ifunc): self.iclass = tuple(iclass) self.ifunc = tuple(ifunc) d = len(self.iclass) - len(self.ifunc) if d not in (0, 1): raise TypeError( 'Constructor expected n classes and n-1 or n functions') self._is_cyclic = not d self._index_gen = singledispatch(_default_index) for i, cls in enumerate(self.iclass): self._index_gen.register(cls)(partial(_first_arg, i)) def _index(self, cls): return self._index_gen.dispatch(cls)(cls) def convert(self, obj, cls): """Convert an object to another type by composing converters""" return self.converter(type(obj), cls)(obj) def converter(self, type_A, type_B): """Return a unary conversion function from one type to another""" i, j = self._index(type_A), self._index(type_B) if j < i: if self._is_cyclic: return partial(self._conversion_2, i, j) raise TypeError( 'Converter cannot convert', obj, 'to type', cls) return partial(self._conversion_1, i, j) def _conversion_1(self, i, j, obj): for f in self.ifunc[i:j]: obj = f(obj) return obj def _conversion_2(self, i, j, obj): for f in itt.chain(self.ifunc[i:], self.ifunc[:j]): obj = f(obj) return obj def is_cyclic(self): """Return a boolean indicating if the linear converter is cyclic""" return self._is_cyclic def used_type(self, type_A): """Return the actual ancestor type used when converting an instance of a given type""" return self.iclass[self._index(type_A)] if __name__ == '__main__': class ModuleName(str): pass from types import ModuleType from pathlib import Path import importlib def name_as_module(name): return importlib.import_module(name) def module_as_path(mod): return Path(mod.__file__) def path_as_tuple(path): return path.parts def tuple_as_module_name(t): if t[-1] in ('__init__.py', '__init__.pyc'): return ModuleName(t[-2]) elif t[-1].endswith(('.py', '.pyc')): return ModuleName(t[-1].rsplit('.', 1)[0]) else: raise ValueError(t) cv = LinearConverter( [ModuleName, ModuleType, Path, tuple], [ name_as_module, module_as_path, path_as_tuple, tuple_as_module_name]) t = cv.convert(ModuleName('subprocess'), tuple) print(t) p = cv.convert(importlib, Path) print(p) m = cv.convert(p, ModuleType) print(m)
|