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 : nuitka, bis   Recently updated !

dimanche 12 février 2017 à 17:28

Ça faisait un bail que j’avais pas parlé d’un don du mois. Le don du mois n’est pas un don mensuel, mais un don que je fais pour le mois. Des fois j’oublie. Des fois je n’ai pas de thune. Des fois je ne me sens pas généreux, que l’humanité aille crever dans sa crasse !

Mais quand les astres du pognon et de la bonne humeur sont alignés je m’y remets.

J’ai pris des nouvelles de nuitka, un outil qui permet de compiler du code Python en un exe indépendant. Malgré le fait que l’auteur soit visiblement le seul à vraiment travailler dessus, le projet continue d’avancer avec régularité et détermination. Corrections de bugs, optimisation (amélioration de la comptabilité (la 3.5 est supportée, la 3.6 en cours !)).

J’ai été agréablement surpris de voir que l’outil s’était encore amélioré. Le hello world stand alone m’a pris quelques minutes à mettre en œuvre, d’autant que nuitka est dans les dépôts Ubuntu donc l’installation ne demande aucun effort.

Comme pouvoir shipper du code Python sans demander à l’utilisateur final d’installer Python est quelque chose qui est en forte demande en ce moment, j’ai voulu soutenir le projet, et j’ai fait un don de 50 euros.

Puis je me suis souvenu qu’en fait, j’en avais déjà fait un l’année dernière :) Bah, l’auteur mérite qu’on le soutienne. Des mecs comme ça y en a pas des masses.

Le PEP8 et au delà, par la pratique 9   Recently updated !

vendredi 3 février 2017 à 11:44

Je lis régulièrement des commentaires de batailles d’opinions sur le PEP8. Pas mal sont en fait dues à un manque de compréhension de son application. Je vais donc vous montrer des codes et les transformer pour qu’ils soient plus propres.

Rappelez-vous toujours que des recommandations stylistiques sont essentiellement arbitraires. Elles servent à avoir une base commune, et il n’y en pas de meilleures.

On recommande les espaces au lieu des tabs. Les tabs sont sémantiquement plus adaptés et plus flexibles. Les espaces permettent d’avoir un seul type de caractères non imprimables dans son code et autorise un alignement fin d’une expression divisée en plusieurs lignes. Il n’y a pas de meilleur choix. Juste un choix tranché pour passer à autre chose de plus productif, comme coder.

On recommande par contre une limite de 80 caractères pour les lignes. Cela permet à l’œil, qui scanne par micro-sauts, de parser plus facilement le code. Mais aussi de faciliter le multi-fenêtrage. Néanmoins cette limite peut être brisée ponctuellement si le coût pour la lisibilité du code est trop important. Tout est une question d’équilibre.

Ready ? Oh yeah !

L’espacement

def  foo (bar = 'test'):
   if bar=='test' : # this is a test
      bar={1:2 , 3 : 4*4}

Devient:

def foo(bar='test'):
    if bar == 'test':  # this is a test
        bar = {1: 2, 3: 4*4}

On ne double pas les espaces. On n’a pas d’espace avant le ‘:’ ou le ‘,’ mais un après. Les opérateurs sont entourés d’espaces, sauf le ‘=’ quand il est utilisé dans la signature de la fonction ou pour les opérations mathématiques (pour ces dernières, les deux sont tolérés).

Un commentaire inline est précédé de 2 espaces, une indentation est 4 espaces.

Les sauts de lignes aussi sont importants:

import stuff
 
def foo():
    pass
 
def bar():
    pass

Devient:

import stuff
 
 
def foo():
    pass
 
 
def bar():
    pass

Les déclarations à la racine du fichier sont séparées de 2 lignes. Pas plus, pas moins.

Mais à l’intérieur d’une classe, on sépare les méthodes d’une seule ligne.

class Foo:
 
 
    def bar1():
        pass
 
 
    def bar2():
        pass

Devient:

class Foo:
 
    def bar1():
        pass
 
    def bar2():
        pass

Tout cela contribue à donner un rythme régulier et familier au code. Le cerveau humain adore les motifs, et une fois qu’il est ancré, il est facile à reconnaître et demande peu d’effort à traiter.

Les espaces servent aussi à grouper les choses qui sont liées, et à séparer les choses qui ne le sont pas.

Le style de saut de ligne à l’intérieur d’une fonction ou méthode est libre, mais évitez de sauter deux lignes d’un coup.

