Catégorie : python

Python 3.10 : récapitulatif des nouveautés

Pep 604

Tester plusieurs types avec le | :

isinstance(5, int | str)
isinstance(None, int | None)
isinstance(42, None | int)
issubclass(bool, int | float)

Même chose pour les annotations :

def ma_fonction(
        ma_liste: List[int | str],
        param: int | None
    ) -> float | str:
    pass

Messages d’erreur plus parlants

  • L’erreur est maintenant affichée à la ligne de début du problème, et non plus lorsque l’interpréteur n’y comprend plus rien, l’exemple le plus marquant étant avec l’oubli d’une parenthèse fermante, ou d’une mauvaise indentation
  • Les messages ont été corrigés de manière à ce qu’ils soient plus clairs, avec des suggestions très utiles qui correspondent souvent à l’erreur

Le match/case

Le match/case de Python est similaire à l’instruction switch/case, qui est reconnue comme un « pattern matching structurel » en Python.

Le match/case de Python se compose de trois entités principales :

  1. Le mot-clé match
  2. Une ou plusieurs clauses case
  3. Du code pour chaque case

Là où Python se démarque des autres langages, c’est que l’on peut faire un match sur des patterns !

Exemples de match, du plus simple au plus avancé :


Match très simple, avec le « or »

exemple = True
match exemple:
    case (True|False):
        print("C'est un booléen")
    case _ :
        print("Ce n'est pas un booléen")


Récupérer les sous-patterns

def alarm(item):
    match item:
        case [time, action]:
            print(f"{time} ! C'est l'heure de {action}!")
        case [time, *actions]:
            print(f'{time} !')
            for action in actions:
                print(f"C'est l'heure {action}!")
alarm(['Bon après-midi', 'de travailler'])
alarm(['Bonjour', 'du petit déjeuner', 'se laver les dents'])


Nommer les sous-patterns

def alarme(item):
    match item:
        case [('bonjour' | 'bonsoir') as time, action]:
            print(f"{time.title()} ! Il faudrait {action} !")
        case _:
            print('Mot-clé invalide.')
alarme(['bonsoir', 'travailler'])
alarme(['bonjour', 'petit déjeuner', 'se laver les dents'])


Nommer les sous-patterns et filtres conditionnels

def alarme(item):
    match item:
        case ['bonsoir', action] if action not in ['travailler']:
            print(f'Journée finie ! Il faut {action}!')
        case ['bonsoir', _]:
            print('Il faut se reposer !')
        case [time, *action]:
            print(f'{time.title()}! Il faut {" et ".join(action)}.')
        case _:
            print('Mot-clé invalide.')
alarme(['bonsoir', 'travailler'])
alarme(['bonsoir', 'jouer'])
alarme(['bonjour', 'petit déjeuner', 'se laver les dents'])


Match sur des objets

class Move:
    def __init__(self, horizontal=None, vertical=None):
        self.horizontal = horizontal
        self.vertical = vertical
def str_move(move):
    match move:
        case Move(horizontal='est', vertical='nord'):
            print('Dir. nord-est')
        case Move(horizontal='est', vertical='sud'):
            print('Dir. sud-est')
        case Move(horizontal='ouest', vertical='nord'):
            print('Dir. nord-ouest')
        case Move(horizontal='ouest', vertical='sud'):
            print('Dir. sud ouest')
        case Move(horizontal=None):
            print(f'Dir. {move.vertical}')
        case Move(vertical=None):
            print(f'Dir. {move.horizontal}')
        case _:
            print('? Move inconnu ?')
d1 = Move('est', 'sud')
d2 = Move(vertical='nord')
d3 = Move('centre', 'centre')
str_move(d1)
str_move(d2)
str_move(d3)

python, flask, https et wsgi howto

Je ne vais pas dire le temps que j’ai passé à réussir à mettre ça en oeuvre si quelqu’un fait ça en quelques minutes j’aurais honte… mais disons que j’ai passé beaucoup de temps à le faire tourner, mais je suis vraiment impressionné par sa rapidité. Voici – via ma petite expérience – comment faire tourner un serveur Web très optimisé, en mode production  :
  • flask
  • wsgi
  • https

https / certbot

