Найти - Пользователи
Полная версия: Счетчик в рекурсивной функции - как правильно?
Начало » Python для новичков » Счетчик в рекурсивной функции - как правильно?
1
alibek
Делаю функцию, которая рекурсивно считывает все файлы в каталоге.
Хочу сделать так, чтобы через каждые 100 обработанных файлов выводилось информационное сообщение (вида “Найдено файлов: 200…”).
Как лучше сделать? Использовать глобальную переменную-счетчик, или optional-параметр, который будет передаваться при рекурсивных вызовах?
Или может быть в Python есть более подходящие инструменты?
alibek
Судя по всему, в Python нельзя передать обычную переменную по ссылке.
Поэтому второй способ в чистом виде невозможен, но можно передавать объект (который по ссылке).
Сделал так:
 def scanfiles(base: str, ext: list = None, report: int = 100) -> list:
    rec = {'level': 0, 'counter': 0, 'report': report}
    def _scanfiles(base: str, ext: list = None, rec = None) -> list:
        (fl, dl) = ([], [])
        if rec is None:
            rec = {'level': 0, 'counter': 0}
        log.trace(" " * rec['level'] + "Scanning: %s", base)
        for f in os.scandir(base):
            if f.is_dir():
                log.trace(" " * rec['level'] + "- subdir: %s", f.path)
                rec['counter'] += 1
                dl.append(f.path)
            if f.is_file():
                log.trace(" " * rec['level'] + "- file: %s", f.path)
                rec['counter'] += 1
                if not ext or os.path.splitext(f.name)[1].lower()[1:] in ext:
                    fl.append(f.path)
            if rec['report'] and not rec['counter'] % rec['report']:
                log.debug("Processing: %d...", rec['counter'])
        for d in list(dl):
            rec['level'] += 1
            (f, s) = _scanfiles(d, ext, rec)
            rec['level'] -= 1
            dl.extend(s)
            fl.extend(f)
        if rec['report'] and not rec['level']:
            log.debug("Processing: %d", rec['counter'])
        return (fl, dl)
    (fl, dl) = _scanfiles(base, ext, rec)
    return fl
py.user.next
При рекурсии нужно через параметр делать.
  
>>> def fact(n, count=1):
...     if n <= 1:
...         return 1
...     else:
...         if count % 3 == 0:
...             print('seen', count, 'numbers', 'on', n)
...         return n * fact(n - 1, count + 1)
... 
>>> fact(10)
seen 3 numbers on 8
seen 6 numbers on 5
seen 9 numbers on 2
3628800
>>>
py.user.next
alibek
Судя по всему, в Python нельзя передать обычную переменную по ссылке.
Ну ты просто не понимаешь, как работают имена в питоне, потому что ты не читал документацию к питону.
https://docs.python.org/3/reference/index.html

Поэтому твои мысли вот эти типа вумные они выглядят смешно. Как будто какой-то новичок философом заделался. Причём новичок именно в программировании, а не в питоне.

Я тебе объясню, как действует опытный программист: сначал он читает документацию к новому для себя языку, а потом только философствовать начинает, не наоборот; наоборот как раз - это признак ленивых новичков, которые хотят всё и сразу, которые хотят, чтобы всё было просто. Такая инфантильная позиция, занимаемая изначально. А оно просто только в детских сказках про Гарри Поттера. В реальной жизни всё сложно, и чтобы оно было простым, нужно дофига узнать и дофига прочитать и дофига шишек набить на своём собственном лбу, а не на чужом каком-то там.

Какие ссылки? Если бы ты документацию прочитал, ты бы оттуда всё узнал, что имена в питоне вообще по-другому устроены, не как в Паскалях, Бейзиках и всякой древней фигне. Это другие принципы именования вообще. Там число простое, самое простейшее - это уже объект с кучей методов.

