มีวิธีมาตรฐานในการแสดงชื่อโมดูล Python ในแพ็คเกจหรือไม่?


102

มีวิธีที่ตรงไปตรงมาในการแสดงชื่อโมดูลทั้งหมดในแพ็คเกจโดยไม่ต้องใช้__all__หรือไม่?

ตัวอย่างเช่นให้แพ็คเกจนี้:

/testpkg
/testpkg/__init__.py
/testpkg/modulea.py
/testpkg/moduleb.py

ฉันสงสัยว่ามีวิธีมาตรฐานหรือในตัวในการทำสิ่งนี้:

>>> package_contents("testpkg")
['modulea', 'moduleb']

วิธีการด้วยตนเองคือการวนซ้ำผ่านเส้นทางการค้นหาโมดูลเพื่อค้นหาไดเร็กทอรีของแพ็คเกจ จากนั้นเราสามารถแสดงรายการไฟล์ทั้งหมดในไดเร็กทอรีนั้นกรองไฟล์ py / pyc / pyo ที่ไม่ซ้ำกันออกจากนั้นดึงส่วนขยายและส่งคืนรายการนั้น แต่ดูเหมือนจะเป็นงานที่พอใช้สำหรับบางสิ่งที่กลไกการนำเข้าโมดูลกำลังดำเนินการภายในอยู่แล้ว ฟังก์ชั่นนั้นถูกเปิดเผยทุกที่หรือไม่?

คำตอบ:


24

อาจจะทำในสิ่งที่คุณกำลังมองหา?

import imp
import os
MODULE_EXTENSIONS = ('.py', '.pyc', '.pyo')

def package_contents(package_name):
    file, pathname, description = imp.find_module(package_name)
    if file:
        raise ImportError('Not a package: %r', package_name)
    # Use a set because some may be both source and compiled.
    return set([os.path.splitext(module)[0]
        for module in os.listdir(pathname)
        if module.endswith(MODULE_EXTENSIONS)])

1
ฉันจะเพิ่ม 'and module! = " init .py"' ​​ใน 'if' สุดท้ายเนื่องจากinit .py ไม่ได้เป็นส่วนหนึ่งของแพ็กเกจจริงๆ และ. pyo เป็นอีกหนึ่งนามสกุลที่ถูกต้อง นอกเหนือจากนั้นการใช้ imp.find_module ก็เป็นความคิดที่ดีมาก ฉันคิดว่านี่คือคำตอบที่ถูกต้อง
DNS

3
ฉันไม่เห็นด้วย - คุณสามารถนำเข้าinitได้โดยตรงดังนั้นทำไมจึงเป็นกรณีพิเศษ แน่นอนว่าไม่พิเศษพอที่จะแหกกฎ ;-)
cdleary

6
คุณควรใช้imp.get_suffixes()แทนรายการที่เขียนด้วยมือของคุณ
itsadok

3
นอกจากนี้โปรดทราบว่าสิ่งนี้ใช้ไม่ได้กับแพ็กเกจย่อยเช่นxml.sax
itsadok

1
นี่เป็นวิธีที่แย่จริงๆ คุณไม่สามารถบอกได้อย่างน่าเชื่อถือว่าโมดูลจากนามสกุลไฟล์คืออะไร
Wim

193

เมื่อใช้python2.3 ขึ้นไปคุณสามารถใช้pkgutilโมดูล:

>>> import pkgutil
>>> [name for _, name, _ in pkgutil.iter_modules(['testpkg'])]
['modulea', 'moduleb']

แก้ไข:โปรดทราบว่าพารามิเตอร์ไม่ใช่รายการของโมดูล แต่เป็นรายการเส้นทางดังนั้นคุณอาจต้องการทำสิ่งนี้:

>>> import os.path, pkgutil
>>> import testpkg
>>> pkgpath = os.path.dirname(testpkg.__file__)
>>> print [name for _, name, _ in pkgutil.iter_modules([pkgpath])]

16
นี่เป็นการรบกวนที่ไม่มีเอกสาร แต่ดูเหมือนจะเป็นวิธีที่ถูกต้องที่สุดในการดำเนินการนี้ หวังว่าคุณจะไม่รังเกียจฉันเพิ่มโน้ต
itsadok

