Notre programme “Envoyez nous les scripts que vous ne pigez pas” est toujours d’actu


Bon, je me traîne comme une limace hémiplégique pour les pondre mais comme on dit, “qui vol un cheval donné qu’à la fin l’étincelle se casse”.

Salut,

je viens de lire votre article “Envoyez nous les scripts que vous ne pigez pas”.
L’article date un peu, ptêt j’arrive trop tard.
Dans le cas contraire.. read on..

Voilà: je me ronge le cerveau sur un script python depuis un moment déjà, mais il reste aujourd’hui une lamentable énigme.

La situation:

On m’offre l’intégrale (26Go!) des cartes IGN, tombées du cyber-camion, accompagné de quelques lignes de python permettant de soit-disant trouver la carte qui nous intéresse par ses coordonnées GPS et de, une fois qu’on a une carte, trouver celles qui viennent se placer au Nord/Sud/Est/Ouest.

D’la balle, que j’me dis. J’y connais rien au python, mais le code est lisible, commenté, easy quoi.
Après quelques heures de lecture, le monde sera à moi.. C’était il y a deux mois.
Échec total de communication entre mes synapses et le serpent.
Jamais plus je ne toucherai à du Python me suis-dis alors.
Jusqu’à ce que l’espoir renaisse à lecture de votre billet qui-fut-pour-moi-comme-un-arc-en-ciel (et hop, léchage de cul qui fera, à n’en point douter, toute la différence).

Je m’en remets donc à vous, nobles aventuriers, et vous remets votre carte au trésor ci-dessous:

* find_map_by_coordinate.py
http://0bin.net/paste/8a658d3a8f3149ee3af36b7f9abb0e291d00d3f1#OYNAOG9Bhuk/cdC6SgsgVxoW+s5eADhCwjUGACNI4wk=

* find_close_maps.py
http://0bin.net/paste/5a1a9236cddfbea5abbc4c21459856acfd21fd17#MncBXtbHcQFaKW8l3aEVmyNc+0gqe4/iTU6aIatAK4w=

* ign25.py
http://0bin.net/paste/f50e61ad54b9e53d6dd99a655d9cf564eb7a5ad7#QWa6Fz9GejLNK5+niZNqc6/fTm0Pwvl2fBjtPGL8I5Y=

Et pour répondre à votre question, oui, je peux vous envoyer les précieuses cartes.
Reste simplement à trouver un moyen de vous faire passer 26Go de données..

Bon décodage!

Bien à vous,

(pour partouze c’est toujours OK ?)

Bon, d’accord, j’ai rajouté la dernière ligne.

Donc, j’ai lu les scripts, et je les ai commenté au fil de l’eau. Vous allez voir, ça se sent, c’est assez organique et on sent bien se développer mes réactions au fur et à mesure face à ce script.

Spoiler : cher ami lecteur, tes synapses ne sont pas en cause, l’auteur du script n’est pas un Dieu de la prog, et en fait je ne suis même pas sûr que son code marche. Même un petit peu. Auteur anonyme si tu nous lis, toutes mes condoléances.

Comme ça va être long, et conforme à la tradition :

find_map_by_coordinate.py

Là je découvre, c’est l’aventure, on sent l’enthousiasme.

#!/usr/bin/python
 
 
# Ceci est un script de recherche de coordonnées, il trouve une carte qui
# contient ces coordonnées dans la base de données des cartes. Le but est donc
# de prendre en entrée des coordonnées sous forme de latitudes et longitudes
# saisies par l'utilisateur et d'afficher le nom des cartes sur son terminal.
#
# Ex:
#
# $ python ./find_map_by_coordinate.py (5, 32) (3, 44)
# Trying to open database...
# ...OK
# Searching for (104, 32),  (201, 44)...
# ...found.
# ['/chemin/vers/carte1', '/chemin/vers/carte2']
 
# Comme d'habitude on a les imports en premier, ici l'auteur a choisi
# de mettre les imports de ses propres modules avant ceux de la lib
# standard, je recommande l'inverse.
 