alibek
Сделал так:
 def scanfiles(base: str, ext: list = None, report: int = 100) -> list:
    rec = {'level': 0, 'counter': 0, 'report': report}
    def _scanfiles(base: str, ext: list = None, rec = None) -> list:
        (fl, dl) = ([], [])
        if rec is None:
            rec = {'level': 0, 'counter': 0}
        log.trace(" " * rec['level'] + "Scanning: %s", base)
        for f in os.scandir(base):
            if f.is_dir():
                log.trace(" " * rec['level'] + "- subdir: %s", f.path)
                rec['counter'] += 1
                dl.append(f.path)
            if f.is_file():
                log.trace(" " * rec['level'] + "- file: %s", f.path)
                rec['counter'] += 1
                if not ext or os.path.splitext(f.name)[1].lower()[1:] in ext:
                    fl.append(f.path)
            if rec['report'] and not rec['counter'] % rec['report']:
                log.debug("Processing: %d...", rec['counter'])
        for d in list(dl):
            rec['level'] += 1
            (f, s) = _scanfiles(d, ext, rec)
            rec['level'] -= 1
            dl.extend(s)
            fl.extend(f)
        if rec['report'] and not rec['level']:
            log.debug("Processing: %d", rec['counter'])
        return (fl, dl)
    (fl, dl) = _scanfiles(base, ext, rec)
    return fl
Тебе может казаться, что очень умный код, но у нас-то он сразу вопросы вызывает. Ты вообще понимаешь, что ты делаешь? Какого хера ты функцию засунул внутрь функции? Кто тебе сказал так делать? Это ты сам сообразил? Ну вот эта соображалка твоя, она не развита. Вот чтобы знать, почему нельзя функции вот так вкладывать, нужно читать книжки. И в какой-нибудь из них, где-то там в уголке, где-то там в закутке будет написано “не вкладывайте функции в функции, даже если это можно делать, потому что потом, при юнит-тестировании вы до этих функций никак не доберётесь, а эти функции вложенные бывают такими сложными, что без юнит-тестов их никак нельзя оставлять, чтобы не напортачить”. Понимаешь? Вот это вот, информация такого рода, она всегда в каких-то книжках написана и не всегда на первой странице, а где-то там мельком пролетает и всё. И вот если ты книжку читаешь, у тебя это всё равно откладывается, хоть на этом внимание и не заостряется, там много таких мелочей.

Ну главный принцип такой: если тебе приходится много-много-много писать, чтобы понятно всё было и надёжно всё работало, то, скорее всего, у тебя сама идея придуманная, вариант реализации, представляет из себя говно-говно-говно какое-то. И вот они напрямую связаны. Если у тебя много говнокода получается, то это связано с тем, что сама идея, которую он там реализует, тупая какая-то и надо её перепродумать.

Вообще, это называется “вонючий код”.
wiki. ru. вонючий код
wiki. en. code smell

Но от вонючего кода надо не избавляться, а надо его не допускать изначально. Классно ты имена из одних букв напридумывал. Наверное, расчитывал, что потом это исправишь всё на полные имена. Но вот что-то не исправил ты их, тебе пришлось в вопросе про этот код постить его сюда с этими короткими именами, которые одному только тебе известны, да и то временно, потому что через месяц-два ты откроешь этот код и сам уже не вспомнишь, а что эти имена значат, и тебе придётся его по каждой букве изучать с нуля, собственный код! А ты представь, что чужому человеку этот код читать надо и понять что-то в нём. Ты сам его не поймёшь через месяц, а чужой человек тем более, даже читать его не станет.
alibek
Основная функция это _scanfiles, она собственно и выполняет нужную задачу; в нее передается счетчик и она возвращает вспомогательные данные, не нужные для финального результата, но нужные для рекурсии.
Функция scanfiles это просто оболочка _scanfiles, которая сама подготавливает вызов основной функции и возвращает результат в комфортной для пользователя (вызывающего кода) форме. Обычное дело для рекурсивных функций.
То что основная функция вложена в оболочку — сделано специально. Это красиво, когда снаружи не видно ничего постороннего и пользователя не будет возникать вопросов, что это за аргумент rec такой.
py.user.next
alibek
То что основная функция вложена в оболочку — сделано специально.
Да, это заметно. Заметно, что ты специально сделал то, чего делать не надо. Видимо, сказывается отсутствие опыта. Про чистые функции ты не в курсе и про юнит-тестирование и что это вообще такое ты тоже не очень-то и знаешь. Как бы ерунды напридумывали какой-то, а ты один такой умный.

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