Je vous donne des pistes, à vous de finir.
L’idée, c’est que je veux paramétrer pour la n-ième fois un nouveau nom de domaine via certbot, et je ne veux pas qu’il touche à la conf de nginx. L’idée c’est que :
(1) Je le fais simplement via certbot-auto puis après,
(2) toutes les autres fois j’utilise « -d » pour qu’il ne touche pas à la conf, et je le fais à la main, en m’inspirant de la conf que cert-bot a fait dans le (1)
Donc, pour faire le (1), je demande à certbot de me créer uniquement le certificat : ./certbot-auto certonly -d monsite.fr
Pour le https, regardez ici.

Python

Code exemple, qui lit un fichier JSON, lance le serveur et sert le fichier lu en tant que JSON :

import json

from flask import Flask, jsonify

app = Flask(__name__)

with open("mon fichier JSON", 'r') as f:
    json_levels_full_description = json.load(f)


@app.route('/')
def index():
    return jsonify(json_levels_full_description)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8004)
else:
    application = app

Attention : surtout ne faites pas le classique if __name__ == '__main__' car uwsgi inclut ce fichier, donc ce fichier n’est jamais un __main__, par contre la configuration au lancement a besoin d’une variable globale nommée application.

Nginx

Je copie colle la configuration d’un site qui tourne déjà en https, en voici un extrait diminué à mort pour ne pas polluer avec des règles personnelles, regardez bien le texte en gras, c’est le plus important pour faire tourner Nginx en collaboration avec wsgi :

server {
  server_name "~(www\.)?monsite\.(com|fr|org|biz|be)$";

  index index.html index.htm;

  access_log /blah/proxy-access.monsite.log proxylog;
  error_log /blah/proxy-error.monsite.log error;

  listen 443 ssl;
  ssl_certificate /blah/fullchain.pem;
  ssl_certificate_key /blah/privkey.pem;
  include /blah/options-ssl-nginx.conf;
  ssl_dhparam /blah/ssl-dhparams.pem;

  location / {
    include denied_clients;

    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Server $host;

    # (!) here it's uwsgi:
    include uwsgi_params;
    uwsgi_pass 127.0.0.1:8002;

  }
}

# redirect http -> https:
server {
  listen *:80;
  server_name "~^monsite\..*$";
  return 302 https://$host$request_uri;
}

Flask

Pour installer flask, et uwsgi, faites un environnement virtuel puis installez les deux :

$ python3 -m venv venvpython.3.6.6
$ source venvpython.3.6.6/bin/activate
$ pip install --upgrade pip
$ pip install flask

uwsgi

Il faut être dans le venv activé (voir paragraphe précédent), puis l’installer via pip :

$ pip install uwsgi

Enfin, le script de lancement !

uwsgi --chdir=/web/htdocs/blah \
    --module=flask_server:application \
    --master --pidfile=/tmp/uwsgi.blah.pid \
    --socket=127.0.0.1:8004 \
    --processes=5 \
    --uid=1000 --gid=2000 \
    --harakiri=20 \
    --max-requests=5000 \
    --vacuum \
    --home=/web/htdocs/blah/venvpython.3.6.6

flask_server correspond au fichier python flask_server.pydécrit dans la section python au début et application est le nom de la variable globale (lisez le code de la section python)

Lorsque vous le lancerez, tout se passera bien, mais l’exécutable ne sera pas en tâche de fond. Cela a un avantage : un CTRL-C et vous l’arrêtez ! L’inconvénient, c’est que vous n’avez pas la main.

Si vous voulez le lancer en tâche de fond et qu’il écrive les logs dans un fichier, ajoutez l’option     --daemonize=/var/log/uwsgi/blah.uwsgi.log


Notes d’amélioration :
– Le port 8004 peut être passé en tant que variable d’environnement à l’application flask via le paramètre `env` de la commande uwsgi, ce qui rend le code plus portable ;
– Les paramètres uwsgi peuvent être consignés dans un fichier my_app.ini et uwsgi serait alors lancé en faisant uwsgi my_app.ini

Les requêtes « à la » Django : howto / principes

Voici une requête « à la Django ».
« objects » est un objet statique destiné à faire les requêtes
p = un objet du modèle « Person » = un modèle base de données que j’ai fait
g = un objet du modèle « Game » = un modèle base de données que j’ai fait

