Что является хорошим подходом для создания базовых collections.abc
интерфейсов, которые можно легко обернуть в преобразователи ключ / значение?
Рассмотрим следующий код, который предоставляет интерфейс Mapping
для перечисления и чтения файлов.
from collections.abc import Mapping
import os
class FileReader(Mapping):
"""Read data from files under a given rootdir. Keys must be absolute file paths.
"""
def __init__(self, rootdir):
self.rootdir = rootdir
def __getitem__(self, k):
with open(k, 'rb') as fp:
data = fp.read()
return data
def __iter__(self):
yield from filter(os.path.isfile, map(lambda x: os.path.join(self.rootdir, x), os.listdir(self.rootdir)))
def __len__(self):
return sum(1 for _ in self)
Демонстрация того, что вы можете сделать с этим:
>>> fr = FileReader('/Users/twhalen/tmp/')
>>> list(fr) # list (full) file paths in folder
['/Users/twhalen/tmp/example.py', '/Users/twhalen/tmp/some.pkl']
>>> fr['/Users/twhalen/tmp/example.py'] # get binary contents of file
b'import collections as my_favorite_module\nprint("hello world")\n'
>>> fr['/Users/twhalen/tmp/some.pkl'] # get binary contents of file
b'\x80\x03]q\x00(K\x01]q\x01(K\x02K\x03ee.'
Мы можем изменить большую часть поведения вышеупомянутого класса, просто указав, как преобразовывать ключи и / или значения ,
Например, если мы хотим, чтобы ключи были выражены как относительные пути, а не полные пути, и чтобы значения, поступающие из файла .pkl
, были десериализованы с использованием pickle.load
, нам просто нужно обернуть наш класс, используя функции:
import pickle
from io import BytesIO
def newkey_to_oldkey(self, newkey):
return os.path.join(self.rootdir, newkey)
def oldkey_to_newkey(self, oldkey):
if oldkey.startswith(self.rootdir):
return oldkey[len(self.rootdir):]
else:
raise ValueError(f"{oldkey} should start with {self.rootdir}")
def oldval_to_newval(self, k, v):
if k.endswith('.pkl'):
return pickle.load(BytesIO(v))
return v
Один из способов обернуть наш оригинальный класс - это сделать:
class MyFileReader(FileReader):
def __getitem__(self, k):
oldkey = newkey_to_oldkey(self, k) # transform the incoming key (make a full path)
oldval = super().__getitem__(oldkey) # call the parent's getitem with the oldkey
return oldval_to_newval(self, k, oldval) # transform oldval before returning it to the user
def __iter__(self):
yield from (oldkey_to_newkey(self, oldkey) for oldkey in super().__iter__())
Демонстрация:
>>> fr = MyFileReader('/Users/twhalen/tmp/')
>>> list(fr)
['example.py', 'some.pkl']
>>> fr['example.py'] # contents of file (still raw binary)
b'import collections as my_favorite_module\nprint("hello world")\n'
>>> fr['some.pkl'] # contents of pkl file is given already deserialized!
[1, [2, 3]]
Это нормально делать с Небольшой пример, но он не подойдет, если у меня много разных MutableMapping
классов, содержащих более нескольких методов ключ / значение, и я хотел предложить пользователям простой и надежный способ обернуть классы своим собственным ключом / преобразователи значений.
Что-то более "автоматизированное" в порядке. Я предполагаю, что базовые классы MutableMapping будут аннотированы типами Key
и Value
, которые позволят соответствующим образом аннотированным преобразователям ключа / значения "автоматически знать", где их применять.
Я не уверен, какое сочетание подклассы, делегирование, декораторы и, возможно, даже метаклассы, достигли бы такой элегантности.