#python #generator
#питон #генератор #уступка
Вопрос:
Я пытаюсь предоставить единый интерфейс для извлечения всех файлов из одного каталога или списка каталогов.
def get_files(dir_or_dirs):
def helper(indir):
file_list = glob.glob("*.txt")
for file in file_list:
yield file
if type(dir_or_dirs) is list:
# a list of source dirs
for dir in dir_or_dirs:
yield helper(dir)
else:
# a single source dir
yield helper(dir_or_dirs)
def print_all_files(file_iter):
for file in file_iter:
print(file) # error here!
Вопросы:
- Ошибка гласит, что «файл» по-прежнему является генератором, независимо от того, является ли ввод одним каталогом или его списком. Почему это все еще генератор?
- Можно ли обернуть или встроить генераторы в функции? Если да, то как заставить это работать?
Комментарии:
1. Почему вы пытаетесь
yield
вернуть значениеhelper
? Помощник — это функция генератора, и она возвращает итератор генератора. Если вы хотите получить все, что выдает генератор, этоyield from
.2. пытался получить генератор для всех файлов во всех каталогах. Спасибо за «выход из» хедз-апа!
Ответ №1:
Вы helper()
каждый раз уступаете:
yield helper(dir)
но helper()
сам по себе является генератором.
В Python 3.3 и новее используйте yield from
вместо:
yield from helper(dir)
Это делегирует управление другому генератору. Из документации Yield expressions:
Когда
yield from <expr>
используется, он обрабатывает предоставленное выражение как подзаголовок. Все значения, создаваемые этим подзаголовком, передаются непосредственно вызывающей стороне методов текущего генератора.
В более старых версиях Python, включая Python 2.x, используйте другой цикл:
for file in helper(dir):
yield file
Для получения дополнительной информации о том, что yield from
делает, см. PEP 380 — Синтаксис делегирования подгенератору .
Не то чтобы вам действительно нужна вспомогательная функция, она делает немного больше, чем просто перебирает glob.glob()
результаты, вы можете сделать это напрямую.
Вам также необходимо исправить вашу функцию для фактического использования indir
; в настоящее время вы игнорируете этот аргумент, поэтому получаете только текстовые файлы из текущего рабочего каталога.
Далее, вы хотите использовать glob.iglob()
вместо glob.glob()
того, чтобы выполнять отложенную оценку os.scandir()
, а не загружать все результаты в память сразу. Я бы просто превратил dir_or_dirs
значение, отличное от списка, в список, а затем просто использовал один цикл:
import glob
import os.path
def get_files(dirs):
if not isinstance(dirs, list):
# make it a list with one element
dirs = [dirs]
for dir in dirs:
pattern = os.path.join(dir, '*.txt')
yield from glob.iglob(pattern)
Теперь вместо одного аргумента, который является либо строкой, либо списком, я бы использовал переменное количество аргументов с синтаксисом *args
параметра:
def get_files(*dirs):
for dir in dirs:
pattern = os.path.join(dir, '*.txt')
yield from glob.iglob(pattern)
Это может быть вызвано с 0 или более каталогами:
for file in get_files('/path/to/foo', '/path/to/bar'):
# ...
Комментарии:
1. Если вы просто хотите знать, можете ли вы что-то перебирать,
isinstance(dirs, collections.abc.Iterable)
лучше использовать проверку, поскольку это работает для любого итерируемого, а не только для списка. Более питоническим способом может быть использование dirs в качестве vararg:def get_files(*dirs)
, тогда вызывающий может вызывать с помощью одного аргумента, varargs или спискаget_dirs(*iterable_argument)
.2. @zstewart: нет, использование
collections.abc.Iterable
не лучшая проверка, потому что строки тоже являются итеративными. Затем один каталог будет обрабатываться как отдельные отдельные символы.3. Спасибо! советы py2.7 сработали! Я тоже попробую с py3. Также спасибо за иллюстрацию и дополнительные советы по улучшению качества кода на python!
4. @MartijnPieters О, точно. Дух. Я бы лично все равно выбрал решение, которое работает для любого итеративного.
*dirs
может быть лучшим вариантом, поскольку он оставляет открытой утиную типизацию как отдельных аргументов, так и итеративного аргумента. Хотя это означает, что любой итерируемый аргумент оценивается с нетерпением. Другой вариант — явно проверить типы аргументов, принятыхos.path.join
(str
,bytes
, и в версии 3.6os.PathLike
), и преобразовать их только в новый одноэлементный список / кортеж. Тогдаget_files
можно было бы лениво оценить переданный ему ленивый итератор.5. @zstewart: в этот момент вы просто опускаете ногу, и API принимает только одну итерацию каталогов и просто избегает ducktyping.