J’ai crée un modèle intermédiaire « PersonGame » qui lie les deux tables en n/n. Pour comprendre l’idée :
Q(person=p) signifie « dont la personne == p »
~Q(person=p) signifie « dont la personne != p »

Si on met deux « __ » cela signifie « faire une jointure entre les deux modèles », par exemple « person__user » signifie « LEFT JOIN PERSON p ON p.id_user = user.id »

Donc pour tout reprendre :
« Aller chercher dans PersonGame toutes les personnes qui ne sont pas le joueur p »
PersonGame.objects.filter(~Q(person=p), game=g)

…et dont le username vaut « u »
.get(person__user__username=u)

Ce qui donne au final :
PersonGame.objects.filter(
    ~Q(person=p),
    game=g).get(person__user__username=u)

Et voici la requête qu’il aurait fallu écrire « à la main » :

SELECT * FROM PersonGame pg
WHERE pg.id_person != p.id
AND pg.id_game = g.id
LEFT JOIN Person pe on pe.id = p.id
LEFT JOIN User us on us.id = pe.id_user
WHERE us.username = u
LIMIT 1;

En fait ça peut paraître rébarbatif, ou surprenant, mais :
– ça tient en une ligne ;
– quand on a fait 3-4 requêtes comme ça :
  – on arrive à faire n’importe quelle requête un peu complexe très très vite ;
  – on arrive à lire très facilement n’importe quelle requête ;
  – le générateur de requêtes est incroyablement mieux optimisé que celui de Symfony (en fait, techniquement, il est parfait, il génère les LEFT JOIN exactement comme il faut et dans l’ordre le plus efficace en fonction des clés qu’on a précisées dans le modèle, voire il fait des requêtes plus efficaces que ce qu’on aurait éventuellement fait à la main (cela m’est arrivé par deux fois)).

Conclusion : ici aussi, en termes de maintenance = ce qui coûte le plus cher, c’est exceptionnellement rentable.

100 livres Python pour tout le monde !

Pour toutes les personnes qui croient qu’on passe des jours entiers à écrire des bouquins pour les donner, changez d’endroit : oui ils sont payants… et c’est normal.

Bref, pour tous ceux qui veulent apprendre et payer pour avoir les meilleurs livres, écrits par les meilleurs, dans le domaine qui vous intéresse, vous avez tout ici : pythonbooks.org.

Python : exemple simple socket, gethostbyaddr et nom de domaine

Ci suit un exemple simple. Supposons que vous venez tout juste d’acheter le domaine « p-bor.com » mais vous avez beau faire un ping, rien ne fonctionne, parce que malgré que vous l’ayez acheté, il n’est toujours pas actif. En pratique, vous devez attendre et revenir de temps à autre essayer de le « pinger » pour voir si, enfin, vous arriver à le voir sur Internet.
J’ai une solution plus amusante : il suffit de faire ce script python, qui essaie de faire la correspondance entre une adresse « nom de domaine » et son adresse IP.
C’est la fonction gethostbyaddr().
Dès que la correspondance est faite, on l’affiche et on quitte. Dit autrement, tant que votre nom de domaine n’est pas validé, il y aura une boucle de test toutes les minutes. Surtout n’hésitez pas à laisser un commentaire pour améliorer la chose !


#!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket, time, random

random.seed()
nom_de_domaine = 'cuts-coiffure.fr'
Ok = False
while Ok == False:
    try:
        print nom_de_domaine+' => '+ \
            str(socket.gethostbyaddr(nom_de_domaine)[0])+ \
            ' ==> Valide !'
        Ok = True
    except socket.gaierror as E:
        tm_year,tm_mon,tm_mday, \
        tm_hour,tm_min,tm_sec, \
        tm_wday,tm_yday,tm_isdst = \
        time.localtime()
        print "%04d-%02d-%02d / %02d:%02d:%02d : %s." % \
            (tm_year,tm_mon,tm_mday, \
            tm_hour,tm_min,tm_sec,str(E))
        time.sleep(60 + random.randint(1, 90) )

pygame : exemple de transparence

