ISO-8859-1 ou UTF-8/16.
Quel encodage choisir pour afficher correctement tous les caractères accentués qu'il y a dans la langue française ?
Tous les caractères accentués de la langue française sont gérés par la norme ISO-8859-1.
En HTML, il y a une astuce qui consiste à ne pas s'occuper du type de l'encodage des caractères mais utiliser à la place le nom d'entité des caractères (entity name).
Grâce à cette technique, tous les caractères seront toujours affichés correctement.
Source http://www.w3schools.com/
Par exemple, au lieu d'écrire ceci dans une page HTML
<html>
<body>
<p>Ceci est un caractère accentué</p>
</body>
</html>
Qui pourrait provoquer un problème à l'affichage si le codage des caractères est mal défini (comme ceci)
Ceci est un caractère accentué
Il vaut mieux écrire cela
<html>
<body>
<p>Ceci est un caractère accentué</p>
</body>
</html>
Ce qui donne comme résultat
Ceci est un caractère accentué
Plus besoin de se soucier de l'encodage du fichier.
Character | Entity Number | Entity Name | Description |
---|---|---|---|
Œ | Œ | Œ | capital ligature OE |
œ | œ | œ | small ligature oe |
Š | Š | Š | capital S with caron |
š | š | š | small S with caron |
Ÿ | Ÿ | Ÿ | capital Y with diaeres |
ƒ | ƒ | ƒ | f with hook |
ˆ | ˆ | ˆ | modifier letter circumflex accent |
˜ | ˜ | ˜ | small tilde |
  |   | en space | |
  |   | em space | |
  |   | thin space | |
| ‌ | ‌ | zero width non-joiner |
| ‍ | ‍ | zero width joiner |
| ‎ | ‎ | left-to-right mark |
| ‏ | ‏ | right-to-left mark |
– | – | – | en dash |
— | — | — | em dash |
‘ | ‘ | ‘ | left single quotation mark |
’ | ’ | ’ | right single quotation mark |
‚ | ‚ | ‚ | single low-9 quotation mark |
“ | “ | “ | left double quotation mark |
” | ” | ” | right double quotation mark |
„ | „ | „ | double low-9 quotation mark |
† | † | † | dagger |
‡ | ‡ | ‡ | double dagger |
• | • | • | bullet |
… | … | … | horizontal ellipsis |
‰ | ‰ | ‰ | per mille |
′ | ′ | ′ | minutes |
″ | ″ | ″ | seconds |
‹ | ‹ | ‹ | single left angle quotation |
› | › | › | single right angle quotation |
‾ | ‾ | ‾ | overline |
€ | € | € | euro |
™ | ™ | ™ | trademark |
← | ← | ← | left arrow |
↑ | ↑ | ↑ | up arrow |
→ | → | → | right arrow |
↓ | ↓ | ↓ | down arrow |
↔ | ↔ | ↔ | left right arrow |
↵ | ↵ | ↵ | carriage return arrow |
⌈ | ⌈ | ⌈ | left ceiling |
⌉ | ⌉ | ⌉ | right ceiling |
⌊ | ⌊ | ⌊ | left floor |
⌋ | ⌋ | ⌋ | right floor |
◊ | ◊ | ◊ | lozenge |
♠ | ♠ | ♠ | spade |
♣ | ♣ | ♣ | club |
♥ | ♥ | ♥ | heart |
♦ | ♦ | ♦ | diamond |
Character | Entity Number | Entity Name | Description |
---|---|---|---|
Α | Α | Α | Alpha |
Β | Β | Β | Beta |
Γ | Γ | Γ | Gamma |
Δ | Δ | Δ | Delta |
Ε | Ε | Ε | Epsilon |
Ζ | Ζ | Ζ | Zeta |
Η | Η | Η | Eta |
Θ | Θ | Θ | Theta |
Ι | Ι | Ι | Iota |
Κ | Κ | Κ | Kappa |
Λ | Λ | Λ | Lambda |
Μ | Μ | Μ | Mu |
Ν | Ν | Ν | Nu |
Ξ | Ξ | Ξ | Xi |
Ο | Ο | Ο | Omicron |
Π | Π | Π | Pi |
Ρ | Ρ | Ρ | Rho |
Σ | Σ | Σ | Sigma |
Τ | Τ | Τ | Tau |
Υ | Υ | Υ | Upsilon |
Φ | Φ | Φ | Phi |
Χ | Χ | Χ | Chi |
Ψ | Ψ | Ψ | Psi |
Ω | Ω | Ω | Omega |
α | α | α | alpha |
β | β | β | beta |
γ | γ | γ | gamma |
δ | δ | δ | delta |
ε | ε | ε | epsilon |
ζ | ζ | ζ | zeta |
η | η | η | eta |
θ | θ | θ | theta |
ι | ι | ι | iota |
κ | κ | κ | kappa |
λ | λ | λ | lambda |
μ | μ | μ | mu |
ν | ν | ν | nu |
ξ | ξ | ξ | xi |
ο | ο | ο | omicron |
π | π | π | pi |
ρ | ρ | ρ | rho |
ς | ς | ς | sigmaf |
σ | σ | σ | sigma |
τ | τ | τ | tau |
υ | υ | υ | upsilon |
φ | φ | φ | phi |
χ | χ | χ | chi |
ψ | ψ | ψ | psi |
ω | ω | ω | omega |
ϑ | ϑ | ϑ | theta symbol |
ϒ | ϒ | ϒ | upsilon symbol |
ϖ | ϖ | ϖ | pi symbol |
Character | Entity Number | Entity Name | Description |
---|---|---|---|
À | À | À | capital a, grave accent |
Á | Á | Á | capital a, acute accent |
 |  |  | capital a, circumflex accent |
à | à | à | capital a, tilde |
Ä | Ä | Ä | capital a, umlaut mark |
Å | Å | Å | capital a, ring |
Æ | Æ | Æ | capital ae |
Ç | Ç | Ç | capital c, cedilla |
È | È | È | capital e, grave accent |
É | É | É | capital e, acute accent |
Ê | Ê | Ê | capital e, circumflex accent |
Ë | Ë | Ë | capital e, umlaut mark |
Ì | Ì | Ì | capital i, grave accent |
Í | Í | Í | capital i, acute accent |
Î | Î | Î | capital i, circumflex accent |
Ï | Ï | Ï | capital i, umlaut mark |
Ð | Ð | Ð | capital eth, Icelandic |
Ñ | Ñ | Ñ | capital n, tilde |
Ò | Ò | Ò | capital o, grave accent |
Ó | Ó | Ó | capital o, acute accent |
Ô | Ô | Ô | capital o, circumflex accent |
Õ | Õ | Õ | capital o, tilde |
Ö | Ö | Ö | capital o, umlaut mark |
Ø | Ø | Ø | capital o, slash |
Ù | Ù | Ù | capital u, grave accent |
Ú | Ú | Ú | capital u, acute accent |
Û | Û | Û | capital u, circumflex accent |
Ü | Ü | Ü | capital u, umlaut mark |
Ý | Ý | Ý | capital y, acute accent |
Þ | Þ | Þ | capital THORN, Icelandic |
ß | ß | ß | small sharp s, German |
à | à | à | small a, grave accent |
á | á | á | small a, acute accent |
â | â | â | small a, circumflex accent |
ã | ã | ã | small a, tilde |
ä | ä | ä | small a, umlaut mark |
å | å | å | small a, ring |
æ | æ | æ | small ae |
ç | ç | ç | small c, cedilla |
è | è | è | small e, grave accent |
é | é | é | small e, acute accent |
ê | ê | ê | small e, circumflex accent |
ë | ë | ë | small e, umlaut mark |
ì | ì | ì | small i, grave accent |
í | í | í | small i, acute accent |
î | î | î | small i, circumflex accent |
ï | ï | ï | small i, umlaut mark |
ð | ð | ð | small eth, Icelandic |
ñ | ñ | ñ | small n, tilde |
ò | ò | ò | small o, grave accent |
ó | ó | ó | small o, acute accent |
ô | ô | ô | small o, circumflex accent |
õ | õ | õ | small o, tilde |
ö | ö | ö | small o, umlaut mark |
ø | ø | ø | small o, slash |
ù | ù | ù | small u, grave accent |
ú | ú | ú | small u, acute accent |
û | û | û | small u, circumflex accent |
ü | ü | ü | small u, umlaut mark |
ý | ý | ý | small y, acute accent |
þ | þ | þ | small thorn, Icelandic |
ÿ | ÿ | ÿ | small y, umlaut mark |
Character | Entity Number | Entity Name | Description |
---|---|---|---|
" | " | " | quotation mark |
' | ' | ' (does not work in IE) | apostrophe |
& | & | & | ampersand |
< | < | < | less-than |
> | > | > | greater-than |
Character | Entity Number | Entity Name | Description |
---|---|---|---|
  | | non-breaking space | |