Le nom des variables

Si on s’en tient au PEP8, on fait du snake case:

def pasUnTrucCommeCa():
    niCommeCa = True
 
def mais_un_truc_comme_ca():
    ou_comme_ca = True

Sauf pour les classes:

class UnTrucCommeCaEstBon:

Et si on a un acronyme:

def lower_case_lol_ptdr():
    ...
 
class UpperCaseLOLPTDR:
    ...

Malgré la possibilité d’utiliser des caractères non ASCII dans les noms en Python 3, ce n’est pas recommandé. Même si c’est tentant de faire:

>>> Σ = lambda *x: sum(x)
>>> Σ(1, 2, 3)
6

Néanmoins c’est l’arbre qui cache la forêt. Il y a plus important : donner un contexte à son code.

Si on a:

numbers = (random.randint(100) for _ in range(100))
group = lambda x: sum(map(int, str(x)))
numbers = (math.sqrt(x) for x in numbers if group(x) == 9)

Le contexte du code donne peu d’informations et il faut lire toutes les instructions pour bien comprendre ce qui se passe. On peut faire mieux:

def digits_sum(number):
    """ Take a number xyz and return x + y + z """
    return sum(map(int, str(number)))
 
rand_sample = (random.randint(100) for _ in range(100))
sqrt_sample = (math.sqrt(x) for x in rand_sample if digits_sum(x) == 9)

Ici, en utilisant un meilleur nommage et en rajoutant du contexte, on rend son code bien plus facile à lire, même si il est plus long.

Le PEP8 n’est donc que le début. Un bon code est un code qui s’auto-documente.

Notez que certains variables sont longues, et d’autres n’ont qu’une seule lettre. C’est parce qu’il existe une sorte de convention informelle sur certains noms dans la communauté :

i et j sont utilisés dans pour tout ce qui est incrément:

for i, stuff in enumerate(foo):

x, y et z sont utilisés pour contenir les éléments des boucles:

for x in foo:
    for y in bar:

_ est utilisé soit comme alias de gettext:

from gettext import gettext as _

Soit comme variable inutilisée:

(random.randint(100) for _ in range(100))

_ a aussi un sens particulier dans le shell Python : elle contient la dernière chose affichée automatiquement.

f pour un fichier dans un bloc with:

with open(stuff) as f:

Si vous avez deux fichiers, nommez les:

with open(foo) as foo_file, open(bar) as bar_file:

*args et **kwargs pour toute collection d’arguments hétérogènes :

def foo(a, b, **kwarg):

Mais attention, si les arguments sont homogènes, on les nomme:

def merge_files(*paths):

Et bien entendu self pour l’instance en cours, ou cls pour la classe en cours:

class Foo:
 
    def bar(self):
        ...
 
    @classmethod
    def barbar(cls):
        ...

Dans tous les cas, évitez les noms qui sont utilisés par les built-ins :

>>> dir(__builtins__)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '_', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']

Les erreurs les plus communes : list, dict, min, max, next, file, id, input et str.

La longueur des lignes

C’est le point le plus sujet à polémique. Quelques astuces pour se faciliter la vie.

L’indentation est votre amie.

dico = {0: None, 1: None, 2: None, 3: None, 4: None, 5: None, 6: None, 7: None, 8: None, 9: None}
tpl = ('jaune', 'bleu', 'rouge', 'noir', 'octarine')
res = arcpy.FeatureClassToGeodatabase_conversion(['file1.shp', 'file2.shp' ], '/path/to/file.gdb')

Devient:

dico {
    0: None,
    1: None,
    2: None,
    3: None,
    4: None,
    5: None,
    6: None,
    7: None,
    8: None,
    9: None
}
 
tpl = (
    'jaune',
    'bleu',
    'rouge',
    'noir',
    'octarine'
)
 
res = arcpy.FeatureClassToGeodatabase_conversion([
        'file1.shp',
        'file2.shp'
    ],
    '/path/to/file.gdb'
)

Les parenthèses permettent des choses merveilleuses.

from module import package1, package2, package3, package4, package5, package6, package7
query = db.query(MyTableName).filter_by(MyTableName.the_column_name == the_variable, MyTableName.second_attribute > other_stuff).first())
string = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
from module import (package1, package2, package3, package4,
                    package5, package6, package7)