Voici un exemple simple pour gérer la transparence : cet exemple est très simple, il crée un petit carré qui suit la souris partout, et un autre « gros » carré qui apparait autour de la souris puis ne bouge plus, et dès que la souris sort de ce carré, hop, il fait un fondu au noir. Vous pouvez donc vous baser sur cet exemple pour comprendre comment transformer votre Surface en surface transparente destinée à être rapidement affichée à l’écran. Je parle de convert_alpha(). Avec cet appel, pour vous donner une idée, le temps pris est presque 40 fois plus rapide que sans l’appel. Vous pouvez tester par vous même !

Attention !
Le plus important c’est de faire dans l’ordre :

        self.image.set_alpha(valeur_transparence)
        pygame.Surface.convert_alpha(self.image)

Car l’ordre self.image.set_alpha(valeur_transparence) fera créer un masque au complet, et ce n’est qu’après qu’il vous faudra convertir la totale via pygame.Surface.convert_alpha(self.image).

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os, pygame, sys, time, random, copy, pprint
from pygame.locals import *

class MySprite(pygame.sprite.Sprite):
    def __init__(self,width,height):
        # (!) Avant toute chose appeler le constructeur parent :
        pygame.sprite.Sprite.__init__(self)
        self.width = width
        self.height = height
        self.rect  = pygame.Rect(0, 0, width, height)
        self.image = pygame.Surface( (self.rect.width, self.rect.height) )
        self.image.set_colorkey((0,0,0))
        self.image.fill((0, 0, 0))

class SMouse(MySprite):
    def __init__(self,width,height):
        MySprite.__init__(self,width,height)
        pygame.draw.rect(self.image, (205,250,130), self.rect, 5)
        self.image = self.image.convert()

    def update(self):
        self.rect.topleft = pygame.mouse.get_pos()

class Contour(MySprite):
    def __init__(self,width,height,speed):
        MySprite.__init__(self,width,height)
        pygame.draw.rect(self.image, (105,250,230), self.rect, 5)
        self.fadeout = False
        self.speed = speed
        self.alpha = 250
        self.done = False
        self.image.set_alpha(self.alpha)
        # Convertir en transparent :
        pygame.Surface.convert_alpha(self.image)

    def update(self):
        if not self.fadeout:
            if not self.rect.collidepoint( pygame.mouse.get_pos() ):
                self.fadeout = True
        else:
            self.alpha -= self.speed
            if self.alpha<0:
                self.alpha = 0
                self.done = True
            self.image.set_alpha(self.alpha)

def main():
    pygame.init()
    pygame.mouse.set_visible(0)
    screen = pygame.display.set_mode( (0,0),
        pygame.FULLSCREEN | pygame.DOUBLEBUF | pygame.HWSURFACE )
    if pygame.display.Info().bitsize < 24:
        print "This game needs a 24 bits display"
        pygame.quit()

    background = pygame.Surface(screen.get_size())
    background = background.convert()
    background.fill((0, 0, 0))
    screen.blit(background, (0, 0))
    pygame.display.flip()
    clock = pygame.time.Clock()
    c = Contour(500, 500, 5)
    c.rect.center = pygame.mouse.get_pos()
    group_sprites = pygame.sprite.RenderPlain( [ SMouse(50, 50), c ] )

Clonage en Php et en Python

Php fournit une super méthode méga géniale qui donne la possibilité de dupliquer des objets : le clonage.
Waaaaaaaaaah \o/

http://www.php.net/manual/en/language.oop5.cloning.php

Bah seulement y’a comme un petit hic : si les propriétés sont elles-même des objets, alors il faut les cloner à la main. Dit autrement, comme la plupart des propriétés sont des objets, autant faire un

X = new Objet();
Y.assignTo(X);

Et voilà on se l’est fait le clonage parce que la méthode assignTo() est aussi longue à écrire que d’écrire la routine « __clone() ». Donc en fait leur méthode de merde elle est comme le H de Hawaï.

Alors que je suis en train de voir… la méthode existe en Python et elle est exactement ce qu’il me faudrait (=deepcopy()) ici http://docs.python.org/library/copy.html .

Je pense que je ne peux pas éviter Php pour tout ce qu’il y a déjà en place dans le monde d’Internet mais ça me fout vraiment, vraiment les bouboules. J’ai vraiment raison quand je dis qu’en Python on développe en gros deux fois plus vite qu’en Php.

>_<