Вот пример тебе.

Это твоя функция, размноженная на три функции. В каждой функции есть ошибка. Ну это подсказка тебе, потому что в реальных проектах никогда не видно, есть там ошибка или нет там ошибки. Это код полностью твой, ты сам его писал. Я ничего особого там не делал.

Теперь найти эти ошибки!
  
def scanfiles(base: str, ext: list = None, report: int = 100) -> list:
    rec = {'level': 0, 'counter': 0, 'report': report}
    def _scanfiles(base: str, ext: list = None, rec = None) -> list:
        (fl, dl) = ([], [])
        if rec is None:
            rec = {'level': 0, 'counter': 0}
        log.trace(" " * rec['level'] + "Scanning: %s", base)
        for f in os.scandir(base):
            if f.is_dir():
                log.trace(" " * rec['level'] + "- subdir: %s", f.path)
                rec['counter'] += 1
                dl.append(f.path)
            if f.is_file():
                log.trace(" " * rec['level'] + "- file: %s", f.path)
                rec['counter'] += 1
                if not ext or os.path.splitext(f.name)[1].lower()[1:] in ext:
                    fl.append(f.path)
            if rec['report'] and not rec['counter'] % rec['report']:
                log.debug("Processing: %d...", rec['counter'])
        for d in list(dl):
            rec['level'] += 1
            (f, s) = _scanfiles(d, ext, rec)
            rec['level'] -= 1
            dl.extend(s)
            fl.extend(s)
        if rec['report'] and not rec['level']:
            log.debug("Processing: %d", rec['counter'])
        return (fl, dl)
    (fl, dl) = _scanfiles(base, ext, rec)
    return fl
 
def scanfiles(base: str, ext: list = None, report: int = 100) -> list:
    rec = {'level': 0, 'counter': 0, 'report': report}
    def _scanfiles(base: str, ext: list = None, rec = None) -> list:
        (fl, dl) = ([], [])
        if rec is None:
            rec = {'level': 0, 'counter': 0}
        log.trace(" " * rec['level'] + "Scanning: %s", base)
        for f in os.scandir(base):
            if f.is_dir():
                log.trace(" " * rec['level'] + "- subdir: %s", f.path)
                rec['counter'] += 1
                dl.append(f.path)
            if f.is_file():
                log.trace(" " * rec['level'] + "- file: %s", f.path)
                rec['counter'] += 1
                if not ext or os.path.splitext(f.name)[1:].lower()[1:] in ext:
                    fl.append(f.path)
            if rec['report'] and not rec['counter'] % rec['report']:
                log.debug("Processing: %d...", rec['counter'])
        for d in list(dl):
            rec['level'] += 1
            (f, s) = _scanfiles(d, ext, rec)
            rec['level'] -= 1
            dl.extend(s)
            fl.extend(f)
        if rec['report'] and not rec['level']:
            log.debug("Processing: %d", rec['counter'])
        return (fl, dl)
    (fl, dl) = _scanfiles(base, ext, rec)
    return fl
 
def scanfiles(base: str, ext: list = None, report: int = 100) -> list:
    rec = {'level': 0, 'counter': 0, 'report': report}
    def _scanfiles(base: str, ext: list = None, rec = None) -> list:
        (fl, dl) = ([], [])
        if rec is None:
            rec = {'level': 0, 'counter': 0}
        log.trace(" " * rec['level'] + "Scanning: %s", base)
        for f in os.scandir(base):
            if f.is_dir():
                log.trace(" " * rec['level'] + "- subdir: %s", f.path)
                rec['counter'] += 1
                dl.append(f.path)
            if f.is_file():
                log.trace(" " * rec['level'] + "- file: %s", f.path)
                rec['counter'] += 1
                if not ext or os.path.splitext(f.name)[1].lower()[1:] in ext:
                    fl.append(f.path)
            if rec['report'] and not rec['counter'] % rec['report']:
                log.debug("Processing: %d...", rec['counter'])
        for d in list(dl):
            rec['level'] += 1
            (f, s) = scanfiles(d, ext, rec)
            rec['level'] -= 1
            dl.extend(s)
            fl.extend(f)
        if rec['report'] and not rec['level']:
            log.debug("Processing: %d", rec['counter'])
        return (fl, dl)
    (fl, dl) = _scanfiles(base, ext, rec)
    return fl
