Welches Python-Paket steckt hinter Import ...

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Sirius3
User
Beiträge: 8270
Registriert: Sonntag 21. Oktober 2012, 17:20

Sonntag 19. August 2018, 05:10

Ich verstehe nicht so ganz, wo das große Problem ist. Ein einfaches grep

Code: Alles auswählen

grep -r --include '*.py' -h '\bimport\b' . | sort -u
sollte eine Liste aller Importzeilen liefern, die man leicht händisch durchgehen kann.
Relevant ist ja nur die oberste Ebene, die man dank Sortierung schnell findet.
Das kann man noch mit der Liste der eigenen und der Standardmodule (https://docs.python.org/3/library/) abgleichen.

Was übrig bleibt, dürfte nicht mehr all zu viel sein. Im Zweifel mit

Code: Alles auswählen

import xyz
print(xyz.__file__)
nach dem Pfad fragen und den Paketmanager bitten, das Paket zu nennen, das diese Datei installiert hat.
Nobuddy
User
Beiträge: 762
Registriert: Montag 30. Januar 2012, 16:38

Montag 20. August 2018, 09:56

Habe das soweit umgesetzt, dass ich jetzt alle Importe die Python betrifft, in einer Liste vorliegen.
Beim Versuch, diese weiter zu verarbeiten:

Code: Alles auswählen

    for modul in sorted(python_moduls):
        import modul
        print(modul.__file__)
erhalte ich die Fehlermeldung:

Code: Alles auswählen

ImportError: No module named 'modul'
was ja auch klar sein dürfte.

Nun habe ich die Frage an Euch, ob es eine Möglichkeit gibt, dies in dieser Art umsetzen zu können?
Ein automatisierter Ablauf, wäre ja von Vorteil.

Weiter habe ich manuell, mit zwei Beispielen getan:
import sys
sys.__file__
# Ergebnis: AttributeError: 'module' objects has no attribute '__file__'

import reportlab
reportlab.__file__
# Ergebnis: /usr/lib/python3/reportlab/__init__.py
Was kann man tun, um bei z.B. 'sys', ein Ergebnis zu erhalten?
Nobuddy
User
Beiträge: 762
Registriert: Montag 30. Januar 2012, 16:38

Montag 20. August 2018, 10:09

Zu Punkt 1, habe ich diese Lösung gefunden:

Code: Alles auswählen

    for modul in sorted(python_moduls):
        try:
            print(__import__(modul).__file__)
        except AttributeError:
            pass
        except ImportError:
            pass
Fragt sich nur, was es für den 'AttributeError' und den 'ImportError' für eine Lösung gibt, um auch hier ein Ergebnis zuerhalten?
Nobuddy
User
Beiträge: 762
Registriert: Montag 30. Januar 2012, 16:38

Montag 20. August 2018, 14:06

Ich poste mal meine Ergebnisse:

Code: Alles auswählen

####### python_paths #######
['/usr/lib/python3.4/codecs.py',
 '/usr/lib/python3.4/collections/__init__.py',
  '/usr/lib/python3.4/copy.py', 
  '/usr/lib/python3.4/csv.py',
   '/usr/lib/python3.4/datetime.py',
    '/usr/lib/python3.4/ftplib.py',
     '/usr/lib/python3.4/functools.py',
      '/usr/lib/python3.4/io.py',
       '/usr/lib/python3.4/json/__init__.py',
        '/usr/lib/python3.4/os.py',
         '/usr/lib/python3.4/platform.py',
          '/usr/lib/python3.4/queue.py',
           '/usr/lib/python3.4/re.py',
            '/usr/lib/python3.4/shutil.py',
             '/usr/lib/python3.4/smtplib.py',
              '/usr/lib/python3.4/socket.py',
               '/usr/lib/python3.4/subprocess.py',
                '/usr/lib/python3.4/threading.py',
                 '/usr/lib/python3.4/tkinter/__init__.py',
                  '/usr/lib/python3.4/urllib/__init__.py',
                   '/usr/lib/python3.4/zipfile.py',
                    '/usr/lib/python3/dist-packages/cups.cpython-34m-x86_64-linux-gnu.so',
                     '/usr/lib/python3/dist-packages/lxml/__init__.py',
                      '/usr/lib/python3/dist-packages/reportlab/__init__.py',
                       '/usr/lib/python3/dist-packages/requests/__init__.py',
                        '/usr/lib/python3/dist-packages/urllib3/__init__.py',
                         '/usr/lib/python3/dist-packages/xlrd/__init__.py']

####### AttributeError_names #######
[('atexit', <module 'atexit' (built-in)>), 
('sys', <module 'sys' (built-in)>),
 ('time', <module 'time' (built-in)>)]
Vielleicht könnt Ihr mir einen Tip geben, wie ich jetzt weiter komme?
Benutzeravatar
pixewakb
User
Beiträge: 1060
Registriert: Sonntag 24. April 2011, 19:43

Montag 20. August 2018, 14:16

Ich habe mal deinen Quellcode für einige bei mir installierte Pakete genutzt:

Code: Alles auswählen

for modul in sorted(["random", "os", "sys", "pandas", "something"]):
    try:
        print(__import__(modul).__file__)
    except AttributeError:
        pass
    except ImportError:
        pass
Liefert dann bei mir:

Code: Alles auswählen

C:\[...]\Python\Python37\lib\os.py
C:\[...]\Python\Python37\lib\site-packages\pandas\__init__.py
C:\[...]\Python\Python37\lib\random.py
C:\[...]\Python\Python37\lib\site-packages\something.py
Ich schließe daraus, dass im Ordner site-packages Module gelandet sind, die du selbst installiert hast und unter lib direkt die Standardbibliothek zu finden ist.

Die folgenden Module müsstest du bei dir mal prüfen, alles andere sieht für mich nach Standardbibliothek aus.:

Code: Alles auswählen

                    '/usr/lib/python3/dist-packages/cups.cpython-34m-x86_64-linux-gnu.so',
                     '/usr/lib/python3/dist-packages/lxml/__init__.py',
                      '/usr/lib/python3/dist-packages/reportlab/__init__.py',
                       '/usr/lib/python3/dist-packages/requests/__init__.py',
                        '/usr/lib/python3/dist-packages/urllib3/__init__.py',
                         '/usr/lib/python3/dist-packages/xlrd/__init__.py']
