Mensajes: 74
Temas: 12
Registro en: Aug 2017
Reputación:
4
13-07-2018, 07:33 PM
(Última modificación: 13-07-2018, 07:51 PM por calvicius.)
hola,
estoy revisando el codigo de un parser de partidas de ajedrez a fin de reducir cantidad de codigo y mejorar la rapidez del proceso.
se trata de leer un fichero con cierto numero de partidas, que están grabadas en ascii de la siguiente manera:
Código: [Event "Partida 1"]
[Site "Sitio 1"]
[Date "1992.11.04"]
[Round "1"]
[White "Fischer, Robert J."]
[Black "Spassky, Boris V."]
[Result "1/2-1/2"]
1. e4 e5 2. Nf3 Nc6 3. Bb5 {This opening is called the Ruy Lopez.} 3... a6
4. Ba4 Nf6 5. O-O Be7 1/2-1/2
[Event "Partida 2"]
[Site "Sitio 2"]
[Date "1992.11.04"]
[Round "2"]
[White "Fischer, Robert J."]
[Black "Spassky, Boris V."]
[Result "1/2-1/2"]
1. e4 e5 2. Nf3 Nc6 3. Bb5 {This opening is called the Ruy Lopez.} 3... a6
4. Ba4 Nf6 5. O-O Be7 1/2-1/2
en este caso hay solo dos partidas, pero pueden existir miles de ellas.
El problema que tengo es que no encuentro el fin del fichero (EOF) y me repite indefinidamente la última partida.
Pongo el código:
Código: # python 3.6.1
class IteradorPartidaCadena(object):
"""
Iterador que contiene cadenas multilínea que
representan partidas de un archivo PGN
"""
def __init__(self, nom_fich):
"""
Args:
nom_fich (str): PGN nombre fichero
"""
self.nom_fich = nom_fich
self.fich_iter = iter(open(self.nom_fich))
self.lineas_partida = []
self.fin = False
def __iter__(self):
"""iterador"""
return self
def __next__(self):
"""siguiente del iterador"""
if self.fin is True:
# raise StopIteration # python 2.7
return # python 3.5
try:
while True:
linea = next(self.fich_iter)
if linea.startswith("[Event"):
if len(self.lineas_partida) == 0:
self.lineas_partida.append(linea)
continue
else:
lineas_partida = self.lineas_partida[:]
self.lineas_partida = []
self.lineas_partida.append(linea)
partida_cadena = "".join(lineas_partida)
return partida_cadena
else:
self.lineas_partida.append(linea)
except StopIteration:
lineas_partida = self.lineas_partida[:]
partida_cadena = "".join(lineas_partida)
#print(117, partida_cadena)
self.end = True
return partida_cadena
class IteradorPartida(object):
"""
Iterador que contiene partidas de un archivo PGN
"""
def __init__(self, nom_fich):
"""
Args:
nom_fich (str): fichero.pgn (nombre del fichero)
"""
self.iterador_partida_str = IteradorPartidaCadena(nom_fich)
def __iter__(self):
"""iterador"""
return self
def __next__(self):
"""siguiente"""
for parti_str in self.iterador_partida_str:
return parti_str
if __name__ == '__main__':
for parti in IteradorPartida("comentarios.pgn"):
print(parti)
Estoy casi seguro de que es solo cuestión de algún detalle tonto, pero llevo dos días con esto y no logro atinar.
¿Alguien me puede indicar donde puede esta el error?. Gracias anticipadas.
edito:
Lo de la repetición infinita de la ultima partida está arreglado (lo he visto justo al revisar el post
se trataba de sustituir self.end por self.fin (cosa de manejar indistintos idiomas)
ahora la salida que me reporta es un bucle infinito con None:
Código: [Event "Partida 1"]
[Site "Sitio 1"]
[Date "1992.11.04"]
[Round "1"]
[White "Fischer, Robert J."]
[Black "Spassky, Boris V."]
[Result "1/2-1/2"]
1. e4 e5 2. Nf3 Nc6 3. Bb5 {This opening is called the Ruy Lopez.} 3... a6
4. Ba4 Nf6 5. O-O Be7 1/2-1/2
[Event "Partida 2"]
[Site "Sitio 2"]
[Date "1992.11.04"]
[Round "2"]
[White "Fischer, Robert J."]
[Black "Spassky, Boris V."]
[Result "1/2-1/2"]
1. e4 e5 2. Nf3 Nc6 3. Bb5 {This opening is called the Ruy Lopez.} 3... a6
4. Ba4 Nf6 5. O-O Be7 1/2-1/2
None
...
None
...
se puede solucionar dentro del for en main con un
if parti is not None:
print (parti)
else:
break
pero tengo que tener un error en las clases que no encuentro
Mensajes: 1.305
Temas: 3
Registro en: Feb 2016
Reputación:
71
Hola, no quedó muy claro cuál es el problema ahora, pero en estos casos usar el depurador es bastante conveniente. Si usas algún IDE que soporte la depuración en Python, genial, si no puedes usar manualmente el módulo pdb.
Saludos!
Mensajes: 74
Temas: 12
Registro en: Aug 2017
Reputación:
4
15-07-2018, 07:18 AM
(Última modificación: 15-07-2018, 07:38 AM por calvicius.)
(14-07-2018, 10:41 PM)Francisco escribió: Hola, no quedó muy claro cuál es el problema ahora, pero en estos casos usar el depurador es bastante conveniente. Si usas algún IDE que soporte la depuración en Python, genial, si no puedes usar manualmente el módulo pdb.
Saludos!
El problema es que, teóricamente, el iterador, al alcanzar el final del fichero debiere parar y salir del bucle for por si solo.
No sé si debe ser así, o ,bien, tengo que controlarlo obligatoriamente con un try / except así:
-dentro de la clase IteradorPartida, en el método __next__:
for parti_str in self.iterador_partida_str:
try:
return parti_str
except AttributeError:
break
Nota: de este modo me funciona correctamente.
Pongo el programa completo, ya con un funcionamiento correcto:
Código: # python 3.6.1
import re
class PartidaPGN(object):
def __init__(self, event=None, site=None, date=None, round=None,
white=None,
black=None,
result=None):
'''
Inicializamos una partida en formato PGN,
recibimos los siete tags minimos requeridos
para su inicializacion desde el propio pgn
'''
# inicializamos los tag estandar minimos
self.Event = event
self.Site = site
self.Date = date # formato ????-??-??
self.Round = round # "Num."
self.White = white
self.Black = black
self.Result = result # 1-0, 0-1, 1/2-1/2, *
# ahora añadimos los tags no estandar segun pag. oficial
# http://www.saremba.de/chessgml/standards/pgn/pgn-complete.htm#c9.2.1
# estos tags los suelen añadir los programas comerciales
# No se añaden los especificos de USA (terminados en USFC)
# 1. Informacion relativa a los jugadores
self.WhiteTitle = None # se usa un - para un jug. sin titulo
self.BlackTitle = None # los posibles valores son "FM", "IM", y "GM"
self.WhiteElo = None # rating FIDE de cada jugador.
self.BlackElo = None # se usa un - para un jugador sin puntuacion
self.WhiteType = None # los valores son:
self.BlackType = None # 'human' o 'program'
# 2. Informacion relacion con el evento
self.EventDate = None # fecha del inicion del evento ????-??-??
self.EventSponsor = None # string. el patrocinador del torneo
self.Section = None # seccion torneo (p.ej. Open, Senior, ...)
self.Stage = None # estadio del torneo (p.ej. semifinal, etc...)
self.Board = None # num. tablero en equipo
# 3. Informacion de aperturas
self.Opening = None
self.Variation = None
self.SubVariation = None
self.ECO = None # encyclopedia Chess Openings
self.NIC = None # New in Chess
# 4. Informacion adicional fecha y hora
self.Time = None # hora inicio partida 'HH:MM:SS'
self.TimeControl = None
# 5. Posiciones iniciales alternativas
self.SetUp = None # "0" = inic. estandar, "1" = otra FEN distinta
self.FEN = None # posic. inicial no estandar
# 4. Tags varios
self.Termination = None # string explicativo de como termina la partida
self.Annotator = None # comentarista
self.Mode = None
# Mode =
# "OTB" (over the board), "PM" (paper mail), "EM" (electronic mail),
# "ICS" (Internet Chess Server), and "TC" (general telecommunication).
self.PlyCount = None # num. movimientos partida
self.jugadas = []
def __repr__(self):
return '<PartidaPGN "%s" vs "%s">' % (self.White, self.Black)
class IteradorPartidaCadena(object):
"""
Iterador que contiene cadenas multilínea que
representan partidas de un archivo PGN
"""
def __init__(self, nom_fich):
"""
Args:
nom_fich (str): PGN nombre fichero
"""
self.nom_fich = nom_fich
self.fich_iter = iter(open(self.nom_fich))
self.lineas_partida = []
self.fin = False
def __iter__(self):
"""iterador"""
return self
def __next__(self):
"""siguiente del iterador"""
if self.fin is True:
# raise StopIteration # python 2.7
return # python 3.5
try:
while True:
linea = next(self.fich_iter)
if linea.startswith("[Event "):
if len(self.lineas_partida) == 0:
self.lineas_partida.append(linea)
continue
else:
lineas_partida = self.lineas_partida[:]
self.lineas_partida = []
self.lineas_partida.append(linea)
partida_cadena = "".join(lineas_partida)
return partida_cadena
else:
self.lineas_partida.append(linea)
except StopIteration:
lineas_partida = self.lineas_partida[:]
partida_cadena = "".join(lineas_partida)
self.fin = True
return partida_cadena
class IteradorPartida(object):
"""
Iterador que contiene partidas de un archivo PGN
"""
def __init__(self, nom_fich):
"""
Args:
nom_fich (str): fichero.pgn (nombre del fichero)
"""
self.iterador_partida_str = IteradorPartidaCadena(nom_fich)
def __iter__(self):
"""iterador"""
return self
def __next__(self):
"""siguiente"""
for parti_str in self.iterador_partida_str:
try:
partida = carga_str(parti_str)[0]
return partida
except AttributeError:
break
def _pre_procesa_texto(texto):
'''
Esta función es responsable de eliminar los comentarios
de la línea final (; comentario), líneas en blanco y
espacios adicionales. Además, convierte
``\\r \\n`` en ``\\n``.
Asimismo separa los inicios `(`y finales `)` de variantes.
p.ej.: `(29. Ra2)`en `( 29. Ra2 )` para el array final
de la partida; quedando asi: `(`, `Ra2`, `)`
'''
texto = texto.replace('(', '( ')
texto = texto.replace(')', ' )')
texto1 = re.sub(r'\s*(\\r)?\\n\s*', '\n', texto.strip())
lineas = []
for linea in texto1.split('\n'):
linea1 = re.sub(r'(\s*;.*|^\s*)', '', linea)
if linea1:
lineas.append(linea1)
return lineas
def _siguiente_token(lineas):
'''
Obtenga el siguiente token de las líneas
(lista de líneas de archivos pgn de texto).
Hay 2 tipos de tokens: etiquetas y movimientos.
Los tokens de etiquetas comienzan con `` [`` char, p.ej.
`` [TagName "Tag Value"] ``. Las etiquetas Moves siguen el ejemplo:
`` 1. e4 e5 2. d4``.
'''
if not lineas:
return None
token = lineas.pop(0).strip()
if token.startswith('['):
return token
while lineas and not lineas[0].startswith('['):
token += ' '+lineas.pop(0).strip()
return token.strip()
def _parsea_tag(token):
'''
Analiza un token de la cabecer y devuelve una tupla
con (tagName, tagValor).
'''
tag, valor = re.match(r'\[(\w*)\s*(.+)', token).groups()
#return tag.lower(), valor.strip('"[] ')
return tag, valor.strip('"[] ')
def _parsea_jugadas(token):
'''
Analiza un token de jugadas y devuelve una lista con movimientos
'''
movims = []
while token:
token = re.sub(r'^\s*(\d+\.+\s*)?', '', token)
if token.startswith('{'):
pos = token.find('}')+1
else:
pos1 = token.find(' ')
pos2 = token.find('{')
if pos1 <= 0:
pos = pos2
elif pos2 <= 0:
pos = pos1
else:
pos = min([pos1, pos2])
if pos > 0:
movims.append(token[:pos])
token = token[pos:]
else:
movims.append(token)
token = ''
return movims
def carga_str(texto):
'''
Convierte la cadena "texto" en una lista de PartidasPGN
'''
partidas = []
partida = None
lineas = _pre_procesa_texto(texto)
while True:
token = _siguiente_token(lineas)
if not token:
break
if token.startswith('['):
tag, valor = _parsea_tag(token)
if not partida or (partida and partida.jugadas):
partida = PartidaPGN()
partidas.append(partida)
setattr(partida, tag, valor)
else:
partida.jugadas = _parsea_jugadas(token)
return partidas
if __name__ == '__main__':
for parti in IteradorPartida("grande.pgn"):
try:
print(parti.jugadas)
print(parti.White)
except AttributeError:
break
así como una pequeña aplicación de como usarlo:
Código: # Python 3.6.1
# parsea_sql.py
# forma de uso:
# python parsea_sql.py fich_entrada.pgn > salida.sql
import parseapgn
import sys
# elegimos solo estos tags de cabecera para prueba
cabeceras = ['Site', 'Date', 'White', 'Black', 'Result']
def valores_planos (game):
ret = '('
for field in cabeceras:
if hasattr(game, field):
# para escapar los apostrofes (especialmente en ingles)
# p.ej.: O\'Connor
ret += ' \'' + getattr(game, field).replace('\'', '\\\'') + '\', '
else:
ret += ' \'\', '
if hasattr(game, 'jugadas'):
ret += str(len(game.jugadas)) + ', '
ret += ' \'' + ' '.join(game.jugadas) + '\''
"""
lo mas conveniente sería guardar game.jugadas como json y base64
para evitar inyecciones (sql estandar). Por ejemplo:
import json
import base64
...
# serializar la lista en una cadena, y luego codificarla en formato base64
cadena_de_lista = json.dumps(mi_lista)
dato_a_grabar = base64.b64encode(cadena_de_lista)
...
Para recuperar la lista:
mi_lista = json.loads( base64.b64decode(dato_a_grabar))
"""
else:
ret += '0, \'\''
ret += ')'
return ret
if len(sys.argv) != 2:
print ('Uso: python parsea_sql.py input.pgn > out.sql')
i = 0
for partida in parseapgn.IteradorPartida(sys.argv[1]):
if not(hasattr(partida, 'Result')):
break;
if (i % 500) == 0:
print ('INSERT INTO parties (' + ', '.join(cabeceras) + ', moves, game) VALUES ')
print (valores_planos(partida))
if (i % 500) == 499:
print (';')
else:
print (',')
i += 1
print (';')
"""
Creacón de la tabla SQL
CREATE TABLE `parties` (
`ID` int(11) NOT NULL,
`date` varchar(10) NOT NULL,
`site` varchar(32) NOT NULL,
`white` varchar(32) NOT NULL,
`black` varchar(32) NOT NULL,
`result` varchar(10) NOT NULL,
`moves` int(11) NOT NULL DEFAULT '0',
`game` longtext NOT NULL
);
ALTER TABLE `parties` ADD PRIMARY KEY (`ID`);
ALTER TABLE `parties` MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT;
"""
Mensajes: 1.305
Temas: 3
Registro en: Feb 2016
Reputación:
71
15-07-2018, 11:45 AM
(Última modificación: 15-07-2018, 11:45 AM por Francisco.)
Con respecto a este código:
Código: if self.fin is True:
# raise StopIteration # python 2.7
return # python 3.5
¿Cuál es el sentido de retornarn None? El método __next__() siempre debe lanzar StopIteration, incluso en Python 3.5.
Mensajes: 74
Temas: 12
Registro en: Aug 2017
Reputación:
4
(15-07-2018, 11:45 AM)Francisco escribió: Con respecto a este código:
Código: if self.fin is True:
# raise StopIteration # python 2.7
return # python 3.5
¿Cuál es el sentido de retornarn None? El método __next__() siempre debe lanzar StopIteration, incluso en Python 3.5.
si quito el condicional if self.fin is True, me repite la ultima partida indefinidamente
---------
si dejo :
Código: if self.fin is True:
raise StopIteration # python 2.7
sin capturar la excepcion en el bucle for del main me lanza la siguiente excepción:
Código: Traceback (most recent call last):
File "D:VisorJS_v2parseapgn.py", line 264, in <module>
print(parti.jugadas)
AttributeError: 'NoneType' object has no attribute 'jugadas'
El bucle for del __next__ en class IteradorPartidaCadena no lanza excepción
--------
si dejo:
Código: if self.fin is True:
return
me lanza la excepciones del mismo tipo de arriba en _pre_procesa_texto
--------
si lo cargo como modulo en otro programa:
me funciona correctamente sin lanzar excepciones con:
Código: if self.fin is True:
raise StopIteration
asi que he decidido capturar las posibles excepciones en el bucle for de la clase IteradorPartidaCadena.
pero sigo sin entender por qué no para la iteración despues de :
Código: except StopIteration:
lineas_partida = self.lineas_partida[:]
partida_cadena = "".join(lineas_partida)
self.fin = True
return partida_cadena
en la linea 118
Mensajes: 1.305
Temas: 3
Registro en: Feb 2016
Reputación:
71
Al iterar sobre tu clase no deberías estar chequeando nunca si parti is None o bien si lanza AttributeError por la misma razón; y esto ocurre porque en algún momento __next__() (en cualquiera de las dos clases) está retornando None en lugar de lanzar StopIteration que es la encargada de terminar el bucle.
Mensajes: 74
Temas: 12
Registro en: Aug 2017
Reputación:
4
15-07-2018, 08:14 PM
(Última modificación: 15-07-2018, 08:16 PM por calvicius.)
Francisco, gracias por tu interés.
Despues de darle muchas vueltas al problema, creo que ya se cual es la causa. La definición del propio fichero es el origen del problema.
Estos ficheros pgn de partidas de ajedrez tienen la siguiente estructura:
cabeceras-1
\n
jugadas-1
\n
...
cabeceras-n
\n
jugadas-n
\n
si observamos al final del fichero siempre hay como mínimo un linea vacía (la cual debe existir) segun el estandar de este tipo de archivos. Algunos programas comerciales tratan con dos \n al final del fichero.
Por lo que presumo que siempre se añadirá un None al final, se quiera o no.
Teniendo casi la certeza de que este es el problema; la solución viene sola (sin establecer un manejador de excepciones):
Código: import parseapgn
for parti in parseapgn.IteradorPartida("comentarios.pgn"):
if hasattr(parti, 'jugadas'):
print(parti.jugadas)
#print(parti.White)
Mensajes: 74
Temas: 12
Registro en: Aug 2017
Reputación:
4
Bueno ... al final he solucionado mis pequeños problemas. He optado por el uso de generadores.
Pongo una versión resumida para no repetir todo:
Código: def abre_fichero(nom_fich):
lineas_partida = []
partida_cadena = ''
# abrimos el fichero para lectura
fich = open(nom_fich, 'r')
while True:
# read a single line
line = fich.readline()
if not line:
# recolectamos las lineas de la ultima partida
# que están todavia colgando
partida_cadena = ''.join(lineas_partida)
yield partida_cadena
break
if line.startswith("[Event "):
if len(lineas_partida) == 0:
lineas_partida.append(line)
continue
else:
lineas_partida_1 = lineas_partida[:]
lineas_partida = []
lineas_partida.append(line)
partida_cadena = "".join(lineas_partida_1)
yield partida_cadena
else:
lineas_partida.append(line)
# cerramos el puntero al fichero
fich.close()
for valor in abre_fichero('comentarios.pgn'):
print(valor)
para un fichero de datos en utf-8 sin BOM llamado comentarios.pgn, así:
Código: [Round "1"]
[White "Fischer, Robert J."]
[Black "Spassky, Boris V."]
[Result "1/2-1/2"]
1. e4 e5 2. Nf3 Nc6 3. Bb5 {This opening is called the Ruy Lopez.} 3... a6
4. Ba4 Nf6 5. O-O Be7 1/2-1/2
[Event "Partida 2"]
[Site "Sitio 2"]
[Date "fecha 2"]
[Round "2"]
[White "Fischer, Robert J."]
[Black "Spassky, Boris V."]
[Result "1/2-1/2"]
1. e4 e5 2. Nf3 Nc6 3. Bb5 {This opening is called the Ruy Lopez.} 3... a6
4. Ba4 Nf6 5. O-O Be7 1/2-1/2
[Event "Partida 3"]
[Site "Sitio 3"]
[Date "fecha 3"]
[Round "3"]
[White "Fischer, Robert J."]
[Black "Spassky, Boris V."]
[Result "1/2-1/2"]
1. e4 e5 2. Nf3 Nc6 3. Bb5 {This opening is called the Ruy Lopez.} 3... a6
4. Ba4 Nf6 5. O-O Be7 1/2-1/2
Mensajes: 1.305
Temas: 3
Registro en: Feb 2016
Reputación:
71
|