__init__ – Sam & Max http://sametmax.com Du code, du cul Wed, 23 Dec 2020 13:35:02 +0000 en-US hourly 1 https://wordpress.org/?v=4.9.7 32490438 Rendez votre package exécutable avec __main__.py http://sametmax.com/rendez-votre-module-executable-avec-__main__-py/ http://sametmax.com/rendez-votre-module-executable-avec-__main__-py/#comments Tue, 13 Jan 2015 06:47:42 +0000 http://sametmax.com/?p=15704 __init__.py, mais __main__.py est moins connu.]]> Tout le monde connait le fichier __init__.py, mais __main__.py est moins connu.

Il permet de lancer du code si on tente d’exécuter un package (c’est à dire un dossier qui contient un fichier __init__.py):

$ tree monpackage/
monpackakge/
├── __init__.py
└── __main__.py

0 directories, 2 files
$ cat monpackage/__main__.py
print('Hello :)')
$ python monpackage/ # ceci est un dossier 
Hello :)

Le __main__.py est aussi exécuté quand on fait python -m monpackage.

Notez que son exécution suppose l’import préalable du package, et que donc __init__.py sera toujours exécuté avant __main__.py. En revanche, faire juste import monpackage ne déclenche pas l’exécution de __main__.py.

Si vous zippez votre package et appelez la commande python, c’est aussi ce fichier qui sera exécuté. Pratique donc, pour faire un exécutable portable à peu de frais, tout en gardant la lib importable.

]]>
http://sametmax.com/rendez-votre-module-executable-avec-__main__-py/feed/ 5 15704
Vous pouvez mettre du code dans __init__.py http://sametmax.com/vous-pouvez-mettre-du-code-dans-__init__-py/ http://sametmax.com/vous-pouvez-mettre-du-code-dans-__init__-py/#comments Wed, 26 Jun 2013 11:20:04 +0000 http://sametmax.com/?p=3472 __init__.py ne sert pas qu'à déclarer un dossier comme un package importable. C'est aussi le code exécuté automatiquement, une seule fois, quand on importe le module.]]> Le fichier __init__.py ne sert pas qu’à déclarer un dossier comme un package importable. C’est aussi le code exécuté automatiquement, une seule fois, quand on importe le module.

Du coup vous pouvez mettre dedans tout code Python que vous souhaitez lancer à l’import. On y voit souvent :

  • Initialisation du cache.
  • Constantes comme __version__.
  • Import d’autres modules pour les mettre dans l’espace de nom courant.
  • Check de dépendances.
  • Aliasing.
  • Monkey patching and hacks tout moches qui sont là temporairement pour 6 ans.

Je vous déconseille de mettre trop de code dans le fichier __init__.py, notamment du code métier. C’est une mauvaise habitude que l’on peut voir dans le code de Django par exemple. Car ça veut dire que l’import du package déclenche ce code, qui lui-même importe d’autres modules, qui déclenche d’autres codes, etc. Cela donne des effets de bord à l’import, alors que l’import d’un simple package est quelque chose que l’on veut généralement ne pas avoir beaucoup d’effets.

Dans Django par exemple, c’est ce qui fait que beaucoup de modules lèvent une exception à l’import :

django.core.exceptions.ImproperlyConfigured: Requested setting X but settings are not configured.

Alors qu’ils n’ont pas du tout besoin des settings pour fonctionner.

Si vous avez besoin que votre classe Bidule soit directement importable dans votre package machin, faites dans machin/__init__.py :

from .module_qui_contient_bidule import Bidule

Du coup vous pourrez faire n’importe où ailleurs:

from machin import Bidule

C’est bien plus propre que de mettre tout le code de Bidule dans le __init__.py.

]]>
http://sametmax.com/vous-pouvez-mettre-du-code-dans-__init__-py/feed/ 12 3472
La différence entre __new__ et __init__ en Python http://sametmax.com/la-difference-entre-__new__-et-__init__-en-python/ http://sametmax.com/la-difference-entre-__new__-et-__init__-en-python/#comments Fri, 11 Jan 2013 10:06:36 +0000 http://sametmax.com/?p=3534 __new__ et __init__ n'ont rien de spécial. Ce sont des méthodes ordinaires. Mais parce qu'elles sont nommées ainsi, Python les détecte et les appelle automatiquement a un moment précis.]]> Les méthodes __new__ et __init__ n’ont rien de spécial. Ce sont des méthodes ordinaires. Mais parce qu’elles sont nommées ainsi, Python les détecte et les appelle automatiquement a un moment précis.

Ce moment, c’est ce qui différencie __init__ de __new__.

__init__ pour initialiser

__init__ est la méthode qui va être appelée automatiquement après qu’un objet ai été crée. Ce n’est pas un contructeur du tout, c’est un initialiseur.

Si vous faîtes ça:

>>> class Premiere(object):
...         
...         def __init__(self, prix):
...                 print "%s euros" % prix
...         
>>> c = Premiere(10000)
10000 euros

A la ligne c = Premiere(10000), Python va créer une instance de la classe Première(). Il va ensuite immédiatement et automatiquement appeler __init__ en lui passant cette instance en premier argument et les paramètres passés par l’appel Premiere(paramètres). Donc, quand __init__ est appelé, l’objet instancié existe déjà.

On va utiliser __init__ pour initialiser l’objet, c’est à dire pour lui donner son état de départ: changer les attributs, configurer l’objet par rapports aux arguments, etc.

Dans tous les autres langages, on utiliserait le constructeur pour faire ce boulot. Pas en Python.