13
pkgutilมีในpython2.3 ขึ้นไปจริงไหม นอกจากนี้ในขณะที่pkgutil.iter_modules()จะไม่ทำงานซ้ำ แต่ก็มีpkgutil.walk_packages()เช่นกันซึ่งจะเรียกคืน ขอบคุณสำหรับตัวชี้ไปที่แพ็คเกจนี้
Sandip Bhattacharya

เหตุใดจึงiter_modulesไม่ทำงานสำหรับการนำเข้าแบบสัมบูรณ์เช่นa.b.testpkg? กำลังให้ฉัน[]
Hussain

ฉันมองข้ามการแก้ไขของคุณ :( ขออภัยมันใช้งานได้หลังจากที่ฉันทำตามตัวอย่างที่สอง
Hussain

1
ฉันไม่สามารถยืนยันได้ว่าการเรียกpkgutil.walk_packages()ซ้ำมันให้ผลลัพธ์เดียวกันกับpkgutil.iter_modules()ฉันดังนั้นฉันคิดว่าคำตอบไม่สมบูรณ์
rwst

29
import module
help(module)

2
แม้ว่าความช่วยเหลือจะแสดงรายการเนื้อหาแพ็กเกจที่ด้านล่างของข้อความวิธีใช้ แต่คำถามก็มีรายละเอียดวิธีการดำเนินการดังนี้ f (package_name) => ["module1_name", "module2_name"] ฉันคิดว่าฉันสามารถแยกวิเคราะห์สตริงที่ส่งคืนโดยความช่วยเหลือ แต่ดูเหมือนว่าจะเป็นวงเวียนมากกว่าการแสดงรายการไดเรกทอรี
DNS

1
@ DNS: help()พิมพ์สิ่งต่างๆมันไม่ส่งคืนสตริง
Junuxx

ฉันยอมรับว่านี่เป็นทางอ้อม แต่มันส่งฉันลงไปในโพรงกระต่ายเพื่อดูวิธีการhelp()ทำงาน อย่างไรก็ตามในตัวpydocโมดูลสามารถช่วยให้น้ำลายออกสตริงที่เลขหน้า:help() import pydoc; pydoc.render_doc('mypackage')
sraboy

9

ไม่รู้ว่าฉันมองข้ามบางสิ่งไปหรือเปล่าหรือว่าคำตอบนั้นล้าสมัยไปแล้ว แต่;

ตามที่ระบุโดย user815423426 สิ่งนี้ใช้ได้กับอ็อบเจ็กต์ที่มีชีวิตเท่านั้นและโมดูลที่แสดงรายการเป็นโมดูลที่นำเข้ามาก่อนเท่านั้น

การแสดงรายการโมดูลในแพ็คเกจดูเหมือนง่ายมากโดยใช้การตรวจสอบ :

>>> import inspect, testpkg
>>> inspect.getmembers(testpkg, inspect.ismodule)
['modulea', 'moduleb']

ฉันได้ใส่import = import __ ('myproj.mymod.mysubmod') m = Inspector.getmembers (i, Inspector.ismodule) แต่เส้นทางที่นำเข้าคือ ~ / myproj / __ init .pyและ m เป็นรายการที่มี (mymod, '~ /myproj/mymod/__init__.py ')
hithwen

1
@hithwen อย่าถามคำถามในความคิดเห็นโดยเฉพาะอย่างยิ่งหากพวกเขาไม่เกี่ยวข้องโดยตรง การเป็นพลเมืองที่ดี: imported = import importlib; importlib.import_module('myproj.mymod.mysubmod')ใช้ __import__การนำเข้าโมดูลระดับบนสุดดูเอกสารประกอบ
siebz0r

อืมมีแนวโน้มดี แต่มันไม่ได้ผลสำหรับฉัน เมื่อฉันทำimport inspect, mypackageแล้วinspect.getmembers(my_package, inspect.ismodule)ฉันได้รับรายการว่างแม้ว่าฉันจะมีโมดูลต่างๆอยู่ในนั้นก็ตาม
Amelio Vazquez-Reina

1
ตามความเป็นจริงสิ่งนี้ดูเหมือนจะใช้ได้ผลก็ต่อเมื่อฉันimport my_package.fooและไม่เพียง แต่import mypackageในกรณีนี้มันจะกลับfooมา แต่สิ่งนี้เอาชนะจุดประสงค์
Amelio Vazquez-Reina

3
@ user815423426 คุณพูดถูกจริงๆ ;-) ดูเหมือนว่าฉันกำลังมองข้ามบางสิ่งไป
siebz0r

3

