Расширение / упаковка Python коллекций - PullRequest
0 голосов
/ 25 января 2020

Что является хорошим подходом для создания базовых 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, которые позволят соответствующим образом аннотированным преобразователям ключа / значения "автоматически знать", где их применять.

Я не уверен, какое сочетание подклассы, делегирование, декораторы и, возможно, даже метаклассы, достигли бы такой элегантности.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...