from ign25 import load_db, find
 
import os
import sys
import ast
 
# Plutôt que de uniquement des prints, l'auteur choisit d'utiliser
# le module de logging, mais l'utilise au final pour faire des prints.
# Du coup je ne suis pas certains de l'interêt de la chose, peut être
# que l'intention est de le faire plus plus tard.
# Dans tous les cas il met le niveau de logging sur DEBUG, ce qui fait
# que tous les messages seront affichés.
import logging
logging.basicConfig(level=logging.DEBUG)
 
# On a ici l'idiome connu qui permet de n'exécuter le bloc que si
# le script et lancé directement (et pas importé). Dans ce cas c'est inutile,
# en effet il n'y a rien dans ce script qu'on puisse vouloir importer, c'est
# donc un script qui est TOUJOURS destiné à être lancé. Encore une fois
# c'est peut être un indice d'une intention future de l'auteur.
if __name__ == "__main__":
 
    # Le parsing des arguments est fait à la main. Pour un petit script
    # comme ça ça a du sens (on a pas toujours envie de sortir l'artillerie
    # lourde avec argparse)
    # En gros, si il y a les deux arguments nécessaires (sys.argv contient
    # le nom du script et chaque argument passé au script), on les traite,
    # sinon on affiche un message indiquant comment utiliser le script.
    # Le traitement des arguments est une évaluation du code comme si c'était
    # du Python. Encore une fois je ne recommande pas cela, demander les
    # degrés et les minutes sous la forme naturelle (34°27') et le parser
    # avec des regex est plus propre ou de passer des longitudes et latitudes
    # via 4 paramètres au lieu de deux ici.
    if len(sys.argv) >= 3:
        x = ast.literal_eval(sys.argv[1])
        y = ast.literal_eval(sys.argv[2])
    else:
        print("find map for given coordiantes")
        print("")
        print("usage: %s [lat] [lon]" % sys.argv[0])
        print("       with [lat,lon] in degrees or in (degrees, minutes)")
        sys.exit(0)
 
    # Ici on fait appel à la fonction load_db() qui se connecte à la
    # base de données
    db = load_db()
 
    # On lance la recherche dans la base de données avec la fonction find()
    # qui est aussi issue du module ign25, et on affiche les résultats.
    logging.info("Searching for %s, %s..." % (x, y))
    r = find(db, x, y)
    logging.info("...found.")
    print(r)

find_close_maps.py

Je tique un peu. Il y a des trucs bizarres. Mais bon, c’est peut être moi.

#!/usr/bin/python
 
# Même principe, mais pour les cartes proche d'une autre carte. Je ne vais
# donc que commenter les différences.
# Il attend un fichier .map en paramètre et va lister toutes les cartes de
# la base de données qui sont à 10km de la carte dont le fichier a été passé
# en paramètres.
 
 
import os
import os.path
import sys
import math
 
import logging
logging.basicConfig(level=logging.DEBUG)
 
from ign25 import *
 
 
if __name__ == "__main__":
 
    if len(sys.argv) < 2:
        print "find maps around a given map"
        print ""
        print "usage: %s [reference map]" % sys.argv[0]
        sys.exit(0)
 
    db = load_db()
 
    # On ouvre le fichier de carte, et on récupère les coordonnées des 4
    # extrémités du carré que forme la carte sous le forme d'un objet de type
    # MapInfo qui contiendra les attributs nw, ne, se, sw (Nord-Ouest, Nord-Est,
    # Sud-Est, Sud-Est)
    try:
        reference = get_info(open(sys.argv[1] + ".map"))
    except:
        print "map %s not found" % sys.argv[1]
    maxDistance = 10 #in km
    # la distance maximale est écrite en dure, on pourrait la passer en paramètre
 
    # Ensuite la recherche se fait avec encore une fois une fonction de
    # ign25, donc la logique de ce script est assez simple puisque la
    # complexité est dans ign25.py. On récupère une liste de carte.
    closemaps = gen_closemaps(db, reference, maxDistance)
 
    # La liste est affichée mais on remplace la carte par une image de la carte
    # au format PNG (qui je suppose est fournie avec la carte ?)
    for i in closemaps:
        print os.path.abspath("../png/" + i.filename + ".png")

