PROJET AUTOBLOG


Sam & Max: Python, Django, Git et du cul

Site original : Sam & Max: Python, Django, Git et du cul

⇐ retour index

Mise à jour

Mise à jour de la base de données, veuillez patienter...

Le don du mois : sublime text 13

samedi 4 juillet 2015 à 14:20

Se faire plaisir et faire plaisir aux autres n’est pas incompatible.

Par exemple, quand Sublime Text est sorti j’ai pris un an avant d’acheter la licence. Là, j’ai sublime text 3 depuis à peu près la même période sur mon ordi, et je vais me payer la licence.

Pour moi, c’est une forme de don. En effet, l’auteur permet d’utiliser 100% de son logiciel pour toujours, et sans rien raquer. La seule chose, c’est que toutes les X sauvegardes, une pop up apparaît pour vous demander si vous voulez l’acheter. Vous pouvez ne jamais le faire. Beaucoup ne le font jamais.

Aujourd’hui, je vais faire sauter la pop up, et je remercie l’auteur pour me fournir un joujou que j’adore.

N’oubliez pas, si vous utilisez un IDE commercial (PyCharm, Komodo, etc), acheter la licence vous apporte des choses à vous, mais aussi, est un bon moyen de témoigner votre reconnaissance aux auteurs. Après tout, vous gagnez votre vie en partie grace à eux.

Si votre éditeur est gratuit, voire libre (je pense aux trolls de VI et Emacs qui frétillent dans les commentaires), pensez à faire un don à ces projets. Le fait qu’ils soient bien établis ne veut pas dire qu’il n’en ont pas besoin, bien au contraire.

Sublime n’est pas donné, et je paie de ma poche 70$, avec plaisir néanmoins, car cet outil les vaut largement.

Bref, la morale de ce mois, c’est que les donations ne sont pas uniquement pour les groupes à but non lucratif. Vous pouvez donnez à des boîtes, des administrations, des clodos dans la rue. Vous pouvez racheter un livre que vous avez déjà lu, offrir un morceau que vous avez déjà écouté. Le don n’a pas de forme propre, la seule condition étant l’intention.

A propos des attributs préfixés de deux underscores 2

vendredi 3 juillet 2015 à 10:18

En Python, il n’y a pas d’attributs privés au sens propre, uniquement une convention disant que tout ce qu’on préfixe d’un underscore ne fait pas partie de l’API publique.

Cette convention est tellement bien établie que les outils de génération de documentation et de complétion de code la prennent souvent en compte.

Ca fait partie de la philosophie du langage : tout est ouvert. Guido parle même d’open kimono, le petit coquin.

Pourtant il existe une fonctionnalité qui semble rendre un attribut privé, qui est d’utiliser un préfix de DEUX underscores :

class Yo(object):
    def __init__(self):
        self.__bitch = True
>>> Yo().__bitch
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-5-225402ba2794> in <module>()
----> 1 Yo().__bitch
 
AttributeError: 'Yo' object has no attribute '__bitch'

En fait, l’attribut n’a pas été rendu privé, mais son mécanisme de résolution de nom a été changé :

>>> Yo()._Yo__bitch
    True

C’est ce qu’on appelle le name mangling, et cette obscure fonctionnalité a été utilisée pour s’assurer qu’un attribut ne sera pas écrasé par erreur par une classe enfant.

L’attribut n’est pas privé, il est juste accessible autrement, c’est tout.

Compter et grouper : encore plus fainéant 5

mercredi 1 juillet 2015 à 21:37

Après avoir bien galéré à créer un compteur à la main avec un dico, vous avez découvert les joies des méthodes dict.get et dict.setdefault. Puis évidemment quelqu’un vous a pointé vers collections.defaultdict, et enfin, vous avez fini par découvrir collections.Counter. Joie.

Le parcours est à peu près toujours le même quand on veut grouper ou compter des valeurs en Python.

