Я думаю, что вам лучше не пытаться выполнить .seek(0)
, а просто открывать файл из имени файла каждый раз.
И я не рекомендую просто возвращать self
в __iter__()
метод.Это означает, что у вас есть только один экземпляр вашего объекта.Я не знаю, насколько вероятно, что кто-то попытается использовать ваш объект из двух разных потоков, но если это произойдет, результаты будут удивительными.
Итак, сохраните имя файла, а затем в __iter__()
метод создания свежего объекта со вновь инициализированным объектом считывателя и вновь открытым объектом дескриптора файла;вернуть этот новый объект из __iter__()
.Это будет работать каждый раз, независимо от того, что на самом деле является файловым объектом.Это может быть дескриптор сетевой функции, которая получает данные с сервера, или кто знает что, и он может не поддерживать метод .seek()
;но вы знаете, что если вы просто откроете его снова, вы получите новый объект дескриптора файла.И если кто-то использует модуль threading
для параллельного запуска 10 экземпляров вашего класса, каждый из них всегда будет получать все строки из файла, а не каждый случайным образом получать примерно десятую часть строк.
Кроме того, я не рекомендую ваш обработчик исключений в методе .next()
в MappedIterator
.Метод .__iter__()
должен возвращать объект, который может быть надежно повторен.Если глупый пользователь передает целочисленный объект (например, 3), это не будет повторяться.Внутри .__iter__()
вы всегда можете явно вызвать iter()
для аргумента, и если это уже итератор (например, объект дескриптора открытого файла), вы просто получите тот же объект обратно;но если это объект последовательности, вы получите итератор, который работает с последовательностью.Теперь, если пользователь передает 3, вызов iter()
вызовет исключение, которое имеет смысл прямо на линии, где пользователь передал 3, а не исключение, исходящее от первого вызова .next()
.И в качестве бонуса вам больше не нужна переменная-член cnt
, и ваш код будет немного быстрее.
Итак, если вы соберете все мои предложения, вы можете получить что-то вроде этого:
class CSVMapper(object):
def __init__(self, reader, fname, mapping={}, **kwargs):
self._reader = reader
self._fname = fname
self._mapping = mapping
self._kwargs = kwargs
self.line_num = 0
def __iter__(self):
cls = type(self)
obj = cls(self._reader, self._fname, self._mapping, **self._kwargs)
if "open_with" in self._kwargs:
open_with = self._kwargs["open_with"]
f = open_with(self._fname, **self._kwargs)
else:
f = open(self._fname, "rt")
# "itr" is my standard abbreviation for an iterator instance
obj.itr = obj._reader(f)
return obj
def next(self):
item = self.itr.next()
self.line_num += 1
# If no mapping is provided, item is returned unchanged.
if not self._mapping:
return item # csv.reader() returns a list of string values
# we have a mapping so make a mapped object
mapped_obj = {}
key, value = item
if key in self._mapping:
return [self._mapping[key], value]
else:
return item
if __name__ == "__main__":
lst_csv = [
"foo, 0",
"one, 1",
"two, 2",
"three, 3",
]
import csv
mapping = {"foo": "bar"}
m = CSVMapper(csv.reader, lst_csv, mapping, open_with=iter)
for item in m: # will print every item
print item
for item in m: # will print every item again
print item
Теперь метод .__iter__()
дает вам новый объект каждый раз, когда вы вызываете его.
Обратите внимание, как пример кода использует список строк вместо открытия файла.В этом примере вам нужно указать функцию open_with()
, которая будет использоваться вместо значения по умолчанию open()
для открытия файла.Поскольку наш список строк может быть повторен, чтобы возвращать одну строку за раз, мы можем просто использовать iter
в качестве нашей open_with
функции здесь.
Я не понял ваш код отображения.csv.reader
возвращает список строковых значений, а не какой-то словарь, поэтому я написал некоторый тривиальный код отображения, который работает для файлов CSV с двумя столбцами, первый из которых является строкой.Ясно, что вы должны отрубить мой тривиальный код отображения и ввести желаемый код отображения.
Кроме того, я выбрал ваш .__len__()
метод.Возвращает длину последовательности, когда вы делаете что-то вроде len(obj)
;он возвращает line_num
, что означает, что значение len(obj)
будет меняться каждый раз, когда вы вызываете метод .next()
.Если пользователи хотят узнать длину, они должны сохранить результаты в списке и взять длину списка или что-то в этом роде.
РЕДАКТИРОВАТЬ: я добавил **self._kwargs
к вызову call_with()
вметод .__iter__()
.Таким образом, если вашей функции call_with()
нужны дополнительные аргументы, они будут переданы.До того, как я сделал это изменение, не было веской причины сохранять аргумент kwargs
в объекте;было бы так же хорошо добавить аргумент call_with
в метод класса .__init__()
с аргументом по умолчанию None
.Я думаю, что это хорошее изменение.