query = (db.query(MyTableName)
           .filter_by(MyTableName.the_column_name == the_variable,
                      MyTableName.second_attribute > other_stuff)
           .first())
string = ("Lorem ipsum dolor sit amet, consectetur adipisicing elit, "
          "sed do eiusmod tempor incididunt ut labore et dolore magna "
          "aliqua. Ut enim ad minim veniam, quis nostrud exercitation "
          "ullamco laboris nisi ut aliquip ex ea commodo consequat.")

Les variables intermédiaires documentent le code:

sqrt_sample = (math.sqrt(x) for x in (random.randint(100) for _ in range(100)) if sum(map(int, str(number))) == 9)

Devient bien plus clair avec :

def digits_sum(number):
    """ Take a number xyz and return x + y + z """
    return sum(map(int, str(number)))
 
rand_sample = (random.randint(100) for _ in range(100))
sqrt_sample = (math.sqrt(x) for x in rand_sample if digits_sum(x) == 9)

return, break et continue permettent de limiter l’indentation:

def foo():
    if bar:
        rand_sample = (random.randint(100) for _ in range(100))
        return (math.sqrt(x) for x in rand_sample if digits_sum(x) == 9)
 
    return None

Est plus élégant écrit ainsi:

def foo():
    if not bar:
        return None
 
    rand_sample = (random.randint(100) for _ in range(100))
    return (math.sqrt(x) for x in rand_sample if digits_sum(x) == 9)

any, all et itertools.product évident pas mal de blocs:

for x in foo:
    if barbarbarbarbarbarbar(x):
        meh()
        break 
 
for x in foo:
    for y in bar:
        meh(y, y)

Devient:

is_bar = (barbarbarbarbarbarbar(x) for x in foo)
if any(is_bar):
   meh()
 
import itertools
for x, y in itertools.product(foo, bar):
    meh(y, y)

Le fait est que Python est bourré d’outils très expressifs. Oui, il arrivera parfois que vous deviez briser la limite des 80 charactères. Je le fais souvent pour mettre des URLs en commentaire par exemple. Mais ce n’est pas le cas typique si vous utilisez le langage tel qu’il a été prévu.

Passer en mode pro

Le PEP8, c’est un point de départ. Quand on a un script et qu’il grandit sous la forme d’une bibliothèque, il faut plus que le reformatter.

A partir d’un certain point, on voudra coiffer son code et lui mettre un costume. Reprenons :

def digits_sum(number):
    """ Take a number xyz and return x + y + z """
    return sum(map(int, str(number)))
 
rand_sample = (random.randint(100) for _ in range(100))
sqrt_sample = (math.sqrt(x) for x in rand_sample if digits_sum(x) == 9)

On lui fait un relooking pour son entretien d’embauche :

def digits_sum(number):
    """ Take a number xyz and return x + y + z
 
        Arguments:
            number (int): the number with digits to sum.
                            It can't be a float.abs
 
        Returns:
            An int, the sum of all digits of the number.
 
        Example:
            >>> digits_sum(123)
            6
    """
    return sum(map(int, str(number)))
 
def gen_squareroot_sample(size=100, randstart=0, randstop=100, filter_on=9):
    """ Generate a sample of random numbers square root
 
        Take `size` number between `randstart` and `randstop`,
        sum it's digits. If the resulting value is equal to `filter_on`,
        yield it.
 
        Arguments:
            size (int): the size of the pool to draw the numbers from
            randstart (int): the lower boundary to generate a number
            randstop (int): the upper boundary to generate a number
            filter_on (int): the value to compare to the digits_sum
 
        Returns:
            Generator[float]
 
        Example:
            >>> list(gen_squareroot_sample(10, 0, 100, filter_on=5))
            [5.291502622129181, 6.708203932499369, 7.280109889280518]
 
    """
    for x in range(size):
        dsum = digits_sum(random.randint(randstart, randstop))
        if dsum == filter_on:
            yield math.sqrt(x)

Et dans un autre fichier :

sqrt_sample = gen_squareroot_sample()

L’important ici:

Il ne faut pas laisser le PEP8 vous limiter à la vision de la syntaxe. La qualité du code est importante : sa capacité à être lu, compris, utilisé facilement et modifié.

Pour cette même raison, il faut travailler ses APIS.

Si vous avez :

class DataSource:
 
    def open(self):
        ...
 
    def close(self):
        ...
 
    def getTopic(self, topic):
       ...