Malgré cela, je vois encore des gens qui sous utilisent ces collections. Par exemple, Counter peut compter automatiquement :

>>> from collections import Counter
>>> Counter('jfsqmfjdklmqfjsdqklmfjdsqhfdqsjkhfdshjkl')
    Counter({'j': 6, 'f': 6, 'q': 5, 's': 5, 'd': 5, 'k': 4, 'l': 3, 'm': 3, 'h': 3})

Mais ce que ne réalisent pas beaucoup de développeurs, c’est que cet objet accepte n’importe quel itérable en paramètre. Nous sommes en Python, et rededjiou, je me tue à répéter que l’itération est la philosophie centrale du langage.

Donc le compteur peut prendre une expression génératrice en paramètre.

Par exemple, si vous voulez compter un truc un peu plus complexe que des éléments, comme mettons, le ratio de lignes commentées dans un fichier, vous n’avez pas besoin de faire ça :

count = Counter()
for line in open('/etc/fstab', encoding='ascii'):
        count[line.startswith('#')] += 1
 # out : Counter({True: 10, False: 3})

Ceci marchera parfaitement :

count = Counter(line.startswith('#') for line in open('/etc/fstab', encoding='ascii'))
# out : Counter({True: 10, False: 3})

Vous pouvez également utiliser des générateurs plus complexes. Combien de fichiers par types d’extensions ?

import os
import pathlib
 
def get_extensions(path):
    for dirpath, dirnames, files in os.walk(path):
        for name in files:
            ext = pathlib.Path(name).suffix
            if ext: # on ignore les fichiers sans extension
                yield ext
 
 
Counter(get_extensions('/etc')).most_common(9)
 # Out : 
 # ('.conf', 632),
 # ('.0', 348),
 # ('.gz', 323),
 # ('.jhansonxi', 207),
 # ('.pem', 177),
 # ('.load', 127),
 # ('.ttb', 86),
 # ('.ktb', 80),
 # ('.kti', 55)]

Notez que le Counter peut faire plus que compter. Ici il nous donne les 9 plus grandes valeurs du classement, mais en prime, il peut aussi nous faire des opérations ensemblistes :

>>> c = Counter("aabbbbbbbbbbbbcccc")
>>> c & Counter('aaaaaaaaaaaaaaabbcddddddd') # valeurs min
    Counter({'b': 2, 'a': 2, 'c': 1})
>>> c | Counter('aaaaaaaaaaaaaaabbcddddddd') # valeurs max
    Counter({'a': 15, 'b': 12, 'd': 7, 'c': 4})

Le compteur fournit par Python est donc naturellement très, très puissant.

Une autre chose qui est rarement faite : sous-classer ces types.

Par exemple, si vous avez souvent des opérations où il faut grouper des valeurs :

from collections import defaultdict
 
class Grouper(defaultdict):
 
    def __init__(self, iterable):
        super(Grouper, self).__init__(list)
        self.update(iterable)
 
    def update(self, iterable):
        try:
            iterable = iterable.items()
        except AttributeError:
            iterable = iterable
        for k, v in iterable:
            self[k].append(v)

On prend un default dict, on lui dit qu’un update ajoute les éléments à la liste en valeur plutôt que de la remplacer, et zou, vous avez un dictionnaire qui va grouper toutes les valeurs automatiquement.

Liste des fichiers par extensions ? Fastoche !

def get_extensions(path):
    for dirpath, dirnames, files in os.walk(path):
        for name in files:
            ext = pathlib.Path(name).suffix
            if ext: 
                yield ext, name # on rajoute le name ici
 