ign25.py

Photo d'un arbre planté à côté de son pot

... yet so far

#!/usr/bin/python
 
"""Module to work with my set of IGN TOP25 maps
 
"""
 
# Toute la logique des scripts précédents est définie ici.
# Encore une fois la docstring est un peu légère et des commentaires
# en plus ne serait pas de refus car du coup pour comprendre ce que fais
# le script juste en les recevant par email, c'est moins facile, forcément ^^
 
# Encore une fois, je vous invite à ne pas faire plein d'imports sur une ligne
# si ils ne sont pas du même module. D'autant qu'ici les modules shutils et
# math ne sont pas utilisé dans le script, et os.path est importé deux fois.
# Cher auteur de script, loin de moi l'idée d'insulter ton travail, j'ai
# conscience que tu es sans aucun doute un géographe et non un développeur,
# ne m'en veut donc pas, je ne fais que commenter le script à des fins
# pédagogiques et pas du tout pour te descendre. 
import os, re, math, shutil, os.path, sys
import ast
import logging
import os.path
import math
 
# Déclaration de constantes. Mettez les constantes en majuscules car la notion
# de constante n'existe pas en Python et seule cette convention permet
# de savoir qu'il ne faut pas y toucher.
 
easting2km = 0.73847
northing2km = 0.53848
northAdjust = easting2km / northing2km
 
# Une classe vide qui va servir de conteneur. Je comprends que si l'on vient
# d'un langage avec des structs, c'est une tendance naturelle. Néanmoins,
# si il n'y a aucune logique associée à vos données, préférez un dictionnaire
# ou un namedtuple.
class MapInfo:
    """Store map information"""
    pass
 
# Ah, un peu de doc, ça fait plaisir :-)
 
# Routines to read .map files
 
def get_coordinates(line):
    """Read the coordiantes of a point
 
    line:       line describing the point
    returns:    a tuple of tuples ((xdeg, xmin), (ydeg, ymin))"""
    try:
        # La fonction analyse une ligne de texte formattée ainsi:
        # champ1,    champ2    , champ3    ,    champ4...
        # et en extrait les degrés et les minutes avec un split sur
        # le caractère virgule entouré par un nombre indéterminé de caractères
        # non imprimables (espaces, tabulations...)
        fields = re.split( "\s*,\s*", line )
        # adjust for East, West. We are always in the northern hemisphere.
 
        # Detecte si on est dans l'hémisphèse Nord (je suppose que le onzième
        # champ est égale à "W" dans ce cas, et "E" dans l'autre cas mais
        # je n'ai pas le format sous les yeux pour le vérifier).
        # Bref, si on est dans l'hémisphère Nord, on donne aux champs une valeur
        # négative. Je suppose que ça parle aux amateurs de GIS, comme je
        # suis incompétent dans le domaine, je ne vais pas faire mon malin
        # et tenter d'expliquer pourquoi.
        if fields[11] == "W":
            fields[9] = "-" + fields[9]
            fields[10] = "-" + fields[10]
 
        # La fonction retourne ensuite un tuple de deux tuples avec les valeurs
        # ou None si il a eu une erreur.
        # Je ne sais pas où il peu y avoir une ValueError là dedans, donc
        # difficile de dire quelle erreur l'auteur cherche à gérer.
 
        return ((int(fields[6]), float(fields[7])), (int(fields[9]), float(fields[10])))
    except ValueError:
        return None
 
