sscanf в Python

Я ищу эквивалент sscanf() в Python. Я хочу разобрать /proc/net/* файла, в C я мог бы сделать что-то вроде этого:

int matches = sscanf(
        buffer,
        "%*d: %64[0-9A-Fa-f]:%X %64[0-9A-Fa-f]:%X %*X %*X:%*X %*X:%*X %*X %*d %*d %ld %*512s\n",
        local_addr, &local_port, rem_addr, &rem_port, &inode);

Сначала я подумал использовать str.split, но он разбивается не на заданные символы, а на строку sep в целом:

>>> lines = open("/proc/net/dev").readlines()
>>> for l in lines[2:]:
>>>     cols = l.split(string.whitespace + ":")
>>>     print len(cols)
1

Который должен вернуть 17, как объяснено выше.

Есть ли Python, эквивалентный sscanf (не RE), или функция разделения строк в стандартной библиотеке, которая разбивается на любой из диапазона символов, о котором я не знаю?


person Matt Joiner    schedule 01.02.2010    source источник
comment
Есть ли причина, по которой вы настаиваете не на RE? Регулярные выражения - идеальный инструмент для этой работы.   -  person Max Shawabkeh    schedule 01.02.2010
comment
Если вы хотите программировать на C, почему бы не программировать на C? Если вы хотите программировать на Python, используйте регулярное выражение. В документации к модулю re есть даже полезный совет, в котором рассказывается, как преобразовать форматы scanf в регулярные выражения. docs.python.org/library/re.html#simulating-scanf   -  person    schedule 01.02.2010
comment
@ Пол, последнее слово могло бы дать отличный ответ.   -  person Matt Joiner    schedule 01.02.2010
comment
@MattJoiner, я думаю, было бы лучше запрашивать / запрещать функции, чем запрашивать / запрещать реализации. Я хотел бы иметь строки формата, которые определяют тип выходной переменной, чтобы типы преобразовывались для меня, и чтобы утверждать конкретное форматирование входной строки, а не регулярное выражение, объясняющее, почему у вас есть это предпочтение. В конце концов, если бы кто-то использовал регулярное выражение для создания того, что вы хотели, вы бы использовали его, не так ли?   -  person interestinglythere    schedule 13.11.2015
comment
@interestinglythere: ват   -  person Matt Joiner    schedule 14.11.2015
comment
Я думаю, было бы лучше сказать, какие свойства регулярного выражения вы хотите избежать, а не избегать регулярного выражения вообще. В конце концов, регулярное выражение может оказаться наиболее подходящим инструментом для работы. Возможно, кто-то создал инструмент, который вы ищете, за исключением того, что он негласно использует регулярное выражение. В таком случае, я полагаю, вы все равно захотите использовать этот инструмент.   -  person interestinglythere    schedule 15.11.2015
comment
Возможно, панды могут помочь?   -  person gerrit    schedule 15.01.2020


Ответы (13)


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

Вероятно, в основном полезно для перевода C, люди реализовали sscanf, например, в этом модуле: http://hkn.eecs.berkeley.edu/~dyoo/python/scanf/

В этом конкретном случае, если вы просто хотите разделить данные на основе нескольких разделенных символов, re.split - действительно правильный инструмент.

person Mike Graham    schedule 01.02.2010
comment
Я сказал нет, но вы это хорошо оправдываете - person Matt Joiner; 01.02.2010
comment
вот версия py3k связанной реализации: gist.github.com/3875529 - person Janus Troelsen; 12.10.2012
comment
Он не встроен, но здесь есть библиотека pypi.org/project/scanf - person nimig18; 19.08.2020

Также существует модуль parse.

parse() разработан как противоположность format() (более новая функция форматирования строк в Python 2.6 и выше).

>>> from parse import parse
>>> parse('{} fish', '1')
>>> parse('{} fish', '1 fish')
<Result ('1',) {}>
>>> parse('{} fish', '2 fish')
<Result ('2',) {}>
>>> parse('{} fish', 'red fish')
<Result ('red',) {}>
>>> parse('{} fish', 'blue fish')
<Result ('blue',) {}>
person Craig McQueen    schedule 12.10.2012
comment
Я очень рад вашему предложению, так как я просто искал что-то вроде этого. Странное совпадение. - person Janus Troelsen; 12.10.2012
comment
Это настоящий ответ! - person sv_jan5; 25.01.2018

Когда я нахожусь в настроении C, я обычно использую zip-файлы и списки для поведения, подобного scanf. Нравится:

input = '1 3.0 false hello'
(a, b, c, d) = [t(s) for t,s in zip((int,float,strtobool,str),input.split())]
print (a, b, c, d)

Обратите внимание, что для строк более сложного формата вам необходимо использовать регулярные выражения:

import re
input = '1:3.0 false,hello'
(a, b, c, d) = [t(s) for t,s in zip((int,float,strtobool,str),re.search('^(\d+):([\d.]+) (\w+),(\w+)$',input).groups())]
print (a, b, c, d)

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

strtobool = lambda s: {'true': True, 'false': False}[s]
person Chris Dellin    schedule 18.06.2012
comment
Мне очень нравится этот подход, тем более что моя проблема заключалась не только в необходимости scanf, но и sscanf. - person nemesisfixx; 07.10.2012
comment
Это оказалось хорошим решением; к сожалению, bool("false") возвращает True, потому что только пустые строки оцениваются как False. Однако еще не все потеряно, вы можете заменить bool пользовательской функцией, которая ведет себя так, как вам нужно. - person Aky; 14.11.2017
comment
@Aky Хороший улов! Я исправил свой ответ. - person Chris Dellin; 15.11.2017
comment
@rookiepig t(s) заменяется на int(substring1), float(substring2), strtobool(substring3) и str(substring4) по порядку. - person Blair Houghton; 31.03.2019
comment
Спасибо, что обратились к преобразованию типов, а не только за разделение строк. - person dsz; 11.01.2021

Вы можете разделить на ряд символов с помощью модуля re.

>>> import re
>>> r = re.compile('[ \t\n\r:]+')
>>> r.split("abc:def  ghi")
['abc', 'def', 'ghi']
person Dietrich Epp    schedule 01.02.2010
comment
не смешно иметь дело с регулярным выражением в текстовом представлении с плавающей запятой - person ZAB; 31.08.2013
comment
@ZAB: Ничего смешного. Вы используете регулярное выражение для разделения полей, а затем используете float() для его анализа. - person Dietrich Epp; 31.08.2013
comment
для этой конкретной проблемы, чтобы проанализировать / proc / net / *, этот уродливый трюк будет работать, хотя - person ZAB; 03.12.2013
comment
Или, что еще лучше, r = re.compile(r'[\s:]+'). (Я думаю, это хорошая привычка помещать регулярные выражения в необработанные строки, даже если это не имеет никакого значения в этом кейс.) - person Beetle; 25.06.2015

Вы можете выполнять синтаксический анализ с помощью модуля re, используя именованные группы. Он не будет разбирать подстроки на их фактические типы данных (например, int), но это очень удобно при синтаксическом анализе строк.

Учитывая эту примерную строку из /proc/net/tcp:

line="   0: 00000000:0203 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 335 1 c1674320 300 0 0 0"

Пример, имитирующий ваш пример sscanf с переменной, может быть:

import re
hex_digit_pattern = r"[\dA-Fa-f]"
pat = r"\d+: " + \
      r"(?P<local_addr>HEX+):(?P<local_port>HEX+) " + \
      r"(?P<rem_addr>HEX+):(?P<rem_port>HEX+) " + \
      r"HEX+ HEX+:HEX+ HEX+:HEX+ HEX+ +\d+ +\d+ " + \
      r"(?P<inode>\d+)"
pat = pat.replace("HEX", hex_digit_pattern)

values = re.search(pat, line).groupdict()

import pprint; pprint values
# prints:
# {'inode': '335',
#  'local_addr': '00000000',
#  'local_port': '0203',
#  'rem_addr': '00000000',
#  'rem_port': '0000'}
person orip    schedule 01.02.2010

Обновление: документация Python для его модуля регулярных выражений re включает раздел о моделировании scanf, который я нашел более полезным, чем любой из приведенных выше ответов.

https://docs.python.org/2/library/re.html#simulating-scanf

person Jon    schedule 19.12.2016

вы можете повернуть ":" на пробел и выполнить разделение. например

>>> f=open("/proc/net/dev")
>>> for line in f:
...     line=line.replace(":"," ").split()
...     print len(line)

не требуется регулярное выражение (в этом случае)

person ghostdog74    schedule 01.02.2010
comment
Вам все равно придется проверять правильность исходной строки - например, abc def ghi будет анализировать так же, как abc: def: ghi. Это различие может иметь значение. - person Kevin; 18.05.2015

Существует рецепт ActiveState, который реализует базовое сканирование http://code.activestate.com/recipes/502213-simple-scanf-implementation/

person markovg    schedule 11.11.2010

Проголосовали за ответ orip. Я думаю, что это разумный совет использовать re module. Приложение Kodos полезно при приближении к сложной задаче регулярного выражения с помощью Python.

http://kodos.sourceforge.net/home.html

person codeasone    schedule 23.08.2010

Вы можете установить pandas и использовать pandas.read_fwf для файлы формата фиксированной ширины. Пример использования /proc/net/arp:

In [230]: df = pandas.read_fwf("/proc/net/arp")

In [231]: print(df)
       IP address HW type Flags         HW address Mask Device
0   141.38.28.115     0x1   0x2  84:2b:2b:ad:e1:f4    *   eth0
1   141.38.28.203     0x1   0x2  c4:34:6b:5b:e4:7d    *   eth0
2   141.38.28.140     0x1   0x2  00:19:99:ce:00:19    *   eth0
3   141.38.28.202     0x1   0x2  90:1b:0e:14:a1:e3    *   eth0
4    141.38.28.17     0x1   0x2  90:1b:0e:1a:4b:41    *   eth0
5    141.38.28.60     0x1   0x2  00:19:99:cc:aa:58    *   eth0
6   141.38.28.233     0x1   0x2  90:1b:0e:8d:7a:c9    *   eth0
7    141.38.28.55     0x1   0x2  00:19:99:cc:ab:00    *   eth0
8   141.38.28.224     0x1   0x2  90:1b:0e:8d:7a:e2    *   eth0
9   141.38.28.148     0x1   0x0  4c:52:62:a8:08:2c    *   eth0
10  141.38.28.179     0x1   0x2  90:1b:0e:1a:4b:50    *   eth0

In [232]: df["HW address"]
Out[232]:
0     84:2b:2b:ad:e1:f4
1     c4:34:6b:5b:e4:7d
2     00:19:99:ce:00:19
3     90:1b:0e:14:a1:e3
4     90:1b:0e:1a:4b:41
5     00:19:99:cc:aa:58
6     90:1b:0e:8d:7a:c9
7     00:19:99:cc:ab:00
8     90:1b:0e:8d:7a:e2
9     4c:52:62:a8:08:2c
10    90:1b:0e:1a:4b:50

In [233]: df["HW address"][5]
Out[233]: '00:19:99:cc:aa:58'

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

person gerrit    schedule 15.01.2020

Пример официальные документы Python о том, как использовать sscanf из libc:

    # import libc
    from ctypes import CDLL
    if(os.name=="nt"):
        libc = cdll.msvcrt 
    else:
        # assuming Unix-like environment
        libc = cdll.LoadLibrary("libc.so.6")
        libc = CDLL("libc.so.6")  # alternative

    # allocate vars
    i = c_int()
    f = c_float()
    s = create_string_buffer(b'\000' * 32)

    # parse with sscanf
    libc.sscanf(b"1 3.14 Hello", "%d %f %s", byref(i), byref(f), s)

    # read the parsed values
    i.value  # 1
    f.value  # 3.14
    s.value # b'Hello'
person eadmaster    schedule 25.02.2020
comment
замените from ctypes import CDLL на from ctypes import cdll, c_int, c_float, create_string_buffer, byref или from ctypes import * - person Dmitry; 27.06.2021

Если разделителями являются «:», вы можете разделить их на «:», а затем использовать x.strip () для строк, чтобы избавиться от любых начальных или конечных пробелов. int () игнорирует пробелы.

person Lennart Regebro    schedule 01.02.2010

Существует реализация Python 2 от odiak.

person Janus Troelsen    schedule 11.10.2012