J'essaie de comprendre quelle est la motivation qui pousse les fonctions de bibliothèque de Python à exécuter des tâches spécifiques à un système d'exploitation, telles que la création de fichiers/répertoires, la modification d'attributs de fichiers, etc. au lieu de simplement exécuter ces commandes via os.system()
ou subprocess.call()
?
Par exemple, pourquoi voudrais-je utiliser os.chmod
Au lieu de os.system("chmod...")
?
Je comprends qu’il est plus "pythonique" d’utiliser autant que possible les méthodes de bibliothèque disponibles de Python au lieu d’exécuter directement des commandes Shell. Mais y a-t-il une autre motivation derrière cela du point de vue de la fonctionnalité?
Je ne parle que d’exécuter ici des commandes Shell simples d’une ligne. Lorsque nous avons besoin de plus de contrôle sur l'exécution de la tâche, je comprends que l'utilisation du module subprocess
a plus de sens, par exemple.
C'est plus rapide, os.system
Et subprocess.call
Créent de nouveaux processus inutiles pour quelque chose d'aussi simple. En fait, os.system
Et subprocess.call
Avec l'argument Shell
créent généralement au moins deux nouveaux processus: le premier étant le shell et le second la commande que vous ' re en cours d’exécution (si ce n’est pas un shell intégré comme test
).
Certaines commandes sont inutiles dans un processus séparé. Par exemple, si vous exécutez os.spawn("cd dir/")
, le répertoire de travail actuel du processus enfant sera modifié, mais pas le processus Python. Vous devez utiliser os.chdir
pour ça.
Vous n'avez pas à vous soucier de spécial caractères interprétés par le shell. os.chmod(path, mode)
fonctionnera quel que soit le nom du fichier, alors que os.spawn("chmod 777 " + path)
échouera horriblement si le nom du fichier ressemble à ; rm -rf ~
. (Notez que vous pouvez contourner ce problème si vous utilisez subprocess.call
Sans l'argument Shell
.)
Vous n'avez pas à vous soucier de les noms de fichiers commençant par un tiret. os.chmod("--quiet", mode)
modifiera les autorisations du fichier nommé --quiet
, mais os.spawn("chmod 777 --quiet")
échouera, car --quiet
est interprété comme un argument. Ceci est vrai même pour subprocess.call(["chmod", "777", "--quiet"])
.
Vous avez moins d'inquiétudes multiplate-forme et inter-shell, car la bibliothèque standard de Python est censée s'occuper de cela pour vous. Votre système a-t-il la commande chmod
? Est-il installé? Prend-il en charge les paramètres que vous attendez? Le module os
essaiera d’être aussi multi-plateforme que possible et de documenter lorsque ce n’est pas possible.
Si la commande que vous exécutez a sortie qui vous intéressent, vous devez l'analyser, ce qui est plus compliqué qu'il n'y paraît, car vous risqueriez d'oublier les coins-cas (noms de fichiers avec des espaces, des tabulations et des nouvelles lignes). en eux), même lorsque vous ne vous souciez pas de la portabilité.
C'est plus sûr. Pour vous donner une idée, voici un exemple de script
import os
file = raw_input("Please enter a file: ")
os.system("chmod 777 " + file)
Si l'entrée de l'utilisateur était test; rm -rf ~
ceci effacerait alors le répertoire de base.
C'est pourquoi il est plus sûr d'utiliser la fonction intégrée.
Par conséquent, pourquoi vous devriez également utiliser un sous-processus plutôt que un système.
Il y a quatre cas forts pour préférer les méthodes plus spécifiques de Python dans le module os
à l'aide de os.system
ou du subprocess
module lors de l'exécution d'une commande:
os
sont disponibles sur plusieurs plates-formes, tandis que de nombreuses commandes du shell sont spécifiques au système d'exploitation.os
.Vous êtes en train d'exécuter un "intermédiaire" redondant sur votre chemin vers les éventuels appels système (chmod
dans votre exemple). Cet intermédiaire est un nouveau processus ou sous-shell.
De os.system
:
Exécutez la commande (une chaîne) dans un sous-shell ...
Et subprocess
est juste un module pour générer de nouveaux processus.
Vous pouvez faire ce dont vous avez besoin sans engendrer ces processus.
Le module os
a pour but de fournir des services génériques au système d'exploitation. Sa description commence par:
Ce module fournit un moyen portable d’utiliser les fonctionnalités dépendantes du système d’exploitation.
Vous pouvez utiliser os.listdir
sur Windows et Unix. Essayer d'utiliser os.system
/subprocess
pour cette fonctionnalité vous obligera à maintenir deux appels (pour ls
/dir
) et à vérifier le système d'exploitation utilisé. sur. Ce n’est pas aussi portable et causera encore plus de frustration plus tard (voir Traitement de la sortie ).
Supposons que vous souhaitiez répertorier les fichiers dans un répertoire.
Si vous utilisez os.system("ls")
/subprocess.call(['ls'])
, vous ne pouvez récupérer que la sortie du processus, qui est en gros une chaîne contenant les noms de fichiers.
Comment pouvez-vous distinguer un fichier avec un espace dans son nom à partir de deux fichiers?
Que faire si vous n'avez pas la permission de lister les fichiers?
Comment mapper les données sur des objets python?
Celles-ci ne sont là que par hasard et, bien qu’il existe des solutions à ces problèmes, pourquoi résoudre à nouveau un problème qui a été résolu pour vous?
Ceci est un exemple de suivre le principe ne pas répéter (souvent désigné sous le nom de "DRY") par pas en répétant une implémentation qui existe déjà et qui est librement disponible pour vous.
os.system
Et subprocess
sont puissants. C'est bien quand vous avez besoin de ce pouvoir, mais c'est dangereux quand vous n'en avez pas. Lorsque vous utilisez os.listdir
, Vous savez qu'il ne peut rien faire d'autre que répertorier des fichiers ou générer une erreur. Lorsque vous utilisez os.system
Ou subprocess
pour obtenir le même comportement, vous pouvez éventuellement finir par faire quelque chose que vous ne vouliez pas faire.
Sécurité des injections (voir Exemples d'injections de shell ) :
Si vous utilisez les entrées de l'utilisateur comme nouvelle commande, vous lui avez essentiellement attribué un shell. Cela ressemble beaucoup à une injection SQL fournissant un shell dans la base de données à l'utilisateur.
Un exemple serait une commande de la forme:
# ... read some user input
os.system(user_input + " some continutation")
Cela peut être facilement exploité afin d’exécuter n’importe quel code arbitraire en utilisant l’entrée: NASTY COMMAND;#
Pour créer l’éventuel:
os.system("NASTY COMMAND; # some continuation")
De nombreuses commandes de ce type peuvent mettre votre système en danger.
Pour une raison simple - lorsque vous appelez une fonction Shell, il crée un sous-shell qui est détruit après la création de votre commande. Par conséquent, si vous changez de répertoire dans un shell - cela n'affectera pas votre environnement en Python.
De plus, la création de sous-shell prend beaucoup de temps. Par conséquent, l’utilisation directe des commandes du système d’exploitation aura une incidence sur vos performances.
EDIT
J'ai eu des tests de chronométrage en cours:
In [379]: %timeit os.chmod('Documents/recipes.txt', 0755)
10000 loops, best of 3: 215 us per loop
In [380]: %timeit os.system('chmod 0755 Documents/recipes.txt')
100 loops, best of 3: 2.47 ms per loop
In [382]: %timeit call(['chmod', '0755', 'Documents/recipes.txt'])
100 loops, best of 3: 2.93 ms per loop
La fonction interne s'exécute plus de 10 fois plus vite
EDIT2
Il peut arriver que l'invocation d'un exécutable externe donne de meilleurs résultats que Python packages - Je viens de me souvenir d'un courrier électronique envoyé par un de mes collègues dont les performances étaient de gzip appelé via le sous-processus était bien supérieur aux performances d'un paquet Python qu'il avait utilisé. Mais certainement pas lorsqu'il s'agit de packages de système d'exploitation standard émulant des commandes standard
Les appels de shell sont spécifiques au système d'exploitation alors que Python Les fonctions du module OS ne le sont pas, dans la plupart des cas. Cela évite de générer un sous-processus.
C'est beaucoup plus efficace. Le "Shell" est juste un autre binaire du système d'exploitation qui contient beaucoup d'appels système. Pourquoi créer la totalité du processus Shell uniquement pour cet appel système?
La situation est encore pire lorsque vous utilisez os.system
pour quelque chose qui n'est pas intégré à Shell. Vous démarrez un processus Shell qui, à son tour, lance un exécutable qui lance ensuite l'appel système (à deux processus de distance). Au moins subprocess
aurait supprimé la nécessité d'un processus intermédiaire Shell.
Ce n'est pas spécifique à Python, ceci. systemd
est une telle amélioration des temps de démarrage de Linux pour la même raison: il effectue lui-même les appels système nécessaires au lieu de générer un millier de shells.