И тут ты начинаешь читать эту галиматью, в то время как тебе надо не читать всякую хрень уже в сотый раз, а разрабатывать дальше программу, которая сама себя не напишет.

Поэтому нужно сделать что? Нужно сделать юнит-тесты, про которые ты не знаешь.

Дальше. А что нужно тестировать этими юнит-тестами? только внешнюю функцию или только внутреннюю функции? или их обе надо тестировать? Ну в идеале их надо тестировать обе отдельно друг от друга. И как же ты протестируешь то, что вот эта внутренняя функция правильно заполняет словарь rec, а не делает в нём ошибки всякие? Для этого нужно иметь к ней доступ, и к словарю к этому тоже нужно иметь доступ, как-то его проверять.

alibek
Это красиво, когда снаружи не видно ничего постороннего
Кому не видно? Ты думаешь, это будет кто-то смотреть и читать вообще? Ты открой хоть одну реальную программу, которыми ты пользуешься каждый день, и посмотри, какой там “красивый” код у неё. Там будет говнокод на говнокоде. Ты ей можешь пользоваться годами и никогда даже не узнать, что там всякая ерунда делается внутри. А вот когда там ошибка будет, которая снаружи там сотрёт тебе полдиска или выведет тебе какую-то ложную информацию, тогда ты сразу это заметишь. А когда ты полезешь туда исправлять эту ошибку или хотя бы оценить, насколько автору программы будет её легко исправить, вот тогда качество кода и сыграет свою роль. Тогда ты по говнокоду сразу поймёшь, что это не будет исправлено никогда. Потому что автор сам же в своём говнокоде не разберётся и просто не сможет её исправить, потому что говнокод писать легко, а разгребать его потом тяжело. Поэтому то, что тяжело, он делать не будет. Он тебя просто проигнорирует, типа ошибки нет никакой или там она некритическая и так далее, и всё. Дальше ты пойдёшь программку получше искать на просторах Интернета.

Поэтому твои размышления о красивости - это туфта какая-то на уровне школоты, ничего не делавшей никогда серьёзного. Давай, напиши для ракетчиков программу красивую вот в таком своём смысле или там для врачей. И потом что-то раз и ракета летит не в космос вверх, а к тебе домой вниз, а потом тебя из дома из этого увозят в больницу и там тебе врачи делают операцию на головном мозге, а у них тоже твоя программа работает, подсказывает им что-то, которая что-то там им неправильно показывает при этом, а они даже этого не понимают. Вот это ответственность, про которую ты не в курсе.

alibek
Это красиво, когда снаружи не видно ничего постороннего и пользователя не будет возникать вопросов, что это за аргумент rec такой.
Ну ты слышал звон, но не знаешь, где он. Да, вот так вот в голом виде это правильно, но в реале по факту это неправильно. Обёртки такие, да, бывают, но это не значит, что их надо везде делать.

Вот знаешь, есть слово “каково” и есть слово “какого”. И вот люди пишут либо так, либо так всё время. А ведь это два разных слова, которые могут даже одновременно использоваться. Просто у них ума не хватает на это, они не могут этого понять, слишком узкий мозг становится барьером.

Ты знаешь, каково это вообще, писать код в промышленных масштабах, когда у тебя каждая секунда на счету и огромная ответственность за каждую букву? Так какого хера ты тут споришь тогда?

Вот иди и изучай свой код. Программа diff тебе не поможет, потому что этот код обычно всегда разный. А запускать каждую функцию из сотни функций ты тоже не сможешь.
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Powered by DjangoBB