Et que ça s’utilise comme ça:

ds = DataSource()
ds.open()
data = ds.getTopic('foo')
ds.close()

Vous pouvez raler sur le getTopic. Mais si vous vous limitez à ça, vous ratez l’essentiel : cette API n’est pas idiomatique.

Une version plus proche de la philosophie du langage serait:

class DataSource:
 
    def open(self):
        ...
 
    def close(self):
        ...
 
    def get_topic(self, topic):
       ...
 
    def __getitem__(self, index):
        return self.get_topic(index)
 
    def __enter__(self):
        self.open()
        return self
 
    def __exit__(self, *args, **kwargs):
        self.close()

Et que ça s’utilise comme ça:

with DataSource() as ds:
    data = ds['foo']

Le style d’un langage va bien au-delà des règles de la syntaxe.

Les docstrings

Je suis beaucoup plus relaxe sur le style des docstrings. Il y a pourtant bien le PEP 257 pour elles.

Simplement ne pas le respecter parfaitement n’affecte pas autant leur lisibilité car c’est du texte libre.

Quelques conseils tout de même.

En bonus

flake8 est un excellent linter qui vérifiera votre style de code. Il existe en ligne de commande ou en plugin pour la plupart des éditeurs.

Dans le même genre, mccabe vérifira la complexité de votre code et vous dira si vous êtes en train de fumer en vous attributant un score. Il existe aussi intégré comme plugin de flake8 et activable via une option.

Tox vous permet d’orchestrer tout ça, en plus de vos tests unittaires. Je ferai un article dessus un de ces 4.

Si vous voyez des commentaires comme # noqa ou # xxx: ignore ou # xxx: disable=YYY, ce sont des commentaires pour ponctuellement dire à ces outils de ne pas prendre en considération ces lignes.

Car souvenez-vous, ces règles sont là pour vous aider. Si à un moment précis elles cessent d’etre utiles, vous avez tous les droits de pouvoir les ignorer.

Mais ces règles communes font de Python un langage à l’écosystème exceptionnel. Elles facilitent énormément le travail en équipe, le partage et la productivité. Une fois habitué à cela, travailler dans d’autres conditions vous paraitra un poids inutile.

La maison des horreurs de l’encoding 8   Recently updated !

dimanche 29 janvier 2017 à 15:58

Ah, l’encoding, le truc que tout le monde veut mettre sous le tapis. Il faut dire que c’est dur à gérer. En fait tellement dur que:

Tout ce bordel amène les devs à essayer d’ignorer le problème le plus longtemps possible. Ca marche assez bien pour les anglophones car leur environnement est assez homogène, orienté ASCII, et certains peuvent faire une très belle carrière en restant joyeusement ignorant.

Ca marche beaucoup moins bien pour les européens, et pas du tout pour le monde arabe et asiatique. Néanmoins, pas besoin de chercher bien loin pour trouver des échecs critiques.

Naviguez tranquillement sur un site espagnol a priori joli, moderne, utilisant des tildes et tout ce qu’il faut. Maintenant regardez la requête HTTP, vous noterez que le serveur n’indique pas le charset du contenu. Fort heureusement dans le HTML vous trouvez:

<meta http-equiv="Content-Type" content="ISO-8859-1">

Nickel, récupérons le texte du bouton “Ver más ideas”:

>>> import requests
>>> res = requests.get('http://www.airedefiesta.com/76-pinatas-y-chuches.html') 
>>> data = res.content.split(b'http://www.airedefiesta.com/ideas.html?c=76">')[1].split(b'</a>')[0]
>>> data
b'Ver m\xc3\xa1s ideas'

Une suite de bits comme maman les aime. On décode:

>>> data.decode('ISO-8859-1')
'Ver más ideas'
Chenille dans labyrinthe

Et puis on ne voudrait pas que vous arriviez au château trop vite

Enfer et sodomie ! Le charset déclaré n’est pas celui utilisé. Tentons un truc au hasard:

>>> data.decode('utf8')
'Ver más ideas'

Bref, en 2017, on se touche la nouille pour savoir qui a son architecture multi-services load balancée web scale à base de NoSQL, de containers orchestrés et de serveurs asynchrones. Mais pour afficher du texte y a plus personne hein…

Vous croyez que ce ne sont que les amateurs qui font ces erreurs. Naaaaaaaaa. Par exemple le standard pour les fichiers zip a une vision très… hum… personnelle du traitement de l’encoding des noms de fichier.

