python - qué - que es la indentación de codigo y para que sirve
Crear un árbol/dict profundamente anidado de un archivo de texto sangrado en python (3)
Básicamente, quiero iterar a través de un archivo y poner el contenido de cada línea en un dictado profundamente anidado, cuya estructura se define por la cantidad de espacios en blanco al comienzo de cada línea.
Esencialmente, el objetivo es tomar algo como esto:
a
b
c
d
e
Y conviértalo en algo como esto:
{"a":{"b":"c","d":"e"}}
O esto:
apple
colours
red
yellow
green
type
granny smith
price
0.10
dentro de esto:
{"apple":{"colours":["red","yellow","green"],"type":"granny smith","price":0.10}
Para poder enviarlo al módulo JSON de Python y hacer algunos JSON.
Por el momento estoy tratando de hacer un dict y una lista en pasos como este:
-
{"a":""} ["a"]
-
{"a":"b"} ["a"]
-
{"a":{"b":"c"}} ["a","b"]
-
{"a":{"b":{"c":"d"}}}} ["a","b","c"]
-
{"a":{"b":{"c":"d"},"e":""}} ["a","e"]
-
{"a":{"b":{"c":"d"},"e":"f"}} ["a","e"]
-
{"a":{"b":{"c":"d"},"e":{"f":"g"}}} ["a","e","f"]
etc.
La lista actúa como "migas de pan" que muestra dónde colgué por última vez un dict.
Para hacer esto, necesito una manera de recorrer la lista y generar algo como dict["a"]["e"]["f"]
para llegar a esa última frase. He echado un vistazo a la clase AutoVivification que alguien ha hecho y que parece muy útil, pero de lo que no estoy seguro es:
- Si estoy usando la estructura de datos correcta para esto (estoy planeando enviarlo a la biblioteca JSON para crear un objeto JSON)
- Cómo usar AutoVivification en esta instancia
- Si hay una mejor manera en general para abordar este problema.
Se me ocurrió la siguiente función pero no funciona:
def get_nested(dict,array,i):
if i != None:
i += 1
if array[i] in dict:
return get_nested(dict[array[i]],array)
else:
return dict
else:
i = 0
return get_nested(dict[array[i]],array)
Agradecería ayuda!
(El resto de mi código extremadamente incompleto está aquí :)
#Import relevant libraries
import codecs
import sys
#Functions
def stripped(str):
if tab_spaced:
return str.lstrip(''/t'').rstrip(''/n/r'')
else:
return str.lstrip().rstrip(''/n/r'')
def current_ws():
if whitespacing == 0 or not tab_spaced:
return len(line) - len(line.lstrip())
if tab_spaced:
return len(line) - len(line.lstrip(''/t/n/r''))
def get_nested(adict,anarray,i):
if i != None:
i += 1
if anarray[i] in adict:
return get_nested(adict[anarray[i]],anarray)
else:
return adict
else:
i = 0
return get_nested(adict[anarray[i]],anarray)
#initialise variables
jsondict = {}
unclosed_tags = []
debug = []
vividfilename = ''simple.vivid''
# vividfilename = sys.argv[1]
if len(sys.argv)>2:
jsfilename = sys.argv[2]
else:
jsfilename = vividfilename.split(''.'')[0] + ''.json''
whitespacing = 0
whitespace_array = [0,0]
tab_spaced = False
#open the file
with codecs.open(vividfilename,''rU'', "utf-8-sig") as vividfile:
for line in vividfile:
#work out how many whitespaces at start
whitespace_array.append(current_ws())
#For first line with whitespace, work out the whitespacing (eg tab vs 4-space)
if whitespacing == 0 and whitespace_array[-1] > 0:
whitespacing = whitespace_array[-1]
if line[0] == ''/t'':
tab_spaced = True
#strip out whitespace at start and end
stripped_line = stripped(line)
if whitespace_array[-1] == 0:
jsondict[stripped_line] = ""
unclosed_tags.append(stripped_line)
if whitespace_array[-2] < whitespace_array[-1]:
oldnested = get_nested(jsondict,whitespace_array,None)
print oldnested
# jsondict.pop(unclosed_tags[-1])
# jsondict[unclosed_tags[-1]]={stripped_line:""}
# unclosed_tags.append(stripped_line)
print jsondict
print unclosed_tags
print jsondict
print unclosed_tags
Antes que nada, no uses array
y dict
como nombres de variables porque son palabras reservadas en Python y reutilizarlas puede terminar en todo tipo de caos.
De acuerdo, entonces si te encuentro correctamente, tienes un árbol en un archivo de texto, con la paternidad indicada por indentaciones, y quieres recuperar la estructura de árbol real. ¿Derecha?
¿El siguiente parece un bosquejo válido? Porque tengo problemas para poner tu código actual en contexto.
result = {}
last_indentation = 0
for l in f.xreadlines():
(c, i) = parse(l) # create parse to return character and indentation
if i==last_indentation:
# sibling to last
elif i>last_indentation:
# child to last
else:
# end of children, back to a higher level
OK, entonces tu lista son los padres actuales, eso es correcto, pero los mantendré apuntando al diccionario que has creado, no a la letra literal
acaba de comenzar algunas cosas aquí
result = {}
parents = {}
last_indentation = 1 # start with 1 so 0 is the root of tree
parents[0] = result
for l in f.xreadlines():
(c, i) = parse(l) # create parse to return character and indentation
if i==last_indentation:
new_el = {}
parents[i-1][c] = new_el
parents[i] = new_el
elif i>last_indentation:
# child to last
else:
# end of children, back to a higher level
El siguiente código tomará un archivo con sangría de bloque y se convertirá en un árbol XML; esta:
foo
bar
baz
ban
bal
... se convierte en:
<cmd>foo</cmd>
<cmd>bar</cmd>
<block>
<name>baz</name>
<cmd>ban</cmd>
<cmd>bal</cmd>
</block>
La técnica básica es:
- Establecer sangría a 0
- Para cada línea, obtenga la sangría
- Si> actual, baje y guarde el bloque / ident actual en una pila
- Si == actual, anexa al bloque actual
- Si <actual, sal de la pila hasta que llegues a la sangría correspondiente
Asi que:
from lxml import builder
C = builder.ElementMaker()
def indent(line):
strip = line.lstrip()
return len(line) - len(strip), strip
def parse_blockcfg(data):
top = current_block = C.config()
stack = []
current_indent = 0
lines = data.split(''/n'')
while lines:
line = lines.pop(0)
i, line = indent(line)
if i==current_indent:
pass
elif i > current_indent:
# we''ve gone down a level, convert the <cmd> to a block
# and then save the current ident and block to the stack
prev.tag = ''block''
prev.append(C.name(prev.text))
prev.text = None
stack.insert(0, (current_indent, current_block))
current_indent = i
current_block = prev
elif i < current_indent:
# we''ve gone up one or more levels, pop the stack
# until we find out which level and return to it
found = False
while stack:
parent_indent, parent_block = stack.pop(0)
if parent_indent==i:
found = True
break
if not found:
raise Exception(''indent not found in parent stack'')
current_indent = i
current_block = parent_block
prev = C.cmd(line)
current_block.append(prev)
return top
Aquí hay una solución recursiva. Primero, transforma la entrada de la siguiente manera.
Entrada:
person:
address:
street1: 123 Bar St
street2:
city: Madison
state: WI
zip: 55555
web:
email: [email protected]
Resultado del primer paso:
[{''name'':''person'',''value'':'''',''level'':0},
{''name'':''address'',''value'':'''',''level'':1},
{''name'':''street1'',''value'':''123 Bar St'',''level'':2},
{''name'':''street2'',''value'':'''',''level'':2},
{''name'':''city'',''value'':''Madison'',''level'':2},
{''name'':''state'',''value'':''WI'',''level'':2},
{''name'':''zip'',''value'':55555,''level'':2},
{''name'':''web'',''value'':'''',''level'':1},
{''name'':''email'',''value'':''[email protected]'',''level'':2}]
Esto es fácil de lograr con la split('':'')
y contando el número de pestañas iniciales:
def tab_level(astr):
"""Count number of leading tabs in a string
"""
return len(astr)- len(astr.lstrip(''/t''))
Luego, alimente la salida del primer paso a la siguiente función:
def ttree_to_json(ttree,level=0):
result = {}
for i in range(0,len(ttree)):
cn = ttree[i]
try:
nn = ttree[i+1]
except:
nn = {''level'':-1}
# Edge cases
if cn[''level'']>level:
continue
if cn[''level'']<level:
return result
# Recursion
if nn[''level'']==level:
dict_insert_or_append(result,cn[''name''],cn[''value''])
elif nn[''level'']>level:
rr = ttree_to_json(ttree[i+1:], level=nn[''level''])
dict_insert_or_append(result,cn[''name''],rr)
else:
dict_insert_or_append(result,cn[''name''],cn[''value''])
return result
return result
dónde:
def dict_insert_or_append(adict,key,val):
"""Insert a value in dict at key if one does not exist
Otherwise, convert value to list and append
"""
if key in adict:
if type(adict[key]) != list:
adict[key] = [adict[key]]
adict[key].append(val)
else:
adict[key] = val