¡ | ¡ | ¡ | inverted exclamation mark |
¢ | ¢ | ¢ | cent |
£ | £ | £ | pound |
¤ | ¤ | ¤ | currency |
¥ | ¥ | ¥ | yen |
¦ | ¦ | ¦ | broken vertical bar |
§ | § | § | section |
¨ | ¨ | ¨ | spacing diaeresis |
© | © | © | copyright |
ª | ª | ª | feminine ordinal indicator |
« | « | « | angle quotation mark (left) |
¬ | ¬ | ¬ | negation |
­ | ­ | soft hyphen | |
® | ® | ® | registered trademark |
¯ | ¯ | ¯ | spacing macron |
° | ° | ° | degree |
± | ± | ± | plus-or-minus |
² | ² | ² | superscript 2 |
³ | ³ | ³ | superscript 3 |
´ | ´ | ´ | spacing acute |
µ | µ | µ | micro |
¶ | ¶ | ¶ | paragraph |
· | · | · | middle dot |
¸ | ¸ | ¸ | spacing cedilla |
¹ | ¹ | ¹ | superscript 1 |
º | º | º | masculine ordinal indicator |
» | » | » | angle quotation mark (right) |
¼ | ¼ | ¼ | fraction 1/4 |
½ | ½ | ½ | fraction 1/2 |
¾ | ¾ | ¾ | fraction 3/4 |
¿ | ¿ | ¿ | inverted question mark |
× | × | × | multiplication |
÷ | ÷ | ÷ | division |
Character | Entity Number | Entity Name | Description |
---|---|---|---|
∀ | ∀ | ∀ | for all |
∂ | ∂ | ∂ | part |
∃ | ∃ | ∃ | exists |
∅ | ∅ | ∅ | empty |
∇ | ∇ | ∇ | nabla |
∈ | ∈ | ∈ | isin |
∉ | ∉ | ∉ | notin |
∋ | ∋ | ∋ | ni |
∏ | ∏ | ∏ | prod |
∑ | ∑ | ∑ | sum |
− | − | − | minus |
∗ | ∗ | ∗ | lowast |
√ | √ | √ | square root |
∝ | ∝ | ∝ | proportional to |
∞ | ∞ | ∞ | infinity |
∠ | ∠ | ∠ | angle |
∧ | ∧ | ∧ | and |
∨ | ∨ | ∨ | or |
∩ | ∩ | ∩ | cap |
∪ | ∪ | ∪ | cup |
∫ | ∫ | ∫ | integral |
∴ | ∴ | ∴ | therefore |
∼ | ∼ | ∼ | similar to |
≅ | ≅ | ≅ | congruent to |
≈ | ≈ | ≈ | almost equal |
≠ | ≠ | ≠ | not equal |
≡ | ≡ | ≡ | equivalent |
≤ | ≤ | ≤ | less or equal |
≥ | ≥ | ≥ | greater or equal |
⊂ | ⊂ | ⊂ | subset of |
⊃ | ⊃ | ⊃ | superset of |
⊄ | ⊄ | ⊄ | not subset of |
⊆ | ⊆ | ⊆ | subset or equal |
⊇ | ⊇ | ⊇ | superset or equal |
⊕ | ⊕ | ⊕ | circled plus |
⊗ | ⊗ | ⊗ | circled times |
⊥ | ⊥ | ⊥ | perpendicular |
⋅ | ⋅ | ⋅ | dot operator |
Appréhender le html et le javascript n'est pas toujours simple pour mettre en place un site internet avec un joli design et surtout "user friendly".
Le site suivant propose quelques templates html facilement adaptables
ainsi que celui-ci https://html5up.net/
Voici un petit squelette afin de mettre en place une page HTML facilement et rapidement.
Elle inclut le CSS et le javascript Bootstrap ainsi que le javascript JQuery.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<title>My Page</title>
<!-- Bootstrap -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/latest/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
<h1>My Page</h1>
<h2>Start here</h2>
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/latest/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>
Un simple copier/coller dans un fichier "index.html" et le tour est joué.
package fr.quennec.date.custom;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.GregorianCalendar;
public class customDate {
public static void dateMoinsUneAnnee(){
GregorianCalendar calStr1 = new GregorianCalendar(); // Création d'un nouveau calendrier
calStr1.setTime(new Date()); // Initialisation du calendrier avec la date du jour
calStr1.add(GregorianCalendar.YEAR, -1); // On retranche 1 année
String formatDate = "yyyy"; // Création du format pour afficher l'année sur 4 chiffres
SimpleDateFormat sdf = new SimpleDateFormat(formatDate); // Initialisation du format de l'année sur 4 chiffres
System.out.println(sdf.format(calStr1.getTime())); // Affichage du résultat
}
}
Ce code calcul l'écart en années / mois / jours (complets) entre 2 dates :
Ajouter les imports suivants :
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.text.ParseException;
import java.text.SimpleDateFormat;
public class ecartDate {
static int UN = 1;
static int DOUZE = 12;
public static void main(String[] args) {
Calendar calStr1 = Calendar.getInstance();
Calendar calStr2 = Calendar.getInstance();
Calendar calStr0 = Calendar.getInstance();
Date date1 = null;
Date date2 = null;
int nbMois = 0;
int nbAnnees = 0;
long nbJours = 0;
try {
date1 = new SimpleDateFormat("dd/MM/yyyy").parse("25/01/2006");
} catch (ParseException e) {
e.printStackTrace();
}
try {
date2 = new SimpleDateFormat("dd/MM/yyyy").parse("11/02/2014");
} catch (ParseException e) {
e.printStackTrace();
}
if (date1.equals(date2)) {
return;
}
calStr1.setTime(date1);
calStr2.setTime(date2);
nbMois = 0;
while (calStr1.before(calStr2)) {
calStr1.add(GregorianCalendar.MONTH, UN);
if (calStr1.before(calStr2) || calStr1.equals(calStr2)) {
nbMois++;
}
}
nbAnnees = (nbMois / DOUZE);
nbMois = (nbMois - (nbAnnees * DOUZE));
calStr0 = Calendar.getInstance();
calStr0.setTime(date1);
calStr0.add(GregorianCalendar.YEAR, nbAnnees);
calStr0.add(GregorianCalendar.MONTH, nbMois);
nbJours = (calStr2.getTimeInMillis() - calStr0.getTimeInMillis()) / 86400000;
System.out.print("Nb Annees : "+nbAnnees+"\n");
System.out.print("Nb Mois : "+nbMois+"\n");
System.out.print("Nb Jours : "+nbJours+"\n");
}
}
Java Decompiler est un programme sous Windows / Linux / Mac qui permet de décompiler des classes JAVA.
Télécharger la version Windows
Pour connaitre la version de java utilisée pour la compilation d'une classe, il suffit d'utiliser la commande javap présente dans le répertoire bin de java.
$ javap -verbose maclasse.class | grep version
minor version: 0
major version: 46
La version majeure permet de connaitre la version de java ayant compilé le fichier .class
Java 1.2 uses major version 46
Java 1.3 uses major version 47
Java 1.4 uses major version 48
Java 5 uses major version 49
Java 6 uses major version 50
Java 7 uses major version 51
Java 8 uses major version 52
Java 9 uses major version 53
Java 10 uses major version 54
Java 11 uses major version 55
Exemple avec l'objet personnalisé suivant
class Personne {
String Nom;
String Prenom;
int Age;
char Sexe;
public Personne(String Nom, String Prenom, int Age, char Sexe) {
this.Nom = Nom;
this.Prenom = Prenom;
this.Age = Age;
this.Sexe = Sexe;
}
public boolean equals(Object o) {
if (o instanceof Personne) {
Personne tocompare = (Personne) o;
if (tocompare.Nom.equals(this.Nom) && tocompare.Prenom.equals(this.Prenom)) {
return true;
}
}
return false;
}
public String toString(){
return String.format("Nom: %-15sPrénom: %-15sAge: %-4dSexe: %1s", this.Nom, this.Prenom, this.Age, this.Sexe);
}
public String getNom() {
return Nom;
}
public void setNom(String nom) {
Nom = nom;
}
public String getPrenom() {
return Prenom;
}
public void setPrenom(String prenom) {
Prenom = prenom;
}
public int getAge() {
return Age;
}
public void setAge(int age) {
Age = age;
}
public char getSexe() {
return Sexe;
}
public void setSexe(char sexe) {
Sexe = sexe;
}
}
Je vais maintenant créer 10 objets "Personne" et les ajouter dans une ArrayList.
import java.util.ArrayList;
public class TriPersoPersonne {
public static void main(String[] args) {
ArrayList<Personne> Personnes = new ArrayList<Personne>();
Personnes.add(new Personne("Martine", "Merle", 38, 'F'));
Personnes.add(new Personne("Martine", "Boutin", 22, 'M'));
Personnes.add(new Personne("Claire", "Lombard", 28, 'M'));
Personnes.add(new Personne("Eugène", "Faivre", 53, 'F'));
Personnes.add(new Personne("Susanne", "Gérard", 91, 'F'));
Personnes.add(new Personne("Zoé", "Lefebvre", 47, 'F'));
Personnes.add(new Personne("Julien", "Ollivier", 86, 'M'));
Personnes.add(new Personne("Thomas", "Lambert", 38, 'M'));
Personnes.add(new Personne("Michel", "Gay", 69, 'M'));
Personnes.add(new Personne("Bernadette", "Clément", 15, 'F'));
for (Personne personne : Personnes) {
System.out.println(personne);
}
}
}
Voici la sortie du System.out:
Nom: Martine Prénom: Merle Age: 38 Sexe: F Nom: Martine Prénom: Boutin Age: 22 Sexe: M Nom: Claire Prénom: Lombard Age: 28 Sexe: M Nom: Eugène Prénom: Faivre Age: 53 Sexe: F Nom: Susanne Prénom: Gérard Age: 91 Sexe: F Nom: Zoé Prénom: Lefebvre Age: 47 Sexe: F Nom: Julien Prénom: Ollivier Age: 86 Sexe: M Nom: Thomas Prénom: Lambert Age: 38 Sexe: M Nom: Michel Prénom: Gay Age: 69 Sexe: M Nom: Bernadette Prénom: Clément Age: 15 Sexe: F
J'aimerais maintenant trier mon ArrayList en fonction de l'age et du sexe de la personne et mettre la priorité sur le sexe "F" en cas d'égalité de l'age.
import java.util.Comparator;
class TriParAgeEtSexe implements Comparator<Personne> {
public int compare(Personne a, Personne b) {
int compA = a.Age * 10 + sexeToInt(a.Sexe);
int compB = b.Age * 10 + sexeToInt(b.Sexe);
return compA - compB;
}
public static int sexeToInt(char sexe) {
if (sexe == 'F') return 0;
return 1;
}
}
Exécutons maintenant le tri de l'ArrayList avec mon tri personnalisé.
import java.util.ArrayList;
import java.util.Collections;
public class TriPersoPersonne {
public static void main(String[] args) {
ArrayList<Personne> Personnes = new ArrayList<Personne>();
Personnes.add(new Personne("Martine", "Merle", 38, 'F'));
Personnes.add(new Personne("Martine", "Boutin", 22, 'M'));
Personnes.add(new Personne("Claire", "Lombard", 28, 'M'));
Personnes.add(new Personne("Eugène", "Faivre", 53, 'F'));
Personnes.add(new Personne("Susanne", "Gérard", 91, 'F'));
Personnes.add(new Personne("Zoé", "Lefebvre", 47, 'F'));
Personnes.add(new Personne("Julien", "Ollivier", 86, 'M'));
Personnes.add(new Personne("Thomas", "Lambert", 38, 'M'));
Personnes.add(new Personne("Michel", "Gay", 69, 'M'));
Personnes.add(new Personne("Bernadette", "Clément", 15, 'F'));
for (Personne personne : Personnes) {
System.out.println(personne);
}
System.out.println("\nAprès le tri par age et sexe\n");
Collections.sort(Personnes, new TriParAgeEtSexe());
for (Personne personne : Personnes) {
System.out.println(personne);
}
}
}
Ce qui donne ceci:
Nom: Martine Prénom: Merle Age: 38 Sexe: F Nom: Martine Prénom: Boutin Age: 22 Sexe: M Nom: Claire Prénom: Lombard Age: 28 Sexe: M Nom: Eugène Prénom: Faivre Age: 53 Sexe: F Nom: Susanne Prénom: Gérard Age: 91 Sexe: F Nom: Zoé Prénom: Lefebvre Age: 47 Sexe: F Nom: Julien Prénom: Ollivier Age: 86 Sexe: M Nom: Thomas Prénom: Lambert Age: 38 Sexe: M Nom: Michel Prénom: Gay Age: 69 Sexe: M Nom: Bernadette Prénom: Clément Age: 15 Sexe: F Après le tri par age et sexe Nom: Bernadette Prénom: Clément Age: 15 Sexe: F Nom: Martine Prénom: Boutin Age: 22 Sexe: M Nom: Claire Prénom: Lombard Age: 28 Sexe: M Nom: Martine Prénom: Merle Age: 38 Sexe: F Nom: Thomas Prénom: Lambert Age: 38 Sexe: M Nom: Zoé Prénom: Lefebvre Age: 47 Sexe: F Nom: Eugène Prénom: Faivre Age: 53 Sexe: F Nom: Michel Prénom: Gay Age: 69 Sexe: M Nom: Julien Prénom: Ollivier Age: 86 Sexe: M Nom: Susanne Prénom: Gérard Age: 91 Sexe: F
Le résultat est parfait.
L'ArrayList est triée par age croissant et si égalité, alors le sexe "F" est positionné avant le sexe "M".
Et pour un tri décroissant, il suffit de modifier le "return" de la méthode "compare" de la classe "TriParAgeEtSexe" par:
return compB - compA;
Pour info, la méthode "equals" de la classe "Personne" permet d'utiliser la méthode "contains" de la classe "ArrayList" afin de vérifier si un objet "Personne" n'est pas déjà dans la liste avant de l'ajouter afin d'éviter les doublons.
Exemple d'une méthode permettant de remplacer tous les caractères accentués d'une chaine (String) par des caractères simples :
public static String translate(String src) {
StringBuffer result = new StringBuffer();
if(src!=null && src.length()!=0) {
int index = -1;
char c = (char)0;
String chars= "àâäéèêëîïôöùûüç";
String replace= "aaaeeeeiioouuuc";
for(int i=0; i<src.length(); i++) {
c = src.charAt(i);
if( (index=chars.indexOf(c))!=-1 )
result.append(replace.charAt(index));
else
result.append(c);
}
}
return result.toString();
}
1 - Créer une classe "Char.java" contenant les différentes variables utilisées :
public class Char {
public static final char SPACE = (char)32;
public static final char LOWER_N = (char)110;
public static final char LOWER_U = (char)117;
public static final char LOWER_L = (char)108;
public static final char DOT = (char)46;
public static final char COMMA = (char)44;
public static final int DIGIT_BEGIN = 48;
public static final int DIGIT_END = 57;
}
public class TestUtil {
public static boolean isNull(String s) {
if (s == null) {
return true;
}
int counter = 0;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == Char.SPACE) {
continue;
}
else if (counter > 3) {
return false;
}
if (counter == 0) {
if (c != Char.LOWER_N) {
return false;
}
}
else if (counter == 1) {
if (c != Char.LOWER_U) {
return false;
}
}
else if ((counter == 2) || (counter == 3)) {
if (c != Char.LOWER_L) {
return false;
}
}
counter++;
}
if ((counter == 0) || (counter == 4)) {
return true;
}
return false;
}
public static boolean isDouble(String number){
// On test si la valeur est nulle
if (isNull(number)) {
return false;
}
// On remplace les virgules par les points (séparateur décimal)
number = number.replace(Char.COMMA, Char.DOT);
boolean firstDot = true;
// On test chaque caractère
// Si le caractère est un chiffre on test le suivant
// Si le caractère est un "point" on initialise la variable "firstDot" à "false" et on test le suivant
// Si le caractère n'est pas un chiffre ou un autre "point" on retourne "false"
// Sinon, la valeur est un "Double" et on retourne "true"
for (char c : number.toCharArray()) {
if (!isDigit(c)) {
if (c == Char.SPACE) {
continue;
}
if(c == Char.DOT && firstDot){
firstDot = false;
continue;
} else {
return false;
}
}
}
return true;
}
public static boolean isDigit(char c) {
int x = c;
if ((x >= Char.DIGIT_BEGIN) && (x <= Char.DIGIT_END)) {
return true;
}
return false;
}
}
3 - Créer une classe "testDouble.java" afin de tester les différentes fonctions :
public class testDouble {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
String s = "12,5689";
System.out.println(TestUtil.isDouble(s)); // Renvoi true
s = "12.56456456";
System.out.println(TestUtil.isDouble(s)); // Renvoi true
s = null;
System.out.println(TestUtil.isDouble(s)); // Renvoi false
s = "a56456.5646";
System.out.println(TestUtil.isDouble(s)); // Renvoi false
s = "23156.86798,546";
System.out.println(TestUtil.isDouble(s)); // Renvoi false
s = "etet.ert47d6t";
System.out.println(TestUtil.isDouble(s)); // Renvoi false
s = "8989797";
System.out.println(TestUtil.isDouble(s)); // Renvoi true
s = "8989 797";
System.out.println(TestUtil.isDouble(s)); // Renvoi true
s = "89 897.97";
System.out.println(TestUtil.isDouble(s)); // Renvoi true
s = "89 897,97";
System.out.println(TestUtil.isDouble(s)); // Renvoi true
}
}
<script type="text/javascript">
function PMA_focusInput(){
var objet = document.getElementById('id_element_formulaire');
objet.focus();
}
window.setTimeout('PMA_focusInput()', 500);
</script>
Voici une petite fonction javascript qui permet de modifier la valeur du paramètre "maxlength" d'un élément "textarea".
Dans l'exemple suivant, la fonction modifie la valeur du paramètre "maxlength" à illimité (-1) uniquement si la valeur est inférieur à 1000 caractères.
function updMaxLthTxt () {
var txts = document.getElementsByTagName('textarea');
for(var i = 0, l = txts.length; i < l; i++) {
var len = parseInt(txts[i].getAttribute("maxlength"), 10);
if(len < 1000) {
txts[i].setAttribute("maxlength", -1);
}
}
}
Pour exécuter cette fonction automatiquement après le chargement de la page html:
<html>
<head>
</head>
<body onload = "updMaxLthTxt();">
.....
</body>
</html>
Ajouter une classe à une balise en fonction de son contenu :
Ce script javascript parcourt toutes les balises <p> de la page web et si le contenu de chaque balise commence par "$ ", une balise <span> est automatiquement ajoutée avec une classe "code"
<script language=javascript type="text/javascript">
function addCode()
{
var parag = document.getElementsByTagName('p');
var myRegex = /^\$ /;
for (var i=0, c=parag.length; i<c; i++)
{
if(myRegex.test(parag[i].innerHTML))
{
parag[i].innerHTML = '<span class="code">'+(parag[i].innerHTML)+'</span>';
}
}
}
</script>
Pour activer la fonction javascript il suffit simplement de rajouter onload = "addCode();" dans la balise <body> de chaque page web.
<html>
<head>
</head>
<body onload = "addCode();">
.....
</body>
</html>
Avec l'arrivée du HTML5, de nouvelles balises ont fait leur apparition.
Pour la mise en surbrillance de mots/textes dans un contenu html, il existe dorénavant la balise <mark>
Voici une fonction javascript (jquery) qui permet de modifier le contenu html afin d'y insérer ces fameuses balises <mark>
function searchKeyword(keyword){
var content = $("#content").html();
var re = new RegExp(keyword, "gi");
var new_content = content.replace(re, "<mark>" + keyword + "</mark>");
$("#content").html(new_content);
}
Cette fonction prend en paramètre le mot clé à mettre en surbrillance.
Une nouvelle expression Regex est créée avec le mot clé et les paramètres "g" pour une recherche globale, et "i" pour une recherche ignorant la casse.
La méthode "replace" utilise la regex afin d'y insérer la nouvelle balise <mark>
Exécution de la fonction avec un mot clé ou une liste de mots clé
$( document ).ready(function() {
// avec un mot clé
searchKeyword("foo");
// avec une liste de mots clé
var keywords = "foo bar baz qux quux corge grault".split(" ");
keywords.forEach(searchKeyword);
});
Différents liens sur PHP:
Voici une petite fonction PHP qui permet de retourner le numéro de la dernière semaine de l'année en cours.
<?php
function lastWeekNumberOfYear(){
$year = date('Y');
$week_count = date('W', strtotime($year . '-12-31'));
if ($week_count == '01'){
$week_count = date('W', strtotime($year . '-12-24'));
}
return intval($week_count);
}
La fonction preg_match permet d'effectuer une recherche dans une chaine de caractères et renvoie TRUE si le texte cherché est trouvé sinon FALSE.
Cela permet d'effectuer des controles sur des saisies clavier.
<?php preg_match("#^[0-9]{6}$#","123456"); ?>
// Renvoie TRUE car 123456 est bien un nombre de 6 chiffres.
Explications :
#^[0-9]{6}$#
Le caractère # correspond aux délimiteurs du texte à chercher.
Le caractère ^ indique que la recherche doit s'effectuer à partir du début de la chaine de texte.
Le caractère $ indique que la recherche doit s'effectuer jusqu'à la fin de la chaine de texte.
La classe de caractères [0-9] indique que les caractères recherchés dans la chaine de texte doivent être des chiffres allant de 0 à 9.
Le quantificateur {6} indique le nombre exact de caractères à rechercher dans la chaine de texte.
Autres exemples :
<?php preg_match("#^[0-9]{6}$#","123A56"); ?>
// Renvoie FALSE car il y a un 'A' dans la chaine de caractères.
<?php preg_match("#^[0-9]{6}$#","12345"); ?>
// Renvoie également FALSE car il n'y a que 5 chiffres dans la chaine de caractères '12345'
<?php preg_match("#^[0-9]{6}#","546781A"); ?>
// Renvoie TRUE car la recherche demandée s'effectue uniquement à partir du début de la chaine de caractères (suppression du caractère $) et que celle çi doit donc commencer par 6 chiffres allant de 0 à 9.
<?php preg_match("#^[-.\w]{1,}@[-.\w]{2,}\.[a-zA-Z]{2,4}$#","toto@gmail.com"); ?>
// Renvoie TRUE car le texte 'toto@gmail.com' correspond bien à une adresse mail.
Explications :
#^[-.\w]{1,}@[-.\w]{2,}\.[a-zA-Z]{2,4}$#
Nous retrouvons toujours nos délimiteurs #
Nous retrouvons également nos symboles de dédut ^ et de fin $ de recherche
Notre adresse mail doit être de la forme xxxx@xxxx.xxxx
Le début de l'adresse (à la gauche de l' @) peut contenir des caractères allant de 'A' à 'Z' (minuscule et majuscule), un point '.', un tiret haut '-', un tiret bas '_' et des chiffres allant de 0 à 9 et doit contenir au minimum 1 caractère. Pour indiquer tout cela, nous inscrivons donc :
[-.\w]{1,}
La classe de caractères contient donc le '-' (toujours le placer en première position dans une classe de caractères) suivi du '.' et enfin du raccourci '\w' correspondant à la même chose que [a-zA-Z0-9_], c'est à dire toutes les lettres de l'alphabet minuscule et majuscule, tous les chiffres et le tiret bas '_'. Le tout suivi du quantificateur {1,} indiquant qu'il doit y avoir au minimum 1 caractère.
Il faut ensuite indiquer le caractère @
Ensuite, le nom de domaine doit avoir les mêmes caractéristiques que le début de l'adresse mais doit comporter au minimum 2 caractères, nous inscrivons donc :
[-.\w]{2,}
Nous retrouvons la même classe de caractères que pour le début de l'adresse mais nous modifions le quantificateur {2,} pour indiquer qu'il doit y avoir au minimum 2 caractères.
Nous indiquons ensuite le point '.' mais celui çi doit être échappé car en PCRE, il correspond à un métacaractère indiquant 'n'importe quel caractère'.
\.
Enfin, nous controlons l'extension du nom de domaine. Celui çi ne doit contenir que des lettres et avoir au minimum 2 caractères et au maximum 4. Nous indiquons donc :
[a-zA-Z]{2,4}
Nous retrouvons la classe [a-zA-Z] indiquant toutes les lettres de l'alphabet en minuscule et en majuscule ainsi que le quantificateur {2,4} indiquant un minimum de caractères de 2 et un maximum de 4.
Connexion à une base de données MySql
<?php
try
{
$bdd = new PDO('mysql:host=localhost;dbname=test','user','password');
}
catch(Exception $e)
{
die('Erreur :'.$e->getMessage());
}
?>
Exécuter une requête simple
<?php
$req = $bdd->query('SELECT id, nom, prenom FROM carnet WHERE id >= 1 AND id <= 10 ORDER BY id') or die (print_r($bdd->errorInfo()));
?>
Préparer et exécuter une requête avec des variables
<?php
$req = $bdd->prepare('SELECT id, nom, prenom FROM carnet WHERE id >= :idMin AND id <= :idMax ORDER BY id') or die (print_r($bdd->errorInfo()));
$req->execute(array(
'idMin'=> $_POST['postIdMin'],
'idMax' => $_GET['getIdMax']
));
?>
Lire le résultat d'une requête
<?php
while($donnees = $req->fetch())
{
echo $donnees['id'].'<br />';
echo $donnees['nom'].'<br />';
echo $donnees['prenom'];
}
?>
Fermer la requête
<?php
$req->closeCursor();
?>
La bibliothèque Monolog permet de journaliser différentes informations lors de l'exécution de scripts PHP.
Son intégration via un framework de développement tel que Symfony, CakePHP se fait de manière quasi automatique mais s'il s'agit de l'utiliser dans un script fait à la main, c'est un petit peu plus compliqué.
Voici ma méthode (qui peut certainement être améliorée):
Cette bibliothèque pouvant être utilisée dans plusieurs projets, j'ai donc décidé de l'installer dans un dossier commun à tous mes projets web (/var/www/).
Je vais donc l'installer dans le dossier /var/www/commun
$ cd /var/www
$ mkdir commun
$ chown -R www-data:www-data commun
$ cd commun
Cette bibliothèque utilisant les namespaces, il est nécessaire d'installer la bibliothèque ClassLoader qui va nous permettre d'utiliser tout simplement les namespaces dans nos différents scripts.
Toutes les installations seront faites à l'aide de la commande git
$ apt-get install git
$ git clone https://github.com/symfony/ClassLoader.git
$ git clone https://github.com/Seldaek/monolog.git
$ git clone https://github.com/php-fig/log.git
Trois projets sont donc installés dans le dossier commun
Pour protéger l'accès à ce dossier (/var/www/commun):
$ echo "Deny From All" > .htaccess
Pour l'utilisation des namespaces, il est nécessaire de créer un fichier de paramétrage des namespaces (toujours dans le dossier /var/www/commun):
$ cat monolog.php
<?php
require_once __DIR__.'/ClassLoader/ClassLoader.php';
use Symfony\Component\ClassLoader\ClassLoader;
$loader = new ClassLoader();
$loader->register();
$loader->addPrefix('Monolog', __DIR__.'/monolog/src');
$loader->addPrefix('Psr', __DIR__.'/log');
Ce fichier intègre donc la classe ClassLoader.php et configure les namespaces Monolog & Psr nécessaire à l'utilisation de la bibliothèque monolog.
L'installation et le paramétrage est terminé, passons à la manière d'utiliser cette bibliothèque.
Je vais créer un projet test ainsi que deux fichiers index.php et init_log.php.
Le fichier index.php contiendra mon projet et le fichier init_log.php la configuration du fichier de log de mon projet.
$ cd /var/www
$ mkdir test
$ chown -R www-data:www-data test
$ cd test
Pour la documentation de monolog, tout est expliqué sur la page github du projet https://github.com/Seldaek/monolog
Détail de ma configuration:
$ nl init_log.php
1 <?php
2 require_once '/var/www/commun/monolog.php';
3 use Monolog\Logger;
4 use Monolog\Handler\RotatingFileHandler;
5 use Monolog\Handler\StreamHandler;
6 use Monolog\Formatter\LineFormatter;
7 $dateFormat = "Y-m-d H:i:s";
8 $output = "[%datetime%] %channel% %level_name%: %message% %context% %extra%\n";
9 $formatter = new LineFormatter($output, $dateFormat);
10 $stream = new StreamHandler(__DIR__.'/test.log', Logger::DEBUG); // Pour obtenir un fichier de log global
11 // $stream = new RotatingFileHandler(__DIR__.'/test.log', Logger::DEBUG); // Pour obtenir un fichier de log par jour (fichier horodaté)
12 $stream->setFormatter($formatter);
13 $logger = new Logger('test');
14 $logger->pushHandler($stream);
Ligne 2, j'inclus le fichier monolog.php qui contient toute la configuration de monolog.
Ligne 3 à 6, déclaration des différents use comme indiqué surla page du projet.
Ligne 7 à 9, je redéfini le format des données dans les logs.
Ligne 10 ou 11, permet d'indiquer l'emplacement du fichier de log ainsi que la manière dont il sera nommé et le niveau de log (DEBUG, INFO, WARNING etc etc...). Comme indiqué dans les commentaires, soit un fichier de log global (test.log), soit un fichier de log journalier et horodaté (test-2015-08-14.log).
Ligne 12, affectation du format défini au fichier de log.
Ligne 13 à 14, création d'un nouveau logger nommé test (avec le nom du projet par exemple).
Il est possible d'avoir un seul fichier de log commun pour plusieurs projets. Du coup, il sera facile de parser le fichier de log en se basant sur le nom du projet indiqué lors de la création du logger (ligne 13)
Utilisation dans mon fichier index.php:
$ cat index.php
<?php
require_once __DIR__.'/init_log.php';
...
$logger->addInfo('Connexion', array(
'User' => $user,
'Nom' => $nom,
'Prénom' => $prenom,
));
J'inclus mon fichier init_log.php et j'écris tout ce que je veux dans mon fichier de log à l'aide de la commande $logger->addInfo().
Le premier paramètre est une chaine de texte et le second un tableau.
Difficile de faire plus simple.
igalerie est une application php qui permet de créer des galeries d'images en ligne.
L'ajout d'images dans la galerie peut se faire via ftp (très pratique) mais il faut obligatoirement exécuter une fonction pour que les images soient affichées dans la galerie.
Cette fonction est disponible via un bouton accessible uniquement dans la section administration du site.
Il est donc nécessaire d'être authentifié pour exécuter cette fonction.
Pour effectuer ce scan automatiquement à intervalle régulière, j'ai donc mis au point le script php suivant avec l'extension curl.
Ce script est disponible ici .
$ nl curl.igalerie.php
1 <?php
2 session_start();
3 $site = "https://adresse.de.mon.site.igalerie.fr";
4 $lien = "/login";
5 $lien3 = "/admin/?q=ftp";
6 $path_cookie = '/tmp/cookie.'.session_id().'.txt';
7 if (file_exists(realpath($path_cookie))) unlink($path_cookie);
8 if (!file_exists(realpath($path_cookie))) touch($path_cookie);
9 $curl = curl_init();
10 $postfields = array();
11 $postfields['auth_login'] = 'mon.user';
12 $postfields['auth_password'] = 'mon.password';
13 $postfields['submit'] = 'Valider';
14 $postfields = http_build_query($postfields);
15 curl_setopt($curl, CURLOPT_URL, $site.$lien);
16 curl_setopt($curl, CURLOPT_COOKIEFILE, realpath($path_cookie));
17 curl_setopt($curl, CURLOPT_COOKIEJAR, realpath($path_cookie));
18 curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
19 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
20 curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
21 curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
22 curl_setopt($curl, CURLOPT_HEADER, true);
23 curl_setopt($curl, CURLOPT_POST, true);
24 curl_setopt($curl, CURLOPT_POSTFIELDS, $postfields);
25 curl_setopt($curl, CURLOPT_COOKIESESSION, true);
26 $return = curl_exec($curl);
27 $headers = curl_getinfo($curl);
28 if (!$headers['http_code'] == '200'){
29 echo "Erreur Step 1";
30 exit(1);
31 }
32 curl_setopt($curl, CURLOPT_URL, $site.$lien3);
33 curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
34 curl_setopt($curl, CURLOPT_COOKIEFILE, realpath($path_cookie));
35 curl_setopt($curl, CURLOPT_COOKIEJAR, realpath($path_cookie));
36 curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
37 curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
38 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
39 curl_setopt($curl, CURLOPT_HEADER, true);
40 curl_setopt($curl, CURLOPT_COOKIESESSION, true);
41 $return = curl_exec($curl);
42 $headers = curl_getinfo($curl);
43 if (!$headers['http_code'] == '200'){
44 echo "Erreur Step 2";
45 exit(1);
46 }
47 $dom = new DOMDocument;
48 @$dom->loadHTML($return);
49 $inputs = $dom->getElementsByTagName('input');
50 foreach ($inputs as $input) {
51 $cle = $input->getAttribute('name');
52 $valeur = "";
53 if($cle=="anticsrf"){
54 $valeur = $input->getAttribute('value');
55 }
56 if(!$valeur=="") break;
57 }
58 $postfields = array();
59 $postfields['publish_images'] = 'on';
60 $postfields['anticsrf'] = "$valeur";
61 $postfields['action'] = 'scan';
62 $postfields = http_build_query($postfields);
63 curl_setopt($curl, CURLOPT_URL, $site.$lien3);
64 curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
65 curl_setopt($curl, CURLOPT_COOKIEFILE, realpath($path_cookie));
66 curl_setopt($curl, CURLOPT_COOKIEJAR, realpath($path_cookie));
67 curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
68 curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
69 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
70 curl_setopt($curl, CURLOPT_HEADER, true);
71 curl_setopt($curl, CURLOPT_COOKIESESSION, true);
72 curl_setopt($curl, CURLOPT_POST, true);
73 curl_setopt($curl, CURLOPT_POSTFIELDS, $postfields);
74 $return = curl_exec($curl);
75 $headers = curl_getinfo($curl);
76 if (!$headers['http_code'] == '200'){
77 echo "Erreur Step 3";
78 exit(1);
79 }
80 @$dom->loadHTML($return);
81 $div = $dom->getElementById('ftp_report');
82 $ps = $div->getElementsByTagName('p');
83 foreach ($ps as $p => $value) {
84 echo "$value->nodeValue\n";
85 }
86 curl_close($curl);
87 exit(0);
Quelques petites explications :
Ligne 4 : adresse http(s) de la galerie.
Ligne 5 : lien qui permet d'accéder à la page d'authentification.
Ligne 6 : lien qui permet d'accéder à la page du scan ftp.
Ligne 8 à 10 : initialisation du cookie de session.
Ligne 12 : initialisation de l'extension curl.
Ligne 14 à 20 : initialisation du formulaire d'authentification.
Ligne 22 à 35 : validation du formulaire d'authentification.
Le formulaire permettant le scan des albums étant protégé par un champ anti-csrf , il est nécessaire de récupérer la valeur de ce champ avant de poster le formulaire.
Ligne 42 à 53 : récupération du contenu de la page contenant le formulaire permettant le scan des albums.
Ligne 60 à 70 : on parse le contenu de la page afin de récupérer la valeur du champ "anticsrf".
Ligne 72 à 78 : initialisation du formulaire de scan.
Ligne 80 à 93 : validation du formulaire de scan.
Ligne 100 à 106 : on parse le contenu de la page, après validation du formulaire, afin de récupérer le rapport du scan et on l'affiche sur la sortie standard.
Ligne 108 à 110 : on ferme la connexion curl et on quitte le script.
Pour l'exécuter :
$ php curl.igalerie.php
Pour ne pas afficher le rapport :
$ php curl.igalerie.php >/dev/null
Pour envoyer le rapport par mail :
$ php curl.igalerie.php | mail -s "Rapport Scan Auto Igalerie" moi@domain.com
Une entrée crontab pour l'automatisation.
phpSysInfo est un script PHP qui permet d'afficher, dans un navigateur, l'état complet du serveur sur lequel il est exécuté.
Une démo est disponible ici http://phpsysinfo.sourceforge.net/phpsysinfo/index.php?disp=bootstrap
Adresse du site officiel http://phpsysinfo.github.io/phpsysinfo/
Pour le téléchargement, les sources sont disponibles sur GitHub https://github.com/phpsysinfo/phpsysinfo
L'installation est ultra simple.
Il suffit de cloner les sources GIT dans un répertoire du serveur accessible en web.
De faire une copie du fichier phpsysinfo.ini.new en phpsysinfo.ini
D'éditer le fichier phpsysinfo.ini afin d'effectuer les réglages désirés
Et enfin, ouvrir son navigateur et ouvrir l'url correspondante
# cd /var/www
# git clone https://github.com/phpsysinfo/phpsysinfo.git
# cd phpsysinfo
# cp -a phpsysinfo.ini.new phpsysinfo.ini
Les données sont également accessible en XML /xml.php?plugin=complete ou en JSON /xml.php?plugin=complete&json
Une application pour Android est également disponible http://rk4an.github.io/psiandroid/
Pour éviter tous problèmes lors des installations des modules externes pour Python, il est nécessaire d'installer les paquets suivants:
# apt-get install build-essential python-dev python3-dev
python3-pip# apt-get install libmysqlclient-dev gcc
J'essaie de maintenir cette liste le plus à jour possible
Concernant les modules externes:
# pip install bpython httpie mysqlclient lxml requests virtualenv numpy bs4 pandas logger clipboard matplotlib pathlib pyftpdlib python-dateutil faker
La commande pip list permet d'afficher la liste complète de tous les paquets python installés avec la version.
# pip list
asn1crypto (0.22.0)
backports.ssl-match-hostname (3.5.0.1)
batinfo (0.4.2)
blessings (1.6)
bottle (0.12.13)
bpython (0.16)
certifi (2017.4.17)
cffi (1.10.0)
chardet (3.0.4)
...
requests (2.18.1)
setuptools (36.2.0)
six (1.10.0)
statsd (3.2.1)
urllib3 (1.21.1)
wcwidth (0.1.7)
websocket-client (0.44.0)
zeroconf (0.19.1)
La commande suivante affiche les principales options disponibles pour la commande pip list:
# pip list -h
Usage:
pip list [options]
Description:
List installed packages, including editables.
Packages are listed in a case-insensitive sorted order.
List Options:
-o, --outdated List outdated packages
-u, --uptodate List uptodate packages
-e, --editable List editable projects.
-l, --local If in a virtualenv that has global access, do not list globally-installed packages.
--user Only output packages installed in user-site.
--pre Include pre-release and development versions. By default, pip only finds stable versions.
--format <list_format> Select the output format among: legacy (default), columns, freeze or json.
--not-required List packages that are not dependencies of installed packages.
Donc, pour obtenir la liste complète de tous les paquets Python obsolètes, il suffit d'utiliser l'option -o
# pip list -o
Package Version Latest Type
---------- ------- ------ -----
decorator 4.0.11 4.1.1 wheel
setuptools 36.0.1 36.2.0 wheel
Ou alors directement avec la commande python et le module pip
# python3 -m pip list -o
La commande suivante va effectuer la mise à jour complète des paquets obsolètes.
# for x in $(pip list -o --format=columns | sed -n '3,$p' | cut -d' ' -f1); do pip install $x --upgrade; done
Collecting decorator
Downloading decorator-4.1.1-py2.py3-none-any.whl
Installing collected packages: decorator
Found existing installation: decorator 4.0.11
Uninstalling decorator-4.0.11:
Successfully uninstalled decorator-4.0.11
Successfully installed decorator-4.1.1
Collecting setuptools
Downloading setuptools-36.2.0-py2.py3-none-any.whl (477kB)
100% |████████████████████████████████| 481kB 1.1MB/s
Installing collected packages: setuptools
Found existing installation: setuptools 36.0.1
Uninstalling setuptools-36.0.1:
Successfully uninstalled setuptools-36.0.1
Successfully installed setuptools-36.2.0
Attention à la version de pip utilisée.
En général, pip concerne Python2 et pip3 Python3
Une autre méthode:
$ python3 -m pip list -o > outdated
$ for x in $(sed -n '3,$p' outdated | awk '{print $1}'); do python3 -m pip install --upgrade --user $x; done
A ma connaissance, en Python, à l'aide de la méthode timedelta du module datetime, il est très facile d'ajouter ou de retrancher des jours à une date mais quand il s'agit de le faire avec des mois, les choses se compliquent.
En effet tous les mois n'ayant pas le même nombre de jours, il devient incertain d'utiliser cette méthode.
J'ai donc développé cette fonction getNMonthsLessOrMore qui utilise à la fois le module datetime et le module calendar pour arriver au résultat souhaité.
from datetime import datetime
import calendar
def getNMonthsLessOrMore(startDate : datetime, nbMonths : int) -> list:
class DateTimeFormat(Exception):
pass
class NbMonthsFormat(Exception):
pass
def getDayOfMonth(year, month, day):
tmpCal = list(calendar.Calendar().itermonthdays(year, month))
for x in range(day, 0, -1):
if x in tmpCal: return x
return 1
try:
if not isinstance(startDate, datetime): raise DateTimeFormat()
if not isinstance(nbMonths, int): raise NbMonthsFormat()
except DateTimeFormat:
return False, "La date de départ doit être au format datetime.datetime"
except NbMonthsFormat:
return False, "Le nombre de mois doit être un entier positif ou négatif"
else:
if nbMonths == 0: return [startDate]
L = []
tmpDate = datetime(startDate.year, startDate.month, 1)
if nbMonths < 0:
for i in range(0, nbMonths, -1):
y, m = calendar.prevmonth(tmpDate.year, tmpDate.month)
tmpDate = datetime(y, m, getDayOfMonth(y, m, startDate.day))
L.append(tmpDate)
else:
for i in range(0, nbMonths, 1):
y, m = calendar.nextmonth(tmpDate.year, tmpDate.month)
tmpDate = datetime(y, m, getDayOfMonth(y, m, startDate.day))
L.append(tmpDate)
return L
Exécution de la fonction:
>>> getNMonthsLessOrMore(datetime.now(), -6)
[datetime.datetime(2019, 12, 23, 0, 0), datetime.datetime(2019, 11, 23, 0, 0), datetime.datetime(2019, 10, 23, 0, 0), datetime.datetime(2019, 9, 23, 0, 0), datetime.datetime(2019, 8, 23, 0, 0), datetime.datetime(2019, 7, 23, 0, 0)]
>>> getNMonthsLessOrMore(datetime.now(), 8)
[datetime.datetime(2020, 2, 23, 0, 0), datetime.datetime(2020, 3, 23, 0, 0), datetime.datetime(2020, 4, 23, 0, 0), datetime.datetime(2020, 5, 23, 0, 0), datetime.datetime(2020, 6, 23, 0, 0), datetime.datetime(2020, 7, 23, 0, 0), datetime.datetime(2020, 8, 23, 0, 0), datetime.datetime(2020, 9, 23, 0, 0)]
>>> getNMonthsLessOrMore(datetime.now(), 0)
[datetime.datetime(2020, 1, 23, 7, 31, 43, 913244)]
En Python, l'objet dict est très utile pour indexer du contenu avec une clé.
Pour ajouter des données à un dictionnaire, la méthode est très simple.
>>> d = dict()
>>> d['a'] = 1
>>> d
{'a': 1}
Ajout de la valeur '1' au dictionnaire 'd' avec la lettre 'a' comme clé.
Pour mettre à jour la valeur de la clé 'a':
>>> d['a'] = 2
>>> d
{'a': 2}
Il est également possible d'indexer une liste dans un dictionnaire:
>>> d['l'] = list()
>>> d
{'a': 2, 'l': []}
et pour ajouter des données à ma liste:
>>> type(d['l'])
<class 'list'>
>>> d['l'].append(1)
>>> d
{'a': 2, 'l': [1]}
d['l'] étant une liste, je peux utliser la méthode append de la liste pour y ajouter des données
mais avant de pouvoir ajouter des données à une liste, il faut s'assurer que la clé existe, sinon une erreur est renvoyée
>>> d['ll'].append(1)
Traceback (most recent call last):
File "<pyshell#12>", line 1, in <module>
d['ll'].append(1)
KeyError: 'll'
C'est pour cette raison que j'utilise la fonction suivante pour la gestion de mes dictionnaires.
Je ne me soucie plus de savoir si la clé existe ou pas.
Tout est controlé dans la fonction.
>>> def AddValueToDict(k, d, v, i):
# k = key - d = dict - v = value - i = type value
# si le dictionnaire 'd' contient la clé 'k'
# on récupère la valeur
if k in d: i = d[k]
# détermination du type de la valeur
# si la valeur est de type set()
if isinstance(i, set): i.add(v)
# si la valeur est de type list()
elif isinstance(i, list)
: i.append(v)
# si la valeur est de type str()
elif isinstance(i, str)
: i += str(v)
# si la valeur est de type int()
elif isinstance(i, int)
: i += int(v)
# si la valeur est de type float()
elif isinstance(i, float)
: i += float(v)
# on met à jour l'objet 'i' pour la clé 'k' dans le dictionnaire 'd'
d[k] = i
# on retourne le dictionnaire 'd'
return d
>>> d
{'a': 2, 'l': [1]}
>>> # Je veux ajouter au dictionnaire 'd'
>>> # la clé 'll' contenant la valeur '33'
>>> # dans un objet de type list()
>>> d = AddValueToDict('ll', d, '33', list())
>>> d
{'a': 2, 'll': ['33'], 'l': [1]}
>>> # Ajout de la valeur 'aa' dans un objet de type list() pour la clé 'l'
>>> d = AddValueToDict('l', d, 'aa', list())
>>> d
{'a': 2, 'll': ['33'], 'l': [1, 'aa']}
>>> # Ajout de la valeur 'x' dans un objet de type set() pour la clé 's'
>>> d = AddValueToDict('s', d, 'x', set())
>>> d
{'a': 2, 'll': ['33'], 'l': [1, 'aa'], 's': {'x'}}
>>> # Ajout de la valeur 'x' dans un objet de type str() pour la clé 'a'
>>> d = AddValueToDict('a', d, ';x', str())
>>> d
{'a': 2, 'll': ['33'], 'l': [1, 'aa'], 's': {'x'}}
>>> d = AddValueToDict('a', d, 3, int())
>>> d
{'a': 5, 'll': ['33'], 'l': [1, 'aa'], 's': {'x'}}
>>> d = AddValueToDict('b', d, 'bb', str())
>>> d
{'a': 5, 'll': ['33'], 'b': 'bb', 'l': [1, 'aa'], 's': {'x'}}
>>> d = AddValueToDict('b', d, ';cc', str())
>>> d
{'a': 5, 'll': ['33'], 'b': 'bb;cc', 'l': [1, 'aa'], 's': {'x'}}
Dans le cas où la clé est inexistante dans le dictionnaire, elle est automatiquement créée avec la valeur 'v' dans un objet de type 'i'.
Si le type 'i' de la valeur 'v' est un set() ou une list(), la valeur est automatiquement ajoutée.
Si le type 'i' de la valeur 'v' est une chaine de texte str(), elle est concaténée à celle déjà existante.
Si le type 'i' de la valeur 'v' est un entier int() ou un float float(), elle est ajoutée à celle déjà existante.
Qu'en pensez-vous ?
Super simple !
Pandas et Matplotlib sont deux modules Python qui permettent d'analyser des données et de les représenter sous forme de graphiques.
Ce sont deux modules très complets et par conséquent très complexes.
Je vais présenter ici une analyse complète regroupant différentes fonctions utiles permettant de retourner le résultat souhaité.
Pour l'exemple, j'ai décidé d'analyser la consommation de carburant de ma Golf VII Hybride.
Mon analyse va porter sur les 16 dernières fois où je me suis rendu à la station service.
Voici les données brutes que je vais utiliser.
A l'origine mes données sont stockées dans un fichier XML.
<ope date="737309" amount="-29.449999999999999" category="185" wording="v=20.04 d=36773"/>
Ci-dessous une représentation sous forme de tableau.
Pour chaque date (ordinal), il y a le montant et une colonne contenant le volume d'essence acheté ainsi que le kilométrage du compteur au moment du plein.
date | amount | wording | |
---|---|---|---|
0 | 737309 | -29.449999999999999 | v=20.04 d=36773 |
1 | 737317 | -43.170000000000002 | v=30.02 d=37269 |
2 | 737333 | -43.100000000000001 | v=30.12 d=38061 |
3 | 737348 | -44.240000000000002 | v=31.31 d=38850 |
4 | 737358 | -50.710000000000001 | v=36.48 d=39632 |
5 | 737374 | -50.149999999999999 | v=34.16 d=40408 |
6 | 737387 | -56.600000000000001 | v=37.86 d=41032 |
7 | 737398 | -37.369999999999997 | v=25.49 d=41548 |
8 | 737403 | -32.289999999999999 | v=21.54 d=41880 |
9 | 737414 | -49.340000000000003 | v=32.57 d=42469 |
10 | 737427 | -50.920000000000002 | v=33.52 d=43104 |
11 | 737441 | -53.57 | v=34.90 d=43815 |
12 | 737455 | -30.199999999999999 | v=20.70 d=44353 |
13 | 737469 | -39.240000000000002 | v=27.08 d=44737 |
14 | 737484 | -49.450000000000003 | v=33.30 d=45403 |
15 | 737500 | -29.850000000000001 | v=22.98 d=45940 |
Voici donc mon script Python, complet, entièrement détaillé à l'aide des commentaires et le résultat final à la fin de cet article.
Ce script a été développé avec Python 3.7.5 et peut être exécuté avec JupyterLab
Je commence par importer les différents modules dont j'ai besoin.
Pour l'installation des modules nécessaires, tout est expliqué ici.
from pathlib import Path
import pandas as pd
from matplotlib import pyplot as plt
from collections import defaultdict
from datetime import datetime as dt
from bs4 import BeautifulSoup as bs
import numpy as np
from IPython.core.display import HTML
from scipy import stats
# La ligne ci-dessous permet d'afficher les graphiques dans jupyterlab
%matplotlib inline
Différentes fonctions qui vont m'être utiles pour transformer les données
def toDICT(KEYS, OPES, DICT):
for OPE in OPES:
for KEY in KEYS:
DICT[KEY].append(OPE.get(KEY))
return DICT
def toDF(DICT):
# Je créé le dataframe Pandas à partir des données du dictionnaire DICT
DF = pd.DataFrame(data=DICT)
# Je converti la date (ordinal) au format datetime à l'aide de la fonction "convDATE"
DF['date'] = DF['date'].apply(convDATE)
# Je converti le montant en "float" et je récupère la valeur absolue
DF['amount'] = DF['amount'].apply(float).apply(abs)
# A l'aide de la fonction "getVolDist" je récupère le volume d'essence dans le texte de la colonne "wording"
# Pour rappel, le texte contenu dans le champ "wording": v=25.49 d=41548
# Le volume d'essence est converti en "float" à l'aide du module numpy
DF['vol'] = DF['wording'].apply(getVolDist, args=['v']).apply(np.float32)
# Le volume d'essence est arrondi à deux chiffres après la virgule
DF['vol'] = DF['vol'].apply(lambda x: round(x, 2))
# A l'aide de la fonction "getVolDist" je récupère le kilométrage dans le texte de la colonne "wording"
# Le kilométrage est converti en "int" à l'aide du module numpy
DF['dist'] = DF['wording'].apply(getVolDist, args=['d']).apply(np.int32)
# Je supprime la colonne "wording"
DF.drop(columns=['wording'], inplace=True)
# Je trie mon dataframe par date croissante
DF.sort_values('date', inplace=True)
# A l'aide de la fonction "diff", appliquée sur la colonne "dist", je calcul la différence de kilométrage entre date et date+1
DF['kms'] = DF['dist'].diff(periods=-1)
# Je conserve la valeur absolue pour la colonne "kms"
DF['kms'] = DF['kms'].apply(abs)
# Je calcul le prix au litre arrondi à trois chiffres après la virgule
DF['px/l'] = round(DF['amount'] / DF['vol'], 3)
# Je calcul le cumul des kilomètres à l'aide la fonction "cumsum" appliquée sur la colonne "kms"
DF['cum kms'] = DF['kms'].cumsum()
# Je calcul le cumul du volume à l'aide la fonction "cumsum" appliquée sur la colonne "vol"
DF['cum vol'] = DF['vol'].cumsum()
# Je calcul la consommation arrondi à deux chiffres après la virgule
DF['conso'] = round(DF['cum vol'] * 100 / DF['cum kms'], 2)
# Je remplace toutes les valeurs nulles par le chiffre 0
DF.fillna(value=0, inplace=True)
return DF
def convDATE(i):
return dt.fromordinal(int(i))
def getVolDist(s, k):
s = s.split(' ')
d = {x.split('=')[0]: x.split('=')[1] for x in s}
return d.get(k)
Je récupère les donées au format XML et j'y applique les différentes transformation dont j'ai besoin.
# A l'aide du module Path, j'initialise mon fichier XML
FILE = Path('operations.xml')
# A l'aide du module BeautifulSoup, je parse mon fichier XML
XML = bs(FILE.read_text(), features='xml')
# Je recherche ensuite toutes les balises "ope" ayant un attribut "category" égal à 185
# Ceci est un exemple de mon fichier XML
# <ope date="737309" amount="-29.449999999999999" category="185" wording="v=20.04 d=36773"/>
# OPES est une liste qui contiendra toutes les balises recherchées
OPES = XML.findAll(name='ope', attrs={'category': '185'})
# J'initialise une liste avec le nom des colonnes que je souhaite avoir
KEYS = ['date','amount','vol','dist','wording']
# J'initialise un dictionnaire Python dont les valeurs seront par défaut des listes
DICT = defaultdict(list)
# J'exécute la fonction "toDict" qui va ajouter à mon dictionnaire "DICT" toutes les valeurs extraites dans le XML
DICT = toDICT(KEYS, OPES, DICT)
# J'exécute la fonction "toDF" pour créer un dataframe Pandas
DF = toDF(DICT)
# J'affiche le dataframe
HTML(DF.to_html())
date | amount | vol | dist | kms | px/l | cum kms | cum vol | conso | |
---|---|---|---|---|---|---|---|---|---|
0 | 2019-09-07 | 29.45 | 20.04 | 36773 | 496.0 | 1.470 | 496.0 | 20.04 | 4.04 |
1 | 2019-09-15 | 43.17 | 30.02 | 37269 | 792.0 | 1.438 | 1288.0 | 50.06 | 3.89 |
2 | 2019-10-01 | 43.10 | 30.12 | 38061 | 789.0 | 1.431 | 2077.0 | 80.18 | 3.86 |
3 | 2019-10-16 | 44.24 | 31.31 | 38850 | 782.0 | 1.413 | 2859.0 | 111.49 | 3.90 |
4 | 2019-10-26 | 50.71 | 36.48 | 39632 | 776.0 | 1.390 | 3635.0 | 147.97 | 4.07 |
5 | 2019-11-11 | 50.15 | 34.16 | 40408 | 624.0 | 1.468 | 4259.0 | 182.13 | 4.28 |
6 | 2019-11-24 | 56.60 | 37.86 | 41032 | 516.0 | 1.495 | 4775.0 | 219.99 | 4.61 |
7 | 2019-12-05 | 37.37 | 25.49 | 41548 | 332.0 | 1.466 | 5107.0 | 245.48 | 4.81 |
8 | 2019-12-10 | 32.29 | 21.54 | 41880 | 589.0 | 1.499 | 5696.0 | 267.02 | 4.69 |
9 | 2019-12-21 | 49.34 | 32.57 | 42469 | 635.0 | 1.515 | 6331.0 | 299.59 | 4.73 |
10 | 2020-01-03 | 50.92 | 33.52 | 43104 | 711.0 | 1.519 | 7042.0 | 333.11 | 4.73 |
11 | 2020-01-17 | 53.57 | 34.90 | 43815 | 538.0 | 1.535 | 7580.0 | 368.01 | 4.86 |
12 | 2020-01-31 | 30.20 | 20.70 | 44353 | 384.0 | 1.459 | 7964.0 | 388.71 | 4.88 |
13 | 2020-02-14 | 39.24 | 27.08 | 44737 | 666.0 | 1.449 | 8630.0 | 415.79 | 4.82 |
14 | 2020-02-29 | 49.45 | 33.30 | 45403 | 537.0 | 1.485 | 9167.0 | 449.09 | 4.90 |
15 | 2020-03-16 | 29.85 | 22.98 | 45940 | 0.0 | 1.299 | 0.0 | 472.07 | 0.00 |
Mes données sont enfin bien formatées.
Passons ensuite au graphique...
# J'initialise mon graphique
fig, axe = plt.subplots(figsize=(10.6,8))
# Je définis mon axe "x" avec les valeurs de la colonne "date" exceptée la dernière valeur
x = DF.iloc[:-1]['date'].dt.strftime('%Y-%m-%d')
# Je définis mon axe "y" avec les valeurs de la colonne "conso" exceptée la dernière valeur
y = DF.iloc[:-1]['conso']
# Je créé un graphique de type barres et de couleurs bleues
axe.bar(x, y, label='Consommation', color='#1B80EA')
# J'ajoute un label à l'axe "y"
axe.set_ylabel('Consommation')
# J'ajoute un label à l'axe "x"
axe.set_xlabel('Période')
# Je définis la limite minimum de l'axe "y" en prenant en compte la valeur entière minimum de ma consommation
axe.set_ylim(int(DF.iloc[:-1]['conso'].min()))
# J'ajoute un titre au graphique en y indiquant la consommation moyenne calculée à l'aide de la fonction "mean" appliquée sur la colonne "conso" et arrondi à deux chiffres après la virgule
axe.set_title('Golf VII: Conso moyenne: {} l/100kms'.format(round(DF.iloc[:-1].conso.mean(), 2)))
# Je formate automatiquement l'affichage des dates pour l'axe "x"
fig.autofmt_xdate()
# A partir d'ici, je créé un courbe de tendance sur la consommation
# Je définis un axe "x" temporaire contenant uniquement les valeurs de l'index. De 0 à 14
_x = DF.iloc[:-1].index
# A l'aide de la fonction "linregress" du module "scipy", je calcul la tendance de la consommation
lr = stats.linregress(_x, y)
# J'ajoute au graphique, sur les mêmes axes "x" & "y" la courbe de tendance de couleur rouge
axe.plot(x, lr.intercept + lr.slope * _x, marker='.', color='r', label='Tendance Consommation')
# Je positionne la légende dans le coin supérieur gauche
axe.legend(loc='upper left')
# Je créé un nouvel axe "y"
ax2 = axe.twinx()
# Je définis mon nouvel axe "y" avec les valeurs de la colonne "px/l" exceptée la dernière valeur
y = DF.iloc[:-1]['px/l']
# Je crée un graphique de type ligne et de couleur or
ax2.plot(x, y, color='#C7A986', marker='o', label='px/l')
# J'ajoute un label au second axe "y"
ax2.set_ylabel('Prix au litre')
# Je positionne la légende en bas à droite
ax2.legend(loc='lower right')
# J'affiche le graphique
plt.show()
Je vais maintenant faire une synthèse de mes données.
Je souhaite avoir une représentation de ma consommation à chaque fin de mois.
# La fonction "resample" appliquée sur un dataframe permet de redéfinir les données
# Je souhaite regrouper mes données pour chaque fin de mois "M" appliqué sur la colonne "date
R = DF.resample('M',on='date')
# Je créé un nouveau dataframe à partir de ma synthèse et en agrégeant les données
DF2 = R.agg({'amount':sum,'vol':sum,'dist':max,'kms':sum,'px/l':np.mean,'cum kms':max,'cum vol':max,'conso':np.mean})
# Je formate les différents nombres
DF2['conso'] = DF2['conso'].apply(lambda x: round(x, 2))
DF2['px/l'] = DF2['px/l'].apply(lambda x: round(x, 3))
DF2['kms'] = DF2['kms'].apply(int)
DF2['cum kms'] = DF2['cum kms'].apply(int)
# J'affiche le dataframe
HTML(DF2.to_html())
amount | vol | dist | kms | px/l | cum kms | cum vol | conso | |
---|---|---|---|---|---|---|---|---|
date | ||||||||
2019-09-30 | 72.62 | 50.06 | 37269 | 1288 | 1.454 | 1288 | 50.06 | 3.96 |
2019-10-31 | 138.05 | 97.91 | 39632 | 2347 | 1.411 | 3635 | 147.97 | 3.94 |
2019-11-30 | 106.75 | 72.02 | 41032 | 1140 | 1.482 | 4775 | 219.99 | 4.45 |
2019-12-31 | 119.00 | 79.60 | 42469 | 1556 | 1.493 | 6331 | 299.59 | 4.74 |
2020-01-31 | 134.69 | 89.12 | 44353 | 1633 | 1.504 | 7964 | 388.71 | 4.82 |
2020-02-29 | 88.69 | 60.38 | 45403 | 1203 | 1.467 | 9167 | 449.09 | 4.86 |
2020-03-31 | 29.85 | 22.98 | 45940 | 0 | 1.299 | 0 | 472.07 | 0.00 |
Intéressant comme synthèse.
Les dates de fin de mois ont été automatiquement calculées.
Et maintenant, le petit graphique qui va bien.
fig, axe = plt.subplots(figsize=(10.6,8))
x = DF2.iloc[:-1].index.strftime('%Y-%m-%d')
y = DF2.iloc[:-1]['conso']
axe.bar(x, y, label='conso', color='#1B80EA')
axe.set_ylabel('Consommation')
axe.set_xlabel('Période')
axe.set_ylim(int(y.min()))
_x = range(len(DF2.iloc[:-1].index))
lr = stats.linregress(_x, y)
axe.plot(x, lr.intercept + lr.slope * _x, marker='.', color='r', label='Tendance Consommation')
axe.legend(loc='upper left')
axe.set_title('Golf VII: Conso moyenne: {} l/100kms'.format(round(y.mean(), 2)))
fig.autofmt_xdate()
ax2 = axe.twinx()
y = DF2.iloc[:-1]['px/l']
ax2.plot(x, y, color='#C7A986', marker='o', label='px/l')
ax2.set_ylabel('Prix au litre')
ax2.legend(loc='lower right')
fig.savefig(FILE.parent / 'Analyse Conso Fin De Mois.png', format='png')
plt.show()
Vraiment bluffant Pandas et Matplotlib
Les dataframes Pandas offre tout un tas de possibilités qu'il est impossible de résumé en un seul article.
Les pages de documentation des projets sont très bien fournies.
J'utilise de temps en temps des scripts Python qui sont exécutés en tâche de fond sur mon système.
Le principe, une fonction main contenant le code à exécuter et une boucle while infinie qui exécute la fonction main.
#!python3
# -*- coding: UTF-8 -*-
import time
def main():
....
if __name__ == '__main__':
while True:
main()
time.sleep(3)
Toutes les 3 sec, la fonction main est exécutée.
Le seul moyen d'arrêter le script, ctrl+c dans la console sans savoir où le script en est vraiement, au risque de l'arrêter pendant l'écriture de données dans un fichier et de corrompre ledit fichier.
Petite astuce, à mettre dans tous ses scripts exécutés en tâche de fond:
#!python3
# -*- coding: UTF-8 -*-
import time
from pathlib import Path
import sys
def main():
....
if __name__ == '__main__':
while True:
main()
if Path('.kill').exists() and Path('.kill').read_text() == Path(sys.argv[0]).name:
break
time.sleep(3)
sys.exit(0)
L'astuce consiste à utiliser un fichier .kill, dans le même répertoire que le script Python, et d'y écrire le nom exact du script concerné.
Calculer la somme MD5/SHA256/SHA512 de contrôle d'un fichier:
>>> import hashlib
>>> hashlib.md5(open('.bashrc','rb').read()).hexdigest()
'fc0db75cc50c25e8984fa2a958dac042'
>>> hashlib.sha256(open('.bashrc','rb').read()).hexdigest()
'2a684f93356c0cb229d6ee2e464d52e21f6dfbdafd3eef4d44d2f80d66bc20a1'
>>> hashlib.sha512(open('.bashrc','rb').read()).hexdigest()
'e5754975dad2512f024401d7f542a86b266dae7e301ff1f0a19c391e3494b1556544ed2de465da903c9978d7848e8e07fbcd32b03a2b47abbef96b80f4d30a52'
>>>
Le nombre π (3,1415.........), peut être calculé à l'aide de la formule suivante:
π = (4/1) - (4/3) + (4/5) - (4/7) + (4/9) - (4/11) + (4/13) etc etc ...
On démarre les opérations avec le numérateur "4", que l'on conserve jusqu'à la fin, et le dénominateur 1 que l'on incrémente de 2 à chaque opération en alternant soustraction et addition.
Plus le nombre d'itération est important, plus la précision sera importante.
Une petite boucle en Python permet de calculer π avec une bonne précision.
Dans l'exemple suivant, je vais boucler 100 millions de fois afin d'avoir une valeur de π avec 27 chiffres après la virgule, mais comme le souligne Matt, avec une précision de seulement 7 chiffres après la virgule, et cela en quelques minutes seulement.
>>> from decimal import Decimal
>>> P = 100000000 # attention, nombre pair obligatoire (voir commentaire)
>>> C = Decimal(4) / 1
>>> I = 1
>>> for i in range(P, 0, -1):
z = Decimal(4) / (I+2)
if i%2 == 0:
z = Decimal.copy_negate(z)
C += z
I += 2
>>> print(C)
3.141592663589793138462645118
Mais il est quand même plus rapide d'utiliser la fonction suivante:
>>> from math import pi
>>> print(pi)
3.141592653589793
C'était juste pour le fun
Pandas propose une fonction qui permet de calculer très facilement les classes ABC.
Cette fonction se nomme cut
>>> import pandas as pd
>>> import numpy as np
Pour générer un dataframe avec des données aléatoires, voici une petite astuce:
>>> import random
>>> random.seed()
>>> df = pd.DataFrame(np.random.randint(0,1000,100), index=random.sample(range(100000, 999999), 100), columns=['stocks'])
Ca permet de générer un dataframe de 100 lignes avec une colonne "stocks" et contenant des valeurs comprises entre 0 et 1000 et en index, une liste d'articles uniques compris entre 100000 et 999999.
Exemple avec le dataframe suivant:
>>> df
stocks
761920 965
636899 147
835336 324
366511 536
852098 544
...
170837 319
886380 98
676491 201
639583 233
854389 292
[100 rows x 1 columns]
En index, les références de mes articles et les quantités en stock dans la colonne 'stocks'.
J'ai au total 100 articles dans mon dataframe.
Premièrement, trier les données par stocks décroissant:
>>> df = df.sort_values('stocks', ascending=False)
>>> df
stocks
506289 979
549641 977
351719 967
761920 965
874506 962
...
332012 67
797080 41
347642 38
417762 28
284562 13
[100 rows x 1 columns]
Ensuite, calculer le cumul des stocks:
>>> df['cumulStocks'] = df['stocks'].cumsum()
>>> df
stocks cumulStocks
506289 979 979
549641 977 1956
351719 967 2923
761920 965 3888
874506 962 4850
... ...
332012 67 49118
797080 41 49159
347642 38 49197
417762 28 49225
284562 13 49238
[100 rows x 2 columns]
Après, il faut calculer le pourcentage du cumul du stock par rapport à la somme totale du stock:
>>> df['%cumulStocks'] = round(100 * df['cumulStocks'] / df['stocks'].sum(), 3)
>>> df
stocks cumulStocks %cumulStocks
506289 979 979 1.988
549641 977 1956 3.973
351719 967 2923 5.936
761920 965 3888 7.896
874506 962 4850 9.850
... ... ...
332012 67 49118 99.756
797080 41 49159 99.840
347642 38 49197 99.917
417762 28 49225 99.974
284562 13 49238 100.000
[100 rows x 3 columns]
Nous ajoutons une colonne afin d'indiquer le rang des articles:
>>> df['rang'] = df.rank(ascending=False)
>>> df
stocks cumulStocks %cumulStocks rang
506289 979 979 1.988 1.0
549641 977 1956 3.973 2.0
351719 967 2923 5.936 3.0
761920 965 3888 7.896 4.0
874506 962 4850 9.850 5.0
... ... ... ...
332012 67 49118 99.756 96.0
797080 41 49159 99.840 97.0
347642 38 49197 99.917 98.0
417762 28 49225 99.974 99.0
284562 13 49238 100.000 100.0
[100 rows x 4 columns]
Pour finir, calculons les classes ABC avec les pourcentages suivants:
Je veux que ma classe A concerne 80% des stocks cumulés, ma classe B les 15% suivants et ma classe C les 5 % restants.
>>> classes = ['A', 'B', 'C']
>>> pourcent = [0, 80, 95, 100]
>>> df['classe'] = pd.cut(df['%cumulStocks'], bins=pourcent, labels=classes)
>>> df
stocks cumulStocks %cumulStocks rang classe
506289 979 979 1.988 1.0 A
549641 977 1956 3.973 2.0 A
351719 967 2923 5.936 3.0 A
761920 965 3888 7.896 4.0 A
874506 962 4850 9.850 5.0 A
... ... ... ... ...
332012 67 49118 99.756 96.0 C
797080 41 49159 99.840 97.0 C
347642 38 49197 99.917 98.0 C
417762 28 49225 99.974 99.0 C
284562 13 49238 100.000 100.0 C
[100 rows x 5 columns]
Je me retrouve avec la répartition suivante, 56 articles en classe A, 22 articles en classe B et 22 articles en classe C
>>> df['classe'].value_counts(sort=False)
A 56
B 22
C 22
Name: classe, dtype: int64
La classe A contient bien tous les articles dont le pourcentage du stock cumulé est inférieur ou égal à 80%
>>> df[df['classe']=='A']['%cumulStocks'].describe()[['min','max']]
min 1.988000
max 79.776000
Name: %cumulStocks, dtype: float64
La classe B contient tous les articles supérieur à 80% et inférieur ou égal à 95%
>>> df[df['classe']=='B']['%cumulStocks'].describe()[['min','max']]
min 80.584000
max 94.549000
Name: %cumulStocks, dtype: float64
Enfin, la classe C contient tous les articles supérieur à 95% et inférieur ou égal à 100%
>>> df[df['classe']=='C']['%cumulStocks'].describe()[['min','max']]
min 95.022000
max 100.000000
Name: %cumulStocks, dtype: float64
Ajoutons une colonne indiquant le pourcentage du rang:
>>> df['%rang'] = round(100 * df['rang'] / len(df), 3)
>>> df
stocks cumulStocks %cumulStocks rang classe %rang
506289 979 979 1.988 1.0 A 1.0
549641 977 1956 3.973 2.0 A 2.0
351719 967 2923 5.936 3.0 A 3.0
761920 965 3888 7.896 4.0 A 4.0
874506 962 4850 9.850 5.0 A 5.0
... ... ... ... ... ...
332012 67 49118 99.756 96.0 C 96.0
797080 41 49159 99.840 97.0 C 97.0
347642 38 49197 99.917 98.0 C 98.0
417762 28 49225 99.974 99.0 C 99.0
284562 13 49238 100.000 100.0 C 100.0
[100 rows x 6 columns]
Vérifions maintenant si Pareto a toujours raison, à savoir 20% de mes articles doivent représenter 80% de mon stock (plus haut, la répartition indiquait 56 articles dans la classe A, par conséquent 56% puisque j'ai un échantillon de 100 articles):
>>> df[df['classe']=='A'].describe().loc['max',['%cumulStocks','%rang']]
%cumulStocks 79.776
%rang 56.000
Name: max, dtype: float64
On voit bien que 80% de mon stock est constitué par 56% de mes articles.
Sacré Pareto...
class LowerThanTwo(Exception):
pass
def isPrime(num):
try:
num = int(num)
if num < 2: raise LowerThanTwo()
except ValueError:
return False, "Saisir un nombre valide !!!"
except LowerThanTwo:
return False, "Saisir un nombre supérieur égal à 2 !!!"
else:
if num > 2 and num % 2 == 0:
return False, "{:>5d} n'est pas un nombre premier!".format(num)
for x in range(2, num // 2):
if num % x == 0:
return False, "{:>5d} n'est pas un nombre premier!".format(num)
return True, "{:>5d} est un nombre premier!".format(num)
Exécution de la fonction:
for x in range(100):
a, b = isPrime(x)
if a: print(b)
2 est un nombre premier!
3 est un nombre premier!
5 est un nombre premier!
7 est un nombre premier!
11 est un nombre premier!
13 est un nombre premier!
17 est un nombre premier!
19 est un nombre premier!
23 est un nombre premier!
29 est un nombre premier!
31 est un nombre premier!
37 est un nombre premier!
41 est un nombre premier!
43 est un nombre premier!
47 est un nombre premier!
53 est un nombre premier!
59 est un nombre premier!
61 est un nombre premier!
67 est un nombre premier!
71 est un nombre premier!
73 est un nombre premier!
79 est un nombre premier!
83 est un nombre premier!
89 est un nombre premier!
97 est un nombre premier!
Un autre possibilité, consiste à utiliser une regex.
Astuce trouvée sur http://montreal.pm.org/tech/neil_kandalgaonkar.shtml?s=09
La négation de cette regex permet de savoir si le nombre est premier.
import re
r1 = re.compile(r'^1?$|^(11+?)\1+$')
not r1.match("1"*9) # False car 9 n'est pas un nombre premier
not r1.match("1"*13) # True car 13 est un nombre premier
not r1.match("1"*32002) # False car 32002 n'est pas un nombre premier
not r1.match("1"*32003) # True car 32003 est un nombre premier
Intéressant comme regex
... entre le 01/01/1970 et aujourd'hui.
>>> import time
>>> import random
>>> time.strftime('%d/%m/%Y', time.gmtime(random.randint(0, int(time.time()))))
'20/09/1992'
>>> time.strftime('%d/%m/%Y', time.gmtime(random.randint(0, int(time.time()))))
'18/11/2007'
Ca peut toujours servir
Comme indiqué dans le titre, j'ai une liste de nombre, par exemple 5, 10, 25, 30, 45, 60 et j'aimerais savoir quels sont ceux, quand on les additionne, qui me donne le résultat 35 par exemple.
Avec ces nombres, facile, il y a 10 + 25 mais également 5 + 30, mais quand on a une liste de nombres comme celle-ci 8.42, 6.94, 5.40, 1.77, 4.32, 4.26, 3.49, 2.33, 3.90, 1.09 (avec des décimales, c'est plus fun) et que je veux que la somme soit égale à 29.44, c'est un peu plus compliqué.
J'ai imaginé cette fonction en Python qui me permet de trouver le résultat.
Bien évidemment, plus il y a de nombres dans la liste de départ, plus le temps de calcul est long.
Pour m'aider, j'utilise les classes permutations et accumulate du module itertools ainsi que la classe Decimal du module decimal.
Peut-être auriez vous des idées d'amélioration ?
Voici la fonction.
Toutes les lignes sont commentées pour une meilleure compréhension.
from itertools import permutations
from itertools import accumulate
from decimal import Decimal
def trouve_les_arguments_de_la_somme():
t = Decimal('29.44') # la somme à trouver
l = [8.42, 6.94, 5.40, 1.77, 4.32, 4.26, 3.49, 2.33, 3.90, 1.09] # la liste des arguments, 10 au total
l = [Decimal(str(x)) for x in l] # je convertis les arguments en objet "Decimal"
l = [x for x in l if x <= t] # je conserve uniquement les valeurs inférieures ou égales à la somme à trouver
z = permutations(l, len(l)) # j'utilise la classe permutations afin de générer toutes les combinaisons possible, 3 628 800 dans mon cas [factorial(10)]
se = set() # je créé un set afin d'y stocker toutes les solutions possible
for x in z: # je parcours toutes les combinaisons possible
s = list(accumulate(x)) # pour chaque combinaison, je calcul la somme cumulée à l'aide de la classe accumulate
if t in s: # si ma somme à trouver est dans la liste des sommes cumulées
i = s.index(t) # je recherche la position de ma somme dans la liste
se.add(tuple(sorted(x[:i + 1]))) # je récupère dans ma combinaison tous les arguments concernés grâce à mon index i et je les trie par ordre croissant avant de les ajouter à mon set
for i in se:
print(str(i)) # j'affiche les résultats
(Decimal('1.09'), Decimal('1.77'), Decimal('2.33'), Decimal('3.49'), Decimal('5.4'), Decimal('6.94'), Decimal('8.42'))
Dans mon cas, je n'ai qu'une seule solution possible (trouvée en 10 sec max).
Comparer le contenu de fichiers communs à deux dossiers (par exemple pour comparer les sources JAVA entre un environnement de DEV et un environnement de PROD) est relativement simple avec Python et le module Path.
La classe Path du module pathlib est indispensable pour tout ce qui touche au système de fichiers.
Il contient tout un tas d'outils très simple d'utilisation et qui facilite grandement toutes les opérations à faire.
J'utilise également la classe md5 du module hashlib afin de calculer la somme md5 de chaque fichier.
Import de la classe Path du module pathlib et de la classe md5 du module hashlib
>>> from pathlib import Path
>>> from hashlib import md5
Initialisation des deux dossiers à comparer.
>>> D1 = Path(r'd:\dossier1')
>>> D2 = Path(r'd:\dossier2')
A l'aide de la méthode rglob, je parcours récursivement tous les fichiers JAVA présents dans le dossier.
La méthode relative_to permet d'obtenir le chemin du fichier relatif à la racine (passée en paramètre).
La méthode exists permet de tester l'existence du fichier.
La méthode read_bytes permet d'accéder directement au contenu du fichier en mode binaire.
La méthode hexdigest de la classe md5 permet de calculer en hexadécimal la somme md5 du contenu du fichier.
>>> for file in D1.rglob('*.java'): file2 = D2/ file.relative_to(D1) if file2.exists(): if md5(file.read_bytes()).hexdigest() != md5(file2.read_bytes()).hexdigest(): print(f'Le fichier {file.relative_to(D1)} est différent entre {D1.as_posix()} et {D2.as_posix()}') else: print(f'Le fichier {file.relative_to(D1)} n\'existe pas dans
{D2.as_posix()}
')
Utilisation des f-strings pour l'affichage des messages.
Comparer deux fichiers archives ZIP afin d'ajouter dans le premier tous les fichiers présents dans le second sans prendre en compte les fichiers communs aux deux fichiers archives ZIP.
from pathlib import Path
from zipfile import ZipFile as zf
DOSSIER = Path('/mondossier')
filename1 = DOSSIER / 'archive1.zip'
# on ouvre la première archive en mode 'append'
zip1 = zf(filename1, mode='a')
# on fait un 'set' de tous les fichiers de la première archive
s1 = set([(x.filename, x.file_size) for x in zip1.filelist])
filename2 = DOSSIER / 'archive2.zip'
# on ouvre la seonde archive en mode 'read'
zip2 = zf(filename2, mode='r')
# on fait un 'set' de tous les fichiers de la seconde archive
s2 = set([(x.filename, x.file_size) for x in zip2.filelist])
# on parcourt tous les fichiers de la seconde archive qui ne sont pas dans la première archive
for f, s in s2 - s1:
# on écrit dans la première archive le contenu du fichier de la seconde archive
zip1.writestr(f, zip2.read(f))
zip1.close()
zip2.close()
On peut améliorer le script en vérifiant la taille du fichier (variable 's' dans la boucle 'for') à ajouter dans le cas où un fichier commun aurait été modifié entre les deux archives ZIP.
En Python, il existe un type d'éléments qui permet de faire différentes comparaisons.
Ce type, c'est le set.
Exemples avec les deux éléments suivants:
>>> s1 = {1,2,3,4,5}
>>> s2 = {3,4,5,6,7}
Afficher les éléments présents uniquement dans s1:
>>> s1 - s2
{1, 2}
Afficher les éléments présents uniquement dans s2:
>>> s2 - s1
{6, 7}
Afficher les éléments communs à s1 et à s2:
>>> s1 & s2
{3, 4, 5}
Afficher les éléments présents dans s1 et dans s2 (sans les doublons):
>>> s1 | s2
{1, 2, 3, 4, 5, 6, 7}
Vraiment très pratique pour comparer deux contenus.
Fonctionne également avec des chaines de textes.
Pour calculer le nombre de jours ouvrés dans un mois, je vais utiliser les modules calendar et numpy
>>> import numpy as np
>>> import calendar
La méthode monthcalendar de la classe calendar permet d'obtenir un array avec tous les jours du mois classés par semaine (du lundi au dimanche)
>>> calendar.monthcalendar(2019,10)
[[0, 1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11, 12, 13], [14, 15, 16, 17, 18, 19, 20], [21, 22, 23, 24, 25, 26, 27], [28, 29, 30, 31, 0, 0, 0]]
Je le convertis en array numpy
>>> a = np.array(calendar.monthcalendar(2019,10))
>>> a
array([[ 0, 1, 2, 3, 4, 5, 6],
[ 7, 8, 9, 10, 11, 12, 13],
[14, 15, 16, 17, 18, 19, 20],
[21, 22, 23, 24, 25, 26, 27],
[28, 29, 30, 31, 0, 0, 0]])
J'affiche uniquement les 5 premiers jours de chaque semaine
>>> a[:,:-2]
array([[ 0, 1, 2, 3, 4],
[ 7, 8, 9, 10, 11],
[14, 15, 16, 17, 18],
[21, 22, 23, 24, 25],
[28, 29, 30, 31, 0]])
Enfin, je compte le nombre d'éléments supérieurs à 0
>>> len(a[np.where(a[:,:-2] > 0)])
23
Le mois d'octobre de l'année 2019 compte 23 jours ouvrés.
Il est également possible d'utiliser la méthode nonzero de la classe numpy
>>> len(a[np.nonzero(a[:,:5])])
23
Une autre méthode consiste à utiliser le puissant module dateutil
>>> from dateutil.parser import *
>>> from dateutil.rrule import *
>>> from dateutil.relativedelta import *
>>> dtstart = parse('20200601')
>>> len(list(rrule(DAILY, dtstart=dtstart, until=dtstart+relativedelta(months=+1, days=-1), byweekday=[MO, TU, WE, TH, FR])))
22
La classe parser permet de convertir une date d'un format texte à un format datetime.datetime
La classe relativedelta permet d'incrémenter une date ou de calculer l'écart entre deux dates
La classe rrule permet de générer toute une liste de dates suivant différents critères
Exécuter un script Python sur plusieurs machines différentes, que ce soit au niveau du système d'exploitation et/ou de son nom d'hôte, peut engendrer des problèmes concernant certaines tâches à exécuter.
Par exemple, une variable initialisée différement en fonction du système d'exploitation et/ou du nom d'hôte de la machine sur laquelle le script est exécuté.
Les deux modules suivants permettent de résoudre ces problèmes.
Pour le nom du système d'exploitation:
>>> import platform
>>> print(platform.system())
Les différents retours en fonction du système:
Pour le nom d'hôte de la machine:
>>> import socket
>>> print(socket.gethostname())
Pour convertir des secondes en heures, minutes et secondes, il existe une librairie Python, dateutil, qui permet de le faire tout simplement.
Pour ce faire, il suffit d'utiliser la méthode relativedelta de la classe dateutil.relativedelta.
Par exemple, pour convertir 16433.9 secondes:
>>> from dateutil.relativedelta import relativedelta
>>> relativedelta(seconds=16433.9)
relativedelta(hours=+4, minutes=+33, seconds=+53.9)
>>>
facile, non !
Voici un script Python qui permet de convertir en XML un contenu JSON et/ou un dictionnaire de données Python.
Quelques exemples d'utilisations:
En ligne de commande (BASH par exemple) avec un pipe et un flux JSON via CURL:
# curl "http://api.geonames.org/citiesJSON?formatted=true&north=44.1&south=-9.9&east=-22.4&west=55.2&lang=de&username=demo&style=full" -o - -s | python3 ToXml.py
<?xml version="1.0" ?>
<XML>
<geonames>
<name>Mexiko-Stadt</name>
<countrycode>MX</countrycode>
<geonameId>3530597</geonameId>
<toponymName>Mexico City</toponymName>
<wikipedia>en.wikipedia.org/wiki/Mexico_City</wikipedia>
<fclName>city, village,...</fclName>
<fcode>PPLC</fcode>
<lat>19.428472427036</lat>
<lng>-99.12766456604</lng>
<fcl>P</fcl>
<fcodeName>capital of a political entity</fcodeName>
<population>12294193</population>
</geonames>
<geonames>
<name>Peking</name>
<countrycode>CN</countrycode>
<geonameId>1816670</geonameId>
<toponymName>Beijing</toponymName>
<wikipedia>en.wikipedia.org/wiki/Beijing</wikipedia>
<fclName>city, village,...</fclName>
<fcode>PPLC</fcode>
<lat>39.9074977414405</lat>
<lng>116.397228240967</lng>
<fcl>P</fcl>
<fcodeName>capital of a political entity</fcodeName>
<population>11716620</population>
</geonames>
</XML>
Utilisé avec un pipe, seul un flux JSON est autorisé.
Sinon, en important la classe dans un script Python:
A partir d'un fichier:
# cat flux.json
{
"un": "one",
"deux": "two",
" 3": "three",
"4": "four"
}
Code Python:
>>> from ToXml import ToXml
>>> toxml = ToXml()
>>> toxml.fromjson('flux.json')
>>> print(toxml.topretty())
<?xml version="1.0" ?>
<XML>
<XMLNODE v="3">three</XMLNODE>
<XMLNODE v="4">four</XMLNODE>
<un>one</un>
<deux>two</deux>
</XML>
>>>
A partir d'une chaine de texte représentant un contenu JSON:
>>> from ToXml import ToXml
>>> s = '{" 3": "three", "4": "four", "un": "one", "deux": "two"}'
>>> toxml = ToXml()
>>> toxml.fromjsons(s)
>>> print(toxml.topretty())
<?xml version="1.0" ?>
<XML>
<XMLNODE v="3">three</XMLNODE>
<XMLNODE v="4">four</XMLNODE>
<un>one</un>
<deux>two</deux>
</XML>
>>>
A partir d'un dictionnaire de données:
>>> from ToXml import ToXml
>>> d = {' 3': 'three', '4': 'four', 'un': 'one', 'deux': 'two'}
>>> type(d)
<class 'dict'>
>>> toxml = ToXml()
>>> toxml.fromdict(d)
>>> print(toxml.topretty())
<?xml version="1.0" ?>
<XML>
<XMLNODE v="3">three</XMLNODE>
<XMLNODE v="4">four</XMLNODE>
<un>one</un>
<deux>two</deux>
</XML>
>>>
Et enfin, une fonction permettant d'enregistrer le contenu XML dans un fichier:
>>> toxml.tofile('flux.xml')
# cat flux.xml
<?xml version="1.0" ?>
<XML>
<XMLNODE v="3">three</XMLNODE>
<XMLNODE v="4">four</XMLNODE>
<un>one</un>
<deux>two</deux>
</XML>
N'hésitez pas à laisser vos avis.
J'ai essayé de documenter le code au maximum mais au cas où ...
#!/usr/bin/env python
# -*-coding: utf-8 -*
entetes = [
u'Colonne1',
u'Colonne2',
u'Colonne3',
u'Colonne4',
u'Colonne5'
]
valeurs = [
[u'Valeur1', u'Valeur2', u'Valeur3', u'Valeur4', u'Valeur5'],
[u'Valeur6', u'Valeur7', u'Valeur8', u'Valeur9', u'Valeur10'],
[u'Valeur11', u'Valeur12', u'Valeur13', u'Valeur14', u'Valeur15']
]
f = open('monFichier.csv', 'w')
ligneEntete = ";".join(entetes) + "\n"
f.write(ligneEntete)
for valeur in valeurs:
ligne = ";".join(valeur) + "\n"
f.write(ligne)
f.close()
La fonction open() de Python2 utilise l'encodage du système pour encoder le fichier (contrairement à Python3, il n'est pas possible de préciser l'encodage lors de louverture du fichier).
Pour encoder le fichier dans un encodage différent du système, il est nécessaire d'utiliser la fonction open() de la librairie codecs.
#!/usr/bin/env python
# -*-coding: utf-8 -*
import codecs
f = codecs.open('monFichier.csv', mode='w', encoding='iso-8859-1')
f.write(u'Ecriture réussie.')
f.close()
Ce qui donne:
$ file --mime-encoding monFichier.csv
monFichier.csv: iso-8859-1
Le module BeautifulSoup permet de parser un fichier XML (ou HTML) très facilement mais il peut, tout aussi facilement, créer du contenu XML de toute pièce.
Pour l'exemple je vais utiliser le module faker qui permet de générer des données aléatoires en tout genre.
# python3 -m pip install --upgrade bs4 faker
>>> from bs4 import BeautifulSoup as bs
>>> from faker import Faker
>>> f = Faker('fr_FR')
Je vais générer des profils aléatoires ressemblant à ceci:
>>> f.profile()
{'job': 'aide-soignant',
'company': 'Dias',
'ssn': '318-53-8242',
'residence': '74, rue Boyer\n04256 Hoarau',
'current_location': (Decimal('17.293354'), Decimal('165.196114')),
'blood_group': 'A+',
'website': ['http://boulay.com/'],
'username': 'arnaudmonique',
'name': 'Patrick Valette',
'sex': 'M',
'address': '86, boulevard Raymond\n67114 Sainte Honoré',
'mail': 'nbesnard@bouygtel.fr',
'birthdate': datetime.date(1944, 10, 31)}
Les données générées sont au format json.
Je vais utiliser les clés comme noms des balises (tags) XML.
Mon fichier XML contiendra une balise (tag) principale "<profiles>" qui contiendra toutes les balises (tag) "<profile>".
La clé "ssn" sera un attribut de la balise (tag) "<profile>".
La valeur de la clé "current_location" sera découpée en deux balises (tags) XML, "<latitude>" et "<longitude>" et ces deux balises (tags) seront intégrées à la balise (tag) "<current_location>".
Toutes les valeurs de la clé "website" auront leurs propres balises (tags) XML "<website>" et elles seront toutes regroupées dans une balise (tag) XML "<websites>".
C'est parti...
# Création de mon enveloppe XML
>>> xml = bs(features='xml')
# Création de mon tag <profiles>
>>> profiles = xml.new_tag('profiles')
# Ajout du tag <profiles> à l'enveloppe XML
>>> xml.append(profiles)
# Une boucle pour créer 10 profils aléatoires
>>> for x in range(10):
# Génération d'un profil aléatoire
prof = f.profile()
# Création de mon tag avec l'attribut "ssn" <profile ssn="xxx-xx-xxxx">
profile = xml.new_tag('profile', attrs={'ssn':prof.get('ssn')})
# Suppression de la clé "ssn" (plus besoin)
prof.pop('ssn')
# Je parcours toutes les clés de mon profil généré
for k, c in prof.items():
# Création du tag correspond à la clé du json <xxxx>
y = xml.new_tag(k)
# Si ma clé est "current_location" je découpe la valeur en "latitude" et "longitude"
if k == 'current_location':
# Création du tag <latitude>
z1 = xml.new_tag('latitude')
# Je renseigne la valeur du tag <latitude> avec la première valeur du tuple "current_location"
z1.string = str(c[0])
# Création du tag <longitude>
z2 = xml.new_tag('longitude')
# Je renseigne la valeur du tag <longitude>
avec la seconde valeur du tuple "current_location"
z2.string = str(c[1])
# On ajoute les deux tags <latitude> et <longitude> au tag <current_location>
y.append(z1)
y.append(z2)
# Si ma clé est "website" je détaille toutes les valeurs
elif k == 'website':
# je renomme mon tag en <websites>
y = xml.new_tag('websites')
# je parcours toutes les valeurs de la clé "website"
for z in c:
# Création du tag <website>
z1 = xml.new_tag('website')
# Je renseigne la valeur du tag <website>
z1.string = z
# J'ajoute mon tag <website> au tag <websites>
y.append(z1)
else:
# Sinon, je renseigne la valeur du tag <xxxx>
y.string = str(c)
# J'ajoute le tag <xxxx> au tag <profile>
profile.append(y)
# J'ajoute le tag <profile> au tag <profiles>
profiles.append(profile)
Et voilà, mon contenu XML est terminé.
Il ressemble à ceci:
<?xml version="1.0" encoding="utf-8"?>
<profiles>
<profile ssn="002-43-2880">
<job>projectionniste</job>
<company>Rossi</company>
<residence>rue Gomez
11458 Francoisnec</residence>
<current_location>
<latitude>-25.928603</latitude>
<longitude>169.931343</longitude>
</current_location>
<blood_group>B+</blood_group>
<websites>
<website>https://www.bonnin.com/</website>
</websites>
<username>caronmargaret</username>
<name>David Joubert</name>
<sex>M</sex>
<address>avenue Geneviève Giraud
90573 Martineaudan</address>
<mail>aubertjacqueline@wanadoo.fr</mail>
<birthdate>1968-02-29</birthdate>
</profile>
<profile ssn="007-06-6107">
<job>prototypiste en matériaux souples</job>
<company>Maurice Blin S.A.S.</company>
<residence>798, rue Françoise Ramos
76458 Saint MargaudBourg</residence>
<current_location>
<latitude>41.5629005</latitude>
<longitude>15.774012</longitude>
</current_location>
<blood_group>B+</blood_group>
<websites>
<website>https://fischer.com/</website>
<website>http://daniel.net/</website>
<website>https://paul.com/</website>
<website>https://www.costa.com/</website>
</websites>
<username>marie50</username>
<name>Inès Daniel</name>
<sex>F</sex>
<address>705, rue Thérèse Collet
50313 Jacques-les-Bains</address>
<mail>hortensehuet@wanadoo.fr</mail>
<birthdate>1968-06-15</birthdate>
</profile>
<profile ssn="017-32-3783">
<job>hydraulicien</job>
<company>Letellier et Fils</company>
<residence>9, rue Gilles
61321 Sainte Virginie</residence>
<current_location>
<latitude>23.7306235</latitude>
<longitude>-59.305275</longitude>
</current_location>
<blood_group>A+</blood_group>
<websites>
<website>https://www.chevallier.com/</website>
<website>https://www.hoareau.net/</website>
</websites>
<username>rene93</username>
<name>Céline Blin</name>
<sex>F</sex>
<address>43, avenue Vidal
82960 Teixeira</address>
<mail>davidmartine@laposte.net</mail>
<birthdate>1954-05-08</birthdate>
</profile>
<profile ssn="023-75-0442">
<job>électricien installateur installatrice</job>
<company>Dupuis</company>
<residence>7, rue Jacob
27949 Saint Yves</residence>
<current_location>
<latitude>-36.2609615</latitude>
<longitude>-40.304003</longitude>
</current_location>
<blood_group>O-</blood_group>
<websites>
<website>http://www.jean.com/</website>
<website>https://gregoire.org/</website>
<website>http://www.payet.org/</website>
<website>http://www.dumas.org/</website>
</websites>
<username>wlegrand</username>
<name>Thierry Martins</name>
<sex>M</sex>
<address>33, boulevard Henri Pierre
00436 Georges</address>
<mail>dumashelene@wanadoo.fr</mail>
<birthdate>1966-07-04</birthdate>
</profile>
<profile ssn="031-65-0389">
<job>ingénieur logiciel</job>
<company>Masse SA</company>
<residence>boulevard de Perrin
96676 Ferrandnec</residence>
<current_location>
<latitude>-27.915336</latitude>
<longitude>161.219956</longitude>
</current_location>
<blood_group>A+</blood_group>
<websites>
<website>https://www.rousseau.com/</website>
<website>http://lecomte.com/</website>
<website>http://lopez.fr/</website>
</websites>
<username>gabriel61</username>
<name>Patrick Delattre</name>
<sex>M</sex>
<address>4, rue de Levy
46800 Hamon-sur-Laporte</address>
<mail>aliceleveque@dbmail.com</mail>
<birthdate>1943-08-04</birthdate>
</profile>
<profile ssn="042-20-4813">
<job>télévendeur</job>
<company>Peltier Lacombe S.A.R.L.</company>
<residence>5, avenue Briand
68000 Coste</residence>
<current_location>
<latitude>-56.7088745</latitude>
<longitude>149.494499</longitude>
</current_location>
<blood_group>O+</blood_group>
<websites>
<website>https://www.lelievre.fr/</website>
<website>https://bernier.fr/</website>
</websites>
<username>noel32</username>
<name>Luc Meyer</name>
<sex>M</sex>
<address>684, boulevard Margaret Garnier
96838 Da Costa</address>
<mail>nicole82@bouygtel.fr</mail>
<birthdate>1921-04-27</birthdate>
</profile>
<profile ssn="064-95-7931">
<job>contremaître</job>
<company>Roche</company>
<residence>35, avenue de Andre
80266 Jacob-les-Bains</residence>
<current_location>
<latitude>-79.434306</latitude>
<longitude>-50.008269</longitude>
</current_location>
<blood_group>O-</blood_group>
<websites>
<website>http://www.parent.fr/</website>
<website>https://dufour.fr/</website>
<website>http://www.humbert.com/</website>
<website>https://www.boyer.net/</website>
</websites>
<username>ghernandez</username>
<name>Gilbert Poirier</name>
<sex>M</sex>
<address>60, rue de Collet
40531 Morvanboeuf</address>
<mail>clemence22@club-internet.fr</mail>
<birthdate>1968-08-18</birthdate>
</profile>
<profile ssn="065-83-0793">
<job>critique d'art</job>
<company>Meunier</company>
<residence>91, avenue Bertin
66624 Guillou</residence>
<current_location>
<latitude>40.394601</latitude>
<longitude>28.203459</longitude>
</current_location>
<blood_group>AB+</blood_group>
<websites>
<website>http://www.pichon.com/</website>
<website>http://chauveau.com/</website>
</websites>
<username>gillessabine</username>
<name>Dominique Ferrand</name>
<sex>F</sex>
<address>rue Arnaude Baron
22300 Barbe</address>
<mail>utorres@voila.fr</mail>
<birthdate>1912-10-02</birthdate>
</profile>
<profile ssn="066-39-4549">
<job>ingénieur de la police technique et scientifique</job>
<company>Perret</company>
<residence>96, chemin de Perret
91066 Perrier-la-Forêt</residence>
<current_location>
<latitude>-67.974417</latitude>
<longitude>-32.491563</longitude>
</current_location>
<blood_group>O-</blood_group>
<websites>
<website>http://richard.net/</website>
<website>http://www.fischer.fr/</website>
</websites>
<username>williamalbert</username>
<name>Alex Godard</name>
<sex>F</sex>
<address>85, boulevard Guichard
20974 Marionnec</address>
<mail>stephanieneveu@club-internet.fr</mail>
<birthdate>1993-02-27</birthdate>
</profile>
<profile ssn="070-76-9294">
<job>assistant en architecture</job>
<company>Aubert Georges S.A.</company>
<residence>31, rue de Garcia
41968 Saint Sabine</residence>
<current_location>
<latitude>36.045194</latitude>
<longitude>131.046682</longitude>
</current_location>
<blood_group>O-</blood_group>
<websites>
<website>http://poirier.fr/</website>
</websites>
<username>denisalix</username>
<name>Christophe Coste-Baron</name>
<sex>M</sex>
<address>44, boulevard Bertrand
85123 Blondel</address>
<mail>bouvierremy@ifrance.com</mail>
<birthdate>1908-03-12</birthdate>
</profile>
Super simple quand même !
Avec Python, il existe différentes possibilités pour initialiser un dictionnaire (dict()).
En voici trois.
La première:
>>> d = dict(un=1, deux=2, trois=3)
>>> d
{'un': 1, 'deux': 2, 'trois': 3}
La seconde, avec deux listes, une pour les clés et une pour les valeurs et la fonction zip:
>>> l1 = ['un','deux','trois']
>>> l2 = [1,2,3]
>>> d1 = dict(zip(l1, l2))
>>> d1
{'un': 1, 'deux': 2, 'trois': 3}
La troisième, toujours avec les deux listes et la fonction zip mais en utilisant, en plus, la compréhension de dictionnaire:
>>> l1 = ['un','deux','trois']
>>> l2 = [1,2,3]
>>> d2 = {k: v for k, v in zip(l1, l2)}
>>> d2
{'un': 1, 'deux': 2, 'trois': 3}
Avec la compréhension de dictionnaire, il est possible d'appliquer des traitements supplémentaires sur les clés et les valeurs.
Il suffit de quelques lignes Python pour faire une recherche textuelle dans des fichiers textes.
A l'aide des deux modules pathlib et re, vous allez voir comme c'est simple.
Voici un exemple qui permet de rechercher une portion de code dans des fichiers JAVA.
>>> from pathlib import Path
>>> import re
>>> D = Path(r'C:\Users\ronan\javasrc')
>>> R = re.compile('public double .+;')
>>> for F in D.rglob('*.java'):
if R.search(F.read_text()):
print(F)
Et voilà, à chaque fois que la chaine recherchée est trouvée dans le contenu du fichier, son nom est affiché dans la console.
Pour rappel, la fonction rglob de la classe Path du module pathlib permet d'effectuer une recherche récursive dans le dossier concerné.
La classe Path du module pathlib permet de lire le contenu des fichiers très simplement à l'aide de la fonction read_text.
Pour encoder une URL (python2):
import urllib
url = "http://www.quennec.fr"
urlCodee = urllib.quote_plus(url)
print(urlCodee)
'http%3A%2F%2Fwww.quennec.fr'
Pour décoder une URL (python2):
import urllib
urlCodee = "http%3A%2F%2Fwww.quennec.fr"
url = urllib.unquote_plus(urlCodee)
print(url)
'http://www.quennec.fr'
Pour encoder une URL (python3):
Il faut utiliser le module urllib.parse (idem pour le décodage).
import urllib
url = "http://www.quennec.fr"
urlCodee = urllib.parse.quote_plus(url)
print(urlCodee)
'http%3A%2F%2Fwww.quennec.fr'
Il est parfois nécessaire d'encoder en base64 des fichiers binaires tels que des images pour pouvoir les envoyer par mail par exemple.
import base64
with open("image.png", "rb") as image_file:
encoded_string = base64.b64encode(image_file.read())
Le contenu de la variable encoded_string peut être envoyé dans un mail, inséré dans un champ d'une table d'une base de données etc etc ...
import smtplib
from email.utils import formatdate
server = smtplib.SMTP()
# server.set_debuglevel(1) # Décommenter pour activer le debug
server.connect('smtp.toto.fr')
# (220, 'toto ESMTP Postfix') # Réponse du serveur
server.helo()
# (250, 'toto\nPIPELINING\nSIZE 10240000\nVRFY\nETRN\nSTARTTLS\nENHANCEDSTATUSCODES\n8BITMIME\nDSN') # Réponse du serveur
fromaddr = 'TOTO <moi@toto.fr>'
toaddrs = ['lui@toto.fr', 'elle@toto.fr'] # On peut mettre autant d'adresses que l'on souhaite
sujet = "Un Mail avec Python"
message = u"""\
Velit morbi ultrices magna integer.
Metus netus nascetur amet cum viverra ve cum.
Curae fusce condimentum interdum felis sit risus.
Proin class condimentum praesent hendrer
it donec odio facilisi sit.
Etiam massa tempus scelerisque curae habitasse vestibulum arcu metus iaculis hac.
"""
msg = """\
From: %s\r\n\
To: %s\r\n\
Subject: %s\r\n\
Date: %s\r\n\
\r\n\
%s
""" % (fromaddr, ", ".join(toaddrs), sujet, formatdate(localtime=True), message)
try:
server.sendmail(fromaddr, toaddrs, msg)
except smtplib.SMTPException as e:
print(e)
# {} # Réponse du serveur
server.quit()
# (221, '2.0.0 Bye') # Réponse du serveur
import smtplib
from email.utils import formatdate
server = smtplib.SMTP_SSL() # On utilise SMTP_SSL() à la place de SMTP()
# server.set_debuglevel(1) # Décommenter pour activer le debug
server.connect('smtp.toto.fr')
# (220, 'toto ESMTP Postfix') # Réponse du serveur
server.ehlo() # ATTENTION, avec SSL, c'est la commande EHLO au lieu de HELO
#(250, 'toto\nPIPELINING\nSIZE 10240000\nVRFY\nETRN\nAUTH LOGIN PLAIN\nAUTH=LOGIN PLAIN\nENHANCEDSTATUSCODES\n8BITMIME\nDSN') # Réponse du serveur
server.login('user', 'pass') # On s'authentifie
# (235, '2.7.0 Authentication successful') # Réponse du serveur
fromaddr = 'TOTO <moi@toto.fr>'
toaddrs = ['lui@toto.fr', 'elle@toto.fr'] # On peut mettre autant d'adresses que l'on souhaite
sujet = "Un Mail avec Python"
message = u"""\
Velit morbi ultrices magna integer.
Metus netus nascetur amet cum viverra ve cum.
Curae fusce condimentum interdum felis sit risus.
Proin class condimentum praesent hendrer
it donec odio facilisi sit.
Etiam massa tempus scelerisque curae habitasse vestibulum arcu metus iaculis hac.
"""
msg = """\
From: %s\r\n\
To: %s\r\n\
Subject: %s\r\n\
Date: %s\r\n\
\r\n\
%s
""" % (fromaddr, ", ".join(toaddrs), sujet, formatdate(localtime=True), message)
try:
server.sendmail(fromaddr, toaddrs, msg)
except smtplib.SMTPException as e:
print(e)
# {} # Réponse du serveur
server.quit()
# (221, '2.0.0 Bye')
import smtplib from email.utils import formatdate server = smtplib.SMTP(
'smtp.toto.fr', 587
) # Avec TLS, on utilise SMTP() # server.set_debuglevel(1) # Décommenter pour activer le debug server.connect('smtp.toto.fr', 587) # On indique le port TLS # (220, 'toto ESMTP Postfix') # Réponse du serveur server.ehlo() # On utilise la commande EHLO # (250, 'toto\nPIPELINING\nSIZE 10240000\nVRFY\nETRN\nSTARTTLS\nENHANCEDSTATUSCODES\n8BITMIME\nDSN') # Réponse du serveur server.starttls() # On appelle la fonction STARTTLS # (220, '2.0.0 Ready to start TLS') # Réponse du serveur server.login('user', 'pass') # (235, '2.7.0 Authentication successful') # Réponse du serveur fromaddr = 'TOTO <moi@toto.fr>' toaddrs = ['lui@toto.fr', 'elle@toto.fr'] # On peut mettre autant d'adresses que l'on souhaite sujet = "Un Mail avec Python" message = u"""\ Velit morbi ultrices magna integer. Metus netus nascetur amet cum viverra ve cum. Curae fusce condimentum interdum felis sit risus. Proin class condimentum praesent hendrer it donec odio facilisi sit. Etiam massa tempus scelerisque curae habitasse vestibulum arcu metus iaculis hac. """ msg = """\ From: %s\r\n\ To: %s\r\n\ Subject: %s\r\n\ Date: %s\r\n\ \r\n\ %s """ % (fromaddr, ", ".join(toaddrs), sujet, formatdate(localtime=True), message) try: server.sendmail(fromaddr, toaddrs, msg) except smtplib.SMTPException as e: print(e) # {} # Réponse du serveur server.quit() # (221, '2.0.0 Bye')
En cas d'erreur, lors de l'exécution de la commande server.sendmail(fromaddr, toaddrs, msg), le serveur répond en indiquant l'erreur rencontrée
{'elle@toto.fr': (450, '4.2.0 <elle@toto.fr>: Recipient address rejected: User unknown in relay recipient table')}
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.utils import formatdate
server = smtplib.SMTP()
# server.set_debuglevel(1) # Décommenter pour activer le debug
server.connect('smtp.toto.fr')
server.helo()
fromaddr = 'TOTO <moi@toto.fr>'
toaddrs = ['lui@toto.fr', 'elle@toto.fr'] # On peut mettre autant d'adresses que l'on souhaite
sujet = "Un Mail avec Python"
html = u"""\
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<div>
Velit morbi ultrices magna integer.
Metus netus nascetur amet cum viverra ve cum.
Curae fusce condimentum interdum felis sit risus.
Proin class condimentum praesent hendrer
it donec odio facilisi sit.
Etiam massa tempus scelerisque curae habitasse vestibulum arcu metus iaculis hac.
</div>
</body
</html>
"""
msg = MIMEMultipart('alternative')
msg['Subject'] = sujet
msg['From'] = fromaddr
msg['To'] = ','.join(toaddrs)
msg["Date"] = formatdate(localtime=True)
part = MIMEText(html, 'html')
msg.attach(part)
try:
server.sendmail(fromaddr, toaddrs, msg.as_string())
except smtplib.SMTPException as e:
print(e)
server.quit()
source:
https://docs.python.org/3/library/email.examples.html#email-examples
https://docs.python.org/3.8/library/mimetypes.html
from email.message import EmailMessage
from email.utils import formatdate
import mimetypes
from pathlib import Path
import smtplib
msg = EmailMessage()
msg['Subject'] = 'Un Mail avec Python
'
msg['From'] = 'TOTO <moi@toto.fr>
'
msg['To'] = ', '.join(['lui@toto.fr', 'elle@toto.fr']
)
msg["Date"] = formatdate(localtime=True)
msg.set_content("""\
Salut!
Ci joint le fichier demandé.
""")
cfile = Path('myfile.txt')
ctype, encoding = mimetypes.guess_type(cfile)
if ctype is None or encoding is not None:
ctype = 'application/octet-stream'
maintype, subtype = ctype.split('/', 1)
msg.add_attachment(cfile.read_bytes(), maintype=maintype, subtype=subtype, filename=cfile.name)
with smtplib.SMTP('smtp.toto.fr
') as csmtp:
csmtp.send_message(msg)
Python 3.9 - Envoi d'un mail via le SMTP office365 (outlook):
import smtplib
from email.utils import formatdate
server = smtplib.SMTP('SMTP.office365.com', 587)
server.connect('SMTP.office365.com', 587)
server.ehlo()
server.starttls()
server.login('julieraymond@example.org', 'Q7!7KgR3+5')
fromaddr = 'Antoine-Marcel Weber <julieraymond@example.org
>'
toaddrs = ['renee88@example.org', 'etienneguyot@example.net']
sujet = "Reculer voile finir arrêter complètement lever."
message = u"""\
Extraordinaire longtemps taille musique. Discussion expérience français anglais fleur toujours.
Professeur dieu agir ouvrir escalier facile supposer. Action maintenir contenter bois. Même puis autre discours de mince veille quarante.
Fille retomber air accuser. Suivant dans bien sous oncle vent. Travail jour refuser nuit réel."""
msg = f"""\
From: {fromaddr}\r\n\
To: {", ".join(toaddrs)}\r\n\
Subject: {sujet}\r\n\
Date: {formatdate(localtime=True)}\r\n\
\r\n\
{message}"""
try:
server.sendmail(fromaddr, toaddrs, msg)
except smtplib.SMTPException as e:
print(e)
server.quit()
Pour sauvegarder dans un fichier requirements.txt tous les packages installés dans l'environnement Python:
$ python -m pip list --format freeze > requirements.txt
Et pour restaurer la liste complète, pour une nouvelle installation, par exemple:
$ python -m pip install -U -r .\requirements.txt
Un fichier GPX est un ensemble de coordonnées géographiques qui permet de créer des traces ou des itinéraires.
Pour utiliser ce script il est nécessaire d'installer les modules python suivants:
# python3 -m pip install --upgrade geopy requests dateutil bs4 numpy
#!/usr/bin/python3
# -*- coding: UTF-8 -*-
from geopy.geocoders import Nominatim
import requests
from dateutil.relativedelta import relativedelta
from bs4 import BeautifulSoup as bs
import numpy as np
cities = ['nantes, france', 'ancenis, france', 'angers, france']
geolocator = Nominatim(user_agent="Python3")
LAT = []
LNG = []
url = "https://routing.openstreetmap.de/routed-car/route/v1/driving/{frlng},{frlat};{tolng},{tolat}"
params = dict(overview='false', geometries='polyline', steps='true')
xml = bs(features='xml')
gpx = xml.new_tag('gpx')
gpx.attrs = {'creator':"Script Python", 'version':"1.1",\
'xmlns':"http://www.topografix.com/GPX/1/1", 'xmlns:xsi':"http://www.w3.org/2001/XMLSchema-instance",\
'xsi:schemaLocation':"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"}
trkl = []
for idxcity in enumerate(cities[:-1]):
idx, city = idxcity
FROM = geolocator.geocode(city)
TO = geolocator.geocode(cities[idx+1])
datas = dict(frlat=FROM.latitude, frlng=FROM.longitude, tolat=TO.latitude, tolng=TO.longitude)
req1 = requests.get(url.format(**datas), params)
js = req1.json()
steps = js.get('routes')[0].get('legs')[0].get('steps')
distance = round(js.get('routes')[0].get('distance') / 1000, 0)
duration = relativedelta(seconds=js.get('routes')[0].get('duration'))
frname = FROM.raw.get('display_name')
toname = TO.raw.get('display_name')
print(f'Itinéraire entre {frname} et {toname}.')
wpt = xml.new_tag('wpt')
wpt.attrs = {'lat':FROM.latitude , 'lon':FROM.longitude}
wptname = xml.new_tag('name')
wptname.string = frname
wpt.append(wptname)
gpx.append(wpt)
trk = xml.new_tag('trk')
name = xml.new_tag('name')
name.string = f'{frname} - {toname}'
desc = xml.new_tag('desc')
desc.string = f'{distance} km, {int(duration.hours)}:{int(duration.minutes)}'
trkseg = xml.new_tag('trkseg')
for x in steps:
for y in x['intersections']:
lon, lat = y['location']
LAT.append(lat)
LNG.append(lon)
trkpt = xml.new_tag('trkpt')
trkpt.attrs = dict(lat=lat, lon=lon)
trkseg.append(trkpt)
trk.append(name)
trk.append(desc)
trk.append(trkseg)
trkl.append(trk)
wpt = xml.new_tag('wpt')
wpt.attrs = {'lat':TO.latitude , 'lon':TO.longitude}
wptname = xml.new_tag('name')
wptname.string = toname
wpt.append(wptname)
gpx.append(wpt)
for trk in trkl:
gpx.append(trk)
xml.append(gpx)
print(xml.prettify())
print(f'Position centrale: {np.mean(LAT)},{np.mean(LNG)}')
Dans mon exemple, je souhaite créer un itinéraire Nantes - Ancenis - Angers
Ce script va générer un contenu xml comme ceci:
<?xml version="1.0" encoding="utf-8"?>
<gpx creator="Script Python" version="1.1" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
<wpt lat="47.2186371" lon="-1.5541362">
<name>
Nantes, Loire-Atlantique, Pays de la Loire, France métropolitaine, France
</name>
</wpt>
<wpt lat="47.3648141" lon="-1.1816088">
<name>
Ancenis, Châteaubriant-Ancenis, Loire-Atlantique, Pays de la Loire, France métropolitaine, 44150, France
</name>
</wpt>
<wpt lat="47.4739884" lon="-0.5515588">
<name>
Angers, Maine-et-Loire, Pays de la Loire, France métropolitaine, France
</name>
</wpt>
<trk>
<name>
Nantes, Loire-Atlantique, Pays de la Loire, France métropolitaine, France - Ancenis, Châteaubriant-Ancenis, Loire-Atlantique, Pays de la Loire, France métropolitaine, 44150, France
</name>
<desc>
44.0 km, 0:37
</desc>
<trkseg>
<trkpt lat="47.218536" lon="-1.554075"/>
<trkpt lat="47.218427" lon="-1.55398"/>
<trkpt lat="47.218355" lon="-1.553938"/>
<trkpt lat="47.218337" lon="-1.553975"/>
<trkpt lat="47.218143" lon="-1.553968"/>
<trkpt lat="47.218345" lon="-1.553476"/>
<trkpt lat="47.217903" lon="-1.553421"/>
<trkpt lat="47.217333" lon="-1.553389"/>
<trkpt lat="47.21738" lon="-1.552416"/>
<trkpt lat="47.217183" lon="-1.55225"/>
<trkpt lat="47.216706" lon="-1.551836"/>
<trkpt lat="47.214756" lon="-1.550238"/>
<trkpt lat="47.214525" lon="-1.55024"/>
<trkpt lat="47.214364" lon="-1.55024"/>
<trkpt lat="47.214335" lon="-1.550143"/>
<trkpt lat="47.214401" lon="-1.549983"/>
<trkpt lat="47.215347" lon="-1.547803"/>
<trkpt lat="47.215441" lon="-1.547616"/>
<trkpt lat="47.215485" lon="-1.547787"/>
<trkpt lat="47.215777" lon="-1.547168"/>
<trkpt lat="47.214924" lon="-1.546208"/>
<trkpt lat="47.214733" lon="-1.546091"/>
<trkpt lat="47.214651" lon="-1.546014"/>
<trkpt lat="47.213826" lon="-1.545304"/>
<trkpt lat="47.21329" lon="-1.54487"/>
<trkpt lat="47.21323" lon="-1.544817"/>
<trkpt lat="47.213195" lon="-1.544786"/>
<trkpt lat="47.211943" lon="-1.543759"/>
<trkpt lat="47.211785" lon="-1.543751"/>
<trkpt lat="47.21172" lon="-1.543605"/>
<trkpt lat="47.211718" lon="-1.543567"/>
<trkpt lat="47.211794" lon="-1.543374"/>
<trkpt lat="47.211883" lon="-1.543345"/>
<trkpt lat="47.212079" lon="-1.542566"/>
<trkpt lat="47.212058" lon="-1.542522"/>
<trkpt lat="47.212074" lon="-1.542362"/>
<trkpt lat="47.212166" lon="-1.542314"/>
<trkpt lat="47.212658" lon="-1.541764"/>
<trkpt lat="47.213442" lon="-1.540108"/>
<trkpt lat="47.213524" lon="-1.539675"/>
<trkpt lat="47.213516" lon="-1.539587"/>
<trkpt lat="47.213565" lon="-1.539412"/>
<trkpt lat="47.213187" lon="-1.537239"/>
<trkpt lat="47.21313" lon="-1.53719"/>
<trkpt lat="47.213121" lon="-1.537007"/>
<trkpt lat="47.213037" lon="-1.536746"/>
<trkpt lat="47.212985" lon="-1.536673"/>
<trkpt lat="47.213028" lon="-1.536488"/>
<trkpt lat="47.213094" lon="-1.536477"/>
<trkpt lat="47.213187" lon="-1.534501"/>
<trkpt lat="47.21322" lon="-1.533176"/>
<trkpt lat="47.213208" lon="-1.532928"/>
<trkpt lat="47.213599" lon="-1.529777"/>
<trkpt lat="47.21352" lon="-1.529614"/>
<trkpt lat="47.213549" lon="-1.529374"/>
<trkpt lat="47.213601" lon="-1.52929"/>
<trkpt lat="47.213848" lon="-1.527269"/>
<trkpt lat="47.213891" lon="-1.527133"/>
<trkpt lat="47.214444" lon="-1.525283"/>
<trkpt lat="47.214487" lon="-1.525111"/>
<trkpt lat="47.215749" lon="-1.521155"/>
<trkpt lat="47.215802" lon="-1.521012"/>
<trkpt lat="47.216484" lon="-1.519088"/>
<trkpt lat="47.21657" lon="-1.518772"/>
<trkpt lat="47.216602" lon="-1.518664"/>
<trkpt lat="47.218048" lon="-1.515754"/>
<trkpt lat="47.218123" lon="-1.515623"/>
<trkpt lat="47.218259" lon="-1.515588"/>
<trkpt lat="47.218354" lon="-1.515663"/>
<trkpt lat="47.219157" lon="-1.516011"/>
<trkpt lat="47.219943" lon="-1.515468"/>
<trkpt lat="47.232161" lon="-1.485789"/>
<trkpt lat="47.232229" lon="-1.485681"/>
<trkpt lat="47.235333" lon="-1.482751"/>
<trkpt lat="47.2354" lon="-1.482516"/>
<trkpt lat="47.235546" lon="-1.482416"/>
<trkpt lat="47.237493" lon="-1.479355"/>
<trkpt lat="47.237476" lon="-1.479187"/>
<trkpt lat="47.237571" lon="-1.47894"/>
<trkpt lat="47.237679" lon="-1.478875"/>
<trkpt lat="47.237841" lon="-1.478929"/>
<trkpt lat="47.237865" lon="-1.478955"/>
<trkpt lat="47.23968" lon="-1.47769"/>
<trkpt lat="47.252684" lon="-1.477342"/>
<trkpt lat="47.255057" lon="-1.47844"/>
<trkpt lat="47.26823" lon="-1.479836"/>
<trkpt lat="47.270611" lon="-1.479649"/>
<trkpt lat="47.273086" lon="-1.479482"/>
<trkpt lat="47.287258" lon="-1.479302"/>
<trkpt lat="47.290543" lon="-1.476443"/>
<trkpt lat="47.291107" lon="-1.471735"/>
<trkpt lat="47.399341" lon="-1.200555"/>
<trkpt lat="47.39931" lon="-1.195302"/>
<trkpt lat="47.399266" lon="-1.192826"/>
<trkpt lat="47.399277" lon="-1.192706"/>
<trkpt lat="47.399304" lon="-1.192254"/>
<trkpt lat="47.399387" lon="-1.191168"/>
<trkpt lat="47.399341" lon="-1.190568"/>
<trkpt lat="47.399294" lon="-1.190479"/>
<trkpt lat="47.396962" lon="-1.190001"/>
<trkpt lat="47.395567" lon="-1.189791"/>
<trkpt lat="47.395442" lon="-1.189771"/>
<trkpt lat="47.394798" lon="-1.189592"/>
<trkpt lat="47.394688" lon="-1.189555"/>
<trkpt lat="47.391687" lon="-1.188792"/>
<trkpt lat="47.391615" lon="-1.18885"/>
<trkpt lat="47.391446" lon="-1.188684"/>
<trkpt lat="47.391328" lon="-1.188495"/>
<trkpt lat="47.390855" lon="-1.18821"/>
<trkpt lat="47.390459" lon="-1.188124"/>
<trkpt lat="47.390307" lon="-1.188215"/>
<trkpt lat="47.39019" lon="-1.18816"/>
<trkpt lat="47.390099" lon="-1.187937"/>
<trkpt lat="47.390099" lon="-1.18787"/>
<trkpt lat="47.386784" lon="-1.185549"/>
<trkpt lat="47.386387" lon="-1.185516"/>
<trkpt lat="47.386278" lon="-1.185596"/>
<trkpt lat="47.386194" lon="-1.185538"/>
<trkpt lat="47.386165" lon="-1.185462"/>
<trkpt lat="47.385743" lon="-1.185042"/>
<trkpt lat="47.383247" lon="-1.183994"/>
<trkpt lat="47.382652" lon="-1.183919"/>
<trkpt lat="47.382623" lon="-1.183975"/>
<trkpt lat="47.382522" lon="-1.184013"/>
<trkpt lat="47.382465" lon="-1.18397"/>
<trkpt lat="47.382435" lon="-1.18385"/>
<trkpt lat="47.37964" lon="-1.182634"/>
<trkpt lat="47.378879" lon="-1.182151"/>
<trkpt lat="47.377554" lon="-1.181295"/>
<trkpt lat="47.377402" lon="-1.181282"/>
<trkpt lat="47.377354" lon="-1.181357"/>
<trkpt lat="47.377255" lon="-1.181394"/>
<trkpt lat="47.377135" lon="-1.181101"/>
<trkpt lat="47.376946" lon="-1.180899"/>
<trkpt lat="47.375527" lon="-1.179969"/>
<trkpt lat="47.373767" lon="-1.178955"/>
<trkpt lat="47.373402" lon="-1.178983"/>
<trkpt lat="47.373302" lon="-1.179059"/>
<trkpt lat="47.373222" lon="-1.178978"/>
<trkpt lat="47.372174" lon="-1.178905"/>
<trkpt lat="47.372112" lon="-1.178948"/>
<trkpt lat="47.372092" lon="-1.178902"/>
<trkpt lat="47.371988" lon="-1.178904"/>
<trkpt lat="47.3704" lon="-1.178883"/>
<trkpt lat="47.370202" lon="-1.178866"/>
<trkpt lat="47.369961" lon="-1.178839"/>
<trkpt lat="47.369467" lon="-1.178797"/>
<trkpt lat="47.369026" lon="-1.178756"/>
<trkpt lat="47.368278" lon="-1.178584"/>
<trkpt lat="47.366732" lon="-1.178582"/>
<trkpt lat="47.3667" lon="-1.178583"/>
<trkpt lat="47.366514" lon="-1.178598"/>
<trkpt lat="47.365991" lon="-1.1786"/>
<trkpt lat="47.365403" lon="-1.178805"/>
<trkpt lat="47.364949" lon="-1.178912"/>
<trkpt lat="47.364547" lon="-1.178981"/>
<trkpt lat="47.364474" lon="-1.17934"/>
<trkpt lat="47.364424" lon="-1.17992"/>
<trkpt lat="47.364136" lon="-1.180728"/>
<trkpt lat="47.36416" lon="-1.180945"/>
<trkpt lat="47.364268" lon="-1.181819"/>
<trkpt lat="47.364763" lon="-1.18192"/>
</trkseg>
</trk>
<trk>
<name>
Ancenis, Châteaubriant-Ancenis, Loire-Atlantique, Pays de la Loire, France métropolitaine, 44150, France - Angers, Maine-et-Loire, Pays de la Loire, France métropolitaine, France
</name>
<desc>
57.0 km, 0:41
</desc>
<trkseg>
<trkpt lat="47.364763" lon="-1.18192"/>
<trkpt lat="47.364848" lon="-1.18196"/>
<trkpt lat="47.365275" lon="-1.182104"/>
<trkpt lat="47.365965" lon="-1.182286"/>
<trkpt lat="47.36612" lon="-1.18113"/>
<trkpt lat="47.366403" lon="-1.179826"/>
<trkpt lat="47.366732" lon="-1.178582"/>
<trkpt lat="47.368278" lon="-1.178584"/>
<trkpt lat="47.369026" lon="-1.178756"/>
<trkpt lat="47.369467" lon="-1.178797"/>
<trkpt lat="47.369961" lon="-1.178839"/>
<trkpt lat="47.370202" lon="-1.178866"/>
<trkpt lat="47.3704" lon="-1.178883"/>
<trkpt lat="47.371988" lon="-1.178904"/>
<trkpt lat="47.372092" lon="-1.178902"/>
<trkpt lat="47.37213" lon="-1.178835"/>
<trkpt lat="47.372174" lon="-1.178905"/>
<trkpt lat="47.373149" lon="-1.178876"/>
<trkpt lat="47.373211" lon="-1.178881"/>
<trkpt lat="47.373272" lon="-1.178766"/>
<trkpt lat="47.373372" lon="-1.17878"/>
<trkpt lat="47.373415" lon="-1.178882"/>
<trkpt lat="47.373767" lon="-1.178955"/>
<trkpt lat="47.375527" lon="-1.179969"/>
<trkpt lat="47.376946" lon="-1.180899"/>
<trkpt lat="47.377202" lon="-1.180991"/>
<trkpt lat="47.377317" lon="-1.180973"/>
<trkpt lat="47.377418" lon="-1.18115"/>
<trkpt lat="47.378879" lon="-1.182151"/>
<trkpt lat="47.37964" lon="-1.182634"/>
<trkpt lat="47.382469" lon="-1.183726"/>
<trkpt lat="47.38252" lon="-1.183687"/>
<trkpt lat="47.382627" lon="-1.183736"/>
<trkpt lat="47.382649" lon="-1.183773"/>
<trkpt lat="47.383247" lon="-1.183994"/>
<trkpt lat="47.385743" lon="-1.185042"/>
<trkpt lat="47.386187" lon="-1.185301"/>
<trkpt lat="47.386324" lon="-1.18524"/>
<trkpt lat="47.386395" lon="-1.185331"/>
<trkpt lat="47.386784" lon="-1.185549"/>
<trkpt lat="47.390184" lon="-1.18765"/>
<trkpt lat="47.390236" lon="-1.187609"/>
<trkpt lat="47.390418" lon="-1.187636"/>
<trkpt lat="47.390457" lon="-1.187677"/>
<trkpt lat="47.390521" lon="-1.187884"/>
<trkpt lat="47.390855" lon="-1.18821"/>
<trkpt lat="47.391328" lon="-1.188495"/>
<trkpt lat="47.391505" lon="-1.18847"/>
<trkpt lat="47.391608" lon="-1.188436"/>
<trkpt lat="47.391718" lon="-1.188566"/>
<trkpt lat="47.394176" lon="-1.18923"/>
<trkpt lat="47.394707" lon="-1.189419"/>
<trkpt lat="47.394816" lon="-1.189455"/>
<trkpt lat="47.396101" lon="-1.189755"/>
<trkpt lat="47.396578" lon="-1.189826"/>
<trkpt lat="47.398083" lon="-1.190038"/>
<trkpt lat="47.39932" lon="-1.190194"/>
<trkpt lat="47.399436" lon="-1.190105"/>
<trkpt lat="47.399616" lon="-1.190257"/>
<trkpt lat="47.399625" lon="-1.190297"/>
<trkpt lat="47.399598" lon="-1.190518"/>
<trkpt lat="47.399572" lon="-1.190561"/>
<trkpt lat="47.399396" lon="-1.192017"/>
<trkpt lat="47.399366" lon="-1.192722"/>
<trkpt lat="47.399355" lon="-1.192841"/>
<trkpt lat="47.39927" lon="-1.194329"/>
<trkpt lat="47.399938" lon="-1.194827"/>
<trkpt lat="47.401803" lon="-1.191068"/>
<trkpt lat="47.434482" lon="-0.816724"/>
<trkpt lat="47.433855" lon="-0.809441"/>
<trkpt lat="47.46242" lon="-0.695047"/>
<trkpt lat="47.467222" lon="-0.677305"/>
<trkpt lat="47.466628" lon="-0.674247"/>
<trkpt lat="47.462466" lon="-0.64317"/>
<trkpt lat="47.4658" lon="-0.631566"/>
<trkpt lat="47.466724" lon="-0.628554"/>
<trkpt lat="47.46786" lon="-0.624756"/>
<trkpt lat="47.469056" lon="-0.620722"/>
<trkpt lat="47.470005" lon="-0.617478"/>
<trkpt lat="47.469578" lon="-0.612166"/>
<trkpt lat="47.468996" lon="-0.605593"/>
<trkpt lat="47.469353" lon="-0.597749"/>
<trkpt lat="47.469475" lon="-0.594958"/>
<trkpt lat="47.468084" lon="-0.585639"/>
<trkpt lat="47.464676" lon="-0.574428"/>
<trkpt lat="47.465802" lon="-0.568425"/>
<trkpt lat="47.46848" lon="-0.564903"/>
<trkpt lat="47.469906" lon="-0.562375"/>
<trkpt lat="47.470104" lon="-0.561996"/>
<trkpt lat="47.471074" lon="-0.560347"/>
<trkpt lat="47.471585" lon="-0.559436"/>
<trkpt lat="47.472442" lon="-0.558075"/>
<trkpt lat="47.474047" lon="-0.556018"/>
<trkpt lat="47.475157" lon="-0.555216"/>
<trkpt lat="47.476197" lon="-0.554405"/>
<trkpt lat="47.476813" lon="-0.553653"/>
<trkpt lat="47.476842" lon="-0.553634"/>
<trkpt lat="47.477151" lon="-0.553118"/>
<trkpt lat="47.476517" lon="-0.551532"/>
<trkpt lat="47.476224" lon="-0.550865"/>
<trkpt lat="47.47594" lon="-0.550269"/>
<trkpt lat="47.475462" lon="-0.549161"/>
<trkpt lat="47.475096" lon="-0.549804"/>
<trkpt lat="47.475076" lon="-0.549856"/>
<trkpt lat="47.474792" lon="-0.550535"/>
<trkpt lat="47.474586" lon="-0.551368"/>
<trkpt lat="47.474561" lon="-0.55151"/>
<trkpt lat="47.474384" lon="-0.552383"/>
<trkpt lat="47.473949" lon="-0.551608"/>
</trkseg>
</trk>
</gpx>
Ce contenu xml peut ensuite être chargé dans n'importe quelle application ou site internet utilisant ce genre de fichier.
Pouvoir générer la liste de tous les jours ouvrés d'un mois, d'un trimestre, d'un semestre ou d'une année et en excluant les jours fériés existants.
Pour cela je vais également utiliser la classe JoursFeries créée dans un précédent article.
from dateutil.parser import parse
from dateutil.rrule import rrule, DAILY
from dateutil.relativedelta import relativedelta
from dateutil.relativedelta import MO, TU, WE, TH, FR
dtstart = parse('2022-01-01')
list_jours_ouvres = list(
map(
lambda x: x.date(),
rrule(DAILY, dtstart=dtstart, until=dtstart + relativedelta(months=4, day=1, days=-1),
byweekday=[MO, TU, WE, TH, FR])
)
)
Je vais donc générer une liste de tous les jours ouvrés, du lundi au vendredi, du 1er janvier au 30 avril (4 mois).
>>> list_jours_ouvres
[datetime.date(2022, 1, 3),
datetime.date(2022, 1, 4),
datetime.date(2022, 1, 5),
datetime.date(2022, 1, 6),
datetime.date(2022, 1, 7),
datetime.date(2022, 1, 10),
datetime.date(2022, 1, 11),
datetime.date(2022, 1, 12),
datetime.date(2022, 1, 13),
...
datetime.date(2022, 4, 18),
datetime.date(2022, 4, 19),
datetime.date(2022, 4, 20),
datetime.date(2022, 4, 21),
datetime.date(2022, 4, 22),
datetime.date(2022, 4, 25),
datetime.date(2022, 4, 26),
datetime.date(2022, 4, 27),
datetime.date(2022, 4, 28),
datetime.date(2022, 4, 29)]
>>> len(list_jours_ouvres)
85
J'ai donc une liste comprenant 85 jours ouvrés du lundi au vendredi du 3 janvier au 29 avril.
Pour supprimer les jours fériés de la liste
>>> set(list_jours_ouvres) - set(JoursFeries(dtstart.year).to_list())
{datetime.date(2022, 1, 3),
datetime.date(2022, 1, 4),
datetime.date(2022, 1, 5),
datetime.date(2022, 1, 6),
datetime.date(2022, 1, 7),
datetime.date(2022, 1, 10),
datetime.date(2022, 1, 11),
datetime.date(2022, 1, 12),
datetime.date(2022, 1, 13),
datetime.date(2022, 1, 14),
datetime.date(2022, 1, 17),
datetime.date(2022, 1, 18),
datetime.date(2022, 1, 19),
datetime.date(2022, 1, 20),
datetime.date(2022, 1, 21),
datetime.date(2022, 1, 24),
...
datetime.date(2022, 4, 13),
datetime.date(2022, 4, 14),
datetime.date(2022, 4, 15),
datetime.date(2022, 4, 19),
datetime.date(2022, 4, 20),
datetime.date(2022, 4, 21),
datetime.date(2022, 4, 22),
datetime.date(2022, 4, 25),
datetime.date(2022, 4, 26),
datetime.date(2022, 4, 27),
datetime.date(2022, 4, 28),
datetime.date(2022, 4, 29)}
>>> len(set(list_jours_ouvres) - set(JoursFeries(dtstart.year).to_list()))
84
Résultat, 1 jour a été supprimé de la liste.
Il s'agit du lundi de Pâques le 18 avril 2022.
>>> for x in JoursFeries(dtstart.year).proprietes:
print(f"{x:<20} {getattr(JoursFeries(dtstart.year), x).strftime('%a %d %b %Y')}")
JOUR_DE_L_AN Sat 01 Jan 2022
PAQUES Sun 17 Apr 2022
LUNDI_DE_PAQUES Mon 18 Apr 2022
FETE_DU_TRAVAIL Sun 01 May 2022
VICTOIRE_1945 Sun 08 May 2022
ASCENSION Thu 26 May 2022
PENTECOTE Sun 05 Jun 2022
LUNDI_DE_PENTECOTE Mon 06 Jun 2022
FETE_NATIONALE Thu 14 Jul 2022
ASSOMPTION Mon 15 Aug 2022
TOUSSAINT Tue 01 Nov 2022
ARMISTICE_1918 Fri 11 Nov 2022
NOEL Sun 25 Dec 2022
Voici un petit script Python qui me permet d'afficher à l'écran n'importe quel contenu JSON correctement indenté (afin de faciliter la lisibilité).
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import json, sys
from pprint import pprint
if not sys.stdin.isatty():
JSON = json.loads(sys.stdin.read())
pprint(JSON)
else:
print("Use this script with a pipe command.")
print("")
print("echo '...' | python3 prettyJson.py")
print("or")
print("cat ... | python3 prettyJson.py")
Par exemple, avec un flux JSON non formaté:
# cat flux.json
{"time": "2016-06-29 15:54","countryName": "Austria","sunset": "2016-06-29 21:18","rawOffset": 1,"dstOffset": 2,"countryCode": "AT","gmtOffset": 1,"lng": 10.2,"sunrise": "2016-06-29 05:27","timezoneId": "Europe/Vienna","lat": 47.01}
Une fois traité par le script:
# cat flux.json | python3 prettyJson.py
{'countryCode': 'AT',
'countryName': 'Austria',
'dstOffset': 2,
'gmtOffset': 1,
'lat': 47.01,
'lng': 10.2,
'rawOffset': 1,
'sunrise': '2016-06-29 05:27',
'sunset': '2016-06-29 21:18',
'time': '2016-06-29 15:54',
'timezoneId': 'Europe/Vienna'}
Fonctionne également avec la commande CURL:
# curl "http://api.geonames.org/citiesJSON?formatted=true&north=44.1&south=-9.9&east=-22.4&west=55.2&lang=de&username=demo&style=full" -o - -s | python3 prettyJson.py
{'geonames': [{'countrycode': 'MX',
'fcl': 'P',
'fclName': 'city, village,...',
'fcode': 'PPLC',
'fcodeName': 'capital of a political entity',
'geonameId': 3530597,
'lat': 19.428472427036,
'lng': -99.12766456604,
'name': 'Mexiko-Stadt',
'population': 12294193,
'toponymName': 'Mexico City',
'wikipedia': 'en.wikipedia.org/wiki/Mexico_City'},
{'countrycode': 'CN',
'fcl': 'P',
'fclName': 'city, village,...',
'fcode': 'PPLC',
'fcodeName': 'capital of a political entity',
'geonameId': 1816670,
'lat': 39.9074977414405,
'lng': 116.397228240967,
'name': 'Peking',
'population': 11716620,
'toponymName': 'Beijing',
'wikipedia': 'en.wikipedia.org/wiki/Beijing'}]}
Voici une petite fonction permettant d'initialiser une liste en saisissant les valeurs à la volée à l'aide de la fonction input.
Certe inutile pour une courte liste [1,2,3,4], mais vraiment très pratique pour une longue liste et contenant surtout des données alpha-numériques (évite de saisir les quotes entourant la chaine de texte)
Compatible Python2 et Python3
>>> def initializeList():
l = []
# Pour la compatibilité Python2 et Python3
_input = input
if hasattr(__builtins__, 'raw_input'):
_input = raw_input
# Boucle infinie pour remplir la liste
while True:
d = _input("--> ").strip()
if d == '':
# On quitte si la valeur est vide
break
else:
# On tente une conversion en int
try:
l.append(int(d))
except ValueError:
# Si erreur, on tente une conversion en float
try:
l.append(float(d))
except ValueError:
# Sinon, on laisse la valeur au format chaine de texte
l.append(d)
# On retourne la liste
return l
>>> myList = initializeList()
--> 1
--> 2
--> 3
--> 4
--> 5
--> 6
--> a
--> z
--> er
--> r
--> t
--> y
--> fg
--> d
-->
>>> print(myList)
[1, 2, 3, 4, 5, 6, 'a', 'z', 'er', 'r', 't', 'y', 'fg', 'd']
>>>
Ca peut toujours servir
Pour l'exemple, je vais utiliser un base de donnée sqlite3 en mémoire.
>>> import sqlite3
>>> from pathlib import Path
>>> con = sqlite3.connect(':memory:')
>>> cur = con.cursor()
>>> cur.execute('create table datas (ID INTEGER primary key, IMAGE BLOB)')
>>> p = Path(r'C:\Users\toto\Pictures')
>>> f = p / 'toto.png'
>>> cur.execute("insert into datas (IMAGE) values (?)", (sqlite3.Binary(f.read_bytes()), ))
>>> con.commit()
Python fournit tout un tas de modules complémentaires pour tous les domaines mêmes les plus improbables.
PIL est un module Python qui permet de travailler sur tous les types d'images.
L'astuce suivante permet d'afficher l'image avec le contraste inversé en seulement quatre lignes de script Python.
Tous les tests sont réalisés avec cette image:
>>> from PIL import Image
>>> img = Image.open('firefox_i9MPoqCoYe_2.png')
>>> arr = img.split()
>>> arr[-1].show()
Le résultat:
Comment ça marche ?
En règle générale le format PNG contient 4 canaux, les 3 canaux principaux "RGB" et un canal supplémentaire "A" pour la translucidité.
Grâce à la méthode split, on récupère un tuple contenant tous les canaux séparément.
>>> print(arr)
(<PIL.Image.Image image mode=L size=351x330 at 0x789953440550>, <PIL.Image.Image image mode=L size=351x330 at 0x7899545902E0>, <PIL.Image.Image image mode=L size=351x330 at 0x789954A7F4C0>, <PIL.Image.Image image mode=L size=351x330 at 0x789954A7FEB0>)
La commande précédente montre les canaux disponibles, 4 au total.
Si on affiche le contenu des canaux grâce au module numpy
>>> import numpy as np
>>> np.array(img)
array([[[255, 255, 255, 0],
[255, 255, 255, 0],
[255, 255, 255, 0],
...,
[255, 255, 255, 0],
[255, 255, 255, 0],
[255, 255, 255, 0]],
[[255, 255, 255, 0],
[255, 255, 255, 0],
[255, 255, 255, 0],
...,
[255, 255, 255, 0],
[255, 255, 255, 0],
[255, 255, 255, 0]],
[[255, 255, 255, 0],
[255, 255, 255, 0],
[255, 255, 255, 0],
...,
[255, 255, 255, 0],
[255, 255, 255, 0],
[255, 255, 255, 0]],
...,
[[255, 255, 255, 0],
[255, 255, 255, 0],
[255, 255, 255, 0],
...,
[255, 255, 255, 0],
[255, 255, 255, 0],
[255, 255, 255, 0]],
[[255, 255, 255, 0],
[255, 255, 255, 0],
[255, 255, 255, 0],
...,
[255, 255, 255, 0],
[255, 255, 255, 0],
[255, 255, 255, 0]],
[[255, 255, 255, 0],
[255, 255, 255, 0],
[255, 255, 255, 0],
...,
[255, 255, 255, 0],
[255, 255, 255, 0],
[255, 255, 255, 0]]], dtype=uint8)
La différence entre les 3 premiers canaux "RGB" et le quatrième "A" est flagrante.
Si toutefois l'image ne contient pas de canal "A", il est possible d'obtenir le même résultat en agissant sur le troisième canal, le "B".
>>> arr[2].point(lambda x: (256-x)**2).show()
Voici une classe que j'ai développé afin de gérer efficacement les jours fériés français.
Elle pourra être utilisée ultérieurement dans différentes applications.
Voici le code:
from enum import IntEnum
from datetime import datetime, date
from dateutil.easter import easter, EASTER_WESTERN
from dateutil.relativedelta import relativedelta
import json
from collections import namedtuple
import serpy
class Mois(IntEnum):
JANVIER = 1
FEVRIER = 2
MARS = 3
AVRIL = 4
MAI = 5
JUIN = 6
JUILLET = 7
AOUT = 8
SEPTEMBRE = 9
OCTOBRE = 10
NOVEMBRE = 11
DECEMBRE = 12
class JoursFeries(object):
def __init__(self, annee: int = datetime.now().year):
self.CURRENT_YEAR = int(annee)
@property
def CURRENT_YEAR(self) -> int:
return self._annee
@CURRENT_YEAR.setter
def CURRENT_YEAR(self, value: int) -> None:
self._annee = value
@property
def JOUR_DE_L_AN(self) -> date:
return date(self.CURRENT_YEAR, Mois.JANVIER, 1)
@property
def PAQUES(self) -> date:
return easter(self.CURRENT_YEAR, method=EASTER_WESTERN)
@property
def LUNDI_DE_PAQUES(self) -> date:
return self.PAQUES + relativedelta(days=1)
@property
def FETE_DU_TRAVAIL(self) -> date:
return date(self.CURRENT_YEAR, Mois.MAI, 1)
@property
def VICTOIRE_1945(self) -> date:
return date(self.CURRENT_YEAR, Mois.MAI, 8)
@property
def ASCENSION(self) -> date:
return self.PAQUES + relativedelta(days=39)
@property
def PENTECOTE(self) -> date:
return self.PAQUES + relativedelta(days=49)
@property
def LUNDI_DE_PENTECOTE(self) -> date:
return self.PENTECOTE + relativedelta(days=1)
@property
def FETE_NATIONALE(self) -> date:
return date(self.CURRENT_YEAR, Mois.JUILLET, 14)
@property
def ASSOMPTION(self) -> date:
return date(self.CURRENT_YEAR, Mois.AOUT, 15)
@property
def TOUSSAINT(self) -> date:
return date(self.CURRENT_YEAR, Mois.NOVEMBRE, 1)
@property
def ARMISTICE_1918(self) -> date:
return date(self.CURRENT_YEAR, Mois.NOVEMBRE, 11)
@property
def NOEL(self) -> date:
return date(self.CURRENT_YEAR, Mois.DECEMBRE, 25)
@property
def proprietes(self) -> list:
return list(self.dumps().keys())
def to_list(self) -> list:
return [getattr(self, x) for x in self.dumps().keys()]
def __str__(self) -> str:
return '\n'.join([f'{x:<20s}: {getattr(self, x)}' for x in self.dumps().keys()])
def __repr__(self) ->str:
return json.dumps(self.dumps(), indent=4, ensure_ascii=False)
def dumps(self) -> dict:
return JoursFeriesSerialize(self).data
def to_namedtuple(self) -> namedtuple:
return namedtuple('JourFeries', self.dumps().keys())(**self.dumps())
class JoursFeriesSerialize(serpy.Serializer):
JOUR_DE_L_AN = serpy.StrField()
PAQUES = serpy.StrField()
LUNDI_DE_PAQUES = serpy.StrField()
FETE_DU_TRAVAIL = serpy.StrField()
VICTOIRE_1945 = serpy.StrField()
ASCENSION = serpy.StrField()
PENTECOTE = serpy.StrField()
LUNDI_DE_PENTECOTE = serpy.StrField()
FETE_NATIONALE = serpy.StrField()
ASSOMPTION = serpy.StrField()
TOUSSAINT = serpy.StrField()
ARMISTICE_1918 = serpy.StrField()
NOEL = serpy.StrField()
La classe Mois énumère tous les mois de l'année grâce au package enum.IntEnum.
La classe JoursFeriesSerialize permet de sérialiser les données de la classe JoursFeries grâce au package serpy.
Pour finir, la classe JoursFeries qui permet de générer tous les jours fériés de l'année en cours ou de celle passée en paramètre au constructeur de la classe.
Le jour férié correspondant à Pâques est récupéré grâce au package dateutil.easter.easter et tous les jours fériés ayant un rapport avec Pâques sont générés grâce au package dateutil.relativedelta.relativedelta.
Pour l'utiliser, rien de plus simple:
>>> jf = JoursFeries()
>>> jf
{
"JOUR_DE_L_AN": "2022-01-01",
"PAQUES": "2022-04-17",
"LUNDI_DE_PAQUES": "2022-04-18",
"FETE_DU_TRAVAIL": "2022-05-01",
"VICTOIRE_1945": "2022-05-08",
"ASCENSION": "2022-05-26",
"PENTECOTE": "2022-06-05",
"LUNDI_DE_PENTECOTE": "2022-06-06",
"FETE_NATIONALE": "2022-07-14",
"ASSOMPTION": "2022-08-15",
"TOUSSAINT": "2022-11-01",
"ARMISTICE_1918": "2022-11-11",
"NOEL": "2022-12-25"
}
Sans paramètre, la classe retourne tous les jours fériés de l'année en cours.
La méthode __repr__ permet d'afficher les jours fériés au format json (str).
Avec l'année passée en paramètre:
>>> jf = JoursFeries(2024)
>>> jf
{
"JOUR_DE_L_AN": "2024-01-01",
"PAQUES": "2024-03-31",
"LUNDI_DE_PAQUES": "2024-04-01",
"FETE_DU_TRAVAIL": "2024-05-01",
"VICTOIRE_1945": "2024-05-08",
"ASCENSION": "2024-05-09",
"PENTECOTE": "2024-05-19",
"LUNDI_DE_PENTECOTE": "2024-05-20",
"FETE_NATIONALE": "2024-07-14",
"ASSOMPTION": "2024-08-15",
"TOUSSAINT": "2024-11-01",
"ARMISTICE_1918": "2024-11-11",
"NOEL": "2024-12-25"
}
La méthode to_list retourne une liste contenant tous les jours fériés au format datetime.date
>>> jf.to_list()
[datetime.date(2022, 1, 1),
datetime.date(2022, 4, 17),
datetime.date(2022, 4, 18),
datetime.date(2022, 5, 1),
datetime.date(2022, 5, 8),
datetime.date(2022, 5, 26),
datetime.date(2022, 6, 5),
datetime.date(2022, 6, 6),
datetime.date(2022, 7, 14),
datetime.date(2022, 8, 15),
datetime.date(2022, 11, 1),
datetime.date(2022, 11, 11),
datetime.date(2022, 12, 25)]
Un print de l'objet:
>>> print(jf)
JOUR_DE_L_AN : 2022-01-01
PAQUES : 2022-04-17
LUNDI_DE_PAQUES : 2022-04-18
FETE_DU_TRAVAIL : 2022-05-01
VICTOIRE_1945 : 2022-05-08
ASCENSION : 2022-05-26
PENTECOTE : 2022-06-05
LUNDI_DE_PENTECOTE : 2022-06-06
FETE_NATIONALE : 2022-07-14
ASSOMPTION : 2022-08-15
TOUSSAINT : 2022-11-01
ARMISTICE_1918 : 2022-11-11
NOEL : 2022-12-25
La méthode dumps retourne une représentation json (dict)
>>> jf.dumps()
{'JOUR_DE_L_AN': '2022-01-01',
'PAQUES': '2022-04-17',
'LUNDI_DE_PAQUES': '2022-04-18',
'FETE_DU_TRAVAIL': '2022-05-01',
'VICTOIRE_1945': '2022-05-08',
'ASCENSION': '2022-05-26',
'PENTECOTE': '2022-06-05',
'LUNDI_DE_PENTECOTE': '2022-06-06',
'FETE_NATIONALE': '2022-07-14',
'ASSOMPTION': '2022-08-15',
'TOUSSAINT': '2022-11-01',
'ARMISTICE_1918': '2022-11-11',
'NOEL': '2022-12-25'}
La méthode to_namedtuple retourne un objet collections.namedtuple
>>> jf.to_namedtuple()
JourFeries(JOUR_DE_L_AN='2022-01-01', PAQUES='2022-04-17', LUNDI_DE_PAQUES='2022-04-18', FETE_DU_TRAVAIL='2022-05-01', VICTOIRE_1945='2022-05-08', ASCENSION='2022-05-26', PENTECOTE='2022-06-05', LUNDI_DE_PENTECOTE='2022-06-06', FETE_NATIONALE='2022-07-14', ASSOMPTION='2022-08-15', TOUSSAINT='2022-11-01', ARMISTICE_1918='2022-11-11', NOEL='2022-12-25')
Pour afficher le jour férié correspondant à Pâques
>>> jf.PAQUES
datetime.date(2022, 4, 17)
>>> jf.PAQUES.year
2022
>>> jf.PAQUES.day
17
>>> jf.PAQUES.month
4
>>> jf.PAQUES.isocalendar()
(2022, 15, 7) # le 7ème jour de la semaine 15 de l'année 2022
>>> jf.PAQUES.isoformat()
2022-04-17
>>> jf.PAQUES.timetuple()
time.struct_time(tm_year=2022, tm_mon=4, tm_mday=17, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=6, tm_yday=107, tm_isdst=-1)
Tous les jours fériés disposent des mêmes fonctions.
Comme les jours fériés sont au format datetime.date, toutes les méthodes de l'objet datetime.date sont également accessibles.
La méthode proprietes affiche la liste des noms de tous les jours fériés
>>> jf.proprietes
['JOUR_DE_L_AN',
'PAQUES',
'LUNDI_DE_PAQUES',
'FETE_DU_TRAVAIL',
'VICTOIRE_1945',
'ASCENSION',
'PENTECOTE',
'LUNDI_DE_PENTECOTE',
'FETE_NATIONALE',
'ASSOMPTION',
'TOUSSAINT',
'ARMISTICE_1918',
'NOEL']
Ce qui permet de faire ceci
>>> for nom in jf.proprietes: ... print(f"{nom:<20} {getattr(JoursFeries(dtstart.year), nom).strftime('%a %d %B %Y')}")
JOUR_DE_L_AN Sat 01 Jan 2022 PAQUES Sun 17 Apr 2022 LUNDI_DE_PAQUES Mon 18 Apr 2022 FETE_DU_TRAVAIL Sun 01 May 2022 VICTOIRE_1945 Sun 08 May 2022 ASCENSION Thu 26 May 2022 PENTECOTE Sun 05 Jun 2022 LUNDI_DE_PENTECOTE Mon 06 Jun 2022 FETE_NATIONALE Thu 14 Jul 2022 ASSOMPTION Mon 15 Aug 2022 TOUSSAINT Tue 01 Nov 2022 ARMISTICE_1918 Fri 11 Nov 2022 NOEL Sun 25 Dec 2022
Vraiment super pratique.
Serpy est un package Python qui permet sérialiser en json n'importe quel objet.
Serpy permet d'implémenter facilement et rapidement les méthodes __str__ et __repr__ d'une classe.
Il permet également d'implémenter une méthode dumps.
Exemple avec la classe suivante:
import serpy
import json
from decimal import Decimal
class Personne(object):
def __init__(self, firstname: str, lastname: str, sexe: str, age: int, salaire: float, position: dict):
self.firstname = firstname
self.lastname = lastname
self.sexe = sexe
self.age = age
self.salaire = salaire
self.position = position
Cette classe permet de créer un objet Personne.
L'argument position est un dictionnaire dans lequel on renseigne une latitude et une longitude. Il devra contenir les 2 clés 'latitude' et 'longitude'.
Je vais maintenant créer deux nouvelles classes, qui vont permettre de sérialiser mon objet Personne et mon sous-objet Position.
class PositionSerializer(serpy.DictSerializer):
latitude = serpy.FloatField()
longitude = serpy.FloatField()
class PersonneSerializer(serpy.Serializer):
firstname = serpy.StrField()
lastname = serpy.StrField()
sexe = serpy.MethodField()
age = serpy.IntField()
salaire = serpy.FloatField()
position = PositionSerializer()
def get_sexe(self, obj):
if obj.sexe == 'M':
return 'Homme'
elif obj.sexe == 'F':
return 'Femme'
else:
return 'N/A'
La classe PersonneSerializer étend la classe serpy.Serializer.
Elle reprend les différents arguments de la classe Personne en indiquant pour chaque argument son type Serpy (StrField, IntField, FloatField).
Le type particulier MethodField permet d'associer une méthode à l'argument afin d'effectuer des calculs spécifiques. La méthode doit être nommée comme le nom de l'argument précédé du terme 'get_'
Il est également possible d'associer à l'argument une classe qui permet de sérialiser le contenu de l'objet. Dans mon exemple, l'argument position est une dictionnaire qui contient 2 données décimales. La classe PositionSerializer étend la classe serpy.DictSerializer et reprend les 2 clés de mon objet dict position.
Je vais maintenant modifier ma classe Personne afin d'y ajouter les 3 méthodes __str__, __repr__ et dumps.
class Personne(object):
def __init__(self, firstname: str, lastname: str, sexe: str, age: int, salaire: float, position: dict):
self.firstname = firstname
self.lastname = lastname
self.sexe = sexe
self.age = age
self.salaire = salaire
self.position = position
def __str__(self):
return json.dumps(PersonneSerializer(self).data, indent=4)
def __repr__(self):
return json.dumps(PersonneSerializer(self).data)
def dumps(self):
return PersonneSerializer(self).data
La méthode __str__ retourne mon objet sérialisé au format json via la classe PersonneSerializer.
La méthode __repr__ retourne également mon objet sérialisé au format json via la classe PersonneSerializer mais sans mise en forme.
La méthode dumps retourne mon objet sérialisé via la classe PersonneSerializer.
Et voici le résultat:
personne1 = Personne(firstname='Matthieu', lastname='Lopes', sexe='M', age=43, salaire=1509.56,
position={'latitude': Decimal('-55.5232795'), 'longitude': Decimal('157.460948')})
print(f'{" Personne 1 ":-^30}')
print(repr(personne1))
print(personne1)
dumps = personne1.dumps()
print(type(dumps))
print(dumps)
personne2 = Personne(firstname='Constance', lastname='Lopez', sexe='F', age=87, salaire=2056.35,
position={'latitude': Decimal('-56.710608'), 'longitude': Decimal('-154.540259')})
print('')
print(f'{" Personne 2 ":-^30}')
print(repr(personne2))
print(personne2)
dumps = personne2.dumps()
print(type(dumps))
print(dumps)
--------- Personne 1 ---------
{"firstname": "Matthieu", "lastname": "Lopes", "sexe": "Homme", "age": 43, "salaire": 1509.56, "position": {"latitude": -55.5232795, "longitude": 157.460948}}
{
"firstname": "Matthieu",
"lastname": "Lopes",
"sexe": "Homme",
"age": 43,
"salaire": 1509.56,
"position": {
"latitude": -55.5232795,
"longitude": 157.460948
}
}
<class 'dict'>
{'firstname': 'Matthieu', 'lastname': 'Lopes', 'sexe': 'Homme', 'age': 43, 'salaire': 1509.56, 'position': {'latitude': -55.5232795, 'longitude': 157.460948}}
--------- Personne 2 ---------
{"firstname": "Constance", "lastname": "Lopez", "sexe": "Femme", "age": 87, "salaire": 2056.35, "position": {"latitude": -56.710608, "longitude": -154.540259}}
{
"firstname": "Constance",
"lastname": "Lopez",
"sexe": "Femme",
"age": 87,
"salaire": 2056.35,
"position": {
"latitude": -56.710608,
"longitude": -154.540259
}
}
<class 'dict'>
{'firstname': 'Constance', 'lastname': 'Lopez', 'sexe': 'Femme', 'age': 87, 'salaire': 2056.35, 'position': {'latitude': -56.710608, 'longitude': -154.540259}}
Le résultat est parfait.
La compréhension de listes et de dictionnaires en Python est une méthode très puissante pour transposer un texte structuré en liste, dictionnaire etc etc.
Nous allons utiliser la même chaine de texte que pour la compréhension de liste.
>>> s = """AIN 1 01 00
AISNE 1 02 00
ALLIER 1 03 00
ALPES(BASSES-) 1 04 00
ALPES(HAUTES-) 1 05 00
ALPES-MARITIMES 1 06 00
ARDECHE 1 07 00
ARDENNES 1 08 00
ARIEGE 1 09 00
AUBE 1 10 00
AUDE 1 11 00
AVEYRON 1 12 00
BOUCHES-DU-RHONE 1 13 00
CALVADOS 1 14 00
CANTAL 1 15 00
CHARENTE 1 16 00
CHARENTE-INFERIEURE 1 17 00
CHER 1 18 00
CORREZE 1 19 00
CORSE 1 20 00
COTE-D'OR 1 21 00
COTES-DU-NORD 1 22 00
CREUSE 1 23 00
"""
Nous allons initialiser un dictionnaire, ayant en clé le nom du département (par exemple), et les données numériques dans une liste correspondante à la valeur de la clé.
>>> d = {x.split('\t')[0]: x.split('\t')[1:] for x in s.split('\n')}
>>> pprint(d)
{'': [],
'AIN': ['1', '01', '00'],
'AISNE': ['1', '02', '00'],
'ALLIER': ['1', '03', '00'],
'ALPES(BASSES-)': ['1', '04', '00'],
'ALPES(HAUTES-)': ['1', '05', '00'],
'ALPES-MARITIMES': ['1', '06', '00'],
'ARDECHE': ['1', '07', '00'],
'ARDENNES': ['1', '08', '00'],
'ARIEGE': ['1', '09', '00'],
'AUBE': ['1', '10', '00'],
'AUDE': ['1', '11', '00'],
'AVEYRON': ['1', '12', '00'],
'BOUCHES-DU-RHONE': ['1', '13', '00'],
'CALVADOS': ['1', '14', '00'],
'CANTAL': ['1', '15', '00'],
'CHARENTE': ['1', '16', '00'],
'CHARENTE-INFERIEURE': ['1', '17', '00'],
'CHER': ['1', '18', '00'],
'CORREZE': ['1', '19', '00'],
'CORSE': ['1', '20', '00'],
"COTE-D'OR": ['1', '21', '00'],
'COTES-DU-NORD': ['1', '22', '00'],
'CREUSE': ['1', '23', '00']}
Par contre, notre dictionnaire contient une ligne vide (la première), nous allons donc en tenir compte lors de la création de notre dictionnaire.
>>> d = {x.split('\t')[0]: x.split('\t')[1:] for x in s.split('\n') if x.split('\t')[0] != ''}
>>> pprint(d)
{'AIN': ['1', '01', '00'],
'AISNE': ['1', '02', '00'],
'ALLIER': ['1', '03', '00'],
'ALPES(BASSES-)': ['1', '04', '00'],
'ALPES(HAUTES-)': ['1', '05', '00'],
'ALPES-MARITIMES': ['1', '06', '00'],
'ARDECHE': ['1', '07', '00'],
'ARDENNES': ['1', '08', '00'],
'ARIEGE': ['1', '09', '00'],
'AUBE': ['1', '10', '00'],
'AUDE': ['1', '11', '00'],
'AVEYRON': ['1', '12', '00'],
'BOUCHES-DU-RHONE': ['1', '13', '00'],
'CALVADOS': ['1', '14', '00'],
'CANTAL': ['1', '15', '00'],
'CHARENTE': ['1', '16', '00'],
'CHARENTE-INFERIEURE': ['1', '17', '00'],
'CHER': ['1', '18', '00'],
'CORREZE': ['1', '19', '00'],
'CORSE': ['1', '20', '00'],
"COTE-D'OR": ['1', '21', '00'],
'COTES-DU-NORD': ['1', '22', '00'],
'CREUSE': ['1', '23', '00']}
Et voilà, tout est nickel. Et tout ça à partir d'une simple chaine de texte.
Pour obtenir les données relatives au département de la Charente:
>>> d['CHARENTE']
['1', '16', '00']
Par exemple, avec la chaine de texte suivante qui contient des noms de départements et quelques données numériques:
>>> s = """AIN 1 01 00
AISNE 1 02 00
ALLIER 1 03 00
ALPES(BASSES-) 1 04 00
ALPES(HAUTES-) 1 05 00
ALPES-MARITIMES 1 06 00
ARDECHE 1 07 00
ARDENNES 1 08 00
ARIEGE 1 09 00
AUBE 1 10 00
AUDE 1 11 00
AVEYRON 1 12 00
BOUCHES-DU-RHONE 1 13 00
CALVADOS 1 14 00
CANTAL 1 15 00
CHARENTE 1 16 00
CHARENTE-INFERIEURE 1 17 00
CHER 1 18 00
CORREZE 1 19 00
CORSE 1 20 00
COTE-D'OR 1 21 00
COTES-DU-NORD 1 22 00
CREUSE 1 23 00
"""
Quand on l'affiche dans une console Python:
>>> s.__str__()
"AIN\t1\t01\t00\nAISNE\t1\t02\t00\nALLIER\t1\t03\t00\nALPES(BASSES-)\t1\t04\t00\nALPES(HAUTES-)\t1\t05\t00\nALPES-MARITIMES\t1\t06\t00\nARDECHE\t1\t07\t00\nARDENNES\t1\t08\t00\nARIEGE\t1\t09\t00\nAUBE\t1\t10\t00\nAUDE\t1\t11\t00\nAVEYRON\t1\t12\t00\nBOUCHES-DU-RHONE\t1\t13\t00\nCALVADOS\t1\t14\t00\nCANTAL\t1\t15\t00\nCHARENTE\t1\t16\t00\nCHARENTE-INFERIEURE\t1\t17\t00\nCHER\t1\t18\t00\nCORREZE\t1\t19\t00\nCORSE\t1\t20\t00\nCOTE-D'OR\t1\t21\t00\nCOTES-DU-NORD\t1\t22\t00\nCREUSE\t1\t23\t00\n"
On s'aperçoit que la chaine est composée de lignes séparées par '\n' et des colonnes séparées par '\t'.
Pour convertir cette chaine de texte en une liste simple il suffit de faire:
>>> l = s.split('\n')
>>> print(l)
['AIN\t1\t01\t00', 'AISNE\t1\t02\t00', 'ALLIER\t1\t03\t00', 'ALPES(BASSES-)\t1\t04\t00', 'ALPES(HAUTES-)\t1\t05\t00', 'ALPES-MARITIMES\t1\t06\t00', 'ARDECHE\t1\t07\t00', 'ARDENNES\t1\t08\t00', 'ARIEGE\t1\t09\t00', 'AUBE\t1\t10\t00', 'AUDE\t1\t11\t00', 'AVEYRON\t1\t12\t00', 'BOUCHES-DU-RHONE\t1\t13\t00', 'CALVADOS\t1\t14\t00', 'CANTAL\t1\t15\t00', 'CHARENTE\t1\t16\t00', 'CHARENTE-INFERIEURE\t1\t17\t00', 'CHER\t1\t18\t00', 'CORREZE\t1\t19\t00', 'CORSE\t1\t20\t00', "COTE-D'OR\t1\t21\t00", 'COTES-DU-NORD\t1\t22\t00', 'CREUSE\t1\t23\t00', '']
C'est mieux car on peut maintenant parcourir la liste ligne par ligne mais ce qui suit est encore mieux:
>>> l = [x.split('\t') for x in s.split('\n')]
>>> print(l)
[['AIN', '1', '01', '00'], ['AISNE', '1', '02', '00'], ['ALLIER', '1', '03', '00'], ['ALPES(BASSES-)', '1', '04', '00'], ['ALPES(HAUTES-)', '1', '05', '00'], ['ALPES-MARITIMES', '1', '06', '00'], ['ARDECHE', '1', '07', '00'], ['ARDENNES', '1', '08', '00'], ['ARIEGE', '1', '09', '00'], ['AUBE', '1', '10', '00'], ['AUDE', '1', '11', '00'], ['AVEYRON', '1', '12', '00'], ['BOUCHES-DU-RHONE', '1', '13', '00'], ['CALVADOS', '1', '14', '00'], ['CANTAL', '1', '15', '00'], ['CHARENTE', '1', '16', '00'], ['CHARENTE-INFERIEURE', '1', '17', '00'], ['CHER', '1', '18', '00'], ['CORREZE', '1', '19', '00'], ['CORSE', '1', '20', '00'], ["COTE-D'OR", '1', '21', '00'], ['COTES-DU-NORD', '1', '22', '00'], ['CREUSE', '1', '23', '00'], ['']]
Chaque colonne est séparée dans une liste. On appelle ça une compréhension de liste.
Il est donc très facile d'accéder à une donnée en particulier:
>>> print(l[0][0])
AIN
Voici un exemple, qui ne sert pas à grand chose, mais qui permet de montrer les différents calculs complexes qu'il est possible de faire avec la compréhension de liste.
Dans cet exemple, j'ai une classe qui permet de générer, aléatoirement, des codes EAN13.
>>> from random import randint
>>> class EAN13():
GENERATES = []
def __init__(self):
pass
def new(self, count=1):
ct = count
while ct > 0:
ean13 = '{:03}{}'.format(randint(40, 49), ''.join([((x+4)*'0'+str(randint(1, int((x+4)*'9'))))[-(x+4):] for x in range(2)]))
ean13 += str(10 - (sum([int(y) * 3 if x % 2 == 0 else int(y) for x, y in enumerate(list(ean13), start=1)]) % 10))[-1]
if ean13 not in EAN13.GENERATES:
EAN13.GENERATES.append(ean13)
ct -= 1
return EAN13.GENERATES[-count:]
>>> EAN13().new(count=20)
['0467764013590', '0442457715586', '0478050754264', '0498544873950', '0419792606732', '0436177825380', '0487308428246', '0449011102394', '0487403914866', '0462030759684', '0438507088359', '0405068027851', '0489779856153', '0437057321695', '0465499916742', '0467772469648', '0493671951373', '0483943989975', '0499830229161', '0462787735498']
Comme indiqué sur wikipédia:
Un code EAN13 est composé de 13 chiffres
Je vais "exploser" mon code pour expliquer les différentes étapes.
Voici la ligne qui permet de générer aléatoirement les 12 premiers chiffres:
>>> ean13 = '{:03}{}'.format(randint(40, 49), ''.join([((x+4)*'0'+str(randint(1, int((x+4)*'9'))))[-(x+4):] for x in range(2)]))
Les 3 premiers chiffres de mon code, ceux correspondant au pays de provenance du produit, ou à une classe normalisée de produits, est un nombre aléatoire allant de 040 à 049 (à l'aide la fonction randint et format)
>>> '{:03}{}'.format(randint(40, 49), '')
'041'
Voici la fameuse compréhension de liste qui va permettre de générer deux nombres.
Le premier composé de 4 chiffres et le second composé de 5 chiffres.
>>> [((x+4)*'0'+str(randint(1, int((x+4)*'9'))))[-(x+4):] for x in range(2)]
['2983', '23696']
Si nous faisions la même chose mais sans utiliser la compréhension de liste, ça donnerait ceci:
>>> L = []
>>> for x in range(2):
L.append(((x+4)*'0'+str(randint(1, int((x+4)*'9'))))[-(x+4):])
>>> L
['5237', '92948']
J'utilise donc ma boucle for pour gérérer la première fois (x=0) un nombre de 4 chiffres et la fois suivante (x=1) un nombre de 5 chiffres.
J'utilise également le slicing ([-(x+4):]) pour conserver uniquement les x derniers chiffres de mes deux nombres aléatoires auquels j'ai ajoutés des '0' à gauche pour être certain d'avoir le bon nombre de chiffres.
J'aurais également pû utiliser la fonction format comme ceci:
>>> L.append('{0:0{1}}'.format(randint(1, int((x+4)*'9')), x+4))
Il ne reste plus qu'à calculer la clé qui sera donc le treizième et dernier chiffre de notre code.
Voici donc la ligne de code qui permet de le faire:
>>> ean13 += str(10 - (sum([int(y) * 3 if x % 2 == 0 else int(y) for x, y in enumerate(list(ean13), start=1)]) % 10))[-1]
Cette ligne de code utilise également la compréhension de liste.
J'utilise donc une boucle for et la fonction enumerate qui permet d'indexer chaque chiffres de mon code.
Je vais donc pouvoir faire la somme de tous mes chiffres et en ayant multiplié par 3 les rangs pairs (comme indiqué dans la formule de calcul de la clé).
>>> list(ean13)
['0', '4', '6', '5', '0', '3', '9', '9', '9', '0', '9', '9']
>>> [int(y) * 3 if x % 2 == 0 else int(y) for x, y in enumerate(list(ean13), start=1)]
[0, 12, 6, 15, 0, 9, 9, 27, 9, 0, 9, 27]
>>> sum([int(y) * 3 if x % 2 == 0 else int(y) for x, y in enumerate(list(ean13), start=1)])
123
Ci-dessous le reste du code détaillé ligne par ligne pour obtenir la clé finale (le caractère '_' représentant le résultat de la dernière exécution)
>>> _ % 10
3
>>> 10 - _
7
>>> str(_)[-1]
'7'
Pour info:
La première ligne permet d'obtenir le reste de la division par 10 (123%10=3)
La seconde ligne permet de soustraire à 10 le chiffre précédement obtenu.
La troisième ligne permet uniquement de garder le bon chiffre, dans le cas où le reste de la division est égal à 0.
La compréhension de list en Python est vraiment très puissante.
Elle permet de faire beaucoup de choses d'une manière plus concentrée et parfois plus facile à comprendre.
J'espère avoir été assez clair dans mes explications...
Testé avec Python 3.5
Le module csv de python permet de lire et d'écrire des fichiers csv très facilement.
Créer un fichier csv:
Exemple avec la liste suivante (cette liste contient 13 sous-listes de 7 valeurs)
>>> pprint(l)
[['root', 'x', '0', '0', 'root', '/root', '/bin/bash'],
['daemon', 'x', '1', '1', 'daemon', '/usr/sbin', '/usr/sbin/nologin'],
['bin', 'x', '2', '2', 'bin', '/bin', '/usr/sbin/nologin'],
['sys', 'x', '3', '3', 'sys', '/dev', '/usr/sbin/nologin'],
['sync', 'x', '4', '65534', 'sync', '/bin', '/bin/sync'],
['games', 'x', '5', '60', 'games', '/usr/games', '/usr/sbin/nologin'],
['man', 'x', '6', '12', 'man', '/var/cache/man', '/usr/sbin/nologin'],
['lp', 'x', '7', '7', 'lp', '/var/spool/lpd', '/usr/sbin/nologin'],
['mail', 'x', '8', '8', 'mail', '/var/mail', '/usr/sbin/nologin'],
['news', 'x', '9', '9', 'news', '/var/spool/news', '/usr/sbin/nologin'],
['uucp', 'x', '10', '10', 'uucp', '/var/spool/uucp', '/usr/sbin/nologin'],
['proxy', 'x', '13', '13', 'proxy', '/bin', '/usr/sbin/nologin'],
['www-data', 'x', '33', '33', 'www-data', '/var/www', '/usr/sbin/nologin']]
>>> import csv
>>> with open('passwd.csv', 'w', newline='') as f:
writer = csv.writer(f)
writer.writerows(l)
Vachement simple, non !
La fonction "writerows" avec un "s" à la fin, permet d'écrire en une seule fois tout le contenu d'une liste contenant elle-même des sous-listes (comme dans l'exemple ci-dessus).
Par contre, pour écrire uniquement le contenu d'une liste, sans sous-liste, il faut utiliser la fonction "writerow" (sans le "s" à la fin).
>>> import csv
>>> with open('passwd.csv', 'w', newline='') as f:
writer = csv.writer(f)
for x in l:
writer.writerow(x)
Lors de l'ouverture du fichier à l'aide de la commande open, il faut obligatoirement indiqué le paramètre newline='' sinon des sauts de lignes supplémentaires seront ajoutés à chaque écriture.
Lire un fichier csv:
>>> import csv
>>> with open('passwd.csv', newline='') as f:
reader = csv.reader(f)
for row in reader:
print(row)
['root', 'x', '0', '0', 'root', '/root', '/bin/bash']
['daemon', 'x', '1', '1', 'daemon', '/usr/sbin', '/usr/sbin/nologin']
['bin', 'x', '2', '2', 'bin', '/bin', '/usr/sbin/nologin']
['sys', 'x', '3', '3', 'sys', '/dev', '/usr/sbin/nologin']
['sync', 'x', '4', '65534', 'sync', '/bin', '/bin/sync']
['games', 'x', '5', '60', 'games', '/usr/games', '/usr/sbin/nologin']
['man', 'x', '6', '12', 'man', '/var/cache/man', '/usr/sbin/nologin']
['lp', 'x', '7', '7', 'lp', '/var/spool/lpd', '/usr/sbin/nologin']
['mail', 'x', '8', '8', 'mail', '/var/mail', '/usr/sbin/nologin']
['news', 'x', '9', '9', 'news', '/var/spool/news', '/usr/sbin/nologin']
['uucp', 'x', '10', '10', 'uucp', '/var/spool/uucp', '/usr/sbin/nologin']
['proxy', 'x', '13', '13', 'proxy', '/bin', '/usr/sbin/nologin']
['www-data', 'x', '33', '33', 'www-data', '/var/www', '/usr/sbin/nologin']
Toujours aussi simple !
Ne pas oublier le paramètre newline='' avec la commande open
En prime, lecture d'un fichier csv à l'aide d'un tuple nommé contenant le nom des champs du fichiers csv.
Très utile pour la manipulation d'un fichier csv complexe.
Pour cela, nous allons utiliser la fonction namedtuple du module collections
>>> from collections import namedtuple
>>> # Nous initialisons la liste des noms de champs
>>> Headers = namedtuple('Headers', 'LoginName, EncryptedPassword, UserId, GroupId, UserName, HomeDirectory, Interpreter')
>>> with open('passwd.csv', newline='') as f:
reader = csv.reader(f)
for header in map(Headers._make, reader):
# Nous pouvons afficher les valeurs à l'aide des attributs nommés
print(header.LoginName, header.HomeDirectory)
root /root
daemon /usr/sbin
bin /bin
sys /dev
sync /bin
games /usr/games
man /var/cache/man
lp /var/spool/lpd
mail /var/mail
news /var/spool/news
uucp /var/spool/uucp
proxy /bin
www-data /var/www
Que dire de plus ...
Le module enum est très pratique pour créer et utiliser des constantes dans des programmes python.
Dans l'exemple suivant, je crée une classe qui va me permettre de générer des constantes pour les codes HTTP à l'aide de la classe Enum du module enum.
Cette classe pourra ensuite être utilisée dans n'importe quel programme ayant besoin d'utiliser ces codes.
Pour l'exemple, j'ai utilisé que quelques codes.
La liste complète étant disponible ici.
from enum import Enum
class Status(Enum):
HTTP_OK = 200
HTTP_CREATED = 201
HTTP_ACCEPTED = 202
HTTP_NON_AUTHORITATIVE = 203
HTTP_NO_CONTENT = 204
HTTP_PARTIAL_CONTENT = 206
HTTP_MULTIPLE_CHOICES = 300
HTTP_MOVED_PERMANENTLY = 301
HTTP_MOVED_TEMPORARILY = 302
HTTP_SEE_OTHER = 303
HTTP_NOT_MODIFIED = 304
HTTP_BAD_REQUEST = 400
HTTP_UNAUTHORIZED = 401
HTTP_PAYMENT_REQUIRED = 402
HTTP_FORBIDDEN = 403
HTTP_NOT_FOUND = 404
HTTP_METHOD_NOT_ALLOWED = 405
HTTP_INTERNAL_SERVER_ERROR = 500
HTTP_NOT_IMPLEMENTED = 501
HTTP_BAD_GATEWAY = 502
J'ai donc créé une classe Status qui étend la classe Enum du module enum.
Pour créer les constantes, il suffit juste de créer des paires clés / valeurs.
Les valeurs peuvent être numériques ou des chaines de caractères.
Pour utiliser cette classe personnalisée, il suffit de procéder de cette manière.
Pour l'exemple, je pars du principe que la classe Status est enregistrée dans un fichier nommé Apache.py et que ce fichier est disponible dans un répertoire listé dans la variable path du module sys.
>>> from Apache import Status
>>> Status
<enum 'Status'>
>>> Status(200)
<Status.HTTP_OK: 200>
>>> Status['HTTP_UNAUTHORIZED']
<Status.HTTP_UNAUTHORIZED: 401>
>>> Status.HTTP_BAD_REQUEST
<Status.HTTP_BAD_REQUEST: 400>
>>> Status(200).value
200
>>> Status(200).name
'HTTP_OK'
>>> Status['HTTP_OK'].value
200
On voit bien que la classe Status étend le module enum.
Pour afficher la description (le nom de la constante correspondante) d'un code, il suffit d'appeler la classe avec le code entre parenthèses.
Si le code n'existe pas, une exception "ValueError" est levée.
On peut également afficher l'information à l'aide du nom de la constante entre crochets (comme un dictionnaire).
Les constantes sont accessibles directement via la classe Status comme n'importe quelles constantes de classes ordinaires.
Il est également très facile d'obtenir la valeur d'une constante et/ou son nom que ce soit à partir de sa valeur ou de son nom.
Il est également possible d'ajouter des informations supplémentaires comme un label par exemple.
from enum import Enum
class Status(int, Enum):
HTTP_OK = (200, 'Ok')
HTTP_CREATED = (201, 'Created')
HTTP_ACCEPTED = (202, 'Accepted')
HTTP_NON_AUTHORITATIVE = (203, 'Non Authoritative')
HTTP_NO_CONTENT = (204, 'No Content')
HTTP_PARTIAL_CONTENT = (206, 'Partial Content')
HTTP_MULTIPLE_CHOICES = (300, 'Multiple Choices')
HTTP_MOVED_PERMANENTLY = (301, 'Moved Permanently')
HTTP_MOVED_TEMPORARILY = (302, 'Moved Temporarily')
HTTP_SEE_OTHER = (303, 'See Other')
HTTP_NOT_MODIFIED = (304, 'Not Modified')
HTTP_BAD_REQUEST = (400, 'Bad Request')
HTTP_UNAUTHORIZED = (401, 'Unauthorized')
HTTP_PAYMENT_REQUIRED = (402, 'Payment Required')
HTTP_FORBIDDEN = (403, 'Forbidden')
HTTP_NOT_FOUND = (404, 'Not Found')
HTTP_METHOD_NOT_ALLOWED = (405, 'Method Not Allowed')
HTTP_INTERNAL_SERVER_ERROR = (500, 'Internal Server Error')
HTTP_NOT_IMPLEMENTED = (501, 'Not Implemented')
HTTP_BAD_GATEWAY = (502, 'Bad Gateway')
def __new__(cls, value, label):
obj = int.__new__(cls, value)
obj._value_ = value
obj.label = label
return obj
Pour ajouter des labels par exemple, il est nécessaire de modifier la classe Status.
Il faut ajouter une dépendance à l'objet int.
Il faut ensuite modifier les valeurs en les remplaçant par des tuples contenant la valeur et le label.
Pour finir, il faut ajouter une fonction __new__ afin d'indiquer quelle valeur du tuple sera affectée à l'attribut _value_
La nouvelle classe est sauvegardée dans un fichier nommé HTTP_Status.py
Résultat:
from HTTP_Status import Status
Status
Out[3]: <enum 'Status'>
Status.HTTP_OK
Out[4]: <Status.HTTP_OK: 200>
Status.HTTP_OK.name
Out[5]: 'HTTP_OK'
Status.HTTP_OK.value
Out[6]: 200
Status.HTTP_OK.label
Out[7]: 'Ok'
Status.HTTP_NOT_FOUND
Out[8]: <Status.HTTP_NOT_FOUND: 404>
Status.HTTP_NOT_FOUND.name
Out[9]: 'HTTP_NOT_FOUND'
Status.HTTP_NOT_FOUND.value
Out[10]: 404
Status.HTTP_NOT_FOUND.label
Out[11]: 'Not Found'
Status(404)
Out[12]: <Status.HTTP_NOT_FOUND: 404>
Status(404).label
Out[13]: 'Not Found'
Status['HTTP_NOT_FOUND']
Out[14]: <Status.HTTP_NOT_FOUND: 404>
Status['HTTP_NOT_FOUND'].label
Out[15]: 'Not Found'
Status['HTTP_NOT_FOUND'].value
Out[16]: 404
Sous Python, le module json permet de créer et de lire des données au format json.
Repartons de cet exemple: Python: Parser et indenter un flux XML
J'ai donc un dictionnaire de données contenant les valeurs suivantes:
>>> from pprint import pprint
>>> pprint(datas)
{'2782113': {'countryCode': 'AT',
'countryName': 'Austria',
'fcl': 'A',
'fcode': 'PCLI',
'lat': '47.33333',
'lng': '13.33333',
'name': 'Austria',
'toponymName': 'Republic of Austria'},
'2921044': {'countryCode': 'DE',
'countryName': 'Germany',
'fcl': 'A',
'fcode': 'PCLI',
'lat': '51.5',
'lng': '10.5',
'name': 'Germany',
'toponymName': 'Federal Republic of Germany'},
'3017382': {'countryCode': 'FR',
'countryName': 'France',
'fcl': 'A',
'fcode': 'PCLI',
'lat': '46',
'lng': '2',
'name': 'France',
'toponymName': 'Republic of France'},
'3042058': {'countryCode': 'LI',
'countryName': 'Liechtenstein',
'fcl': 'A',
'fcode': 'PCLI',
'lat': '47.16667',
'lng': '9.53333',
'name': 'Liechtenstein',
'toponymName': 'Principality of Liechtenstein'},
'3175395': {'countryCode': 'IT',
'countryName': 'Italy',
'fcl': 'A',
'fcode': 'PCLI',
'lat': '42.83333',
'lng': '12.83333',
'name': 'Italy',
'toponymName': 'Repubblica Italiana'}}
>>>
Pour convertir ce dictionnaire au format json, il suffit d'utiliser le module json de Python:
>>> import json
>>> print(json.dumps(datas, indent=4))
{
"3017382": {
"toponymName": "Republic of France",
"lat": "46",
"countryName": "France",
"fcl": "A",
"fcode": "PCLI",
"name": "France",
"countryCode": "FR",
"lng": "2"
},
"3175395": {
"toponymName": "Repubblica Italiana",
"lat": "42.83333",
"countryName": "Italy",
"fcl": "A",
"fcode": "PCLI",
"name": "Italy",
"countryCode": "IT",
"lng": "12.83333"
},
"2921044": {
"toponymName": "Federal Republic of Germany",
"lat": "51.5",
"countryName": "Germany",
"fcl": "A",
"fcode": "PCLI",
"name": "Germany",
"countryCode": "DE",
"lng": "10.5"
},
"3042058": {
"toponymName": "Principality of Liechtenstein",
"lat": "47.16667",
"countryName": "Liechtenstein",
"fcl": "A",
"fcode": "PCLI",
"name": "Liechtenstein",
"countryCode": "LI",
"lng": "9.53333"
},
"2782113": {
"toponymName": "Republic of Austria",
"lat": "47.33333",
"countryName": "Austria",
"fcl": "A",
"fcode": "PCLI",
"name": "Austria",
"countryCode": "AT",
"lng": "13.33333"
}
}
>>>
Dans la commande précédente, j'ai utiliser la fonction dumps du module json qui permet de formater n'importe quel objet Python (dictionnaire, liste, une chaine de caractères, un nombre) au format json avec en plus une indentation avec 4 espaces (indent=4).
Il est donc tout à fait possible d'enregistrer ce résultat dans un fichier:
>>> with open('datas.json', 'w') as f:
f.write(json.dumps(datas, indent=4))
1293
>>>
Ou alors avec la fonction dump (sans le `s` à la fin).
Cette commande permet d'enregistrer les données directement dans le flux précédemment ouvert.
>>> with open('datas.json', 'w') as f:
json.dump(datas, f, indent=4)
>>>
Et voilà, je me retrouve avec un beau fichier json tout beau tout neuf.
Et pour lire un contenu json:
>>> with open('datas.json', 'r') as f:
datas = json.load(f)
>>> type(datas)
<class 'dict'>
>>> pprint(datas)
{'2782113': {'countryCode': 'AT',
'countryName': 'Austria',
'fcl': 'A',
'fcode': 'PCLI',
'lat': '47.33333',
'lng': '13.33333',
'name': 'Austria',
'toponymName': 'Republic of Austria'},
'2921044': {'countryCode': 'DE',
'countryName': 'Germany',
'fcl': 'A',
'fcode': 'PCLI',
'lat': '51.5',
'lng': '10.5',
'name': 'Germany',
'toponymName': 'Federal Republic of Germany'},
'3017382': {'countryCode': 'FR',
'countryName': 'France',
'fcl': 'A',
'fcode': 'PCLI',
'lat': '46',
'lng': '2',
'name': 'France',
'toponymName': 'Republic of France'},
'3042058': {'countryCode': 'LI',
'countryName': 'Liechtenstein',
'fcl': 'A',
'fcode': 'PCLI',
'lat': '47.16667',
'lng': '9.53333',
'name': 'Liechtenstein',
'toponymName': 'Principality of Liechtenstein'},
'3175395': {'countryCode': 'IT',
'countryName': 'Italy',
'fcl': 'A',
'fcode': 'PCLI',
'lat': '42.83333',
'lng': '12.83333',
'name': 'Italy',
'toponymName': 'Repubblica Italiana'}}
>>>
Grâce à la fonction load du module json, je peux charger tout le contenu d'un fichier json directement dans une variable.
Cette variable est de type dict (dictionnaire)
La fonction loads (avec un `s` à la fin) permet de charger une chaine de caractères au format json.
Comme son nom l'indique, enfin non, pas vraiment, le module pickle permet de sauvegarder dans un fichier, au format binaire, n'importe quel objet Python.
En clair, si pour une raison quelconque, dans un script Python, vous avez besoin de sauvegarder, temporairement ou même de façon plus pérenne, le contenu d'un objet Python comme une liste, un dictionnaire, un tuple etc etc ... au lieu d'utiliser une base de données ou un simple fichier texte, le module pickle est fait pour ça.
Il permet de stocker et de restaurer un objet Python tel quel sans aucune manipulation supplémentaire.
C'est vraiment super pratique.
Il fonctionne comme le module json mais n'est pas limité à un seul format d'objet.
Exemples:
>>> import pickle
>>> import string
>>> L = list(string.ascii_letters)
>>> print(L)
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
>>> with open('mypicklefile', 'wb') as f1:
pickle.dump(L, f1)
>>> with open('mypicklefile', 'r') as f1:
f1.read()
'€\x03]q\x00(X\x01\x00\x00\x00aq\x01X\x01\x00\x00\x00bq\x02X\x01\x00\x00\x00cq\x03X\x01\x00\x00\x00dq\x04X\x01\x00\x00\x00eq\x05X\x01\x00\x00\x00fq\x06X\x01\x00\x00\x00gq\x07X\x01\x00\x00\x00hq\x08X\x01\x00\x00\x00iq\tX\x01\x00\x00\x00jq\nX\x01\x00\x00\x00kq\x0bX\x01\x00\x00\x00lq\x0cX\x01\x00\x00\x00mq\nX\x01\x00\x00\x00nq\x0eX\x01\x00\x00\x00oq\x0fX\x01\x00\x00\x00pq\x10X\x01\x00\x00\x00qq\x11X\x01\x00\x00\x00rq\x12X\x01\x00\x00\x00sq\x13X\x01\x00\x00\x00tq\x14X\x01\x00\x00\x00uq\x15X\x01\x00\x00\x00vq\x16X\x01\x00\x00\x00wq\x17X\x01\x00\x00\x00xq\x18X\x01\x00\x00\x00yq\x19X\x01\x00\x00\x00zq\x1aX\x01\x00\x00\x00Aq\x1bX\x01\x00\x00\x00Bq\x1cX\x01\x00\x00\x00Cq\x1dX\x01\x00\x00\x00Dq\x1eX\x01\x00\x00\x00Eq\x1fX\x01\x00\x00\x00Fq X\x01\x00\x00\x00Gq!X\x01\x00\x00\x00Hq"X\x01\x00\x00\x00Iq#X\x01\x00\x00\x00Jq$X\x01\x00\x00\x00Kq%X\x01\x00\x00\x00Lq&X\x01\x00\x00\x00Mq\'X\x01\x00\x00\x00Nq(X\x01\x00\x00\x00Oq)X\x01\x00\x00\x00Pq*X\x01\x00\x00\x00Qq+X\x01\x00\x00\x00Rq,X\x01\x00\x00\x00Sq-X\x01\x00\x00\x00Tq.X\x01\x00\x00\x00Uq/X\x01\x00\x00\x00Vq0X\x01\x00\x00\x00Wq1X\x01\x00\x00\x00Xq2X\x01\x00\x00\x00Yq3X\x01\x00\x00\x00Zq4e.'
>>> OL = None
>>> print(OL)
None
>>> with open('mypicklefile', 'rb') as f1:
OL = pickle.load(f1)
>>> type(OL)
<class 'list'>
>>> print(OL)
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
>>> L == OL
True
>>>
Dans l'exemple ci-dessus, j'ai créé une liste "L" contenant toutes les lettres de l'alphabet.
J'ai sauvegardé mon objet "L" (liste Python) dans un fichier "mypicklefile" grâce à la méthode dump du module pickle.
Précision importante, le module pickle écrit les données uniquement dans un fichier ouvert en mode binaire.
J'ai ouvert le fichier et afficher son contenu pour bien montrer que pickle écrit les données au format binaire.
J'ai créé un nouvel objet "OL" ayant None comme valeur (cette étape n'est pas obligatoire - uniquement pour montrer que l'objet "OL" n'existait pas auparavant).
J'ai ensuite chargé le contenu du fichier "mypicklefile" dans mon objet "OL" grâce à la méthode load du module pickle.
J'affiche le contenu de la nouvelle liste "OL" et le test d'égalité de l'objet "L" et "OL" pour bien montrer que les deux objets sont bien identiques.
La liste "OL" peut être modifiée (ajout, modification, suppression des valeurs) et à nouveau sauvegardée dans le fichier grâce à la méthode dump du module pickle pour une prochaine utilisation.
Petite précision, pour la méthode dump, le fichier doit être ouvert en mode 'wb' afin d'écraser le contenu précédent.
Si le fichier est ouvert en mode 'ab', les données sont écrites à la fin du fichier mais la méthode load récupère les données au début du fichier.
De toute manière, le mode append n'a aucun intérêt pour ce genre de stockage de données. On dump et on load l'intégralité du contenu d'un objet.
Et ça fonctionne pour tous types d'objets
Avec un tuple
>>> import pickle
>>> T = tuple(string.ascii_letters)
>>> T
('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z')
>>> with open('mypicklefile', 'wb') as f1:
pickle.dump(T, f1)
>>> with open('mypicklefile', 'rb') as f1:
OT = pickle.load(f1)
>>> OT
('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z')
>>>
Avec un dictionnaire
>>> import pickle
>>> I = list(range(52))
>>> L = list(string.ascii_letters)
>>> D = {i: l for i, l in zip(I, L)}
>>> D
{0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e', 5: 'f', 6: 'g', 7: 'h', 8: 'i', 9: 'j', 10: 'k', 11: 'l', 12: 'm', 13: 'n', 14: 'o', 15: 'p', 16: 'q', 17: 'r', 18: 's', 19: 't', 20: 'u', 21: 'v', 22: 'w', 23: 'x', 24: 'y', 25: 'z', 26: 'A', 27: 'B', 28: 'C', 29: 'D', 30: 'E', 31: 'F', 32: 'G', 33: 'H', 34: 'I', 35: 'J', 36: 'K', 37: 'L', 38: 'M', 39: 'N', 40: 'O', 41: 'P', 42: 'Q', 43: 'R', 44: 'S', 45: 'T', 46: 'U', 47: 'V', 48: 'W', 49: 'X', 50: 'Y', 51: 'Z'}
>>> with open('mypicklefile', 'wb') as f1:
pickle.dump(D, f1)
>>> with open('mypicklefile', 'rb') as f1:
OD = pickle.load(f1)
>>> OD
{0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e', 5: 'f', 6: 'g', 7: 'h', 8: 'i', 9: 'j', 10: 'k', 11: 'l', 12: 'm', 13: 'n', 14: 'o', 15: 'p', 16: 'q', 17: 'r', 18: 's', 19: 't', 20: 'u', 21: 'v', 22: 'w', 23: 'x', 24: 'y', 25: 'z', 26: 'A', 27: 'B', 28: 'C', 29: 'D', 30: 'E', 31: 'F', 32: 'G', 33: 'H', 34: 'I', 35: 'J', 36: 'K', 37: 'L', 38: 'M', 39: 'N', 40: 'O', 41: 'P', 42: 'Q', 43: 'R', 44: 'S', 45: 'T', 46: 'U', 47: 'V', 48: 'W', 49: 'X', 50: 'Y', 51: 'Z'}
>>>
Avec un deque (du module collections)
>>> import pickle
>>> from collections import deque
>>> L = list(string.ascii_letters)
>>> L
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
>>> DE = deque(L)
>>> DE
deque(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'])
>>> with open('mypicklefile', 'wb') as f1:
pickle.dump(DE, f1)
>>> with open('mypicklefile', 'rb') as f1:
ODE = pickle.load(f1)
>>> ODE
deque(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'])
>>>
Précision importante, la méthode load importe automatiquement l'objet nécessaire au chargement des données.
C'est à dire que dans l'exemple ci-dessus, l'objet deque du module collections n'a pas besoin d'être importé pour être correctement chargé via la méthode load. Il doit bien évidement être disponible dans la liste des modules de Python.
Et enfin, avec un objet perso
>>> class MyObject():
def __init__(self):
self.un = 1
self.deux = 2
self.trois = 3
def __repr__(self):
return "{}, {}, {}".format(self.un, self.deux, self.trois)
def __str__(self):
return "un: {}\ndeux: {}\ntrois: {}".format(self.un, self.deux, self.trois)
>>> A = MyObject()
>>> A
1, 2, 3
>>> print(A)
un: 1
deux: 2
trois: 3
>>> with open('mypicklefile', 'wb') as f1:
pickle.dump(A, f1)
>>> with open('mypicklefile', 'rb') as f1:
B = pickle.load(f1)
>>> B
1, 2, 3
>>> print(B)
un: 1
deux: 2
trois: 3
>>>
Dans ce cas, la class MyObject() doit être disponible pour pouvoir être utilisée via la méthode load sinon, une erreur "AttributeError" est levée.
Associé au module tempfile, nous avons un système complet de sauvegarde temporaire d'objets utilisable dans n'importe quel script Python.
Aujourd'hui, le web a prit une place importante dans notre vie de tous les jours.
Que ce soit pour lire ses mails, lire les actualités, consulter la météo, se documenter, jouer, bref la liste est longue et non exhaustive.
Python permet de faire différents types de requêtes sur le web.
Pour ce faire, Python propose le module urllib3.
Ce module est très complet et par conséquent un peu complexe à utiliser.
Le module requests, qui utilise toute la puissance du module urllib3 est beaucoup plus simple d'utilisation et énormément utilisé pour interagir avec le web.
Comme un petit exemple vaut mieux qu'un long discours...
>>> import requests
>>> html = requests.get('https://www.python.org/downloads/')
>>> html.status_code
200
>>> html.url
'https://www.python.org/downloads/'
>>> html.raw
<urllib3.response.HTTPResponse object at 0x0B80D6D0>
>>> html.encoding
'utf-8'
>>> html.headers
{'Server': 'nginx', 'Content-Type': 'text/html; charset=utf-8', 'X-Frame-Options': 'DENY', 'Cache-Control': 'max-age=604800, public', 'Via': '1.1 vegur, 1.1 varnish, 1.1 varnish', 'Content-Length': '113054', 'Accept-Ranges': 'bytes', 'Date': 'Thu, 06 Jun 2019 13:08:10 GMT', 'Age': '132076', 'Connection': 'keep-alive', 'X-Served-By': 'cache-iad2130-IAD, cache-cdg20735-CDG', 'X-Cache': 'HIT, HIT', 'X-Cache-Hits': '1, 5', 'X-Timer': 'S1559826490.039199,VS0,VE1', 'Strict-Transport-Security': 'max-age=63072000; includeSubDomains'}
>>> html.is_redirect
False
Voici donc un exemple d'une requête "GET" et des différentes informations que nous obtenons (dans l'ordre):
- Le statut de la requête (ici 200, indiquant que la requête s'est bien déroulée)
- L'url requêtée
- Le format brut de notre requête (on voit qu'il s'agit d'un objet HTTPResponse du module urllib3)
- De l'encodage utilisé (ici, UTF-8)
- Tout le contenu de l'en-tête
- Si la requête effectuée est une redirection
Mais une des choses les plus importantes est quand même le contenu de la requête....
>>> len(html.content)
113054
>>> html.content[:500]
b'<!doctype html>\n<!--[if lt IE 7]> <html class="no-js ie6 lt-ie7 lt-ie8 lt-ie9"> <![endif]-->\n<!--[if IE 7]> <html class="no-js ie7 lt-ie8 lt-ie9"> <![endif]-->\n<!--[if IE 8]> <html class="no-js ie8 lt-ie9"> <![endif]-->\n<!--[if gt IE 8]><!--><html class="no-js" lang="en" dir="ltr"> <!--<![endif]-->\n\n<head>\n <meta charset="utf-8">\n <meta http-equiv="X-UA-Compatible" content="IE=edge">\n\n <link rel="prefetch" href="//ajax.googleapis.com/ajax/libs/jqu'
>>> html.content[-500:]
b'ncludes.js"></script>\n\n <script type="text/javascript" src="/static/js/main-min.fbfe252506ae.js" charset="utf-8"></script>\n \n\n <!--[if lte IE 7]>\n <script type="text/javascript" src="/static/js/plugins/IE8-min.16868e6a5d2f.js" charset="utf-8"></script>\n \n \n <![endif]-->\n\n <!--[if lte IE 8]>\n <script type="text/javascript" src="/static/js/plugins/getComputedStyle-min.c3860be1d290.js" charset="utf-8"></script>\n \n \n <![endif]-->\n\n \n\n \n \n\n</body>\n</html>\n'
J'affiche les 500 premiers/derniers caractères du contenu de la requête qui en contient au total 113054.
Nous voilà donc avec tout le contenu de la page https://www.python.org/downloads/.
Si les données de la requête sont au format JSON, très courant dans le monde des APIs et des WebServices.
L'objet retourné par request contient une méthode json qui permet de formater le contenu en JSON si celui-ci est reconnu comme tel.
Par exemple, avec la liste des codes postaux...
>>> req = requests.get('https://unpkg.com/codes-postaux@3.2.0/codes-postaux.json')
>>> req.status_code
200
>>> json = req.json()
>>> type(json)
<class 'list'>
>>> len(json)
35728
>>> pprint(json[:5])
[{'codeCommune': '01001',
'codePostal': '01400',
'libelleAcheminement': "L'ABERGEMENT-CLEMENCIAT",
'nomCommune': "L'Abergement-Clémenciat"},
{'codeCommune': '01002',
'codePostal': '01640',
'libelleAcheminement': 'ABERGEMENT-DE-VAREY (L )',
'nomCommune': "L'Abergement-de-Varey"},
{'codeCommune': '01004',
'codePostal': '01500',
'libelleAcheminement': 'AMBERIEU-EN-BUGEY',
'nomCommune': 'Ambérieu-en-Bugey'},
{'codeCommune': '01005',
'codePostal': '01330',
'libelleAcheminement': 'AMBERIEUX-EN-DOMBES',
'nomCommune': 'Ambérieux-en-Dombes'},
{'codeCommune': '01006',
'codePostal': '01300',
'libelleAcheminement': 'AMBLEON',
'nomCommune': 'Ambléon'}]
Quoi de plus simple.
Si une authentification HTTP basique est nécessaire, il est possible de passer le nom d'utilisateur et le mot de passe lors de l'exécution de la requête.
>>> req = requests.get('https://unpkg.com/codes-postaux@3.2.0/codes-postaux.json', auth=('username','password'))
>>> req.status_code
200
>>> req.request.headers
{'User-Agent': 'python-requests/2.22.0', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive', 'Authorization': 'Basic dXNlcm5hbWU6cGFzc3dvcmQ='}
>>> import base64
>>> base64.b64decode(req.request.headers.get('Authorization').split(' ')[1])
b'username:password'
En examinant l'en-tête de la requête, on s'aperçoit qu'une clé 'Authorization' a été ajouté, que le type est 'Basic' et le contenu, nom d'utilisateur et mot de passe, sont encodés en base64.
Si on décode le contenu à l'aide du module base64, on récupère les informations d'authentification.
D'autres méthodes d'authentification sont diponibles, tout est expliqué ici.
Pour finir, il est également possible d'utiliser une session pour effectuer plusieurs requêtes sur un site, ce qui permet de conserver les informations d'identification et les cookies pendant l'exécution de toutes les requêtes.
>>> sess = requests.Session()
>>> req = sess.get('https://unpkg.com/codes-postaux@3.2.0/codes-postaux.json')
>>> req.status_code
200
A vous de jouer.
Besoin d'un fichier temporaire pour y stocker provisoirement des données.
Nul besoin de se prendre la tête en créant un fichier de toute pièce où il faudra penser au répertoire de stockage, au nom du fichier, à sa suppression etc etc ...
Le module tempfile, disponible sous Python 2 et 3 permet de gérer des fichiers temporaires de leurs créations à leurs suppressions.
Fait partie des modules à utiliser sans modération.
Exemple:
>>> import tempfile
# Je créé un nouveau fichier temporaire
>>> mytmpfile = tempfile.NamedTemporaryFile()
# je peux afficher son nom
>>> mytmpfile.name
'/tmp/tmp00plrd7l'
# je peux y écrire du contenu très facilement
>>> mytmpfile.file.write(b'the first test')
>>> mytmpfile.file.write(b'the second test')
# tant que la méthode flush ou close (de la sous-classe file) n'a pas été exécutée, le contenu est stocké en mémoire
>>> with open(mytmpfile.name, 'rb') as f1:
... f1.read()
...
...
b''
# J'enregistre le contenu dans le fichier
>>> mytmpfile.file.flush()
# je vérifie que le contenu a bien été enregistré (pour info)
>>> with open(mytmpfile.name, 'rb') as f1:
... f1.read()
...
...
b'the first testthe second test'
# ATTENTION, la méthode flush (de la sous-classe file) permet d'enregistrer dans le fichier tout le contenu écrit avec la méthode write (entre deux flush)
# Il est donc possible de rajouter du contenu si nécessaire.
# La méthode close (de la sous-classe file) permet d'enregistrer dans le fichier tout le contenu écrit avec la méthode write et ferme le fichier.
# Il n'est donc plus possible de rajouter du contenu.
# Tant que le script est exécuté et que la méthode close de la classe tempfile n'est pas exécutée, le fichier temporaire est existant sur le disque.
# La commande suivante supprime définitivement le fichier sur le disque.
>>> mytmpfile.close()
Un tas d'options est disponible mais celles par défaut sont amplement suffisantes.
Voici une astuce qui permet de maintenir à jour ses paquets Python sans trop se prendre la tête.
Premièrement, générer le fichier des paquets installés
# python -m pip freeze > requirement
# head requirement
aiofiles==0.6.0
certifi==2020.12.5
chardet==3.0.4
click==7.1.2
gunicorn==20.0.4
h11==0.11.0
httptools==0.1.1
idna==2.10
Jinja2==2.11.2
MarkupSafe==1.1.1
Deuxièmement, créer un fichier de mise à jour à partir du fichier requirement
# sed 's/==/>=/g' requirement > upgrade_requirement
# head upgrade_requirement
aiofiles>=0.6.0
certifi>=2020.12.5
chardet>=3.0.4
click>=7.1.2
gunicorn>=20.0.4
h11>=0.11.0
httptools>=0.1.1
idna>=2.10
Jinja2>=2.11.2
MarkupSafe>=1.1.1
Et enfin, lancer la mise à jour des paquets
# python -m pip install -U -r upgrade_requirement
Et voilà, c'est fini
Voici un petit exemple qui permet de modifier la valeur d'une balise en fonction de son attribut et de sa valeur.
XML d'origine:
<xml>
<ma_balise mon_attribut="ok">valeur1</ma_balise>
<ma_balise mon_attribut="ko">valeur2</ma_balise>
<ma_balise>valeur3</ma_balise>
<ma_balise mon_attribut="ok">valeur4</ma_balise>
<ma_balise>valeur5</ma_balise>
</xml>
Script Python:
>>> from lxml import etree
>>> xml = etree.parse('monfichier.xml')
>>> for ma_balise in xml.getchildren():
if 'mon_attribut' in ma_balise.attrib and ma_balise.attrib['mon_attribut'] == 'ok':
ma_balise.text = 'nouvelle valeur'
# Si besoin d'une section CDATA
# ma_balise.text = etree.CDATA('nouvelle valeur')
>>> with open('monfichier.xml', 'w') as f1:
f1.write(etree.tounicode(xml))
>>>
XML après traitement:
<xml>
<ma_balise mon_attribut="ok">nouvelle valeur</ma_balise>
<ma_balise mon_attribut="ko">valeur2</ma_balise>
<ma_balise>valeur3</ma_balise>
<ma_balise mon_attribut="ok">nouvelle valeur</ma_balise>
<ma_balise>valeur5</ma_balise>
</xml>
Seules les balises ayant l'attribut 'mon_attribut' et sa valeur égale à 'ok' ont été modifiées.
Un évènement en Martinique le 26 novembre 2020 à 12:42 (heure locale of course), ça fait quelle heure en Métropole ?
>>> from datetime import datetime
>>> from dateutil import tz
>>> MTQ = tz.gettz('America/Martinique')
>>> dt1 = datetime(2020, 11, 26, 12, 42, 0, tzinfo=MTQ)
>>> print(dt1)
2020-11-26 12:42:00-04:00
>>> repr(dt1)
"datetime.datetime(2020, 11, 26, 12, 42, tzinfo=tzfile('America/Martinique'))"
>>> FRA = tz.gettz('Europe/Paris')
>>> dt2 = dt1.astimezone(tz=FRA)
>>> print(dt2)
2020-11-26 17:42:00+01:00
>>> repr(dt2)
"datetime.datetime(2020, 11, 26, 17, 42, tzinfo=tzfile('Europe/Paris'))"
ça fait 17:42 heure de Paris (le même jour)
Un autre exemple:
>>> dt1 = datetime(2020, 11, 26, 20, 45, 0, tzinfo=MTQ)
>>> dt2 = dt1.astimezone(tz=FRA)
>>> print(dt1)
2020-11-26 20:45:00-04:00
>>> print(dt2)
2020-11-27 01:45:00+01:00
Le 26 novembre 2020 20:45 heure de Martinique, correspond au 27 novembre 2020 01:45 heure de Paris.
Pré requis:
$ apt-get install python-mysqldb
Requête simple (select)
#!/usr/bin/env python
# -*- coding: utf-8 -*
# on importe le module MySQLdb
import MySQLdb
# On créé un dictionnaire contenant les paramètres de connexion MySQL
paramMysql = {
'host' : 'localhost',
'user' : 'username',
'passwd' : 'password',
'db' : 'mabase'
}
# Bien respecter les noms des paramètres (host, user, passwd, db)
sql = """\
SELECT * FROM matable
WHERE monchamp1 = 'valeur1'
"""
try:
# On créé une conexion MySQL
conn = MySQLdb.connect(**paramMysql)
# On créé un curseur MySQL
cur = conn.cursor(MySQLdb.cursors.DictCursor)
# On exécute la requête SQL
cur.execute(sql)
# On récupère toutes les lignes du résultat de la requête
rows = cur.fetchall()
# On parcourt toutes les lignes
for row in rows:
# Pour récupérer les différentes valeurs des différents champs
valeur1 = row['monchamp1']
valeur2 = row['monchamp2']
valeur3 = row['monchamp3']
# etc etc ...
except MySQLdb.Error, e:
# En cas d'anomalie
print "Error %d: %s" % (e.args[0],e.args[1])
sys.exit(1)
finally:
# On ferme la connexion
if conn:
conn.close()
Requête insert, update, delete
#!/usr/bin/env python
# -*- coding: utf-8 -*
# on importe le module MySQLdb
import MySQLdb
# On créé un dictionnaire contenant les paramètres de connexion MySQL
paramMysql = {
'host' : 'localhost',
'user' : 'username',
'passwd' : 'password',
'db' : 'mabase'
}
# Bien respecter les noms des paramètres (host, user, passwd, db)
sql = """\
INSERT INTO matable
(champ1, champ2, champ3)
VALUES ('valeur1', 'valeur2', 'valeur3')
"""
try:
# On créé une conexion MySQL
conn = MySQLdb.connect(**paramMysql)
# On créé un curseur MySQL
cur = conn.cursor()
try:
# On exécute la requête SQL
cur.execute(sql)
# On commit
conn.commit()
except MySQLdb.Error, e:
# En cas d'erreur on annule les modifications
conn.rollback()
except MySQLdb.Error, e:
# En cas d'anomalie
print "Error %d: %s" % (e.args[0],e.args[1])
sys.exit(1)
finally:
# On ferme la connexion
if conn:
conn.close()
Différentes manières d'installer pip pour Python 2 et 3:
Pré-requis:
$ apt-get install build-essential python-dev python3-dev
Pour python 2 (3 méthodes):
$ apt-get install python-pip
$ apt-get install python-setuptools
$ easy_install pip
$ curl https://bootstrap.pypa.io/get-pip.py | python
Pour python 3 (3 méthodes):
$ apt-get install python3-pip
$ apt-get install python3-setuptools
$ easy_install3 pip
$ curl https://bootstrap.pypa.io/get-pip.py | python3
Petite astuce pour ne plus être embêté avec pip quand on a plusieurs version de python installée.
Cette astuce fonctionne avec toutes les versions de python.
Remplacer le x par la version désirée
Lister tous les paquets obsolètes:
$ pythonx -m pip list -o
Installer un paquet:
$ pythonx -m pip install --upgrade mysqlclient
Installer une version spécifique d'un paquet:
$ pythonx -m pip install --upgrade mysqlclient==1.3.14
Comment analyser des données en les regroupant par les valeurs d'une colonne et en les ré-échantillonant, via une colonne "date", à chaque fin de mois.
J'ai un DataFrame Pandas "df1" avec les données suivantes:
>>> df1
DATE USER DUREE
0 2020-09-03 USER#1 0.50
1 2020-09-02 USER#1 0.00
2 2020-09-02 USER#1 0.25
3 2020-09-01 USER#1 0.00
4 2020-09-01 USER#1 0.25
... ... ...
10715 2017-08-01 USER#2 0.75
10716 2017-07-19 USER#2 0.00
10717 2017-07-19 USER#2 0.00
10718 2017-07-17 USER#2 0.25
10719 2017-07-17 USER#2 0.00
[10720 rows x 3 columns]
J'ai donc 3 colonnes, une colonne "DATE", une colonne "USER" et une colonne "DUREE" avec les formats suivants:
>>> df1.dtypes
DATE datetime64[ns]
USER object
DUREE float64
dtype: object
J'aimerais donc connaitre le cumul du temps passé par chaque "USER" à chaque fin de mois et obtenir le résultat suivant:
USER USER#1 USER#2
DATE
2011-04-30 76.25 0.00
2011-05-31 109.75 0.00
2011-06-30 74.00 0.00
2011-07-31 31.25 0.00
2011-08-31 83.75 0.00
... ...
2020-05-31 8.75 17.50
2020-06-30 25.50 18.25
2020-07-31 4.25 38.00
2020-08-31 92.75 5.25
2020-09-30 1.25 4.50
[114 rows x 2 columns]
Ce résultat peut-être obtenu en utilisant tout simplement 5 méthodes disponibles pour les DataFrame.
Premièrement, je ré-index mon DataFrame en utilisant la colonne "DATE" - Indispensable pour le ré-échantillonage
>>> df1.set_index('DATE')
USER DUREE
DATE
2020-09-03 USER#1 0.50
2020-09-02 USER#1 0.00
2020-09-02 USER#1 0.25
2020-09-01 USER#1 0.00
2020-09-01 USER#1 0.25
... ...
2017-08-01 USER#2 0.75
2017-07-19 USER#2 0.00
2017-07-19 USER#2 0.00
2017-07-17 USER#2 0.25
2017-07-17 USER#2 0.00
[10720 rows x 2 columns]
Ensuite, j'effectue mon regroupement sur la colonne "USER"
>>> df1.set_index('DATE').groupby('USER')
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x00000151789EDEB0>
J'obtiens donc un objet "DataFrameGroupBy"
Pour le ré-échantillonage, j'utilise la méthode "resample" qui va agir sur les données contenues dans mon index (par défaut).
Le paramètre "M" va ré-échantilloner mes dates à chaque fin de mois.
>>> df1.set_index('DATE').groupby('USER').resample('M')
<pandas.core.resample.DatetimeIndexResamplerGroupby object at 0x000001517888F3A0>
J'obtiens donc un objet "DatetimeIndexResamplerGroupby"
Je fais maintenant la somme des valeurs de ma colonne "DUREE"
>>> df1.set_index('DATE').groupby('USER').resample('M')['DUREE'].sum()
USER DATE
USER#1 2011-04-30 76.25
2011-05-31 109.75
2011-06-30 74.00
2011-07-31 31.25
2011-08-31 83.75
...
USER#2 2020-05-31 17.50
2020-06-30 18.25
2020-07-31 38.00
2020-08-31 5.25
2020-09-30 4.50
Name: DUREE, Length: 153, dtype: float64
Pour finir, je souhaite transposer le premier niveau "USER" de mon index en colonne.
Pour cela j'utilise la méthode "unstack" avec le paramètre "level=0" pour lui indiquer le premier niveau de mon index et je lui demande de remplacer les valeurs nulles par "0" grâce à "fill_value=0"
>>> df1.set_index('DATE').groupby('USER').resample('M')['DUREE'].sum().unstack(level=0, fill_value=0)
USER USER#1 USER#2
DATE
2011-04-30 76.25 0.00
2011-05-31 109.75 0.00
2011-06-30 74.00 0.00
2011-07-31 31.25 0.00
2011-08-31 83.75 0.00
... ...
2020-05-31 8.75 17.50
2020-06-30 25.50 18.25
2020-07-31 4.25 38.00
2020-08-31 92.75 5.25
2020-09-30 1.25 4.50
[114 rows x 2 columns]
Et voilà, mes données ont été agrégées par "USER" et ré-échantillonées à chaque fin de mois.
Il est possible de filtrer les données par année
>>> df1.set_index('DATE').groupby('USER').resample('M')['DUREE'].sum().unstack(level=0, fill_value=0).loc['2020']
USER USER#1 USER#2
DATE
2020-01-31 25.50 50.50
2020-02-29 35.25 15.25
2020-03-31 33.25 26.75
2020-04-30 6.50 1.25
2020-05-31 8.75 17.50
2020-06-30 25.50 18.25
2020-07-31 4.25 38.00
2020-08-31 92.75 5.25
2020-09-30 1.25 4.50
Ou de calculer le total par "USER"
>>> df1.set_index('DATE').groupby('USER').resample('M')['DUREE'].sum().unstack(level=0, fill_value=0).sum()
USER
USER#1 8219.5
USER#2 2432.5
dtype: float64
Ou de calculer le total par "USER" pour une année précise
>>> df1.set_index('DATE').groupby('USER').resample('M')['DUREE'].sum().unstack(level=0, fill_value=0).loc['2020'].sum()
USER
USER#1 233.00
USER#2 177.25
dtype: float64
Simple comme bonjour.
Merci Pandas
Parser un flux XML afin de le valider, extraire des données et l'afficher à l'écran avec une bonne indentation, tout ceci est possible grâce au module xml.dom.minidom.
Exemple avec Python3 et le flux XML suivant:
>>> flux = '<?xml version="1.0" encoding="UTF-8" standalone="no"?><geonames style="MEDIUM"><totalResultsCount>5</totalResultsCount><geoname><toponymName>Republic of Austria</toponymName><name>Austria</name><lat>47.33333</lat><lng>13.33333</lng><geonameId>2782113</geonameId><countryCode>AT</countryCode><countryName>Austria</countryName><fcl>A</fcl><fcode>PCLI</fcode></geoname><geoname><toponymName>Republic of France</toponymName><name>France</name><lat>46</lat><lng>2</lng><geonameId>3017382</geonameId><countryCode>FR</countryCode><countryName>France</countryName><fcl>A</fcl><fcode>PCLI</fcode></geoname><geoname><toponymName>Federal Republic of Germany</toponymName><name>Germany</name><lat>51.5</lat><lng>10.5</lng><geonameId>2921044</geonameId><countryCode>DE</countryCode><countryName>Germany</countryName><fcl>A</fcl><fcode>PCLI</fcode></geoname><geoname><toponymName>Repubblica Italiana</toponymName><name>Italy</name><lat>42.83333</lat><lng>12.83333</lng><geonameId>3175395</geonameId><countryCode>IT</countryCode><countryName>Italy</countryName><fcl>A</fcl><fcode>PCLI</fcode></geoname><geoname><toponymName>Principality of Liechtenstein</toponymName><name>Liechtenstein</name><lat>47.16667</lat><lng>9.53333</lng><geonameId>3042058</geonameId><countryCode>LI</countryCode><countryName>Liechtenstein</countryName><fcl>A</fcl><fcode>PCLI</fcode></geoname></geonames>'
>>> from xml.dom.minidom import parseString
>>> from pprint import pprint
>>> # La commande suivante permet de créer le parseur et de valider le flux XML par la même occasion
>>> parser = parseString(flux)
>>> # La commande suivante permet d'afficher le flux XML correctement indenté
>>> print(parser.toprettyxml())
<?xml version="1.0" ?>
<geonames style="MEDIUM">
<totalResultsCount>5</totalResultsCount>
<geoname>
<toponymName>Republic of Austria</toponymName>
<name>Austria</name>
<lat>47.33333</lat>
<lng>13.33333</lng>
<geonameId>2782113</geonameId>
<countryCode>AT</countryCode>
<countryName>Austria</countryName>
<fcl>A</fcl>
<fcode>PCLI</fcode>
</geoname>
<geoname>
<toponymName>Republic of France</toponymName>
<name>France</name>
<lat>46</lat>
<lng>2</lng>
<geonameId>3017382</geonameId>
<countryCode>FR</countryCode>
<countryName>France</countryName>
<fcl>A</fcl>
<fcode>PCLI</fcode>
</geoname>
<geoname>
<toponymName>Federal Republic of Germany</toponymName>
<name>Germany</name>
<lat>51.5</lat>
<lng>10.5</lng>
<geonameId>2921044</geonameId>
<countryCode>DE</countryCode>
<countryName>Germany</countryName>
<fcl>A</fcl>
<fcode>PCLI</fcode>
</geoname>
<geoname>
<toponymName>Repubblica Italiana</toponymName>
<name>Italy</name>
<lat>42.83333</lat>
<lng>12.83333</lng>
<geonameId>3175395</geonameId>
<countryCode>IT</countryCode>
<countryName>Italy</countryName>
<fcl>A</fcl>
<fcode>PCLI</fcode>
</geoname>
<geoname>
<toponymName>Principality of Liechtenstein</toponymName>
<name>Liechtenstein</name>
<lat>47.16667</lat>
<lng>9.53333</lng>
<geonameId>3042058</geonameId>
<countryCode>LI</countryCode>
<countryName>Liechtenstein</countryName>
<fcl>A</fcl>
<fcode>PCLI</fcode>
</geoname>
</geonames>
>>> # On peut également en profiter pour écrire le contenu dans un fichier et correctement indenté
>>> with open('monFlux.xml', 'w') as f:
f.write(parser.toprettyxml()
)
1494
>>> # Afficher le nombre total d'éléments (correspond à la valeur de la balise totalResultsCount)
>>> print(parser.getElementsByTagName('totalResultsCount')[0].firstChild.data)
5
>>> # La commande suivante permet d'extraire les données d'un tag précis
>>> # et de les sauvegarder dans un dictionnaire
>>> datas = dict()
>>> geonames = parser.getElementsByTagName('geoname')
>>> for geoname in geonames:
toponymName = geoname.getElementsByTagName('toponymName')[0].firstChild.data
name = geoname.getElementsByTagName('name')[0].firstChild.data
lat = geoname.getElementsByTagName('lat')[0].firstChild.data
lng = geoname.getElementsByTagName('lng')[0].firstChild.data
geonameid = geoname.getElementsByTagName('geonameId')[0].firstChild.data
countrycode = geoname.getElementsByTagName('countryCode')[0].firstChild.data
countryname = geoname.getElementsByTagName('countryName')[0].firstChild.data
fcl = geoname.getElementsByTagName('fcl')[0].firstChild.data
fcode = geoname.getElementsByTagName('fcode')[0].firstChild.data
datas[geonameid] = {'toponymName': toponymName, 'name': name, 'lat': lat, 'lng': lng, 'countryCode': countrycode, 'countryName': countryname, 'fcl': fcl, 'fcode': fcode}
>>> pprint(datas)
{'2782113': {'countryCode': 'AT',
'countryName': 'Austria',
'fcl': 'A',
'fcode': 'PCLI',
'lat': '47.33333',
'lng': '13.33333',
'name': 'Austria',
'toponymName': 'Republic of Austria'},
'2921044': {'countryCode': 'DE',
'countryName': 'Germany',
'fcl': 'A',
'fcode': 'PCLI',
'lat': '51.5',
'lng': '10.5',
'name': 'Germany',
'toponymName': 'Federal Republic of Germany'},
'3017382': {'countryCode': 'FR',
'countryName': 'France',
'fcl': 'A',
'fcode': 'PCLI',
'lat': '46',
'lng': '2',
'name': 'France',
'toponymName': 'Republic of France'},
'3042058': {'countryCode': 'LI',
'countryName': 'Liechtenstein',
'fcl': 'A',
'fcode': 'PCLI',
'lat': '47.16667',
'lng': '9.53333',
'name': 'Liechtenstein',
'toponymName': 'Principality of Liechtenstein'},
'3175395': {'countryCode': 'IT',
'countryName': 'Italy',
'fcl': 'A',
'fcode': 'PCLI',
'lat': '42.83333',
'lng': '12.83333',
'name': 'Italy',
'toponymName': 'Repubblica Italiana'}}
>>>
C'est assez simple dans l'ensemble.
Selon wikipédia, une anagramme est un mot ou une expression obtenu en permutant les lettres d'un mot ou d'une expression de départ.
Pour l'exemple, je vais rechercher tous les anagrammes présents dans l'oeuvre de Jules Verne, "Le Tour Du Monde En 80 Jours".
Première étape, récupérer l'oeuvre au format txt.
Le site gutenberg permet de faire ce genre de choses.
Le module requests permet d'exécuter des requêtes HTTP
$ python3 -m pip install --upgrade requests
>>> import requests
>>> r1 = requests.get('https://www.gutenberg.org/files/800/800-8.txt')
Récupération du texte dans la réponse de la requête et nettoyage de tous les caractères non indispensables.
Le texte commence au 3 941 ème caractères et se termine au 431 252 ème caractères.
Tous les accents sont supprimés.
Tout le texte est converti en minuscule.
La regex recherche tous les mots de 3 caractères et plus.
Tous les mots en double sont supprimés.
Pour la suppression des caractères accentués, j'utilise le module removeaccents
$ python3 -m pip install --upgrade removeaccents
>>> from removeaccents.removeaccents import remove_accents
>>> text = r1.text[3941:431252]
>>> text = remove_accents(text)
>>> text = text.lower()
>>> words = re.findall(r'\b[A-Za-z]{3,}\b', text)
>>> words = list(set(words))
Nous nous retrouvons avec une liste de 8426 mots de 3 caractères et plus.
>>> len(words)
8426
Nous allons maintenant indexer tous les anagrammes dans un dictionnaire.
>>> anagrammes = dict()
>>> for word in words:
letters = ''.join(sorted(word))
if letters in anagrammes:
anagrammes.get(letters).append(word)
else:
anagrammes[letters] = [word]
>>> anagrammes = {k: v for k, v in anagrammes.items() if len(v) > 1}
Nous obtenons une liste de 312 anagrammes
>>> len(anagrammes)
312
Voici la liste des 30 premiers anagrammes triée par ordre décroissant du nombre de mots.
>>> print(sorted(anagrammes.items(), key=lambda x: len(x[1]), reverse=True)[:30])
[('aerstu', ['sauter', 'autres', 'surate']), ('eeprst', ['pertes', 'pester', 'pretes']), ('eeelrv', ['releve', 'revele', 'elever']), ('aeenprst', ['esperant', 'presenta', 'separent']), ('aeiprt', ['partie', 'pirate', 'patrie']), ('efmnort', ['froment', 'forment', 'fremont']), ('aisv', ['vais', 'avis', 'visa']), ('aceinrt', ['crainte', 'ecriant', 'certain']), ('aceerss', ['sacrees', 'caresse', 'cessera']), ('aerrs', ['rares', 'raser', 'serra']), ('aeirstt', ['artiste, 'restait', 'attires']), ('eimprs', ['permis', 'mepris', 'primes']), ('eegnr', ['green', 'genre', 'gener']), ('adegr', ['garde', 'egard', 'grade']), ('eirsv', ['servi', 'viser', 'rives']), ('aeegilns', ['inegales', 'alignees', 'signalee']), ('aegnt', ['agent', 'geant', 'etang']), ('acert', ['ecart', 'trace', 'carte']), ('eeinrtt', ['interet', 'retenti', 'retient']), ('adegnrs', ['grandes', 'dangers', 'gardens']), ('eerrsv', ['verres', 'revers', 'verser']), ('aeirrtt', ['traiter', 'attirer', 'traitre']), ('acers', ['sacre', 'races', 'arecs']), ('ersu', ['user', 'rues', 'ruse']), ('cdeirt', ['decrit', 'credit', 'direct']), ('einru', ['nuire', 'ruine', 'reuni']), ('bceimno', ['combien', 'combine']), ('eirtv', ['vitre', 'revit']), ('aceimnoprt', ['importance', 'comprenait']), ('aceinrst', ['certains', 'craintes'])]
En Python, comme en Bash, il est possible de rediriger tous les flux des commandes d'affichages (print, pprint, etc...) vers un fichier.
Voici un exemple tout simple:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import sys
from pprint import pprint
# We open the log file in writting mode
with open('myLogFile', 'w') as f:
# We redirect the 'sys.stdout' command towards the descriptor file
sys.stdout = f
# Now, all the print commands write directly in the file
print('This is a test.')
D = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
pprint(D)
A = 'Hello, how are you ?\n'
sys.stdout.write(A)
Quand on exécute le script:
# python3 testStdoutToFile.py
# cat myLogFile
This is a test.
{'a': 1, 'b': 2, 'c': 3, 'd': 4}
Hello, how are you ?
Soo simple !
Pour l'exemple je vais appeler un webservice SOAP qui me retourne le résultat d'une addition de deux entiers.
J'ai utilisé SoapUI afin de récupérer le format du fichier XML à utiliser.
Le WSDL du webservice SOAP est disponible à cette adresse http://www.dneonline.com/calculator.asmx?wsdl
Le WSDL contient toutes les informations nécessaires pour l'utilisation du webservice.
>>> import requests
>>> url = 'http://www.dneonline.com/calculator.asmx'
>>> xml = '''\
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:tem="http://tempuri.org/">
<soap:Header/>
<soap:Body>
<tem:Add>
<tem:intA>50</tem:intA>
<tem:intB>20</tem:intB>
</tem:Add>
</soap:Body>
</soap:Envelope>'''
>>> headers = {'content-type': 'application/soap+xml; charset=utf-8'}
>>> r1 = requests.post(url, data=xml, headers=headers)
>>> print(r1.text)
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<AddResponse xmlns="http://tempuri.org/">
<AddResult>70</AddResult>
</AddResponse>
</soap:Body>
</soap:Envelope>
Pour un appel de webservice SOAP, il est très important de modifier le content-type du header de la requête.
Parfois, il faut indiquer 'application/soap+xml; charset=utf-8' et d'autres fois 'text/xml; charset=utf-8'.
Ca dépend du webservice SOAP.
Si le content-type n'est pas correct, l'erreur sera renseignée dans la réponse.
Dans mon exemple, le résultat de l'addition est retourné dans la balise <AddResult>
Voici un petit script en Python qui permet de faire la rotation des fichiers de log d'un serveur Apache.
# -*- coding: UTF-8 -*-
"""
logrotate.py
============
Permet la rotation des logs Apache
:Example:
>>> import logrotate
>>> logrotate.main()
"""
import os, shutil, zipfile
import datetime as dt
F = r'C:\wamp\logs' # Répertoire des logs Apache
LOG = 'logrotate.log' # Fichier de log pour les rotations
TXT = 'ROTATION DU FICHIER' # Texte affiché dans le log des rotations
TXT2 = 'SUPPRESSION DU FICHIER' # Texte affiché dans le log des rotations
TXT3 = 'COMPRESSION DU FICHIER' # Texte affiché dans le log des rotations
NBARCHIVE = 20 # Nombre de fichier de log historisé .0 .1 .2 etc etc ...
def now():
"""
Retourne la date et l'heure courante au format ISO 2018-06-28T10:30:23.816122
:return: La date et heure courante
:rtype: datetime.datetime
"""
return dt.datetime.now().isoformat()
def cleanList(LIST = [], LISTERR = []):
"""
Supprime de la liste ``LIST`` tous les fichiers
correspondant à ceux en erreur présent dans la liste ``LISTERR``
:return: Une nouvelle liste de fichiers nettoyées
:rtype: list
"""
for ERR in LISTERR:
LIST = list(filter(lambda f: not f.startswith(ERR), LIST))
return LIST
def log(FLOG, TXT, FILE, MSG):
"""
Ecrit dans le fichier ``FLOG`` les infos
``TXT``, ``FILE`` et ``MSG`` avec la date courante
"""
print('{DATETIME} {T:<25s} {FILE:<40s} --> {MSG}'.format(DATETIME=now(), FILE=FILE, T=TXT, MSG=MSG), file=FLOG)
def delFile(FILE, FLOG):
"""
Supprime le fichier ``FILE``
"""
try: os.remove(FILE)
except: log(FLOG, TXT2, FILE, 'KO')
else: log(FLOG, TXT2, FILE, 'OK')
def listLog(LIST, EXT):
"""
Extrait tous les fichiers de log avec l'extension ``EXT``
de la liste ``LIST``
:return: Une liste contenant uniquement les fichiers ayant l'extension ``EXT``
:rtype: list
"""
return list(filter(lambda f: f.endswith(EXT), LIST))
def removeUnusedFiles(FLOG):
"""
Supprime tous les fichiers qui ne sont plus concernés par des fichiers LOG
Retourne une nouvelle liste avec les fichiers supprimés en moins
:return: Une nouvelle liste avec les fichiers supprimés en moins
:rtype: list
"""
LIST = os.listdir(F)
LISTLOG = listLog(LIST, '.log')
LISTDEL = list(filter(lambda x: '.'.join(x.split('.')[:2]) not in LISTLOG, LIST))
for FILE in LISTDEL:
delFile(os.path.join(F, FILE), FLOG)
return list(set(LIST) - set(LISTDEL))
def incrementZipFile(LIST, FLOG, LISTERR = []):
"""
Incrémente les archives ZIP jusqu'à ``NBARCHIVE``
:return: Une liste de fichiers en erreur
:rtype: list
"""
LISTLOG = listLog(LIST, '.log')
LISTZIP = listLog(LIST, '.zip')
for f in LISTLOG:
WLIST = sorted(list(filter(lambda x: x.startswith(f), LISTZIP)), key=lambda y: int(y.split('.')[2]), reverse=True)
ERR = False
for FILE in WLIST:
TF = FILE.split('.')
IDX = int(TF[2])
if IDX < NBARCHIVE:
OLDFILE = os.path.join(F, FILE)
TF[2] = str(IDX + 1)
NEWFILE = os.path.join(F, '.'.join(TF))
try:
with zipfile.ZipFile(OLDFILE, mode='r', compression=zipfile.ZIP_DEFLATED) as f1:
try:
CT = f1.read(os.path.splitext(FILE)[0])
except: ERR = True
else:
BASE = os.path.splitext(os.path.basename(NEWFILE))[0]
try:
with zipfile.ZipFile(NEWFILE, mode='w', compression=zipfile.ZIP_DEFLATED) as f2:
f2.writestr(BASE, CT)
except: ERR = True
else: log(FLOG, TXT, OLDFILE, '{NEWFILE}'.format(NEWFILE=NEWFILE))
except: ERR = True
finally:
if ERR:
LISTERR.append(f)
log(FLOG, TXT, OLDFILE, '{NEWFILE} ***KO***'.format(NEWFILE=NEWFILE))
break
return LISTERR
def log_0ToZip(LIST, FLOG, LISTERR = []):
"""
On écrit tout le contenu des fichiers LOG *.log.0
dans de nouveaux fichiers ZIP *.log.1.zip
On supprime ensuite tous les fichiers *.log.0
:return: Une liste de fichiers en erreur
:rtype: list
"""
LISTLOG0 = listLog(LIST, '.log.0')
for FILE in LISTLOG0:
ERR = False
with open(os.path.join(F, FILE), mode='r', encoding='UTF-8') as f1:
BASE = FILE[:-1] + '1'
ZIP = os.path.join(F, BASE + '.zip')
with zipfile.ZipFile(ZIP, 'w', compression=zipfile.ZIP_DEFLATED) as f2:
try: f2.writestr(BASE, f1.read().encode('UTF-8'))
except:
ERR = True
LISTERR.append(FILE[:-2])
log(FLOG, TXT3, os.path.join(F, FILE), '{NEWFILE} ***KO***'.format(NEWFILE=ZIP))
else: log(FLOG, TXT3, os.path.join(F, FILE), '{NEWFILE}'.format(NEWFILE=ZIP))
if not ERR: delFile(os.path.join(F, FILE), FLOG)
return LISTERR
def logTo0(LIST, FLOG):
"""
On écrit tout le contenu des fichiers LOG *.log
dans de nouveaux fichiers numérotés *.log.0
On écrase ensuite le contenu des fichiers LOG
"""
LISTLOG = listLog(LIST, '.log')
for FILE in LISTLOG:
OLDFILE = os.path.join(F, FILE)
NEWFILE = os.path.join(F, FILE + '.0')
try:
with open(OLDFILE, mode='r', encoding='UTF-8') as f1:
with open(NEWFILE, mode='w', encoding='UTF-8') as f2:
f2.write(f1.read())
except: log(FLOG, TXT, OLDFILE, '{NEWFILE} ***KO***'.format(NEWFILE=NEWFILE))
else:
log(FLOG, TXT, OLDFILE, '{NEWFILE}'.format(NEWFILE=NEWFILE))
try:
with open(OLDFILE, mode='w', encoding='UTF-8') as f1:
f1.write('')
except: log(FLOG, TXT2, OLDFILE, 'KO')
else: log(FLOG, TXT2, OLDFILE, 'OK')
def main():
"""
Fonction principale
Pour chaque fichier de log (*.log) trouvé dans le dossier (variable ``F``)
Chaque niveau d'archive est incrémenté de 1 dans la limite du nombre d'archive
indiqué dans la variable ``NBARCHIVE``
"""
with open(os.path.join(F, LOG), mode='a', encoding='UTF-8') as FLOG:
LIST = removeUnusedFiles(FLOG)
LIST.remove(LOG) # On supprime de la liste le fichier de LOG du script
LISTERR = incrementZipFile(LIST, FLOG)
LIST = cleanList(LIST, LISTERR)
LISTERR = log_0ToZip(LIST, FLOG, LISTERR)
LIST = cleanList(LIST, LISTERR)
logTo0(LIST, FLOG)
if __name__ == '__main__':
main()
Et pour l'exécuter (après avoir adapté les variables F, LOG, NBARCHIVE):
$ python3 logrotate.py
Fonctionne sous Linux et Windows
Une tâche cron sous Linux ou une tâche planifiée sous Windows permet d'automatiser l'exécution du script toutes les semaines par exemple.
Le script peut-être téléchargé via le lien ci-dessous
https://git.quennec.fr/ronan/scripts_pub/raw/master/logrotate.py
Par défaut, lors d'une requête select sur une table SQLite, les valeurs des colonnes sont indexées par numéro, la position de la colonne dans le résultat.
>>> import sqlite3
>>> from faker import Faker
>>> fake = Faker('fr_FR')
>>> con = sqlite3.connect(':memory:')
>>> cur = con.cursor()
>>> cur.execute('create table contact (id integer primary key, firstname text, lastname text, email text, mobile_phone text)')
>>> for x in range(100):
cur.execute('insert into contact (firstname, lastname, email, mobile_phone) values (?, ?, ?, ?)', (fake.first_name(), fake.last_name(), fake.email(), fake.phone_number()))
>>> for row in cur.execute('select * from contact where firstname = ?', ('Pierre', )):
print(row)
(38, 'Pierre', 'Collin', 'marc71@bigot.com', '+33 2 53 97 25 45')
>>> cur.execute('select * from contact where firstname = ?', ('Pierre', ))
>>> cur.fetchall()
[(38, 'Pierre', 'Collin', 'marc71@bigot.com', '+33 2 53 97 25 45')
]
L'objet row retourné correspond à un tuple, par conséquent pour obtenir, par exemple, la valeur de l'adresse mail, il faut utiliser l'index correspondant, c'est à dire 3.
>>> for row in cur.execute('select * from contact where firstname = ?', ('Pierre', )):
print(row[3])
marc71@bigot.com
Pour pouvoir utiliser le nom des colonnes, il faut obligatoirement paramétrer la connexion de cette manière:
>>> con.row_factory = sqlite3.Row
>>> cur = con.cursor()
>>> for row in cur.execute('select * from contact where firstname = ?', ('Pierre', )):
drow = dict(row) # on convertit l'objet row en objet dict
print(drow)
{'id': 38, 'firstname': 'Pierre', 'lastname': 'Collin', 'email': 'marc71@bigot.com', 'mobile_phone': '+33 2 53 97 25 45'}
>>> for row in cur.execute('select * from contact where firstname = ?', ('Pierre', )):
# on peut accéder au noms de colonnes directement
print(row['email'])
marc71@bigot.com
>>> cur.execute('select * from contact where firstname = ?', ('Pierre', ))
>>> list(map(dict, cur.fetchall()))
[{'id': 38, 'firstname': 'Pierre', 'lastname': 'Collin', 'email': 'marc71@bigot.com', 'mobile_phone': '+33 2 53 97 25 45'}
]
Avec Python, il est possible d'utiliser une base de donnée SQlite montée en mémoire vive.
Très pratique dans le cas d'une utilisation occasionnelle et non persistente (analyse d'un fichier csv par exemple).
Exemple avec Python3
Pour l'exemple, j'utilise le fichier des codes postaux français.
#!/usr/bin/env python3
import csv
import sqlite3
import time
# Execution start time
start = time.time()
# Fields list
fieldsList = [
'countryCode', 'postalCode', 'placeName', 'adminName1', 'adminCode1', 'adminName2',
'adminCode2', 'adminName3', 'adminCode3', 'latitude', 'longitude', 'accuracy'
]
# Create database in memory
con = sqlite3.connect(':memory:')
cur = con.cursor()
# Create table in database
cur.execute('create table postalCodes \
(id integer primary key, {0} text, {1} text, {2} text, \
{3} text, {4} text, {5} text, {6} text, {7} text, \
{8} text, {9} real, {10} real, {11} integer)'.format(*fieldsList)
)
# Insert request
sqlInsert = 'insert into postalCodes ({0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}, {11}) \
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'.format(*fieldsList)
# Read file and insert values into table 'postalCodes
'
with open('FR.txt', 'r', newline='', encoding='utf-8') as f:
reader = csv.reader(f, delimiter='\t')
for row in reader:
cur.execute(sqlInsert, row)
# Commit
con.commit()
# Execution end time
end = time.time()
# Start of analysis
for row in cur.execute('select count(*) from postalCodes'):
totalLines = row[0]
print("{:n} lines inserted in {:f} sec.".format(totalLines, end - start), end='\n\n')
# Result
51141 lines inserted in 0.493353 sec.
print("All lines with the postal code: '44150'.")
for row in cur.execute('select * from postalCodes where postalCode = ?', ('44150', )):
print(row)
# Result
All lines with the postal code: '44150'.
(27254, 'FR', '44150', 'Saint-Herblon', 'Pays de la Loire', '52', 'Loire-Atlantique', '44', 'Arrondissement d’Ancenis', '444', 47.4079, -1.0974, 5)
(27255, 'FR', '44150', 'Anetz', 'Pays de la Loire', '52', 'Loire-Atlantique', '44', 'Arrondissement d’Ancenis', '444', 47.3806, -1.1058, 5)
(27256, 'FR', '44150', 'Saint-Géréon', 'Pays de la Loire', '52', 'Loire-Atlantique', '44', 'Arrondissement d’Ancenis', '444', 47.3677, -1.2026, 5)
(27257, 'FR', '44150', 'Ancenis', 'Pays de la Loire', '52', 'Loire-Atlantique', '44', 'Arrondissement d’Ancenis', '444', 47.3667, -1.1667, 5)
print()
print("Minimum and maximum postal code for the adminCode2: 'Côtes-d'Armor'.")
for row in cur.execute('select min(postalCode), max(postalCode) from postalCodes where adminName2 = ?', ('Côtes-d\'Armor', )):
print(row)
# Result
Minimum and maximum postal code for the adminCode2: 'Côtes-d'Armor'.
('22000', '22980')
print()
print("The number of placeName by adminName1 and adminName2.")
for row in cur.execute('select adminName1, adminName2, count(placeName) from postalCodes group by adminName1, adminName2'):
print(row)
# Result
The number of placeName by adminName1 and adminName2.
('', '', 1)
('Alsace-Champagne-Ardenne-Lorraine', 'Ardennes', 496)
('Alsace-Champagne-Ardenne-Lorraine', 'Aube', 497)
('Alsace-Champagne-Ardenne-Lorraine', 'Bas-Rhin', 802)
...
('Île-de-France', 'Yvelines', 601)
Super pratique, non !!!
Voici un script Python qui permet, comme le titre l'indique, de parcourir toute l'arborescence d'un serveur FTP à la recherche des dossiers vides et de les supprimer.
Ce script a été testé avec Python 2.7, 3.4 et 3.5.
Détail du script:
La fonction principale qui permet de parcourir, récursivement, l'arborescence du serveur FTP.
def checkFtpEmptyFolders(ftp, dir):
ctn = 0
l = []
ftp.dir(dir, l.append)
l = [x.split() for x in l]
"""
If the list is empty, the folder is empty.
So, deletion of the folder.
"""
if len(l) == 0:
try:
print("Delete folder %s" % dir)
ftp.rmd(dir)
ctn = 1
except:
pass
else:
"""
Browse the folder.
If the item is a folder,
then, recall the function
with the subfolder in parameter.
"""
for x in l:
try:
_dir = (dir + "/" + x[-1]).replace('//', '/')
ftp.cwd(_dir)
ftp.cwd(dir)
ctn = ctn | checkFtpEmptyFolders
(ftp, _dir)
except:
continue
return ctn
Et enfin, la méthode pour appeler cette fonction.
from ftplib import FTP
host = 'monFtp.monDomaine.fr'
user = 'monUser'
passwd = 'monPass'
# Indiquer "/" pour débuter à la racine
dir = '/monDossier'
# Cette variable permet de sortir de la boucle while
# s'il n'y a plus de dossier vide à supprimer
ctn = 1
try:
ftp = FTP(host)
ftp.login(user, passwd)
ftp.set_pasv(1)
ftp.cwd(dir)
while ctn:
ctn = checkFtpEmptyFolders
(ftp, dir)
ftp.quit()
finally:
try:
ftp.close()
except:
pass
Et voilà, tous les dossiers vides, enfin supprimés.
Plusieurs adaptations peuvent être faites à partir de ce script.
En python, l'objet dict ne conserve pas l'ordre dans lequel les éléments sont ajoutés et ne possède pas de fonction sort permettant de trier les données suivants les clés ou les valeurs.
Pour trier un objet dict, il suffit d'utiliser la fonction sorted.
Cette fonction retourne une liste contenant les valeurs triées.
Dans le cas d'un objet dict, les données (clés + valeurs) sont converties en tuple.
Exemple:
>>> d1 = {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'a1': 5, 'b1': 10, 'c1': 8, 'd1': 6}
>>> print(d1)
{'d1': 6, 'c': 3, 'd': 4, 'b': 2, 'a1': 5, 'c1': 8, 'b1': 10, 'a': 1}
On voit que l'ordre d'insertion n'a pas été conservé.
Tri de l'objet dict en fonction des clés
>>> sorted(d1.items(), key=lambda t: t[0])
[('a', 1), ('a1', 5), ('b', 2), ('b1', 10), ('c', 3), ('c1', 8), ('d', 4), ('d1', 6)]
On utilise donc la fonction sorted avec en paramètre la liste des données (clé + valeurs) de l'objet dict (la fonction items() de l'objet dict) et en clé de tri, une fonction lambda indiquant l'indice à utiliser pour le tri.
L'indice '0' (zéro) indique la clé de l'objet dict.
Pour un tri suivant les valeurs, il suffit d'indiquer l'indice '1' (un)
>>> sorted(d1.items(), key=lambda t: t[1])
[('a', 1), ('b', 2), ('c', 3), ('d', 4), ('a1', 5), ('d1', 6), ('c1', 8), ('b1', 10)]
Si les valeurs sont des tuples ou des lists, il suffit d'indiquer, un second indice (x), correspondant à la position sur laquelle on souhaite faire le tri.
>>> sorted(d1.items(), key=lambda t: t[1][x])
Pour conserver l'ordre de tri dans un objet dict, il est obligatoire d'utiliser l'objet OrderedDict du module collections.
>>> from collections import OrderedDict
>>> d2 = OrderedDict(sorted(d1.items(), key=lambda t: t[0]))
>>> print(d2)
OrderedDict([('a', 1), ('a1', 5), ('b', 2), ('b1', 10), ('c', 3), ('c1', 8), ('d', 4), ('d1', 6)])
L'itération d'un objet OrderedDict s'utilise de la même manière que pour un objet dict.
>>> for k, v in d2.items():
print("{:>3s} : {:<d}".format(k, v))
a : 1
a1 : 5
b : 2
b1 : 10
c : 3
c1 : 8
d : 4
d1 : 6
Testé avec Python2 et Python3
BeautifulSoup est un module Python qui permet de manipuler très facilement n'importe quel fichier XML.
Pour l'installer, rien de plus simple que:
$ python3 -m pip install --upgrade bs4
Exemple avec le fichier XML suivant
<?xml version="1.0" encoding="utf-8"?>
<towns>
<town name="Paris"/>
<town name="Lyon"/>
<town name="Marseille"/>
<town name="Nantes"/>
<town name="Ancenis"/>
<town name="Bordeaux"/>
<town name="Toulouse"/>
<town name="Rouen"/>
<town name="Brest"/>
</towns>
On importe le XML à l'aide du module BeautifulSoup
>>> from bs4 import BeautifulSoup as bs
>>> xml = bs("""<?xml version="1.0" encoding="utf-8"?>
<towns>
<town name="Paris"/>
<town name="Lyon"/>
<town name="Marseille"/>
<town name="Nantes"/>
<town name="Ancenis"/>
<town name="Bordeaux"/>
<town name="Toulouse"/>
<town name="Rouen"/>
<town name="Brest"/>
</towns>""", 'xml')
>>> xml
<?xml version="1.0" encoding="utf-8"?>
<towns>
<town name="Paris"/>
<town name="Lyon"/>
<town name="Marseille"/>
<town name="Nantes"/>
<town name="Ancenis"/>
<town name="Bordeaux"/>
<town name="Toulouse"/>
<town name="Rouen"/>
<town name="Brest"/>
</towns>
>>>
Je souhaite trier par ordre croissant toutes les balises town en fonction de la valeur de l'attribut name.
Pour cela, je vais dans un premier temps récupérer toutes les balises town.
>>> towns = xml.findAll('town')
>>> towns
[<town name="Paris"/>, <town name="Lyon"/>, <town name="Marseille"/>, <town name="Nantes"/>, <town name="Ancenis"/>, <town name="Bordeaux"/>, <town name="Toulouse"/>, <town name="Rouen"/>, <town name="Brest"/>]
Me voici donc avec une liste towns contenant toutes mes balises town.
Ensuite, je trie par ordre croissant le contenu de ma liste towns.
>>> towns.sort(key=lambda x: x.get('name'))
>>> towns
[<town name="Ancenis"/>, <town name="Bordeaux"/>, <town name="Brest"/>, <town name="Lyon"/>, <town name="Marseille"/>, <town name="Nantes"/>, <town name="Paris"/>, <town name="Rouen"/>, <town name="Toulouse"/>]
Pour trier correctement le contenu de ma liste, j'utilise le paramètre key de la fonction sort en y indiquant une fonction lambda dans laquelle je lui indique d'utiliser l'attribut name pour effectuer le tri.
Avec BeautifulSoup, pour obtenir la valeur d'un attribut d'une balise, il suffit d'utiliser la méthode get sur n'importe quel élément avec le nom de l'attribut, en l'occurence name.
Dans ma fonction lambda, x étant remplacé par chaque élément town de la liste.
Me voici donc avec ma liste parfaitement trié.
Il suffit donc maintenant de remplacer tout le contenu de la balise towns de mon XML avec le contenu de ma liste.
>>> xml.towns.clear()
>>> xml.towns
<towns/>
>>> xml.towns.extend(towns)
>>> print(xml.prettify())
<?xml version="1.0" encoding="utf-8"?>
<towns>
<town name="Ancenis"/>
<town name="Bordeaux"/>
<town name="Brest"/>
<town name="Lyon"/>
<town name="Marseille"/>
<town name="Nantes"/>
<town name="Paris"/>
<town name="Rouen"/>
<town name="Toulouse"/>
</towns>
J'ai donc utilisé la méthode clear de l'élément towns pour vider tous les éléments town.
Enfin, comme pour une liste Python, la méthode extend de l'élément towns permet d'ajouter tout le contenu de la liste towns correctement triée.
Simple comme bonjour, isn't it....
Sous Python, il est possible d'utiliser dans les scripts des fichiers de paramètres à la manière des fichier INI sous Windows.
Un fichier de paramètres doit contenir une ou plusieurs sections et pour chaque section, une paire paramètre/valeur.
Exemple:
$ cat monFichierDeParametres
[MYSQL]
host: localhost
user: toto
pass: pass4toto
db: maBase
[MAIL]
server: monserveursmtp.fr
from: toto@domaine.com
to: tutu@domaine.com
sujet: bla bla bla
Utilisation dans un script Python:
#!/usr/bin/env python
# -*- coding: utf-8 -*
import ConfigParser # Permet de parser le fichier de paramètres
config = ConfigParser.RawConfigParser() # On créé un nouvel objet "config"
config.read('monFichierDeParametres') # On lit le fichier de paramètres
# On récupère les valeurs des différents paramètres
# ATTENTION, cette syntaxe est spécifique pour les paramètres MySQL
# On créé un dictionnaire contenant les paires clés/valeurs
# Pour chaque paramètre, on utilise la fonction "get" de notre objet "config" en lui indiquant la section et le nom du paramètre
paramMysql = {
'host' : config.get('MYSQL','host'),
'user' : config.get('MYSQL','user'),
'passwd' : config.get('MYSQL','pass'),
'db' : config.get('MYSQL','db')
}
# Récupération basique dans des variables
serveurMail = config.get('MAIL','server')
from = config.get('MAIL','from')
to = config.get('MAIL','to')
sujet = config.get('MAIL','sujet')
C'est quand même vachement pratique.
Pour vérifier la version TLS d'un serveur Python propose 2 modules qui permet de le faire.
Voici un exemple qui permet de controler la version TLS de mon serveur.
import socket import ssl hostname = 'quennec.fr' context = ssl.create_default_context() with socket.create_connection((hostname, 443)) as sock: with context.wrap_socket(sock, server_hostname=hostname) as ssock: print(ssock.version()) TLSv1.2
La version TLS de mon serveur est la 1.2
Cette regex permet de décortiquer une URL et de nommer les différents éléments.
regexp_url = "^(?i)\
(?P<proto>(http(s)*|ftp|ssh))\
(://)\
((?P<user>\w+)(:(?P<password>\w+))?@)?\
(?P<hostname>[\w\.-]+)\
(:(?P<port>[0-9]+))?\
(?P<path>.*)?\
$"
Je l'ai mise sur plusieurs lignes pour mieux la comprendre (d'où les '\' à chaque fin de ligne).
Sur la première ligne, le groupement (?i) permet d'indiquer que la regex doit être insensible à la casse.
La seconde ligne permet de parser et mémoriser le protocole utilisé (http/https/ftp/ssh).
La quatrième ligne permet de parser et mémoriser, si besoin, le user et le password.
La cinquième ligne permet de parser et mémoriser le hostname.
La sixième ligne permet de parser et mémoriser, si besoin, le port.
Pour terminer, la septième ligne permet de parser et mémoriser, si besoin, le path.
Le couple user:password est facultatif ainsi que le port et le path.
Quelques exemples
>>> import re
>>> from pprint import pprint
>>> regexp_url = "^(?i)\
(?P<proto>(http(s)*|ftp|ssh))\
(://)\
((?P<user>\w+)(:(?P<password>\w+))?@)?\
(?P<hostname>[\w\.-]+)\
(:(?P<port>[0-9]+))?\
/\
(?P<path>.*)?\
$"
>>> pprint(re.match(regexp_url, 'https://www.google.fr/').groupdict())
{'hostname': 'www.google.fr',
'password': None,
'path': '/',
'port': None,
'proto': 'https',
'user': None}
>>> pprint(re.match(regexp_url, 'https://mail.google.com/mail/ca/u/0/#inbox').groupdict())
{'hostname': 'mail.google.com',
'password': None,
'path': '/mail/ca/u/0/#inbox',
'port': None,
'proto': 'https',
'user': None}
>>> pprint(re.match(regexp_url, 'http://un-site:8080/greenhis/issues/14431').groupdict())
{'hostname': 'un-site',
'password': None,
'path': '/greenhis/issues/14431',
'port': '8080',
'proto': 'http',
'user': None}
>>> pprint(re.match(regexp_url, 'https://docs.python.org/2/library/re.html#regular-expression-syntax').groupdict())
{'hostname': 'docs.python.org',
'password': None,
'path': '/2/library/re.html#regular-expression-syntax',
'port': None,
'proto': 'https',
'user': None}
>>> pprint(re.match(regexp_url, 'ftp://toto:1234@ftpperso.free.fr/').groupdict())
{'hostname': 'ftpperso.free.fr',
'password': '1234',
'path': '/',
'port': None,
'proto': 'ftp',
'user': 'toto'}
>>> pprint(re.match(regexp_url, 'ftp://toto:1234@ftpperso.free.fr:21/').groupdict())
{'hostname': 'ftpperso.free.fr',
'password': '1234',
'path': '/',
'port': '21',
'proto': 'ftp',
'user': 'toto'}
>>> pprint(re.match(regexp_url, 'ftp://toto@ftpperso.free.fr:21/').groupdict())
{'hostname': 'ftpperso.free.fr',
'password': None,
'path': '/',
'port': '21',
'proto': 'ftp',
'user': 'toto'}
>>>
Penser à partager vos améliorations si besoin.
Les fonctions urlparse et urlsplit de la classe parse du module urllib permettent aproximativement de faire la même chose
>>> import urllib
>>> urllib.parse.urlparse('http://un-site:8080/greenhis/issues/14431')
ParseResult(scheme='http', netloc='un-site:8080', path='/greenhis/issues/14431', params='', query='', fragment='')
>>> urllib.parse.urlparse('ftp://toto:1234@ftpperso.free.fr:21/')
ParseResult(scheme='ftp', netloc='toto:1234@ftpperso.free.fr:21', path='/', params='', query='', fragment='')
>>> urllib.parse.urlsplit('ftp://toto:1234@ftpperso.free.fr:21/')
SplitResult(scheme='ftp', netloc='toto:1234@ftpperso.free.fr:21', path='/', query='', fragment='')
Compatible Python2 & Python3
Exemple avec le contenu de la variable "d" organisée sous la forme d'un tableau
>>> print(d)
2015 0 391371 374179 765550
2014 1 403204 385442 788646
2013 2 405502 386831 792333
2005 10 426532 406488 833020
2004 11 423605 404592 828197
2003 12 421432 402484 823916
1929 86 97489 180249 277738
1928 87 86122 165344 251466
1927 88 72603 149288 221891
1919 96 6011 21172 27183
1918 97 3649 13644 17293
1917 98 2013 9475 11488
1916 99 1324 6313 7637
>>>
Chaque ligne contient exactement le même nombre de données.
Chaque donnée (de longueur fixe) se trouve exactement à la même place sur chaque ligne.
Dans l'exemple, chaque ligne contient donc 5 colonnes.
>>> print(columns)
['Année de naissance', 'Age révolu', "Nombre d'hommes", 'Nombre de femmes', 'Ensemble']
>>>
Les données de la première colonne commencent à l'index 0
Les données de la seconde colonne commencent à l'index 5
Les données de la troisième colonne commencent à l'index 8
Les données de la quatrième colonne commencent à l'index 15
Et enfin, les données de la cinquième colonne commencent à l'index 22
Et c'est pareil pour chaque ligne.
>>> print(i)
[0, 5, 8, 15, 22]
>>>
Une petite boucle va permettre d'extraire toutes les données en fonction de la liste d'index ci-dessus
>>> for line in d.split('\n'):
x = 0
for pos in i:
print("{COLUMN:>20s} : {DATA:<6s}".format(COLUMN=columns[x], DATA=line[pos:i[i.index(pos)+1] if i.index(pos)+1 < len(i) else None].strip()))
x += 1
print('*' * 31)
Année de naissance : 2015
Age révolu : 0
Nombre d'hommes : 391371
Nombre de femmes : 374179
Ensemble : 765550
*******************************
Année de naissance : 2014
Age révolu : 1
Nombre d'hommes : 403204
Nombre de femmes : 385442
Ensemble : 788646
*******************************
Année de naissance : 2013
Age révolu : 2
Nombre d'hommes : 405502
Nombre de femmes : 386831
Ensemble : 792333
*******************************
Année de naissance : 2005
Age révolu : 10
Nombre d'hommes : 426532
Nombre de femmes : 406488
Ensemble : 833020
*******************************
Année de naissance : 2004
Age révolu : 11
Nombre d'hommes : 423605
Nombre de femmes : 404592
Ensemble : 828197
*******************************
Année de naissance : 2003
Age révolu : 12
Nombre d'hommes : 421432
Nombre de femmes : 402484
Ensemble : 823916
*******************************
Année de naissance : 1929
Age révolu : 86
Nombre d'hommes : 97489
Nombre de femmes : 180249
Ensemble : 277738
*******************************
Année de naissance : 1928
Age révolu : 87
Nombre d'hommes : 86122
Nombre de femmes : 165344
Ensemble : 251466
*******************************
Année de naissance : 1927
Age révolu : 88
Nombre d'hommes : 72603
Nombre de femmes : 149288
Ensemble : 221891
*******************************
Année de naissance : 1919
Age révolu : 96
Nombre d'hommes : 6011
Nombre de femmes : 21172
Ensemble : 27183
*******************************
Année de naissance : 1918
Age révolu : 97
Nombre d'hommes : 3649
Nombre de femmes : 13644
Ensemble : 17293
*******************************
Année de naissance : 1917
Age révolu : 98
Nombre d'hommes : 2013
Nombre de femmes : 9475
Ensemble : 11488
*******************************
Année de naissance : 1916
Age révolu : 99
Nombre d'hommes : 1324
Nombre de femmes : 6313
Ensemble : 7637
*******************************
>>>
glances est un module python qui permet d'afficher un état détaillé du système, rafraichit automatiquement, à la manière de la commande htop.
Pré requis:
$ apt-get install python
$ apt-get install python-dev
$ apt-get install python-pip
$ pip install psutil
Pour l'installer:
$ pip install glances
Pour les mises à jour:
$ pip install psutil --upgrade
$ pip install glances --upgrade
Pour l'exécuter:
$ glances
Résultat:
Vraiment très pratique.
Comment connaitre le fuseau horaire à partir d'une latitude et d'une longitude ?
Grâce au module timezonefinder.
# python3 -m pip install --upgrade timezonefinder
Et comment fonctionne-t-il ?
Tout simplement de cette manière:
Par exemple, avec les coordonnées de Montréal (Canada)
>>> from timezonefinder import TimezoneFinder
>>> timezoneid = TimezoneFinder().timezone_at(lat=45.5704, lng=-73.7674)
>>> timezoneid
'America/Toronto'
Avec les coordonnées de Tokyo (Japon)
>>> timezoneid = TimezoneFinder().timezone_at(lat=35.876, lng=136.912)
>>> timezoneid
'Asia/Tokyo'
Avec les coordonnées de Nantes (France)
>>> timezoneid = TimezoneFinder().timezone_at(lat=47.2383, lng=-1.5603)
>>> timezoneid
'Europe/Paris'
pydf est un module python qui permet d'afficher à l'écran des statistiques sur l'espace disque (version évoluée de la commande df).
Pré requis:
$ apt-get install python
$ apt-get install python-dev
$ apt-get install python-pip
Pour l'installer:
$ pip install pydf
Pour les mises à jour:
$ pip install pydf --upgrade
Pour l'exécuter:
$ pydf
Résultat:
Plutôt sympa et vraiment très utile.
Juste comme ça, pour s'amuser.
Voici un script Python 'pytail.py' qui fait exactement la même chose que la commande tail -f /path/to/log/file sous Linux, sauf que ce script peut-être utilisé sur tous les systèmes où la commande tail -f fait défaut (comme sous Windows par exemple).
Testé avec Python 3
#!/usr/bin/env python3
import re, os, time, sys
from time import time, sleep
from os.path import join, getsize, getmtime
if len(sys.argv) == 1:
print("Argument Missing !")
print("Usage:\n {0:s} /path/to/log/file".format(sys.argv[0]))
sys.exit(1)
startTime = time()
mTimeStart = 0
fileName = sys.argv[1]
if not os.path.exists(fileName) or not os.path.isfile(fileName):
print("This file '{0:s}' does not exist !".format(fileName))
sys.exit(2)
try:
with open(fileName, 'r') as f:
f.read(1)
except Exception as e:
print("This file '{0:s}' cannot be opened for reading !".format(fileName))
sys.exit(3)
folderLogs = os.path.dirname(fileName)
copyLines = []
try:
while True:
with open(fileName, 'r') as f:
lines = f.readlines()
show = False
if len(copyLines) == 0:
showLines = lines[-10:]
show = True
elif len(copyLines) != len(lines):
showLines = lines[len(copyLines) - len(lines):]
show = True
if show:
copyLines = lines.copy()
for line in showLines:
print(line, end='')
sleep(1)
except KeyboardInterrupt as e:
print("Program stopped by user !")
sys.exit(0)
except Exception as e:
print("Unknown error during execution !")
print(e)
sys.exit(4)
Pour l'exécuter:
# python3 pytail.py /var/log/mail.log
Merci qui, merci Python.