L’encoding, c’est la raison majeur de l’incompatibilité de Python 2 et 3, mais aussi un signe de la bonne santé de la techno puisque c’est un des rares vieux langages (je rappelle que Python est plus vieux que Java) à gérer la chose correctement. A savoir:

Python n’est pas parfait pour autant. Par exemple il garantit un accès 0(1) indexing sur les strings, ce qui à mon sens est inutile. Swift a un meilleur design pour ce genre de choses. Mais globalement c’est quand même super bon.

Si ne savez toujours pas comment ça marche, on a évidement un tuto pour ça.

Alors pourquoi l’encoding c’est un truc compliqué ?

Et bien parce que comme pour le temps ou l’i18n, ça touche à la culture, au langage, à la politique, et on a accumulé les problèmes au fil des années.

A solid dick from an iron man

Mais je vous jure ça avait du sens y a 40 ans !

Par exemple, parlons un peu d’UTF.

Vous savez, on vous dit toujours d’utiliser utf8 partout pour être tranquille…

Mais déjà se pose la question : avec ou sans BOM ?

Le BOM, c’est une suite d’octets qui indique en début de fichier qu’il contient de l’UTF. Si ça à l’air pratique, c’est parce que ça l’est. Malheureusement, celui-ci n’est pas obligatoire, certaines applications le requièrent, d’autres l’ignorent, et d’autres plantent face au BOM. D’ailleurs, le standard unicode lui-même ne le recommande pas:

Use of a BOM is neither required nor recommended for UTF-8

Ca aide vachement à faire son choix.

Perso je ne le mets jamais, sauf si je dois mélanger des fichiers de différents encodings et les différencier plus tard.

Mais Powershell et Excel par exemple, fonctionnent parfois mieux si vous leur passez des données avec le BOM :)

Si vous avez un peu creusé la question, vous savez qu’il existe aussi UTF16 (par défaut dans l’API de Windows 7 et 8 et les chaînes de .NET), UTF32 et UTF64. Ils ont des variantes Big et Little Endians, qui ne supportent pas le BOM, et une version neutre qui le supporte, pour faciliter la chose.

Bien, bien, bien.

Mais saviez-vous qu’il existe aussi UTF-1, 5 et 6 ? Si, si. Et UTF9 et UTF18 aussi, mais sauf que eux ce sont des poissons d’avril, parce que les gens qui écrivent les RFC sont des mecs trop funs en soirées.

Que sont devenus ces derniers ? Et bien ils ont été proposés comme encoding pour l’internationalisation des noms de domaine. UTF5 est un encoding en base 32, comme son nom l’indique. Si, 2 puissance 5 ça fait 32. Funs en soirée, tout ça.

Néanmoins quelqu’un est arrivé avec une plus grosse bit(e), punycode, en base 36, et a gagné la partie. J’imagine que les gens se sont dit qu’utiliser base64 était déjà trop fait par tout le monde est qu’on allait pas se priver de cette occasion fabuleuse de rajouter un standard.

Standard qui ne vous dispense pas, dans les URLs, d’encoder DIFFÉREMMENT ce qui n’est pas le nom de domaine avec les bons escaping. Et son lot de trucs fantastiques. Encoding qui est différent pour les valeurs de formulaire.

Python supporte par ailleurs très bien tout ça:

>>> 'Père noël'.encode('punycode')
b'Pre nol-2xa6a'
>>> import urllib
>>> urllib.parse.quote('Père Noël')
'P%C3%A8re%20No%C3%ABl'
>>> urllib.parse.quote_plus('Père Noël')
'P%C3%A8re+No%C3%ABl'

En plus, si Punycode est l’encoding par défaut utilisé dans les noms de domaine, c’est donc aussi celui des adresses email. Ce qui vous permettra de profiter des interprétations diverses de la spec, comme par exemple le retour de la valeur d’un HTML input marqué “email”, qui diffère selon les navigateurs.

Président faisant un fuck dans idiocracy

If you don’t encode in Tarrlytons…fuck you!

Pourquoi je vous parle des adresses emails tout à coup ? Ah ah ah ah ah ah ah !

Mes pauvres amis.

Je ne vous avais jamais parlé d’utf7 ?

Non, je ne me fous pas de votre gueule. Je suis très sérieux.

