Самый быстрый способ приведения значений к их соответствующим типам данных в Python - PullRequest
5 голосов
/ 09 сентября 2011

У меня есть список значений - все строки. Я хочу преобразовать эти значения в соответствующие им типы данных. У меня есть сопоставление значений с доступной информацией типов.

Существует три разных типа данных: int, str, datetime. Код должен уметь обрабатывать ошибки с данными.

Я делаю что-то вроде: -

tlist =  [ 'some datetime value', '12', 'string', .... ]

#convert it to: [ datetime object, 12, 'string', ....]

error_data = ['', ' ', '?', ...]

d = { 0: lambda x: datetime.strptime(x,...) if x not in error_data else x, 
      1: lambda x: int(x) if x not in error_data else 0,
      2: lambda x: x 
      ...
     }

result = [ d[i](j) for i, j in enumerate(tlist) ]

Список для преобразования очень длинный, например, 180 значений, и мне нужно сделать это для тысяч таких списков. Производительность приведенного выше кода очень низкая. Какой самый быстрый способ сделать это?

Спасибо

Ответы [ 7 ]

2 голосов
/ 09 сентября 2011

Если ваше значение datetime всегда постоянное, почему бы не разрешить преобразованию типов обрабатывать недопустимые данные, которыми вы пытаетесь управлять в error_data. Это не так привлекательно, как в некоторых решениях, но делает управление преобразованием типов на основе положения данных в списке немного проще в обслуживании и расширении.

def convert(position, val):
    if position == 0:
        try:
            return datetime.strptime(val, '%Y-%m-%d %H:%M:%S') # assuming date is in a constant format
        except ValueError:
            return val
    elif position in (1, 15, 16): # assuming that you have other int values in other "columns"
        try:
            return int(val)
        except ValueError:
            return 0
    else: # string type
       return val

result = [convert(i,j) for i, j in enumerate(tlist)]
2 голосов
/ 09 сентября 2011

Что-то вроде:

>>> values = ["12", "a", "bcd", "2.2"]
>>> types = [int, int, str, float]
>>> defaults = {int: 0, float: 0.0}
>>> res = []
>>> for v, f in itertools.izip(values, types): #Just use zip for Python 3+.
    try:
        res.append(f(v))
    except ValueError:
        res.append(defaults[f])
>>> print(res)
[12, 0, 'bcd', 2.2]

Редактировать:

Это не обрабатывает значения даты и времени.Мое решение для этого заключается в использовании str для этого и преобразовании в datetime после цикла, например:

res[0] = datetime.strptime(res[0], "...")

Как получение, так и установка элемента списка имеют сложность O (1), поэтому не следуетбыть проблемой.

1 голос
/ 09 сентября 2011

В вашем коде есть несоответствие:

если все элементы в списке являются строками, вы не можете написать datetime(x), если x - строка

edit

Это ничего не изображает, так как это неуместно.Сложность того, чего нет в вашем коде, не оправдывает странности, которые есть в вашем коде.Пока вы не будете объяснять, как вы можете передать строку в качестве аргумента функции datetime.datetime () , никто не сможет вам помочь, ИМО.

Редактировать

Я думаю, что лучше создать непосредственно ваш список в момент чтения файла.

Я написал пример:

.

Сначала я создалCSV-файл со следующим кодом:

import csv
from random import randint,choice
from time import gmtime


xx = ['Whose', 'all', 'birth', 'just', 'infant', 'William',
      'dearest', 'rooms', 'find', 'Deserts', 'saucy', 'His',
      'how', 'considerate', 'only', 'other', 'Houses', 'has',
      'Fanny', 'them', 'his', 'very', 'dispense', 'early',
      'words', 'not', 'thus', 'now', 'pettish', 'Worth']

def gen(n):
    for i in xrange(n):
        yield ['AAAA','%d/%02d/%02d %02d:%02d:%02d' % gmtime(randint(0,80000000))[0:6],'@@@']
        yield ['BBBB',randint(100,999),'^^^^^^']
        yield ['CCCC',choice(xx),'-----------------']

with open('zzz.txt','wb') as f:
    writ = csv.writer(f, delimiter='#')
    writ.writerows(x for x in gen(60))

Структура CSV-файла такова:

AAAA#1972/02/11 08:53:53#@@@
BBBB#557#^^^^^^
CCCC#dearest#-----------------
AAAA#1971/10/15 06:55:20#@@@
BBBB#668#^^^^^^
CCCC#?#-----------------
AAAA#1972/07/13 11:10:05#@@@
BBBB#190#^^^^^^
CCCC#infant#-----------------
AAAA#1971/11/22 19:31:42#@@@
BBBB#202#^^^^^^
CCCC##-----------------
AAAA#1971/06/12 05:48:39#@@@
BBBB#81#^^^^^^
CCCC#find#-----------------
AAAA#1970/12/09 06:26:29#@@@
BBBB#72#^^^^^^
CCCC#find#-----------------
AAAA#1972/07/05 10:45:32#@@@
BBBB#270#^^^^^^
CCCC#rooms#-----------------
AAAA#1972/06/23 05:52:20#@@@
BBBB#202#^^^^^^
CCCC##-----------------
AAAA#1972/03/21 23:06:47#@@@
BBBB#883#^^^^^^
CCCC#William#-----------------
...... etc