>>>files = Grouper(get_extensions('/etc'))
>>> files['.tti']
['en-na-ascii.tti',
 'numbers-french.tti',
 'devanagari.tti',
 'letters-cyrillic.tti',
 'punctuation-basic.tti',
 'malayalam.tti',
 'ascii-basic.tti',
 'spaces.tti',
 'letters-latin.tti',
 'letters-latin-dot8.tti',
 'en-chess.tti',
 'numbers-dot8.tti',
 'punctuation-tibetan.tti',
 'boxes.tti',
 'gujarati.tti',
 'numbers-nemeth.tti',
 'punctuation-alternate.tti',
 'common.tti',
 'blocks.tti',
 'gurmukhi.tti',
 'kannada.tti',
 'telugu.tti',
 'tamil.tti',
 'numbers-dot6.tti',
 'de-chess.tti',
 'control-latin.tti',
 'letters-tibetan.tti',
 'oriya.tti',
 'bengali.tti']

Bref, compter et grouper sont des opérations si communes : ne vous faites par chier à refaire tout ça à la main.

Quelques astuces à propos de and et or 19

mardi 30 juin 2015 à 15:46

Dans beaucoup de langages populaires, and et or sont écrits && et ||. Ces symboles existent en Python, mais ils sont là pour appliquer des opérations binaires :

>>> bin(0b010 & 0b111)
'0b10'
 
>>> bin(0b010 | 0b111)
'0b111'

Ce n’est néanmoins pas la seule bizarrerie de Python dans le domaine.

Shortcuts

Les opérateurs and et or court-circuitent les conditions dès que possible, c’est à dire qu’ils retournent la valeur au plus tôt, même si ça signifie ne pas exécuter tout le code.

Par exemple, prenons deux fonctions:

def vrai():
    print('Yeah !')
    return True
 
def faux():
    print('Errrr...')
    return False

Si je fais un or dessus, ça va me retourner True, et afficher deux messages :

>>> faux() or vrai()
Errrr...
Yeah !
True

Mais si j’INVERSE les deux fonctions, alors je n’aurais qu’un seul message qui va s’afficher :

>>> vrai() or faux()
Yeah !
True

La raison est que or sait qu’il peut retourner True dès qu’il obtient au moins une valeur True. vrai() retourne True, donc or sait que tout la condition sera forcément vraie, et il n’exécute pas le code du reste de la condition. Ainsi, faux() n’est jamais appelée.

and fait pareil :

>>> vrai() and faux()
Yeah !
Errrr...
False

Et à l’envers :

>>> faux() and vrai()
Errrr...
False

Car dans le second cas, and sait qu’il doit avoir toutes les valeurs à True pour renvoyer True. Comme il reçoit False dès le premier test, il ne va pas plus loin, et vrai() n’est jamais appelée.

Le but de cette fonctionnalité est d’autoriser le développeur à mettre les fonctions qui sont les plus gourmandes en ressource tout à droite de la condition, ainsi elle ne seront pas toujours appelées, ce qui améliore les perfs.

Si vous avez besoin que les fonctions soient toujours appelées car elles ont des effets de bord (c’est mal, boooouh !), il suffit de mettre leurs résultats dans des variables :

>>> a = vrai()
Yeah !
>>> b = faux()
Errrr...
>>> b and a
False

Pas de bool

La plupart des opérateurs utilisés pour faire des tests retournent des booléans :

>>> 1 > 2
False
 
>>> "a" in "chat"
True

Mais and et or ne retournent pas des booléans. Dès qu’ils sont certains du résultats de la condition, ils retournent la valeurs qu’ils ont sous la main.

Cela est du au fait qu’en Python, tout a une valeur True ou False dans un contexte booléen. Pour faire simple, n’importe quel objet mis dans une condition vaut soit True, soit False.

Par exemple, une liste vide vaut False dans une condition, une liste non vide vaut True :

>>> couleurs = []
>>> if couleurs:
   ...:     print("J'ai une couleur !")
>>> couleurs.append('rouge')
>>> if couleurs:
    print("J'ai une couleur !")
J'ai une couleur !

On peut le vérifier facilement :

>>> bool([])
    False
>>> bool(['rouge'])
    True

Il est facile de se souvenir de ce qui est faux ou vrai en Python. False, None, 0 et tout ce qui est vide est faux :

