web-dev-qa-db-fra.com

sscanf dans Python

Je cherche un équivalent à sscanf() en Python. Je veux analyser /proc/net/* fichiers, en C je pourrais faire quelque chose comme ça:

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);

J'ai d'abord pensé à utiliser str.split, mais il ne se divise pas sur les caractères donnés, mais la chaîne sep dans son ensemble:

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

Qui devrait retourner 17, comme expliqué ci-dessus.

Existe-t-il un Python équivalent à sscanf (pas RE), ou une fonction de fractionnement de chaîne dans la bibliothèque standard qui se divise sur une plage de caractères que je ne connais pas de?

57
Matt Joiner

Python n'a pas d'équivalent sscanf intégré, et la plupart du temps, il est en fait beaucoup plus logique d'analyser l'entrée en travaillant directement avec la chaîne, en utilisant des expressions régulières ou en utilisant un outil d'analyse.

Probablement surtout utile pour traduire C, les gens ont implémenté sscanf, comme dans ce module: http://hkn.eecs.berkeley.edu/~dyoo/python/scanf/

Dans ce cas particulier, si vous souhaitez simplement fractionner les données en fonction de plusieurs caractères fractionnés, re.split est vraiment le bon outil.

28
Mike Graham

Il existe également le module parse .

parse() est conçu pour être l'opposé de format() (la nouvelle fonction de formatage de chaîne dans Python 2.6 et plus)).

>>> 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',) {}>
61
Craig McQueen

Lorsque je suis d'humeur C, j'utilise généralement des compréhensions Zip et List pour un comportement de type scanf. Comme ça:

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)

Notez que pour les chaînes de format plus complexes, vous devez utiliser des expressions régulières:

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)

Notez également que vous avez besoin de fonctions de conversion pour tous les types que vous souhaitez convertir. Par exemple, ci-dessus, j'ai utilisé quelque chose comme:

strtobool = lambda s: {'true': True, 'false': False}[s]
58
Chris Dellin

Vous pouvez diviser une plage de caractères en utilisant le module re.

>>> import re
>>> r = re.compile('[ \t\n\r:]+')
>>> r.split("abc:def  ghi")
['abc', 'def', 'ghi']
23
Dietrich Epp

Vous pouvez analyser avec le module re en utilisant groupes nommés . Il n'analysera pas les sous-chaînes à leurs types de données réels (par exemple int) mais c'est très pratique lors de l'analyse des chaînes.

Étant donné cet exemple de ligne de /proc/net/tcp:

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

Un exemple imitant votre exemple sscanf avec la variable pourrait être:

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'}
14
orip

Il existe une recette ActiveState qui implémente un scanf de base http://code.activestate.com/recipes/502213-simple-scanf-implementation/

2
markovg

Mise à jour: la documentation Python pour son module regex, re, comprend une section sur la simulation de scanf, que j'ai trouvée plus utile que n'importe laquelle des réponses ci-dessus.

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

2
Jon

La réponse de orip a voté. Je pense que c'est un bon conseil d'utiliser le module re. L'application Kodos est utile lors de l'approche d'une tâche d'expression rationnelle complexe avec Python.

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

1
codeasone

vous pouvez transformer le ":" en espace et faire le split.eg

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

aucune expression régulière nécessaire (dans ce cas)

1
ghostdog74

Si les séparateurs sont ":", vous pouvez fractionner sur ":", puis utiliser x.strip () sur les chaînes pour supprimer tout espace de début ou de fin. int () ignorera les espaces.

0
Lennart Regebro
0
Janus Troelsen