L’avantage de __init__, c’est qu’il est très facile à manipuler. Il n’y a pas de magie dangereuse dans __init__: on a l’objet tout neuf, et les arguments passés à l’instancitation, on peut donc manipuler l’objet sans se soucier du reste. Ici on attache deux attributs à l’instance self:

 >>> class Premiere(object):
...         discount = False
...         def __init__(self, prix):
...                 self.prix = prix
...                 if self.prix < 5000:
...                     self.discount = True
...         
>>> c = Premiere(10000)
>>> c.discount
False

Comme en Python les attributs sont dynamiques, on peut attacher un argument même si l’instance ne le déclare pas, et il est créé automatiquement.

En résumé: __init__ est appelé automatiquement APRES la création de l’objet, et on met dedans le code d’initialisation de l’objet (généralement une modification des attributs pour leur donner leur état de départ).

__new__ pour créer

__new__ est le vrai constructeur. Pour cette raison, elle doit retourner un objet.

>>> class Premiere(object):
...
...     def __new__(cls, prix):
...        print "%s euros" % prix
...        return super(Premiere, cls).__new__(cls)
...
>>> c = Premiere(10000)
10000 euros

__new__ est appelée AVANT la création de l’objet, car c’est son boulot de créer l’instance et de la retourner. Comme on ne sait pas retourner une instance nous même (enfin si, mais pas dans cet article :-)), on appelle super() pour utiliser la méthode __new__ de object et créer une instance pour cette classe.

L’objet créé sera ensuite passé à __init__ automatiquement par Python.

On utilise rarement __new__. Les deux cas principaux sont:

  • si on hérite d’un type immutable (str, int, tuple, etc), __new__ est le seul endroit où on puisse initialiser l’objet.
  • dans le cas des métaclasses.

En résumé: __new__ est le vrai constructeur, il est appelé pour créer l’objet, et l’objet ainsi instancié est passé à __init__. Vous n’avez presque aucune raison de vous en servir, c’est vraiment pour les cas particuliers.

Voici l’ordre d’éxécution:

>>> class Premiere(object):
...         def __new__(cls, prix):
...                 print "__new__"
...                 return super(Premiere, cls).__new__(cls)
...         def __init__(self, *args):
...                 print "__init__"

>>> c = Premiere(10000)
__new__
__init__

Exemple d’utilisation de __new__

Généralement on sait très bien utiliser __init__, mais __new__ est moins évident.

L’usage le plus fréquent de __new__ quand on hérite d’objets immutables. Par exemple, si vous voulez faire un objet Temperature qui hérite de float et qui accepte une unité en plus, ceci ne va pas marcher:

class Temperature(float):

    def __init__(self, value, unit):

        super(Temperature, self).__init__(value)
        self.unit = unit

    def __str__(self):
        return "%s%s" % (self.value, self.unit)

print Temperature(10, '°C')

Traceback (most recent call last):
  File "", line 11, in 
    Temperature(10, '°C')
TypeError: float() takes at most 1 argument (2 given)

La raison est que du fait de la nature immutable de float, il est initialisé dans __new__, et il n’attend aucune valeur de plus dans __new__, mais on lui passe malgré tout (via Temperature(10, '°C')).

En revanche, ceci va marcher:

class Temperature(float):

    def __new__(cls, value, unit):

        instance = super(Temperature, cls).__new__(cls, value)
        instance.unit = unit
        return instance

    def __str__(self):
        return "%s%s" % (super(Temperature, self).__str__(), self.unit)

print Temperature(10, '°C')
10.0°C

Comme on override __new__, on lui donne la possibilité d’accepter une argument de plus.

Un autre exemple serait de vouloir créer une chaîne de caractères qui est toujours en majuscule (ce qui est bien moins utile que l’exemple précédent):

class CapsLockString(str):

    def __init__(self, value):

        print value # et maintenant je fais quoi ?

print CapsLockString('test')
test
test

Ça ne plantera pas, mais il n’y a rien que nous puissions faire car str est immutable. On ne peut tout simplement pas faire quoique ce soit avec value. Avec __new__, on peut faire quelque chose sur la chaîne intermédiaire:

class CapsLockString(str):

    def __new__(cls, value):

        return super(CapsLockString, cls).__new__(cls, value.upper())

print CapsLockString('test')
TEST

Deux chaînes sont en fait créées, une normale, puis une en majuscule retournée par upper() qui va servir de valeur à notre objet (en fait il y en a même 3 dans l’implémentation CPython, c’est pour ça que les notations littérales sont plus rapides que l’usage des classes pour créer des built-in).

__new__ permet donc essentiellement de créer de jolis API. On l’utilise par ailleurs dans les metaclasses, mais ce sera pour un autre article.

Un troisième usage de __new__, assez rare (mais en même temps utiliser __new__ est déjà rare), c’est le pattern factory. Les javaistes le connaissent bien, c’est un motif de conception qui permet de gérer la création d’objets qui peuvent eux même créer des objets, qui créer des objets qui… Bref.

Car en fait __new__ peut retourner n’importe quoi. Il peut retourner toujours la même instance pour faire un singleton par exemple. On peut même carrément renvoyer un truc qui n’a rien n’a voir, par exemple une fonction :

class FonctionFactory(object):

    def __new__(self, value, repeat):

        def repeater(string=value):

            return string * repeat

        return repeater


>>> function = FonctionFactory('hello', 2) # création de la fonction
>>> print function()
hellohello
>>> print function('bonjour')
bonjourbonjour

Ici on retourne carrément une fonction, et pas du tout une instance de FonctionFactory() comme prévu. On pourrait faire ceci de manière plus simple avec de la programmation fonctionnelle, mais __new__ permet de bénéficier de tout l’outillage de la POO.

]]>
http://sametmax.com/la-difference-entre-__new__-et-__init__-en-python/feed/ 21 3534