Figurez-vous que le format email MIME accepte l’utilisation d’utf7 en lieu et place de base64.

Mais ce n’est pas ça le plus drôle.

Y a mieux, je vous jure.

UTF7 est en effet l’encoding par défaut pour IMAP, particulièrement les noms des boîtes aux lettres. Vous savez, “INBOX”, “Spams” et “Messages envoy&AOk-s” ;)

Or comme l’enculerie ne serait pas aussi délicieuse sans un peu de sable…

La version utilisée maintenant (et pour toujourssssssss) par IMAP est une version d’UTF7 non standard et modifiée.

Pourquoi ? Ben parce qu’allez-vous faire foutre.

The choosen one, crying

The choosen one would soon realize that some things survived outside of the vault. Like bad UI and terrible IT standards. And his ‘science’ skills is at 42% and life sucks.

Au final je n’ai fait que parloter d’UTF, mais souvenez-vous que:

>>> import encodings
>>> len(encodings.aliases.aliases)
322

Donc on n’a fait qu’effleurer la surface de l’anus boursouflé de la mouche.

J’espère que la nuit, à 3h du mat, lorsque votre prochaine mise en prod agonisera sur un UnicodeDecodeError, vous penserez à moi et pendant un instant, un sourire se dessinera sous vos larmes.

Ecriture extra-terrestre de the arrival

Militaire : Votre avis ? – Unicode Consortium : Tuez les tous.

Tout ce qui fait que Python 3 est meilleur que Python 2 21   Recently updated !

jeudi 26 janvier 2017 à 15:17

Avec de nombreuses distros Linux qui viennent avec Python 3 par défaut ainsi que Django et Pyramid qui annoncent bientôt ne plus supporter Python 2, il est temps de faire un point.

Python 3 est aujourd’hui majoritairement utilisé pour tout nouveau projet ou formation que j’ai pu rencontrer. Les plus importantes dépendances ont été portées ou possèdent une alternative. Six et Python-future permettent d’écrire facilement un code compatible avec les deux versions dans le pire des cas.

Nous sommes donc bien arrivés à destination. Il reste quelques bases de code encore coincées, la vie est injuste, mais globalement on est enfin au bout de la migration. Mais ça en a mis du temps !

Il y a de nombreuses raisons qui ont conduit à la lenteur de la migration de la communauté :

Mais je pense que la raison principale c’est le manque de motivation pour le faire. Il n’y a pas un gros sticker jaune fluo d’un truc genre “gagnez 30% de perfs en plus” que les devs adorent même si ça n’influence pas tant leur vie que ça. Mais les codeurs ne sont pas rationnels contrairement à ce qu’ils disent. Ils aiment les one-liners, c’est pour dire.

Pourtant, il y a des tas de choses excellentes en Python 3. Simplement:

Après 2 ans de Python 3 quasi-fulltime, je peux vous le dire, je ne veux plus coder en Python 2. Et on va voir pourquoi.

Unicode, mon bel unicode

On a crié que c’était la raison principale. A mon avis c’est une erreur. Peu de gens peuvent vraiment voir ce que ça implique.

Mais en tant que formateur, voilà ce que je n’ai plus a expliquer:

Et je peux supprimer de chacun de mes fichiers:

Et toutes les APIS ont un paramètre encoding, qui a pour valeur par défaut ‘UTF8′.

Debuggage for the win

Des centaines d’ajustements ont été faits pour faciliter la gestion des erreurs et le debuggage. Meilleures exceptions, plus de vérifications, meilleurs messages d’erreur, etc.

Quelques exemples…

En Python 2:

>>> [1, 2, 3] < "abc"
True

En Python 3 TypeError: unorderable types: list() < str() of course.

En Python 2, IOError pour tout:

>>> open('/etc/postgresql/9.5/main/pg_hba.conf')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 13] Permission denied: '/etc/postgresql/9.5/main/pg_hba.conf'
>>> open('/etc/postgresql/9.5/main/')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 21] Is a directory: '/etc/postgresql/9.5/main/'

En Python 3, c'est bien plus facile à gérer dans un try/except ou à debugger:

>>> open('/etc/postgresql/9.5/main/pg_hba.conf')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
PermissionError: [Errno 13] Permission denied: '/etc/postgresql/9.5/main/pg_hba.conf'
>>> open('/etc/postgresql/9.5/main/')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IsADirectoryError: [Errno 21] Is a directory: '/etc/postgresql/9.5/main/'

