I suppose how it can be check what type specified on Generic types in __init__
?
On python 3.10, it could not be.
On first, I found this page: Instantiate a type that is a TypeVar
and make some utility for get __orig_class__
.
And test on instancing time, there is no __orig_class__
attribute in that timing on self
.
Then I searched again I found this issue page: https://github.com/python/typing/issues/658.
I recognized that any python might have this limit.
Is there any other way?
The following is sample code (not original one). This code cannot be executed for the reasons stated above. (I haven't run it, so there may be some bugs too.)
from abc import ABC, abstractmethod
from typing import Iterable, Any, TypeVar, Generic
class BaseLoader(ABC):
"""
`BaseLoader` loads something and return some expected object.
- load a file from path(str)
- convert a raw data to some expected data (list like)
- implement `load` in inherited class.
"""
def __init__(self, path: str):
self._path = path
@abstractmethod
def load(self) -> Iterable:
pass
class SimpleLineLoader(BaseLoader):
"""
`SimpleTextBaseLoader` loads text data and return some converted lines.
- load a utf-8 text file.
- convert to a list of text lines.
- implement `_convert_line` in inherited class.
"""
def load(self):
with open(self._path, "r") as f:
lines = f.readlines()
converted_lines = [self._convert_line(line) for line in lines]
return converted_lines
@abstractmethod
def _convert_line(self, line: str) -> Any:
"""
`_convert_line` convert line string to another one.
"""
pass
class RawLineLoader(SimpleLineLoader):
"""
`RawLineLoader` loads raw data. nothing to do.
"""
def _convert_line(self, line):
"""
nothing to do. just return raw value.
"""
return line
class ElementTrimmedLineLoader(SimpleLineLoader):
"""
`ElementTrimmedLineLoader` loads lines and regards each line as comma(,) separated data.
it trims each tokens in the line and get back to the line.
- trims each element separated with comma in each line.
- sample:
>>> from tempfile import TemporaryDirectory
>>> data = ["a, b, d ", "d , e , f"]
>>> with TemporaryDirectory() as dname:
>>> with open(dname, "w") as f:
>>> f.writelines(data)
>>> loader = ElementTrimmedLineLoader(f.name)
>>> print(loader.load())
[['a', 'b', 'd'], ['d', 'e', 'f']]
"""
def __init__(self, path):
super().__init__(path)
self._delim = ","
def _convert_line(self, line):
"""
split with the delim and trim each element,
combine them to one with the delim again.
"""
delim = self._delim
converted = delim.join(elem.strip(' ') for elem in line.split(delim))
return converted
def get_delim(self):
"""
get a delimiter in use
"""
return self._delim
class BaseProcessor(ABC):
"""
`BaseProcessor` process `Iterable` and return a list of result of `_process_element`.
- implement `_process_element` in inherited class.
"""
def __init__(self, loader: BaseLoader, another_param: object):
self._loader = loader
self._another_param = another_param
def process(self, elements: Iterable) -> list[Any]:
data = self._loader.load()
result = []
for element in data:
output = self._process_element(element)
result.append(output)
# print(output)
return result
@abstractmethod
def _process_element(self, element: object) -> Any:
pass
options = {filename: "c:/some/constant/path.txt"}
T = TypeVar(T, bound=BaseLoader)
class SimpleBaseProcessor(BaseProcessor, Generic[T]):
"""
`SimpleBaseProcessor` process a constant file, and return raw values in situ.
- indicate `BaseLoader` class. (what data is expected as prerequisite)
- override `_process_element` in inherited class.
"""
def __init__(self, another_param: object):
loader_class = self.__orig_class__.__args__[0]
loader = loader_class(options["filename"])
super().__init__(loader, another_param)
def _process_element(self, element):
"""
nothing to do. just return raw value. (default)
"""
return element
class LineCountPrefixedProcessor(SimpleBaseProcessor[RawLineLoader]):
"""
`LineCountPrefixedProcessor` return a list of line count prefixed line strings.
"""
def __init__(self, another_param, prefix = "{0} | "):
super().__init__(another_param)
self._prefix = prefix
self._count = 0
def _process_element(self, element):
processed = self._prefix.format(self._count) + element
self._count += 1
return processed
class ElementQuoteProcessor(SimpleBaseProcessor[ElementTrimmedLineLoader]):
"""
`ElementQuoteProcessor` process trimmed elements and quote each of them.
"""
def __init__(self, another_param, quote_char = "'"):
super().__init__(another_param)
self._quote_char = quote_char
def _quote(self, target):
q = self._quote
return q + target + q
def _process_element(self, element):
delim = self._loader.get_delim()
return delim.join(self._quote(elem) for elem in element.split(delim))
When you write something like:
from typing import Generic, TypeVar
T = TypeVar('T')
class MyGeneric(Generic[T]):
def __init__(self):
print(getattr(self, '__orig_class__', None))
x = MyGeneric[int]()
You may see x.orig_class appear on some Python versions and not on others. That is an internal artifact of how the CPython interpreter (and the typing module) tracked Generic information in older versions. It was never specified in any PEP as a stable, user-facing API for “inspect your type parameters at runtime.”
Since standard Python discards generic parameters at runtime, why not just store them yourself
from typing import Generic, TypeVar, Type
T = TypeVar('T')
class MyGenericClass(Generic[T]):
def __init__(self, t: Type[T]):
self._t = t
x - MyGenericClass(int)
print(x._t)
Or just pass the value
class MyGenericClass(Generic[T]):
def __init(self, examplevalue: T):
self._t = type(examplevalue)