Les modules (et packages) sont un excellent moyen pythonique de diviser votre programme en espaces de noms séparés, ce qui semble être un objectif implicite de cette question. En effet, alors que j'apprenais les bases de Python, je me sentais frustré par l'absence de fonctionnalité de portée de bloc. Cependant, une fois que j'ai compris les modules Python, je pourrais réaliser plus élégamment mes objectifs précédents sans avoir besoin d'une portée de bloc.
En tant que motivation et pour orienter les gens vers la bonne direction, je pense qu'il est utile de donner des exemples explicites de certaines des constructions de portée de Python. J'explique d'abord ma tentative infructueuse d'utiliser des classes Python pour implémenter la portée de bloc. Ensuite, j'explique comment j'ai réalisé quelque chose de plus utile en utilisant les modules Python. À la fin, je décris une application pratique des packages au chargement et au filtrage des données.
Tentative de portée de bloc avec des classes
Pendant quelques instants, j'ai pensé que j'avais atteint la portée du bloc en collant du code à l'intérieur d'une déclaration de classe:
x = 5
class BlockScopeAttempt:
x = 10
print(x) # Output: 10
print(x) # Output: 5
Malheureusement, cela se décompose lorsqu'une fonction est définie:
x = 5
class BlockScopeAttempt:
x = 10
print(x) # Output: 10
def printx2():
print(x)
printx2() # Output: 5!!!
C'est parce que les fonctions définies dans une classe utilisent une portée globale. Le moyen le plus simple (mais pas le seul) de résoudre ce problème est de spécifier explicitement la classe:
x = 5
class BlockScopeAttempt:
x = 10
print(x) # Output: 10
def printx2():
print(BlockScopeAttempt.x) # Added class name
printx2() # Output: 10
Ce n'est pas si élégant car il faut écrire les fonctions différemment selon qu'elles sont ou non contenues dans une classe.
De meilleurs résultats avec les modules Python
Les modules sont très similaires aux classes statiques, mais les modules sont beaucoup plus propres d'après mon expérience. Pour faire de même avec les modules, je crée un fichier appelé my_module.py
dans le répertoire de travail courant avec le contenu suivant:
x = 10
print(x) # (A)
def printx():
global x
print(x) # (B)
Ensuite, dans mon fichier principal ou session interactive (par exemple Jupyter), je fais
x = 5
import my_module # Output: 10 from (A)
my_module.printx() # Output: 10 from (B)
print(x) # Output: 5
Comme explication, chaque fichier Python définit un module qui a son propre espace de noms global. L'importation d'un module vous permet d'accéder aux variables de cet espace de noms avec la .
syntaxe.
Si vous travaillez avec des modules dans une session interactive, vous pouvez exécuter ces deux lignes au début
%load_ext autoreload
%autoreload 2
et les modules seront automatiquement rechargés lorsque leurs fichiers correspondants seront modifiés.
Packages de chargement et de filtrage des données
L'idée de packages est une légère extension du concept de modules. Un package est un répertoire contenant un __init__.py
fichier (éventuellement vide) , qui est exécuté lors de l'importation. Les modules / packages de ce répertoire sont accessibles avec la .
syntaxe.
Pour l'analyse des données, j'ai souvent besoin de lire un gros fichier de données, puis d'appliquer de manière interactive divers filtres. La lecture d'un fichier prend plusieurs minutes, donc je ne veux le faire qu'une seule fois. Sur la base de ce que j'ai appris à l'école sur la programmation orientée objet, j'avais l'habitude de penser qu'il fallait écrire le code de filtrage et de chargement en tant que méthodes dans une classe. Un inconvénient majeur de cette approche est que si je redéfinis ensuite mes filtres, la définition de ma classe change, donc je dois recharger toute la classe, y compris les données.
Aujourd'hui, avec Python, je définis un package appelé my_data
qui contient des sous-modules nommés load
et filter
. À l'intérieur de filter.py
je peux faire une importation relative:
from .load import raw_data
Si je modifie filter.py
, puis autoreload
détectera les changements. Il ne se recharge pas load.py
, donc je n'ai pas besoin de recharger mes données. De cette façon, je peux prototyper mon code de filtrage dans un bloc-notes Jupyter, l'envelopper en tant que fonction, puis couper-coller de mon bloc-notes directement dans filter.py
. Comprendre cela a révolutionné mon flux de travail et m'a converti de sceptique à un croyant au «Zen of Python».
One purpose (of many) is to improve code readability
- Le code Python, écrit correctement (c'est-à-dire suivant le zen de python ) n'aurait pas besoin d'une telle garniture pour être lisible. En fait, c'est l'une des (nombreuses) choses que j'aime chez Python.