__repr__ и __str__ не работают в конструкции класса Dynami c - PullRequest
1 голос
/ 18 марта 2020

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

Я достиг своей цели, за исключением этой маленькой ноющей вещи: я хотел __repr__ метод, но он никогда не работал.

Вот мой код:

import os
from glob import glob

path = r'C:\Users\eng_a\Downloads'

def browse(path):
    my_dict = {'_path': path}
    tmp = os.listdir(path)
    key_contents = []
    for akey in tmp:
        key_contents.append(akey.replace(".", "_").replace(" ", "_").replace("-", "_"))
    val_paths = glob(path + '//*')
    for akey, avalue in zip(key_contents, val_paths):
        if os.path.isfile(avalue):
            my_dict[akey] = avalue
        else:
            my_dict[akey] = browse(avalue)

    def func(self):
        return self._path
    my_dict["__repr__"] = func
    my_dict["__str__"] = func
    obj = type(os.path.basename(path), (), dict(zip(my_dict.keys(), my_dict.values())))
    return obj
>>> b = browse(path)
>>> b

К сожалению, он продолжает печатать __main__.

1 Ответ

1 голос
/ 03 апреля 2020

Как отмечается в комментариях, obj - это класс, а не экземпляр. Он содержит функцию __repr__, которая будет привязана к экземпляру, как только вы его создадите.

Простое и элегантное решение этого вопроса - заменить функцию browse классом с тем же именем. , Вызов класса создает экземпляр (если вы на самом деле не связываетесь с метаклассами или __new__), поэтому интерфейс, который у вас сейчас есть, менять не нужно. Однако внутренне вы должны создавать экземпляр своего класса для каждого каталога, в который вы углубились.

Еще одна вещь, которая позволит вам это сделать, - это иметь действительно динамическое c решение. Прямо сейчас вы на самом деле вербуете всех детей вашего root. Это может быть очень дорого как по времени, так и по памяти. В идеале вам нужно только перечислить текущий каталог и указывать дочерние элементы только по запросу.

from os import listdir
from os.path import isdir, join
import re

class browse:
    def __init__(self, path, directory=True):
        # Create an attribute in __dict__ for each child
        self.__path__ = path
        if directory:
            for file in listdir(path):
                full = join(path, file)
                key = re.sub(r'^(?=\d)|\W', '_', file)
                setattr(self, key, full if isdir(full) else browse(full, False))

    def __getattribute__(self, name):
        if name == '__path__':
            return super().__getattribute__(name)
        d = super().__getattribute__('__dict__')
        if name in d:
            child = d[name]
            if isinstance(child, str):
                child = browse(child)
                setattr(self, name, child)
            return child
        return super().__getattribute__(name)

    def __repr__(self):
        return self.__path__

    def __str__(self):
        return self.__path__

Это решение добавляет атрибут для каждой записи в пути root. Файлы записываются как browse объекты, а каталоги записываются как строки. Переопределение __getattribute__ позволяет вам менять строки, которые вы запрашиваете, на полные browse объекты на лету, вместо того, чтобы расширять все ваши папки заранее.

Возможное улучшение, учитывая предполагаемый вариант использования, будет быть удалить строку setattr(self, name, child). Таким образом, вы не будете сохранять ненужные ссылки на каталоги, которые вы случайно просматривали, например.

...