# La fonction utilisée dans find_close_maps.py, qui lit un fichier carte
# (attend un file like object en paramètre) et retourne un objet MapInfo().
# Comme je vous l'ai dis dans ce cas il vaudrait mieux retourner un dictionnaire
# ou un namedtuple.
def get_info(file):
    """Read map information in `file`
 
    file:       file object to read
    returns:    MapInfo"""
    i = MapInfo()
    # Je pense que le but recherche est d'extraire le nom du fichier sans
    # extention. os.path.splitext(file.name)[0] aurait été préférable
    i.filename = file.name[:-4]
    # Pour chaque ligne du fichier carte, on va vérifier si la ligne
    # contient la chaîne Point0x, extraire les coordonnées de la ligne
    # et les stocker dans un attribut représentant un des coins de la carte
    # de l'objet MapInfo().
    #
    # Pour les améliorations possibles :
    # - mettre le return dans la boucle for
    # - re.match(r'regex', line) suffit
    # - retourner None plutôt que False
    for line in file:
        if re.compile(r'Point03').match(line):
            i.nw = get_coordinates(line)
        elif re.compile(r'Point02').match(line):
            i.ne = get_coordinates(line)
        elif re.compile(r'Point04').match(line):
            i.sw = get_coordinates(line)
        elif re.compile(r'Point01').match(line):
            i.se = get_coordinates(line)
    if hasattr(i, 'nw'):
        return i
    else:
        return False
# Une manière concise d'écrire la même fonction aurait pu être:
# data = {'type': os.path.splitext(file.name)[0]}
# for line in file:
#     res = re.search('Point0(?P<se>1)?(?P<ne>2)?(?P<nw>3)?(?P<sw>3)?', line)
#     if res:
#         res = (name for name, val in res.groupdict().items() if val).next()
#         data[name] = get_coordinates(line)
#         if len(data) == 4
#             return data
# return None
# Mais bon, la c'est de l'enculage de mouche, l'important c'est que ça marche.
 
# Cette fonction n'est pas appelée dans les autres scripts.
# Elle retourne un générateur qui retourne, pour chaque fichier passé
# dans l'itérable en paramètre (par exemple une liste de chemins de fichier)
# un générateur donc chaque élément est un objet MapInfo contenant les
# infos de la carte correspondant à chaque nom de fichier
def gen_info(filenames):
    for filename in filenames:
        try:
            logging.info("Reading %s" % filename)
            f = open(filename)
            info = get_info(f)
            if info:
                yield info
        except IOError:
            pass
 
# Pareil, pas utilisé. Ca fabrique une liste en listant les fichiers d'un
# dossier nomme "map" (on va supposé qu'il est déjà là ?), on passant
# cette liste à gen_info() qu'on vient de voir plus haut, et en transformant
# le générateur obtenu en liste.
def generate_database():
    if os.path.isdir('map'):
        mapdir = 'map'
    else:
        mapdir = '.'
    fnames = os.listdir (mapdir)
    return list(gen_info(fnames))
 
# Routine to print db
 
# Idem, pas utilisé. On passe une base de données en paramètres
# Et on print un listing de tous les fichiers de cartes de la BDD et
# leurs coordonnées.
def print_database(db):
    print 'filename,', 'nw,', 'ne,', 'sw,', 'se,'
    for i in db:
        # La seule partie compliquée : on définie une fonction
        # dans le corps de la boucle for et on l'utilise cash pistache.
        # Une manière plus propre aurait été :
        # vals = (i.filename, i.nw, i.ne, i.sw, i.se)
        # print ' '.join(str(x).replace(' ','') for x in vals)
        # Si on avait un namedtuple plutôt qu'un objet, une ligne aurait suffit
        def rms(s):
            return str(s).replace(' ','')
        print i.filename, rms(i.nw), rms(i.ne), rms(i.sw), rms(i.se)
 