.

Следующий код извлекает данные ваналогично тому, что вы хотите.

Нет необходимости в словаре, достаточно кортежа.Учитывая структуру созданного CSV-файла, я определил funcs = 60 * (to_dt, int, lambda x: x), но вы будете использовать последовательность функций, которые являются значениями вашего словаря (отсортированы)

import re
import csv
from datetime import datetime
from itertools import izip

reg = re.compile('(\d{4})/(\d\d)/(\d\d) (\d\d):(\d\d):(\d\d)')

def to_dt(x, error_data = ('', ' ', '?')):
    if x in error_data:
        return x
    else:
        return datetime(*map(int,reg.match(x).groups()))

def teger(x,  error_data = ('', ' ', '?')):
    if x in error_data:
        return 0
    else:
        return int(x)

funcs = 60 * (to_dt, int, lambda y: y)

with open('zzz.txt','rb') as f:
    rid = csv.reader(f, delimiter='#')
    li = [fct(x[1]) for fct,x in izip(funcs,rid)]



# display
it = (str(el) for el in li).next
print '\n'.join('%-21s %4s  %10s' % (it(),it(),it()) for i in xrange(60))

result

1972-02-11 08:53:53    557     dearest
1971-10-15 06:55:20    668           ?
1972-07-13 11:10:05    190      infant
1971-11-22 19:31:42    202            
1971-06-12 05:48:39     81        find
1970-12-09 06:26:29     72        find
1972-07-05 10:45:32    270       rooms
1972-06-23 05:52:20    202            
1972-03-21 23:06:47    883     William
1970-02-08 23:47:26    617            
1970-10-08 09:09:33    387     William
1971-04-30 11:05:07    721           ?
1970-02-12 11:57:48    827     Deserts
1972-03-27 21:30:39    363        just
1971-06-02 00:23:52    977            
1970-04-20 04:38:38    113     William
1971-01-20 23:10:26     75       Whose
1971-07-01 12:46:13    352     dearest
1971-01-31 17:01:34    220     William
1970-06-09 20:38:52    148       rooms
1971-08-08 07:42:10    146            
1970-01-28 15:17:41    903        find
...............etc
1 голос
/ 09 сентября 2011

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

error_set = set((err, None) for err in error_data)

Тогда действуй, как был. Для дальнейших улучшений потребуется профилирование вашего кода, чтобы фактически определить, на что тратится время.

0 голосов
/ 13 сентября 2011

Спасибо вам, ребята, за все эти подходы.Да, я попробовал почти все упомянутые подходы, но ни один из них не работал хорошо.

Я попробовал следующий подход, и он работал довольно хорошо для моих потребностей в производительности.Это то, что я сделал.

  1. Я обработал значения даты и времени, как указано utdemir
  2. Я вставил значение 0 для всех значений ошибок int с кодом, подобным -

    [i] = value if value != '' else 0

  3. Вместо приведения значения к значению с помощью словаря я принудительно ввел все значения сразу, используя список.

    def coerce(l):<br> return [ l[0], int(l[1]), int(l[2]) ... ]

Мои наблюдения:

  1. Обработка случаев ошибок отдельно и одновременное приведение к принуждению очень помогают.
  2. восстановление кода из исключения занимает много времени.
  3. Многократный просмотр списка не повредит (я сделал это для обработки ошибок)
  4. Чтобы дать представление, за пару часов данных (0,60 миллиона записей) мой подход сократил время выполнения с 6-7 минут до 2 м30 с
0 голосов
/ 09 сентября 2011

В качестве варианта ответа Утдемира, если значения ошибок довольно редки, вы можете оптимизировать общий случай:

>>> values = ["12", "a", "bcd", "2.2"]
>>> types = [int, int, str, float]
>>> defaults = {int: 0, float: 0.0}
>>> try: res = [f(v) for v,f in zip(values,types)]
... except: 
...     res = []
...     for v, f in zip(values, types):
...        try:
...             res.append(f(v))
...         except ValueError:
...             res.append(defaults[f])

Т.е. сначала попытайтесь преобразовать всю строку, предполагая, что ничего не пойдет не так. Если что-то пойдет не так, вернитесь и конвертируйте значения по одному, исправляя все ошибки.

0 голосов
/ 09 сентября 2011

Я не знаю, будет ли это намного быстрее, но мне понятнее:

tlist =  [ 'some datetime value', '12', 'string', .... ]

#convert it to: [ datetime object, 12, 'string', ....]

error_data = set(['', ' ', '?', ...])

def s(x):
    return x

def d(x):
    return datetime(x) if x not in error_data else x

def i(x):
    return int(x) if x not in error_data else 0

types = [ d, i, s, s, s, i, i, d, i, ... ]

result = [ t(x) for t, x in zip(types, tlist) ]

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

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