Écrire du code vraiment Pythonic !


>>> import this
        

          Readability counts.
        

Par Alex Garel pour le Nantes Python Meetup #4

Bouh !


if this == other:
  return True
else:
  return False
        

Le vrai de vrai

La valeur du test est un booléen, il suffit de le renvoyer !

Yeah !


            return this == other
        

Bouh !


a,b=23, 12


for x  in range( 12 ) :
    process(x,a = "This is a bit long",
        b = i)
value = theBigObject_on_steroids.theProperty_with_a_long_name.Thelong_method(another_long_indentifier*New_object()).am_i_still_in_screen()
        

Pep8

Un standard qui veut aider à la lecture du code entre développeurs. flake8 est ton ami.

Si une communauté à d'autres normes, suivez les normes de cette communauté dans vos contributions.

Yeah !


a, b = 23, 12

for x in range(12):
    process(
        x, a="This is a bit long", b=i)

value = (the_big_object_on_steroids
    .the_property_with_a_long_name
    .the_long_method(
        another_long_indentifier * NewObject())
    .am_i_still_in_screen())
        

Bouh !


if len(my_list) == 0:
  print("Oh Gosh, this is empty !")
if len(your_list) > 0:
  print("Gimme some !")
        

Le vrai du faux

Pas besoin de tester la longueur pour savoir qu'une liste est vide.

Yeah !


if not my_list:
  print("Oh Gosh, this is empty !")
if your_list:
  print("Gimme some !")
        

Bouh !


def is_empty(a_list):
    return a_list
        

Le vrai du faux

Si une fonction doit renvoyer un booléen, faites le, bool est là pour ça

Soyez tolérant dans ce que vous acceptez, exigeant dans ce que vous donnez.
d’après Jon Postel

Yeah !


def is_empty(a_list):
    return bool(a_list)
        

Bouh !


# adding value to a query string
# we need to escape the '&'
query_string_list = []
if isinstance(value, str):
    try:
        param_value = value.replace('&', '%26')
    except AttributeError:
        param_value = value
    query_string_list.append('%s=%s' % (param, param_value))
elif isinstance(value, list):
    for subvalue in value:
        try:
            param_value = subvalue.replace('&', '%26')
        except AttributeError:
            param_value = subvalue
        query_string_list.append('%s=%s' % (param, param_value))
        

Je ne veux voir qu'une tête !

Avant de traiter, normaliser les entrées évite de dupliquer le code et permet de simplifier sa compréhension.

Special cases aren't special enough
to break the rules
zen of python #8

Yeah !


# adding value to a query string
# we need to escape the '&'
query_string_list = []
for param, value in params.items():
    if not isinstance(value, list):
        value = [value]
    for subvalue in value:
        param_value = str(subvalue).replace('&', '%26')
        query_string_list.append('%s=%s' % (param, param_value))
        

Bouh !


# swap x and y
temp = y
y = x
x = y
        

Échange rapide

En python on peut directement affecter une valeur a un tuple. On appelle ça unpacking (dépaquetage ?)".

Yeah !


x, y = y, x
        

En profondeur


            circle = (2.0, (1.0, 1.0))
            r, (x, y) = circle
        

import math

def distance((x0, y0), (x1, y1)):
    return math.sqrt((x1 - x0)**2 + (y1 - y0)**2)

point1 = (0.0, 0.0)
point2 = (1.0, 1.0)
distance(point1, point2)
        

Although practicality beats purity.
zen of python #9

Bouh !


even_squares = []
for n in range(20):
  if n % 2 == 0:
      even_squares.append(n ** 2)
        

Se montrer Compréhensif

Pour une itération simple on peut utiliser les list comprehension.

la syntaxe de certains langages de programmation permet de définir des listes en compréhension, c'est-à-dire des listes dont le contenu est défini par filtrage du contenu d'une autre liste selon un principe analogue à celui de la définition par compréhension de la théorie des ensembles.
Wikipedia

Yeah !


even_squares = [n ** 2 for n in range(20) if n % 2 == 0]
        

Le carré des nombres de 0 à 20 qui sont pairs.

Savoir enchainer


evens = (n for n in range(20) if n % 2 == 0)
even_squares = [n**2 for n in evens]
        

Bouh !


f = open('path/to/file', 'w')
# lot of maybe failing processing here
f.write('Coucou')
f.close()
        

Fiché !

Les ContextManager sont tes amis. Aussi toujours utiliser open avec with.

L'assurance d'un fichier bien fermé !

Yeah !


with open('path/to/file', 'w') as f:
    f.write("I feel so smart")
        

And more !


import tempfile
with tempfile.NamedTemporaryFile() as f:
    f.write("This message will auto-destruct")
        

Bouh !


# sorting on values
l = [(v, k) for k, v in my_dict.items()]
l.sort()
l = [(k, v) for k, v in l]
        

Ne le traitez pas de la sorte !

sort et sorted acceptent le paramềtre key ou cmp.

Ça fonctionne bien avec lambda, mais également une fonction, hein !

Yeah !


# sorting on values
l = sorted(my_dict.items(), key=lambda e: e[1])
        

Bouh !


v = values.get(key, None)
w = v is not None and int(v) or v
        

Les vieilles habitudes !