# Itou, pas utilisé. On prend chaque carte dd'un dossier, et on écrit
# toutes les infos obtenues avec les fonctions précédentes dans un fichier.
# Encore une fois notez que si on avait utilisé un namedtupple, l'opération
# aurait pris 4 lignes.
# Il y a néanmoins 3 choses qui m'interpellent dans ce code :
# - pourquoi appeler ça une base de données ? C'est juste un listing texte
# - pourquoi ne pas avoir choisit le format CSV (il y a un module pour ça)
# - pourquoi writelineS() et pas writeline() ? Est-ce un bug ?
def save_database(db, path):
    f = open(path, 'w')
    f.writelines(('filename ', 'nw ', 'ne ', 'sw ', 'se ', '\n'))
    for i in db:
        def rms(s):
            return str(s).replace(' ','')
        f.writelines((i.filename, ' ',
                      rms(i.nw), ' ',
                      rms(i.ne), ' ',
                      rms(i.sw), ' ',
                      rms(i.se), '\n'))
 
# Routine to import db L'inverse de la fonction précédente. Chaque tout le
# fichier et retourne une liste d'objet MapInfos(). Je pense que l'auteur ne
# connais pas la fonction float()
def import_database(path):
    db = []
    f = open(path)
    f.readline()
    for line in f:
        fields = line.split()
        i = MapInfo()
        i.filename = str(fields[0])
        i.nw = ast.literal_eval(fields[1])
        i.ne = ast.literal_eval(fields[2])
        i.sw = ast.literal_eval(fields[3])
        i.se = ast.literal_eval(fields[4])
        db.append(i)
    return db
 
# Ca appelle import_database et si ça ne marche pas, ça
# génère une "base de données".
def load_db():
    try:
        logging.info("Trying to open database...")
        db = import_database("map.db")
        logging.info("...OK.")
    except IOError:
        logging.info("...failed. Reading .map files...")
        db = generate_database()
        logging.info("Saving database...")
        save_database(db, "map.db")
    return db
 
# Lat/lon to filename
# Une fonction pour te rendre bien deg.
# Elle converti un tuple ou une liste Latitude / Longitude en une valeur
# en degré. Malheureusement elle tue complètement le duck typing (elle pourrait
# accepter n'importe quel iterable). Une version pythonique serait :
# try:
#   deg, min = x # marche avec TOUT itérable
# except TypeError:
#   deg, min = x, 0
# return float(deg) + float(min)/60. # accepte tout ce qui est castable en float
#
def to_deg(x):
    # isinstance est rarement une bonne idée en Python. Très rarement.
    if isinstance(x, tuple) or isinstance(x, list):
        xdeg, xmin = x # un bon petit unpacking !
    else:
        xdeg = x
        xmin = 0
    return xdeg + xmin/60.
 
# Trouve une carte qui contienne ces coordonnées, retourne le filename
# plutôt que l'objet MapInfo() (pourquoi ?).
def find(db, x, y):
    target_xdeg = to_deg(x)
    target_ydeg = to_deg(y)
    #print "target", target_xdeg, target_ydeg
    for i in db:
        nw_x, nw_y = i.nw
        se_x, se_y = i.se
        nw_xdeg = to_deg(nw_x)
        nw_ydeg = to_deg(nw_y)
        se_xdeg = to_deg(se_x)
        se_ydeg = to_deg(se_y)
        # est-ce que les bordures de la carte contiennent les coordonnées ?
        if nw_xdeg <= target_xdeg and target_xdeg <= se_xdeg:
            #print "candidate x", nw_xdeg, se_xdeg
            if nw_ydeg >= target_ydeg and target_ydeg >= se_ydeg:
                #print "candidate y", nw_ydeg, se_ydeg
                return i.filename
 
# Close maps
# Vérifie qu'une carte est proche d'une autre carte. Le PEP8 recommanderait
# de l'appeler is_close().
def isclose(candidate, reference, max_distance):
    if candidate.filename == reference.filename:
        return False
    # put everything in minutes
    # On repasse toutes les coordonnées en minutes
    infoEastings = to_deg(candidate.nw[0])*60
    infoNorthings = to_deg(candidate.nw[1])*60*northAdjust
    referenceEastings = to_deg(reference.nw[0])*60
    referenceNorthings = to_deg(reference.nw[1])*60*northAdjust
 
    # Pythagore, les maths de 6eme, ça vous revient ?
    distance = ( math.sqrt(
        ( infoEastings - referenceEastings )**2 + ( infoNorthings - referenceNorthings )**2 )
          * easting2km )
    if distance < max_distance:
        return True
    else:
        return False
 