Les cascades d'exceptions en Python 3 sont très claires:

>>> try:
...     open('/') # Erreur, c'est un dossier:
... except IOError:
...     1 / 0 # oui c'est stupide, c'est pour l'exemple
... 
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
IsADirectoryError: [Errno 21] Is a directory: '/' 
 
During handling of the above exception, another exception occurred:
 
  File "<stdin>", line 4, in <module>
ZeroDivisionError: division by zero

La même chose en Python 2:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
ZeroDivisionError: integer division or modulo by zero

Bonne chance pour trouver l'erreur originale.

Plein de duplications ont été retirées.

En Python 2, un dev doit savoir la différence entre:

Sous peine de bugs ou de tuer ses perfs.

Certains bugs sont inévitables, et les modules csv et re sont par exemple pour toujours buggés en Python 2.

Good bye boiler plate

Faire un programme correct en Python 2 requière plus de code. Prenons l'exemple d'une suite de fichier qui contient des valeurs à récupérer sur chaque ligne, ou des commentaires (avec potentiellement des accents). Les fichiers font quelques centaines de méga, et je veux itérer sur leurs contenus.

Python 2:

# coding: utf8
 
from __future__ import unicode_literals, division
 
import os
import sys
 
from math import log
from codecs import open
from glob import glob
from itertools import imap  # pour ne pas charger 300 Mo en mémoire d'un coup
 
FS_ENCODING = sys.getfilesystemencoding()
 
SIZE_SUFFIXES = ['bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
 
def file_size(size):
    order = int(log(size, 2) / 10) if size else 0  # potential bug with log2
    size = size / (1 << (order * 10))
    return '{:.4g} {}'.format(size, suffixes[order])
 
def get_data(dir, *patterns, **kwargs):
    """ Charge les données des fichiers  """
 
    # keyword only args
    convert = kwargs.get('convert', int)
    encoding = kwargs.get('encoding', 'utf8')
 
    for p in patterns:
        for path in glob(os.path.join(dir, p)):
            if os.path.isfile(path):
                upath = path.decode(FS_ENCODING, error="replace")
                print 'Trouvé: ', upath, file_size(os.stat(path).st_size)
 
                with open(path, encoding=encoding, error="ignore") as f:
                    # retirer les commentaires
                    lines = (l for l in f if "#" not in l)
                    for value in imap(convert, f):
                        yield value

C'est déjà pas mal. On gère les caractères non ASCII dans les fichiers et le nom des fichiers, on affiche tout proprement sur le terminal, on itère en lazy pour ne pas saturer la RAM... Un code assez chouette, et pour obtenir ce résultat dans d'autres langages vous auriez plus dégueu (ou plus buggé).

Python 3:

# wow, so much space, much less import
from math import log2 # non buggy log2
from pathlib import Path # plus de os.path bullshit
 
SIZE_SUFFIXES = ['bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
 
def file_size(size):
    order = int(log2(size) / 10) if size else 0   
    size = size / (1 << (order * 10))
    return f'{size:.4g} {suffixes[order]}' # fstring
 
def get_data(dir, *patterns, convert=int, encoding="utf8"): # keyword only
    """ Charge les données des fichiers  """
 
    for p in patterns:
        for path in Path(dir).glob(p):
            if path.is_file():
                print('Trouvé: ', path, file_size(path.stat().st_size))
 
                with open(path, encoding=encoding, error="ignore") as f:
                    # retirer les commentaires
                    lines = (l for l in f if "#" not in l)
                    yield from map(convert, lines) # déroulage automatique

Oui, c'est tout.

Et ce n'est pas juste une question de taille du code. Notez tout ce que vous n'avez pas à savoir à l'avance pour que ça marche. Le code est plus lisible. La signature de la fonction mieux documentée. Et il marchera mieux sur une console Windows car le support a été amélioré en Python 3.

Et encore j'ai été sympa, je n'ai pas fait la gestion des erreurs de lecture des fichiers, sinon on en avait encore pour 3 ans.

Il y a des tas d'autres trucs.

L'unpacking généralisé:

>>> a, *rest, c = range(10)  # récupérer la première et dernière valeur
>>> foo(*bar1, *bar2)  # tout passer en arg
>>> {**dico1, **dico2} # fusionner deux dico :)

Ou la POO simplifiée:

class FooFooFoo(object):
    ...
 
class BarBarBar(FooFooFoo):
    def wololo(self):
        return super(BarBarBar, self).wololo()

Devient:

class FooFooFoo:
    ...
 
class BarBarBar(FooFooFoo):
    def wololo(self):
        return super().wololo()

Python 3 est tout simplement plus simple, et plus expressif.

Bonus

Evidement il y a plein de trucs qui n'intéresseront qu'une certaine catégorie de devs:

Si vous n'êtes pas concernés, ça n'est pas motivant. Mais si ça vous touche personnellement, c'est super cool.

Au passage, depuis la 3.6, Python 3 est enfin plus rapide que Python 2 pour la plupart des opérations :)

Pour finir...

Toutes ces choses là s'accumulent. Un code plus court, plus lisible, plus facile à débugger, plus juste, plus performant. Par un tas de petits détails.

Alors oui, la migration ne va pas restaurer instantanément votre érection et vous faire perdre du poids. Mais sur le long terme, tout ça compte énormément.

Augmenter son volume de sperme pour des super éjaculations faciales. 3   Recently updated !

mardi 24 janvier 2017 à 00:36

Un sale titre putaclic comme je les aime :)