นี่เป็นเวอร์ชันเรียกซ้ำที่ทำงานร่วมกับ python 3.6 ขึ้นไป:

import importlib.util
from pathlib import Path
import os
MODULE_EXTENSIONS = '.py'

def package_contents(package_name):
    spec = importlib.util.find_spec(package_name)
    if spec is None:
        return set()

    pathname = Path(spec.origin).parent
    ret = set()
    with os.scandir(pathname) as entries:
        for entry in entries:
            if entry.name.startswith('__'):
                continue
            current = '.'.join((package_name, entry.name.partition('.')[0]))
            if entry.is_file():
                if entry.name.endswith(MODULE_EXTENSIONS):
                    ret.add(current)
            elif entry.is_dir():
                ret.add(current)
                ret |= package_contents(current)


    return ret

ข้อดีของการใช้os.scandirเป็นตัวจัดการบริบทแทนที่จะทำซ้ำรายการผลลัพธ์โดยตรงคืออะไร
กุฏ

1
@monkut โปรดดูdocs.python.org/3/library/os.html#os.scandirซึ่งแนะนำให้ใช้เป็นตัวจัดการบริบทเพื่อให้แน่ใจว่าcloseมีการเรียกใช้เมื่อคุณดำเนินการเสร็จสิ้นเพื่อให้แน่ใจว่าทรัพยากรที่เก็บไว้จะถูกปล่อย
ทาแคสเวลล์

สิ่งนี้ใช้ไม่ได้reแต่จะแสดงรายการทุกแพ็คเกจ แต่เพิ่มre.ให้กับทั้งหมด
Tushortz

1

จากตัวอย่างของ cdleary นี่คือเส้นทางการแสดงรายการเวอร์ชันซ้ำสำหรับโมดูลย่อยทั้งหมด:

import imp, os

def iter_submodules(package):
    file, pathname, description = imp.find_module(package)
    for dirpath, _, filenames in os.walk(pathname):
        for  filename in filenames:
            if os.path.splitext(filename)[1] == ".py":
                yield os.path.join(dirpath, filename)


0

หากคุณต้องการดูข้อมูลเกี่ยวกับแพ็คเกจของคุณนอกรหัส python (จากพรอมต์คำสั่ง) คุณสามารถใช้ pydoc ได้

# get a full list of packages that you have installed on you machine
$ python -m pydoc modules

# get information about a specific package
$ python -m pydoc <your package>

คุณจะได้ผลลัพธ์เช่นเดียวกับ pydoc แต่อยู่ในล่ามโดยใช้ความช่วยเหลือ

>>> import <my package>
>>> help(<my package>)

-3

พิมพ์ dir (โมดูล)


1
ที่แสดงเนื้อหาของโมดูลที่นำเข้าแล้ว ฉันกำลังมองหาวิธีที่จะแสดงรายการเนื้อหาของแพคเกจที่ยังไม่ได้ถูกนำเข้าเช่นเดียวกับที่ 'จาก x นำเข้า *' ไม่เมื่อทั้งหมดไม่ได้ระบุ
DNS

จาก x import * นำเข้าโมดูลก่อนจากนั้นคัดลอกทุกอย่างไปยังโมดูลปัจจุบัน
Seb

ฉันตระหนักว่า 'จาก x import *' ไม่ได้นำเข้าโมดูลย่อยของแพ็กเกจเนื่องจากปัญหาเกี่ยวกับตัวพิมพ์เล็กและใหญ่ใน Windows ฉันรวมไว้เป็นตัวอย่างของสิ่งที่ฉันอยากทำเท่านั้น ฉันแก้ไขออกจากคำถามเพื่อหลีกเลี่ยงความสับสน
DNS

ที่แสดงแอตทริบิวต์ทั้งหมดของวัตถุที่นำเข้าแล้วไม่ใช่รายการของโมดูลย่อยเท่านั้น มันก็เลยไม่ตอบคำถาม
bignose

-3
def package_contents(package_name):
  package = __import__(package_name)
  return [module_name for module_name in dir(package) if not module_name.startswith("__")]

ใช้ได้กับโมดูลเท่านั้นไม่ใช่แพ็คเกจ ลองใช้loggingแพ็คเกจของ Python เพื่อดูว่าฉันหมายถึงอะไร การบันทึกมีสองโมดูล: ตัวจัดการและการกำหนดค่า รหัสของคุณจะแสดงรายการ 66 รายการซึ่งไม่มีทั้งสองชื่อ
DNS
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.