# un générateur qui yield toutes les cartes proches d'une distance
def gen_closemaps(db, reference, max_distance):
    for i in db:
        if isclose(i, reference, max_distance):
            yield i
 
# Tile
# Une fonction pas utilisée qui convertie les degré en..., heu... lat / long ?
# Merci le module math de Python qui permet de s'amuser avec la trigonométrie
def deg2num(zoom, lat_deg, lon_deg):
    """Lon./lat. to tile numbers"""
    lat_rad = math.radians(lat_deg)
    n = 2.0 ** zoom
    xtile = int((lon_deg + 180.0) / 360.0 * n)
    ytile = int((1.0 - math.log(math.tan(lat_rad) + (1 / math.cos(lat_rad))) / math.pi) / 2.0 * n)
    return (xtile, ytile)
 
# Pareil, pas utilisé. C'est la fête du slip. On fait des fonctions pour
# le fun, si ça se trouve on les importera un jour dans le shell, un dimanche
# de pluie
def num2deg(zoom, xtile, ytile):
    """Tile numbers to lon./lat."""
    n = 2.0 ** zoom
    lon_deg = xtile / n * 360.0 - 180.0
    lat_rad = math.atan(math.sinh(math.pi * (1 - 2 * ytile / n)))
    lat_deg = math.degrees(lat_rad)
    return (lat_deg, lon_deg)
 
# obtenir une les map qui contienne les coordonnées suivantes
# Une tile est un morceau de carte qu'on compte utiliser pour faire une
# carte plus grosse en assemblant les parties
def get_tile(z, x, y):
    lon_deg, lat_deg = num2deg(z, x, y)
    logging.debug("conv deg: %f, %f" % (lon_deg, lat_deg))
    db = load_db()
    filename = find(db, lon_deg, lat_deg)
    return filename
 
# Une version plus simple de find_map_by_coordinate.py qui utilise
# if __name__ == "__main__" à bon escient
if __name__ == "__main__":
 
    if len(sys.argv) >= 3:
        x = ast.literal_eval(sys.argv[1])
        y = ast.literal_eval(sys.argv[2])
    else:
        x = (48, 41.29)
        y = (2, 22)
 
    db = load_db()
 
    logging.info("Searching %f, %f..." % (x, y))
    r = find(db, x, y)
    logging.info("...found: %s" % r)

En conclusion, “tout est bien qui part à point sous les ponts”.