Pour éviter ce design pattern, les expressions conditionnelles ont été ajoutées en Python 2.5

Ne pas en abuser ! (KISS)

Yeah !


v = values.get(key, None)
w = int(v) if v is not None else v
        

Bouh !


for i in range(len(my_list)):
    elt = my_list[i]
    print(i, ': ', elt)
        

Un couple sur qui compter

enumerate renvoie successivement les couples index et élément d'un itérable.

Yeah !


for i, elt in enumerate(my_list):
    print(i, ': ', elt)
        

Bonus...

On peut aligner nos indexes en utilisant les options de %d


print('% 5d: %s' % (i, elt))
        

Bouh !


            # making an index of text
            index = {}
            for token in text.split():
                letter = token[0]
                if letter not in index:
                    index[letter] = []
                index[letter].append(token)
        

Impression de déjà vu…

setdefault permet d'obtenir un élément d'un dictionnaire ou à défaut de mettre une nouvelle valeur dans le dictionnaire qui est ensuite retournée.

Yeah !


            # making an index of text
            index = {}
            for token in text.split():
                values = index.setdefault(letter, [])
                values.append(token)
        

Bouh !


import random

class ListGenerator(object):
    def __init__(self, len):
        self.len = len

    def generate(self):
        return [random.random() for i in range(self.len)]

generator = ListGenerator(len=4)
generator.generate()            
        

__call__ of ’

Une instance d'objet qui possède une méthode __call__ peut être appelé comme une fonction.

Yeah !


import random

class ListGenerator(object):
    def __init__(self, len):
        self.len = len

    def __call__(self):
        return [random.random() for i in range(self.len)]

generator = ListGenerator(len=4)
generator()
        

Bouh !


def all_factorials(n):
    acc = 1
    results = []
    for i in range(1, n):
        acc = acc * i
        results.append(acc)
    return results

for i in all_factorials(10):
    print(i)
        

Production en flux tendu

Une fonction contentant l'instruction yield est un générateur sur lequel on peut itérer.

Lorsque l'on demande le nombre suivant, la fonction reprend là où elle s'était arrété.

Yeah !


def all_factorials(n):
    acc = 1
    for i in range(1, n):
        acc = acc * i
        yield acc

for i in all_factorials(10):
    print(i)
        

Yeah !


def all_factorials(n):
    acc = 1
    for i in range(1, n):
        acc = acc * i
        yield acc

def sqrt(numbers):
    for n in numbers:
        yield n**0.5

for i in sqrt(all_factorials(10)):
    print(i)
        

Bouh !


class Temperature(object):

    def __init__(self, celsius):
        self.celsius = celsius

    def get_celsius(self):
        return self.celsius

    def set_celsius(self, v):
        self.celsius = v

    def get_farenheit(self):
        return self.celsius * 1.8 + 32

    def set_farefheit(self, v):
        self.celsius = (v - 32) / 1.8

t = Temperature(30)
t.get_celsius() # 30
t.set_celsius(3) # chilling
t.get_farenheit()  # 37.4
t.set_farenheit(32)  # celsius is now 0.0
        

La tribu d'Ynamic !

En python on peut définir des attributs de manière dynamique via le décorateur property.

Yeah !


class Temperature(object):

    def __init__(self, celsius):
        self.celsius = float(celsius)

    @property
    def farenheit(self):
        return self.celsius * 1.8 + 32

    @farenheit.setter
    def farenheit(self, v):
        self.celsius = (v - 32) / 1.8

t = Temperature(30)
t.celsius # 30
t.celsius = 3 # chilling
t.farenheit  # 37.4
t.farenheit = 32  # celsius is now 0.0
        

Bouh !


class ArithTemperature(Temperature):

    def add(self, t):
        return ArithTemperature(self.celsius + t.celsius)

    def substract(self, t):
        return ArithTemperature(self.celsius - t.celsius)

    def is_zero(self):
        return self.celsius == - 273.15

t1 = ArithTemperature(30)
t2 = ArithTemperature(22)
t = t1.add(t2)
print(t.celsius) # 52
t = t1.substract(t2)
print(t.celsius) # 8
t.is_zero() # False
        

Opérations en surcharge (non pondérale)

En python on peut surcharger les opérateurs. Dès que ça a du sens, il ne faut pas hésiter !

Yeah !


class ArithTemperature(Temperature):

    def __add__(self, t):
        return ArithTemperature(self.celsius + t.celsius)

    def __sub__(self, t):
        return ArithTemperature(self.celsius - t.celsius)

    def __nonzero__(self):
        return self.celsius != - 273.15

    def __str__(self):
        return "%d°" % self.celsius

t1 = ArithTemperature(30)
t2 = ArithTemperature(22)
print(t1 + t2) # 52°
t = t1 - t2
print(t) # 8°
bool(t) # True
        

Un monde d'opérations

Python permet d'émuler les types arithmétiques mais aussi les listes (découpage, accès a un élément, etc…) les fonctions (voir __call__) et plein d'autres choses

Pour vos longues soirées d'hivers !

Special method namesThe with statement

Builtin functionsBuiltin typesstringredatetimecollectionsenumitertoolsfunctoolsos.pathshutilargparseloggingimportlib

The python tutorialApprendre la néerlandais

Merci de votre attention

Des questions ?