>>> for x in (False, None, 0, "", [], set(), {}, ()):
   ...:     print(type(x), bool(x))
   ...:     
<class 'bool'>, False
<class 'NoneType'>, False
<class 'int'>, False
<class 'str'>, False
<class 'list'>, False
<class 'set'>, False
<class 'dict'>, False
<class 'tuple'>, False

Tout le reste est vrai :

>>> for x in (Ellipsis, True, 432, "foo", ["bar"],  set("ba"), {"pa": "pa"}, 
              ("doh",), lambda : None, len):
    print(type(x), bool(x))
<class 'ellipsis'> True
<class 'bool'> True
<class 'int'> True
<class 'str'> True
<class 'list'> True
<class 'set'> True
<class 'dict'> True
<class 'tuple'> True
<class 'function'> True
<class 'builtin_function_or_method'> True

Du coup, and et or vont vérifier la valeur de chaque objet de la condition, et retourner le premier à partir duquel ils sont certains du résultat de la condition entière.

Par exemple, si je fais :

>>> True and True and False and False
    False

and n’est certain que la condition est fausse qu’au moment où on attend le premier False. C’est donc ce False qu’il retourne.

Cela est beaucoup plus clair quand on le fait avec des objets plus complexes :

>>> "a" and 1 and [] and {}
    []

Puisque :

>>> bool('a')
    True
>>> bool(1)
    True
>>> bool([])
    False
>>> bool({})
    False

and n’est certain du résultat de la condition qu’en arrivant sur [], qu’il retourne.

Si tous les éléments sont vrais, il va donc prendre le dernier :

>>> "a" and 1 and True and [1, 2, 3]
    [1, 2, 3]

C’est la même chose pour or :

>>> "" or None or False or 0
    0

Là, or ne peut pas savoir si la condition est fausse avant d’arriver au tout dernier élément, qu’il retourne.

Mais si je glisse un truc vrai dans le lot :

>>> "" or {1: 2} or False or 0
    {1: 2}

Comme il n’a besoin que d’un élément vrai pour que toute la condition soit vraie, dès qu’il en rencontre un, il le retourne.

Il n’y a pas de XOR

Le “ou” exclusif, opération qui retourne vrai seulement si un élément est vrai mais pas l’autre, n’existe pas sous la forme d’un opérateur en Python. Évidement on peut l’émuler manuellement :

def xor(a, b):
    return (a and not b) or (not a and b)

Mais une astuce de sioux permet un résultat plus court avec une syntaxe un poil plus proche des langages qui possèdent cet opérateur :

bool(a) ^ bool(b)

Exemple :

>>> bool(['pomme']) ^ bool([])
    True
>>> bool(['pomme']) ^ bool(['banane'])
    False

^ est en effet l’opérateur XOR pour les opérations binaires. La partie marrante, c’est qu’en Python :

>>> True == 1
    True
>>> False == 0
    True

Et comme :

>>> 1 ^ 1
    0
>>> 1 ^ 0
    1

Alors:

>>> True ^ True
    False
>>> True ^ False
    True

On obtient le résultat voulu.

Oui, c’est un peu tordu, je vous l’accorde.

Lire un format binaire en Python avec struct 22

vendredi 26 juin 2015 à 08:51

Une suite de valeurs ne veut rien dire en soi, et même le sacro-saint binaire supposé être le socle de toute l’informatique n’a aucun sens si on ne connaît pas le format utilisé pour ce qu’il doit représenter.

Toujours la même opposition entre données et représentation.

Par exemple, le binaire peut représenter un chiffre en base 2 ou un texte encodé.

Pour autant, cela ne veut pas dire qu’il n’existe pas des formats prépondérant. En informatique, beaucoup de données binaires sont organisées pour correspondre aux structures de données du langage C, ces dernières étant une implémentation du standard IEEE 754 (en effet les strings sont des arrays d’int en C, donc le texte et les nombres sont des suites de chiffres).