requests und xlrd (https://pypi.org/project/xlrd/) sind m. E. sicher Fremdbibliotheken, die du installiert hast. atexit, sys und time sind Teil der Standardbibliothek (https://docs.python.org/3.7/library/atexit.html).
Nobuddy
User
Beiträge: 762
Registriert: Montag 30. Januar 2012, 16:38

Montag 20. August 2018, 15:07

Die site-packages Module sind aus Deiner eigenen Installation.
Wie ich sehe, hast Du Python 3.7 installiert, dort ist der Namen des Pfades wohl site-packages.
Bei mir mit Python 3.4 ist es dist-packages.
Vielleicht hat es auch evtl. damit zu tun, das wir beide unterschiedliche Betriebssysteme nutzen!?

Bei dem überwiegenden Teil, sind das ohl Module, die zu dem Paket python3 gehören.
Die mit dem Unterordner dist-packages, sind selbst installierte Pakete, wie z.B. reportlab aus dem Paket python3-reportlab.

Diese < atexit, sys, time > gehören wohl zur Standardbibliothek, konnte aber keine Anhaltspunkte in meiner Paketverwaltung finden.

Naja, zumindets lässt sich das mal recht gut eingrenzen, der Rest ist wohl Handarbeit ...

Mein ganzer Code zu dieser Geschichte, sieht so aus (lässt sich bestimmt noch optimieren, aber es funktioniert):

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# For Python3.x


import os
import sys

def search_import_names(modul, import_names):
    """
    Suche in Python-Dateien nach Import von Modulen.
    Gebe die die Namen der Module aus.
    """

    pool = set()
    with open(modul, 'r') as handler:
        for obj in handler:
            if not obj.startswith(('from ', 'import ')):
                continue
            if obj.startswith('from '):
                modul = obj.split('from ')[1].split(' ')[0]
            elif obj.startswith('import '):
                modul = obj.split('import ')[1].split(' ')[0].strip()
            modul = modul.replace(',', '')
            import_names.add(modul)
    return import_names


def search_py_files():
    """
    Suche in Python-Pfad nach Python-Dateien.
    Überprüfung untergeordnete Ordner in Python-Pfad (Erste Ebene).
    Übergabe der Python-Dateien an Funktion < search_import_names >,
    zum suchen der importierten Module.
    Erstellung aller importierten Modul-Namen in < import_names >
    als Set, sowie ein Dictionary mit eigen erstellten Modulen
    in < modul2privat >.
    Übergabe von < import_names > und < modul2privat > an Funktion
    < build_python_importnames >.
    """

    import_names = set()
    modul2privat = dict()
    for filename in os.listdir(os.getcwd()):
        check = '{}{}{}'.format(os.getcwd(), os.sep, filename)
        if os.path.isfile(check) and filename.endswith('.py'):
            modul = check
            import_names = search_import_names(modul, import_names)
            modul2privat[filename.replace('.py', '')] = True
        elif os.path.isdir(check):
            pathname = filename
            path = '{}{}{}'.format(os.getcwd(), os.sep, pathname)
            for file in os.listdir(path):
                modul = '{}{}{}'.format(path, os.sep, file)
                if os.path.isfile(modul) and modul.endswith('.py'):
                    import_names = search_import_names(modul, import_names)
                    modul2privat[file.replace('.py', '')] = True
    return build_python_importnames(import_names, modul2privat)


def build_python_importnames(import_names, modul2privat):
    """
    Entfernung der eigen erstellten Modulen aus < import_names >.
    Gebe die Namen der importierten Python-Module aus.
    Übergabe von < python_import_names > an Funktion < find_python_pachage_path >,
    zur Findung Python-Pfades der Python-Module.
    """

    python_import_names = set()
    for name in import_names:
        try:
            modul2privat[name]
        except KeyError:
            python_import_names.add(name)
    return find_python_pachage_path(python_import_names)


def find_python_pachage_path(python_import_names):
    """
    Import der Namen aus < python_import_names > mit Ausgabe
    der Python-Pfade < python_paths > und < AttributeError_names >, bei
    denen es kein Resultat gab.
    """

    python_paths = set()
    AttributeError_names = list()
    for name in sorted(python_import_names):
        try:
            python_paths.add(__import__(name).__file__)
        except AttributeError:
            AttributeError_names.append((name, __import__(name)))
    python_paths = sorted(python_paths)
    AttributeError_names = sorted(AttributeError_names)
    print('####### python_paths #######')
    print(python_paths)
    print('####### AttributeError_names #######')
    print(AttributeError_names)
    return python_paths, AttributeError_names


def main():
    search_py_files()

if __name__ == '__main__':
    main()
Benutzeravatar
pixewakb
User
Beiträge: 1060
Registriert: Sonntag 24. April 2011, 19:43

Montag 20. August 2018, 15:18

Ich habe noch keine requirements-Dateien geschrieben, aber den Schritt bei mir vorbereitet. Ich installiere unter Windows alles mit pip und kann mir mit pip freeze ausgeben lassen, was installiert ist und welche Version das hat. Damit lassen sich dann requirements-Dateien zügig erstellen. pip freeze liefert bei mir auch die Pakete mit, die als Abhängigkeiten irgendwo anders mitinstalliert wurden, erleichtert aber m. E. die Arbeit sehr!
Nobuddy
User
Beiträge: 762
Registriert: Montag 30. Januar 2012, 16:38

Montag 20. August 2018, 16:40

Hier stelle mein überarbeiteter Code, für die zu installierenden Python-Pakete, um das Python-Programm lauffähig zu machen:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# For Python3.x


import os
import sys

def search_import_names(modul, import_names):
    """
    Suche in Python-Dateien nach Import von Modulen.
    Gebe die die Namen der Module aus.
    """

    pool = set()
    with open(modul, 'r') as handler:
        for obj in handler:
            if not obj.startswith(('from ', 'import ')):
                continue
            if obj.startswith('from '):
                modul = obj.split('from ')[1].split(' ')[0]
            elif obj.startswith('import '):
                modul = obj.split('import ')[1].split(' ')[0].strip()
            modul = modul.replace(',', '')
            import_names.add(modul)
    return import_names


def search_py_files():
    """
    Suche in Python-Pfad nach Python-Dateien.
    Überprüfung untergeordnete Ordner in Python-Pfad (Erste Ebene).
    Übergabe der Python-Dateien an Funktion < search_import_names >,
    zum suchen der importierten Module.
    Erstellung aller importierten Modul-Namen in < import_names >
    als Set, sowie ein Dictionary mit eigen erstellten Modulen
    in < modul2privat >.
    Übergabe von < import_names > und < modul2privat > an Funktion
    < build_python_importnames >.
    """

    import_names = set()
    modul2privat = dict()
    for filename in os.listdir(os.getcwd()):
        check = '{}{}{}'.format(os.getcwd(), os.sep, filename)
        if os.path.isfile(check) and filename.endswith('.py'):
            modul = check
            import_names = search_import_names(modul, import_names)
            modul2privat[filename.replace('.py', '')] = True
        elif os.path.isdir(check):
            pathname = filename
            path = '{}{}{}'.format(os.getcwd(), os.sep, pathname)
            for file in os.listdir(path):
                modul = '{}{}{}'.format(path, os.sep, file)
                if os.path.isfile(modul) and modul.endswith('.py'):
                    import_names = search_import_names(modul, import_names)
                    modul2privat[file.replace('.py', '')] = True
    return build_python_importnames(import_names, modul2privat)


def build_python_importnames(import_names, modul2privat):
    """
    Entfernung der eigen erstellten Modulen aus < import_names >.
    Gebe die Namen der importierten Python-Module aus.
    Übergabe von < python_import_names > an Funktion < find_python_pachage_path >,
    zur Findung Python-Pfades der Python-Module.
    """

    python_import_names = set()
    for name in import_names:
        try:
            modul2privat[name]
        except KeyError:
            python_import_names.add(name)
    return find_python_package_path(python_import_names)


def find_python_package_path(python_import_names):
    """
    Import der Namen aus < python_import_names >.
    Übergabe der Python-Pfade < python_paths > an Fuktion < build_python_package >,
    zur Erstellung der Python-Pakete.
    """

    python_paths = set()
    AttributeError_names = list()
    for name in sorted(python_import_names):
        try:
            python_paths.add(__import__(name).__file__)
        except AttributeError:
            AttributeError_names.append((name, __import__(name)))
    python_paths = sorted(python_paths)
    AttributeError_names = sorted(AttributeError_names)
    # print('####### python_paths #######')
    # print(python_paths)
    # print('####### AttributeError_names #######')
    # print(AttributeError_names)
    return build_python_package(python_paths)


def build_python_package(python_paths):
    """
    Erstellung der Python-Pakete aus < python_paths > in Liste.
    """

    python_packets = set()
    python_version = set()
    for path in python_paths:
        extra_packet = False
        for folder in path.split(os.sep):
            try:
                python
            except UnboundLocalError:
                if 'python' in folder:
                    python = folder
            if 'packages' in folder:
                extra_packet = True
                continue
            if extra_packet:
                extra_packet = folder.split('.')[0]
                break
        python_packet = python
        if extra_packet:
            python_packet = '{}-{}'.format(python_packet, extra_packet)
        python_packets.add(python_packet)
        python_version.add(python)
    print('Python-Version: ', ''.join(python_version))
    python_packets.add(('{}-minmal').format(''.join(python_version)))
    python_packets = sorted(python_packets)
    print('Zu installierende Python-Paket:')
    for packet in python_packets:
        print('\t{}'.format(packet))
    return python_packets



def main():
    search_py_files()

if __name__ == '__main__':
    main()
Bei meinem Python-Programm, erhalte ich folgende Ausgabe:

Code: Alles auswählen

Python-Version:  python3.4
Zu installierende Python-Paket:
        python3.4
        python3.4-cups
        python3.4-lxml
        python3.4-minmal
        python3.4-reportlab
        python3.4-requests
        python3.4-urllib3
        python3.4-xlrd
Sirius3
User
Beiträge: 8270
Registriert: Sonntag 21. Oktober 2012, 17:20

Dienstag 21. August 2018, 06:27

@Nobuddy: Du hast also meinen shell-Einzeiler in 70 Zeilen Pythoncode umgewandelt. ›search_import_names‹ bekommt ein ›import_names‹, ändert es und gibt es wieder zurück. Das sollte nicht sein. Die Funktion sollte überhaupt nicht eine Datenstruktur bekommen, die gefüllt wird, sondern nur etwas zurückgeben.
›pool‹ wird nicht benutzt. Wenn man über ein Dateiobjekt (das ist kein ›handler‹) iteriert bekommt man Zeilen, keine Objekte; Variablennamen sind wichtig zum Verständnis. Die Funktion übersieht alle Importe, die aus welchen Gründen auch immer, eingerückt sind.
Statt `continue` solltest Du ein `else` benutzen, und das `elif` prüft den letzten möglichen Fall, ist also immer wahr.
`split` tut nicht das, was Du denkst. Sobald Du mehrere Leerzeichen (oder Tabs) benutzt, geht das schief. Das `replace` mußt Du mir erklären, denn da werden mehrere durch Komma getrennte Module zu einem zusammengepackt.

Weil schon mehrfach im Thread angesprochen, das ganze umgesetzt mit dem ast-Modul:

Code: Alles auswählen

def search_import_names(module):
    import_names = set()
    with open(module) as content:
        tree = ast.parse(content.read())
    for node in ast.walk(tree):
        if isinstance(node, ast.Import):
            for alias in node.names:
                import_names.add(alias.name)
        elif isinstance(node, ast.ImportFrom):
            import_names.add(node.module)
    return import_names
›search_py_files‹ tut zu viel. Es durchsucht nicht nur die Dateien, sondern ruft auch noch ›build_python_importnames‹ auf, das dann ›find_python_package_path‹ aufruft, was dann die eigentliche Hauptaufgabe des Programms macht. So tiefe Verschachtelungen sind schwer zu durchschauen. Rufe die Einzelnen Teile aus dem Hauptprogramm auf und schreibe Funktionen mit nur einer einfachen Aufgabe. Dann wird auch der Doc-String nicht so lang.

›search_by_files‹ soll also nur Dateiendurchsuchen, also ›search_in_files‹. ›modul2privat‹ ist eigentlich ein Set, weil das module2bool immer nur auf True mappt. Deine Suche geht exakt zwei Verzeichnisebenen tief, und das noch durch kopierten Code. Eigentlich will man beliebig tief suchen. Dafür gibt es walk von pathlib. Verzeichnisse setzt man auch nicht mit .format zusammen, dafür gibt es auch was aus pathlib.
Damit wird die Funktion zu:

Code: Alles auswählen

def search_in_files():
    import_names = set()
    for filename in pathlib.Path('.').walk('**/*.py'):
        import_names.update(search_import_names(filename))
    return import_names
›build_python_importnames‹ ist ein einfaches Filter, wobei man zum Prüfen, ob etwas in einem Wörterbuch ist, `in´ benutzt, und nicht einen KeyError.

Das mit dem `import` war nur als schnelle händlische Lösung gemeint, weil es bei 5 Modulen eigentlich nicht lohnt, ein ganzes Programm dafür zu schreiben. Will man tatsächlich nach den Dateien für ein Modul suchen, importiert man sie nicht, sondern benutzt importlib.

Code: Alles auswählen

def find_python_package_path(modules):
    python_paths = set()
    for module in modules:
        loader = importlib.find_loader(module)
        try:
            python_paths.add(loader.path)
        except AttributeError:
            # Module has no path, probably a builtin module
            # ignore it
            pass
    return sorted(python_paths)
In ›build_python_package‹ verstehe ich nicht, was da alles magisches passiert. Ein UnboundLocalError ist wirklich ein Fehler, und sollte in einer normalen Funktion nicht abgefangen werden. Für solche Fälle hat man den Wert `None`. `extra_packet` ist mal ein Wahrheitswert mal ein String. Eine Variable sollte nur einen Typ haben.

Zum Schluß, ich hatte angedeutet, dass Du Debian direkt nach dem Packetnamen fragen kannst, Du mußt also nicht raten, in welchem Paket das jeweilige Modul drin ist:

Code: Alles auswählen

dpkg -S /usr/lib/python3/dist-packages/reportlab/__init__.py
Nobuddy
User
Beiträge: 762
Registriert: Montag 30. Januar 2012, 16:38

Dienstag 21. August 2018, 09:55

Hallo Sirius3,

Danke für Deinen Input, werde Deinen Code durcharbeiten, um zu verstehen!

Wie schon zu vor angedeutet, ist mein Code noch besser umsetzbar, wie Du selbst gezeigt hast.
Wie Du vielleicht auch erkennen kannst, ist mein Code sehr einfach gestrickt, weil ...... mir unter anderem auch die Englischkenntnisse fehlen.
Ich bin daher immer dankbar, für Input wie Deinen .....
Copy & Paste ist einfach, aber ohne Verständnis für den Code, kommt man nicht wirklich weiter.
Werde deshalb in Ruhe das Ganze durcharbeiten.

Grüße Nobuddy
Nobuddy
User
Beiträge: 762
Registriert: Montag 30. Januar 2012, 16:38

Dienstag 21. August 2018, 11:54

Fehlermeldung bei 'search_in_files':

Code: Alles auswählen

Traceback (most recent call last):
  File "start_python_packetcontrol_new.py", line 46, in <module>
    search_in_files()
  File "start_python_packetcontrol_new.py", line 26, in search_in_files
    for filename in pathlib.Path('.').walk('**/*.py'):
AttributeError: 'PosixPath' object has no attribute 'walk'
Wie lässt sich das beheben?
Finde 'walk' nur in Verbindung mit 'os', aber mit 'os.walk' komme ich auch nicht weiter.
Sirius3
User
Beiträge: 8270
Registriert: Sonntag 21. Oktober 2012, 17:20

Dienstag 21. August 2018, 12:20

Heißt ja auch `glob` statt `walk`.
Nobuddy
User
Beiträge: 762
Registriert: Montag 30. Januar 2012, 16:38

Mittwoch 22. August 2018, 12:01

Hallo Sirius3,

habe Deinen Code-Vorschlag verwendet, musste aber noch ein paar kleine Änderungen vornehmen:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# For Python3.x

import os
import ast
import subprocess
import pathlib
import importlib


def search_import_names(module):
    import_names = set()
    with open(module) as content:
        tree = ast.parse(content.read())
    for node in ast.walk(tree):
        if isinstance(node, ast.Import):
            for alias in node.names:
                import_names.add(alias.name)
        elif isinstance(node, ast.ImportFrom):
            import_names.add(node.module)
    return import_names


def search_in_files():
    import_names = set()
    for filename in pathlib.Path('.').glob('**/*.py'):
        import_names.update(search_import_names(
            '{}{}{}'.format(os.getcwd(), os.sep, filename)))
    return find_python_paths(import_names)


def find_python_paths(modules):
    python_paths = set()
    for module in sorted(modules):
        try:
            loader = importlib.find_loader(module.split('.')[0])
            python_paths.add(loader.path)
        except (AttributeError, ImportError, KeyError):
            # Module has no path, probably a builtin module
            # ignore it
            pass
    return package_to_python_paths(sorted(python_paths))


def package_to_python_paths(python_paths):
    python_package = set()
    for path in python_paths:
        cmd = 'dpkg -S {}'.format(path)
        proc = subprocess.Popen(cmd, shell='true', stdout=subprocess.PIPE)
        bytes = proc.stdout.read()
        result = ''.join(map(chr, bytes))
        package = result.split(':')[0]
        if package != '':
            python_package.add(package)
    print(sorted(python_package))
    return sorted(python_package)


def main():
    search_in_files()

if __name__ == '__main__':
    main()
Funktioniert prima und erhalte alle erforderlichen Python-Pakete aufgelistet, die in meiner Firmware Einsatz finden.

Grüße Nobuddy
Benutzeravatar
snafu
User
Beiträge: 5494
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Mittwoch 22. August 2018, 12:39

@Nobuddy:
Bytes haben eine decode()-Methode. Dann kann man sich das hier sparen:

Code: Alles auswählen

''.join(map(chr, bytes))
Außerdem sollte man Popen() mit einer Liste verwenden anstatt eine eigene Shell zu starten, weil letzteres potenziell unsicher ist. Und wie kamst du auf shell="true"? Dir ist bekannt, dass es True als Objekt in Python gibt?

Insgesamt ist der Programmfluss nach meinem Geschmack zu verschachtelt. Normalerweise hat man ein paar Lowlevel-Funktionen und dann eine höhere Schicht, die diese Funktionen sinnvoll aufruft und jeweils die Zwischenergebnisse übergibt. Sonst geht die Entkopplung und Wiederverwendbarkeit als ein wichtiges Merkmal von Funktionen verloren, wenn man am Ende doch wieder ein riesiges Knäuel von Abhängigkeiten hat.
shcol (Repo | Doc | PyPi)
Sirius3
User
Beiträge: 8270
Registriert: Sonntag 21. Oktober 2012, 17:20

Mittwoch 22. August 2018, 13:59

@Nobuddy: wenn das Python3 ist, sollte die erste Zeile `python3` heißen, die zweite Zeile ist überflüssig, weil es utf8 defaultencoding ist und die dritte Zeile auch, weil das ja schon in der ersten Zeile steht.

Zeile 29: das man das nicht so macht, habe ich schon geschrieben. os.pah.join wäre eine richtige Möglichkeit, Da Du nur das aktuelle Verzeichnis anfügen willst, os.path.abspath noch besser, `filename.absolute()` noch viel besser, weil es sich ja um Path-Objekte handelt, ganz weglassen am allerbesten, weil sowieso alle Dateien relativ zum aktuellen Pfad referenziert wird.

Zeile 30/43: dass man Aufrufe nicht so tief verschachteln sollte, hab ich schon erwähnt.
Zeile 37: das Aufsplitten macht man am besten schon in `search_in_files`.
Zeile 39: wie soll da ein KeyError auftreten? Einfach irgendwelche Exceptions auf gut Glück abfangen macht man nicht. Es hat ja einen ganz expliziten Grund, warum hier AttributeError ignoriert wird, der auch kommentiert ist.
Zeile 50: shell=True benutzt man nicht, sondern übergibt das Commando als Liste, dann braucht man auch keine Argumente zusammenformatieren. Besser als Popen zu benutzen, wo auch noch ein wait fehlt ist es gleich check_output zu benutzen.
Zeile 56: statt der Ausgabe hier, solltest Du den Rückgabewert benutzen.

Code: Alles auswählen

#!/usr/bin/env python3
import os
import ast
import subprocess
import pathlib
import importlib


def search_import_names(module):
    import_names = set()
    with open(module) as content:
        tree = ast.parse(content.read())
    for node in ast.walk(tree):
        if isinstance(node, ast.Import):
            for alias in node.names:
                import_names.add(alias.name)
        elif isinstance(node, ast.ImportFrom):
            if node.level == 0:
                # ignore relative imports
                import_names.add(node.module)
    return import_names


def search_in_files():
    import_names = set()
    for filename in pathlib.Path('.').glob('**/*.py'):
        for module in search_import_names(filename):
            import_names.add(module.split('.')[0])
    return import_names


def find_python_path(module):
    try:
        print(module)
        loader = importlib.find_loader(module)
        return loader.path
    except (ImportError, AttributeError):
        # Module has no path, probably a builtin module
        print("No loader for", module)
        return None


def package_to_python_path(path):
    cmd = ['dpkg', '-S', path]
    try:
        result = subprocess.check_output(cmd)
        package = result.decode().split(':')[0]
        return package
    except subprocess.CalledProcessError:
        print("no packages for", path)


def main():
    modules = search_in_files()
    packages = []
    for module in modules:
        path = find_python_path(module)
        if path is not None:
            package = package_to_python_path(path)
            packages.append(package)
    packages.sort()
    print("Packages needed:")
    for package in packages:
        print(package)


if __name__ == '__main__':
    main()
Antworten