9 thoughts on “Notre programme “Envoyez nous les scripts que vous ne pigez pas” est toujours d’actu

  • roro

    Belle enquête, y’a pu qu’à attendre les aveux du coupable.
    J’aime bien les com’s de tes com’s, ça éclaire sur:
    Le pourquoi de la pluie le Dimanche.
    Avec Google map “on clic”, on ne voit plus trop l’intérêt de branler 25Go de cartes, mais bon….

  • kontre

    Si la string n’est pas un entier, le int(fields[6]) et consorts renvoient une ValueError, c’est peut-être ça qu’il vérifie ?

    Pour un scientifique, une base de donnée c’est un machin dans lequel il y a des données. Faut pas chercher plus loin. Donc un fichier texte, un tableau html, du xml, un csv, tout ça c’est des bases de données en puissance ! J’ai eu quelques cours sur les “vraies” BDD, (je suis une exception), j’en sais assez pour comprendre le principe mais pas pour les utiliser vraiment, et encore moins pour monter une architecture avec. Par contre je sais utiliser le module csv (ou mieux pour les scientifiques, pandas).

    Je ne comprends pas le problème avec load_db(). get_coordinates() récupère les valeurs dans les fichiers de données (pas la database) et renvoie les 4 champs à stocker dans la database. Je ne vois pas le souci, c’est moi qui ai raté un truc ?

    J’ai eu un cas foireux où j’ai été obligé d’utiliser isinstance, presque dans le même cas qu’ici : j’avais soit une chaîne de caractères, soit un tuple de chaîne. mais comme les chaines sont itérables, je ne pouvais pas utiliser TypeError. Vous verriez un autre moyen ?

    if distance < max_distance:
        return True
    else:
        return False

    Ben, et alors :

    return distance < max_distance

    Je ne crois pas que les “tile numbers” soient des lat/long, mais plutôt les index des différentes cartes : à cause que cette connasse de Terre est ronde, on s’emmerde à trouver des projections pour représenter les cartes. Plus la zone cartographiée est grande, plus il y aura de déformations, donc on préfère faire plein de petites sous-cartes et les indexer. C’est pour ça qu’on s’amuse avec les recherches de cartes à telles cordonnées… D’habitude les index ont une lettre et un nombre plutôt que 2 nombres, mais ça dépend du format des données. C’est clair dans ma tête, mais p’têt pas dans ce que j’ai écrit…

    ign25.py fait partie d’un plan plus global, c’est pour ça qu’il y a plein de trucs dedans. Bientôt, l’IGN dominera le monde !

  • Sam Post author

    Pour le int(fields[6]), peut être, mais dans quel cas ça pourrait ne pas être un int ? Si le champ est vide ? Si le champ n’existe pas ? Il n’y a pas de valeur par défaut ? Par de gestion de IndexError ? C’est très zarb.

    Tu as raison, j’ai dis une connerie sur get_coordinates, il faut que je corrige ça.

    Pour ton cas de tuple, non, c’est un des cas d’utilisation correcte de isinstance, à condition que ce soit fait sur basestring, et pas sur str, unicode, tuple ou list.

    Bien vu pour “distance < max_distance". J'avoue qu'après avoir passé 2 heures sur le machin, il était minuit, sur la fin je me suis relaché ^^ Pour le vocabulaire, clairement je connais rien en géographie, donc pardonnez moi mes grosses bêtises, elles sont là pour montrer que l'auteur du script n'est pas le seul amateur hors de son domaine d'expertise.

  • kontre

    Le TypeError, pour moi c’est dans le cas où il y a un souci dans le fichier texte : il se prémunit contre les données qu’il ne contrôle pas. fields[6] est une string, donc ça pourrait être n’importe quoi, aussi bien “123” que “abcd”.

    Il faut que je vérifie mon isinstance, je crois que je l’ai fait sur (tuple, list). Je voulais avoir au maximum la compatibilité python 2 et 3, donc je ne pouvais pas utiliser basestring directement.

  • Syl

    Un mini…

    J’aimerais génerer une liste des éléments de ‘url’ qui se termine par un élément de ‘tld’

    urls = ["machin.de", "google.com", "ebay.au", "untruc.de"]
    tld = [".de", ".au"]
     
    [url for url in urls if url.endswith(t) for t in tld]
     ==&gt; NameError: name 't' is not defined

    Je comprend pas pourquoi l’interpreteur reconnais ‘url’, mais pas ‘t’.

    J’ai essayé avec des parenthèses:

    [url for url in urls if (url.endswith(t) for t in tld)]

    Mais là, il me retourne l’intégralité de ‘urls’.

    Ne me dites pas que je vais devoir faire une boucle for quand même!

  • kontre

    il faut que tu mettes le if tout à la fin, car le t est déclaré dans le for qui dans ton cas se trouve après, donc ça passe pas.

  • Sam Post author
    >>> urls = ["machin.de", "google.com", "ebay.au", "untruc.de"]
    >>> tld = [".de", ".au"]
    >>> [u for u in urls if any(e in u for e in tld)]
    [u'machin.de', u'ebay.au', u'untruc.de']

    any, all et itertools sont les meilleurs amis du programmeur Python.

  • Syl

    Avec votre site, c’est pratiquement “Un jour, une découverte”…aujourd’hui, c’est “any”!

    Merci!!!!!!!

Comments are closed.

Des questions Python sans rapport avec l'article ? Posez-les sur IndexError.