Comme tous les 2 mois je parts en mission spéciale pour m’adonner aux pratiques les plus salaces afin d’assouvir mes pulsions DSKniennes.

Et comme 99.99% des mecs j’adore les éjacs faciales mais aussi sur les pieds (fétichisme oblige) ou les fesses ou encore une bonne paire de nichons bien juteux.
Il y a quelques années je m’étais renseigné sur des pilules pour augmenter le volume de sperme mais c’était du pipeau, j’ai donc regardé la composition du sperme et grosso modo la voici:

Le sperme contient de nombreux éléments nourriciers pour le spermatozoïde :
vitamines C et B12, sels minéraux comme le calcium, le magnésium, le phosphore, le potassium et le zinc, des sucres (fructose et sorbitol).

Je me suis penché sur les éléments les plus courants à trouver à savoir la vitamine C, B12, magnésium, zinc et fructose.

vitamine C = orange
B12 = banane
magnesium et zinc = cacao
fructose = les fruits

Je me suis donc mis en tête d’ingurgiter quotidiennement ces ingrédients afin de tester une éventuelle augmentation du volume spermique.
Mon test n’est basé sur absolument aucun étude scientifique, mais de toutes façons c’est que des produits naturels, et en plus ça file la pêche.

Bien évidement vous n’allez pas jouer au pompier du jour au lendemain mais j’ai pu noter une certaine augmentation du volume sans que ce soit non plus les chutes du niagara (le titre c’est juste pour être en premier dans les forums d’ados) mais je les trouves supérieures tout de même avec un certain avantage, une femme m’a dit que mon liquide séminal avait le goût de fruits, elle avait l’air d’en être plutôt contente.

 

Passons au cocktail !

2 fois par jour, en général 10h du math et 16/17h:

Dans un mixer versez:

Mixez le tout pendant 5 minutes et servez frais !

Faut faire ce “régime” pendant au moins quelques semaines et vous devriez voir les premiers résultats, madame sera ravie.
Du 100% Bio ! Bon pour le teint.

ban

chériiiiiiie, tes vitamines arrivent…

Aller cadeau, la HD ça rend tout de suite mieux.  

PS: j’ai fait plusieurs tests et c’est celui qui me donne le plus de satisfaction, si vous avez des variantes ou des suggestions allez-y. A côté de ça j’ai une alimentation qui exclue tout produit surgelé ou transformé par l’industrie agro alimentaire, paraît qu’on appelle ça le régime paléo, perso je fais pas de régime, je bouffe comme ma grand-mère et mes parents, pas de junk food et je me sens bien.
Je me gène pas me défoncer un plateau de fruits de mer à l’occas (au passage les huîtres sont blindées de zinc, B12, omega3 et ça a une texture de chatte alors pourquoi s’en priver ?!)

 

Oui je sais c’est très réducteur pour la dignité des femmes, ça rappelle les heures les plus sombres de notre histoire, la météorite qui a fait disparaître les dinosaures et ça prédit même l’arrivé de Nibiru. Pour les féministes défoulez-vous, mais n’oubliez pas vos cachets et le rendez-vous de demain chez le psy.

 

A poil les putes !

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/?47 #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}