Par exemple, si vous créez un array numpy contenant des nombres de 0 à 1000 stockés en int32 et sauvegardez son contenu dans un fichier :

>>> import numpy
>>> numpy.arange(0, 1000, dtype=np.int32).tofile('/tmp/data')

Le fichier va ici contenir une suite de 1 et de 0 représentant 1000 entiers, chacun comme un paquet de 4 octets organisés selon la sémantique que comprend le langage C.

Pour avoir une idée de l’organisation du contenu, on peut prendre un éditeur hexa qui vous affichera :

0000 0000 0100 0000 0200 0000 0300 0000 0400 0000 0500 0000 0600 0000 0700 0000 0800 0000 0900 0000 0a00 0000 0b00 0000 0c00 0000 0d00 0000 0e00 0000 0f00 0000 1000 0000 1100 0000 1200 0000 1300 0000

Ça se lit ainsi :

0000 0000 => 0
0100 0000 => 1
0200 0000 => 2
0300 0000 => 3
0400 0000 => 4
0500 0000 => 5
0600 0000 => 6
0700 0000 => 7
0800 0000 => 8
0900 0000 => 9
0a00 0000 => 10
0b00 0000 => 11
0c00 0000 => 12
0d00 0000 => 13
0e00 0000 => 14
0f00 0000 => 15
1000 0000 => 16
1100 0000 => 17
1200 0000 => 18
1300 0000 => 19
...

Numpy étant codé en C, cela semble plutôt logique qu’il dump tout ça dans ce format.

Mais c’est une représentation tellement courante que de nombreux formats standards l’utilisent. Par exemple, les archives et les images stockent souvent leurs données ainsi.

Prenez le format d’image PNG, la RFC indique que la taille de l’image est stockée dans le fichier sous la forme de deux entiers représentés par 4 octets chacun, ordonnés en big-endian, entre l’octet 16 et l’octet 24.

On peut donc récupérer ces informations en lisant son fichier image :

with open('image.png', 'rb') as f:
    taille = f.read(24)[16:24]

Le problème étant : comment lire cette info ? C’est un blob binaire qui ne veut rien dire pour Python :

print(taille)
b'\x00\x00\x07\x80\x00\x00\x048'

Le module struct est fait pour ça, on lui passe une donnée au format structure C, et il la convertit en type Python. Cela marche ansi, pardon, ainsi :

struct.unpack('motif_du_format_a_convertir', donnee)

Le format à convertir est une chaîne de caractères qui contient des symboles décrivant la structure de la donnée qu’on souhaite récupérer. Little-endian ou big-endian ? String, Int, Bool ?

Pour la taille de la photo, on sait qu’il y a deux entiers, non signés (une taille ne va pas être négative), en big-endian. D’après la doc de struct, on peut lui désigner un entier non signé avec ‘I’, et il faut les qualifier avec ‘>’ pour l’ordre big-endian. Du coup:

taille = struct.unpack('>II', taille)
print(taille)
(1920, 1080)

Il se trouve que mon image de test est un screenshot et que mon écran a une résolution de 1920×1080 :)

On peut faire l’opération inverse avec struct.pack, et bien entendu manipuler des formats plus complexes : il suffit de changer le motif qui représente le format à convertir.

Error happened! 0 - count(): Argument #1 ($value) must be of type Countable|array, null given In: /var/www/ecirtam.net/autoblogs/autoblogs/autoblog.php:428 http://ecirtam.net/autoblogs/autoblogs/sametmaxcom_a844ada43a979e3b1395ab9acb6afafb84340999/?66 #0 /var/www/ecirtam.net/autoblogs/autoblogs/autoblog.php(999): VroumVroum_Blog->update() #1 /var/www/ecirtam.net/autoblogs/autoblogs/sametmaxcom_a844ada43a979e3b1395ab9acb6afafb84340999/index.php(1): require_once('...') #2 {main}