จะโหลดโมดูลทั้งหมดในโฟลเดอร์ได้อย่างไร?


269

ใครช่วยให้ฉันมีวิธีที่ดีในการนำเข้าไดเรกทอรีทั้งหมดของโมดูล?
ฉันมีโครงสร้างเช่นนี้:

/Foo
    bar.py
    spam.py
    eggs.py

ฉันพยายามแปลงเป็นแพ็คเกจด้วยการเพิ่ม__init__.pyและทำfrom Foo import *แต่มันก็ไม่ทำงานอย่างที่ฉันหวังไว้


2
วิธีinit .py ค่อนข้างถูกต้อง คุณช่วยโพสต์โค้ดที่ไม่ได้ผลได้ไหม
balpha

9
คุณช่วยนิยาม "ไม่ทำงาน" ได้ไหม? เกิดอะไรขึ้น? คุณได้รับข้อความแสดงข้อผิดพลาดอะไร
S.Lott


ชัดเจนดีกว่าแน่นอน แต่คุณต้องการที่จะจัดการกับข้อความ 'ไม่พบ' ที่น่ารำคาญเหล่านี้ทุกครั้งที่คุณเพิ่มโมดูลใหม่ ฉันคิดว่าถ้าคุณมีไดเรกทอรีแพ็คเกจที่มีฟังก์ชั่นโมดูลขนาดเล็กจำนวนมากดังนั้นนี่เป็นวิธีที่ดีที่สุดในการดำเนินการ สมมติฐานของฉันคือ: 1. โมดูลค่อนข้างง่าย 2. คุณใช้แพคเกจสำหรับความเป็นระเบียบเรียบร้อยของรหัส
Ido_f

คำตอบ:


400

แสดงรายการ.pyไฟล์หลาม ( ) ทั้งหมดในโฟลเดอร์ปัจจุบันและวางไว้เป็น__all__ตัวแปร__init__.py

from os.path import dirname, basename, isfile, join
import glob
modules = glob.glob(join(dirname(__file__), "*.py"))
__all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]

57
โดยทั่วไปดังนั้นฉันสามารถวางไฟล์หลามในไดเรกทอรีที่ไม่มีการกำหนดค่าเพิ่มเติมและให้พวกเขาถูกดำเนินการโดยสคริปต์ที่ทำงานที่อื่น
Evan Fosmark

5
@NiallDouglas คำตอบนี้สำหรับคำถามเฉพาะที่ OP ถามเขาไม่มีไฟล์ zip และไฟล์ pyc สามารถรวมได้อย่างง่ายดายและคุณลืม. pyd หรือ. so libs ฯลฯ ด้วย
Anurag Uniyal

35
สิ่งเดียวที่ฉันจะเพิ่มคือif not os.path.basename(f).startswith('_')หรืออย่างน้อยที่สุดif not f.endswith('__init__.py')ในตอนท้ายของรายการความเข้าใจ
Pykler

10
เพื่อให้มีประสิทธิภาพมากขึ้นนอกจากนี้ยังให้แน่ใจว่าเป็นos.path.isfile(f) Trueที่จะกรองออก symlink และไดเรกทอรีที่แตกสลายเช่นsomedir.py/(กรณีมุมฉันยอมรับ แต่ก็ยัง ... )
MestreLion

12
เพิ่มfrom . import *หลังจากการตั้งค่า__all__ถ้าคุณต้องการ submodules ที่จะมีการใช้.(เช่นเป็นmodule.submodule1, module.submodule2ฯลฯ )
ostrokach

133

เพิ่ม__all__ตัวแปรเป็น__init__.py:

__all__ = ["bar", "spam", "eggs"]

ดูเพิ่มเติมที่http://docs.python.org/tutorial/modules.html


163
ใช่แล้ว แต่มันมีวิธีการแบบไดนามิกบ้างไหม?
Evan Fosmark

27
การรวมกันของos.listdir()การกรองบางลอกขยายและ.py __all__

ไม่ทำงานสำหรับฉันเป็นตัวอย่างรหัสที่นี่github.com/namgivu/python-import-all/blob/master/error_app.py บางทีฉันอาจจะพลาดบางสิ่งบางอย่างที่นั่น?
Nam G VU

1
ฉันพบว่าตัวเอง - การใช้ตัวแปร / วัตถุที่กำหนดในโมดูลเหล่านั้นเราต้องใช้เส้นทางอ้างอิงเต็มเช่นmoduleName.varNameอ้างอิง stackoverflow.com/a/710603/248616
Nam G VU

1
@NamGVU: รหัสนี้ในคำตอบของฉันสำหรับคำถามที่เกี่ยวข้องจะนำเข้าชื่อโมดูลย่อยสาธารณะทั้งหมดลงในเนมสเปซของแพ็คเกจ
martineau

54

อัปเดตในปี 2560: คุณอาจต้องการใช้ importlibแทน

__init__.pyทำให้ไดเรกทอรีฟูแพคเกจโดยการเพิ่ม ในการ__init__.pyเพิ่มที่:

import bar
import eggs
import spam

เนื่องจากคุณต้องการให้เป็นแบบไดนามิก (ซึ่งอาจจะใช่หรือไม่ใช่ความคิดที่ดี) ให้เขียนรายการ py-files ทั้งหมดที่มี list dir และนำเข้าสิ่งเหล่านี้:

import os
for module in os.listdir(os.path.dirname(__file__)):
    if module == '__init__.py' or module[-3:] != '.py':
        continue
    __import__(module[:-3], locals(), globals())
del module

จากนั้นทำตามรหัสของคุณ:

import Foo

ตอนนี้คุณสามารถเข้าถึงโมดูลด้วย

Foo.bar
Foo.eggs
Foo.spam

ฯลฯfrom Foo import *ไม่ใช่ความคิดที่ดีด้วยเหตุผลหลายประการรวมถึง clashes ชื่อและทำให้มันยากที่จะวิเคราะห์รหัส


1
ไม่เลว แต่อย่าลืมว่าคุณสามารถนำเข้าไฟล์. pyc และ. pyo ด้วย
Evan Fosmark

7
tbh ฉันพบว่า__import__แฮ็คฉันคิดว่ามันจะเป็นการดีกว่าถ้าจะเพิ่มชื่อ__all__และวางไว้from . import *ที่ด้านล่างสุดของสคริปต์
freeforall tousez

1
ฉันคิดว่านี่ดีกว่ารุ่น glob
lpapp

8
__import__ไม่ได้มีไว้สำหรับการใช้งานทั่วไปใช้โดยinterpreterใช้importlib.import_module()แทน
Andrew_1510

2
ตัวอย่างแรกมีประโยชน์มากขอบคุณ! ภายใต้ Python 3.6.4 ฉันต้องทำfrom . import eggsฯลฯ__init__.pyก่อนที่ Python จะนำเข้าได้ มีเพียงimport eggsฉันเท่านั้นที่ได้รับModuleNotFoundError: No module named 'eggs'เมื่อพยายามเข้าไปimport Fooในmain.pyไดเรกทอรีด้านบน
Nick

41

เมื่อเพิ่มคำตอบของ Mihail ฉันเชื่อว่าวิธีที่ไม่แฮ็ก

  1. สร้าง__init__.pyไฟล์ว่างใต้Foo/
  2. ปฏิบัติ
import pkgutil
import sys


def load_all_modules_from_dir(dirname):
    for importer, package_name, _ in pkgutil.iter_modules([dirname]):
        full_package_name = '%s.%s' % (dirname, package_name)
        if full_package_name not in sys.modules:
            module = importer.find_module(package_name
                        ).load_module(full_package_name)
            print module


load_all_modules_from_dir('Foo')

คุณจะได้รับ:

<module 'Foo.bar' from '/home/.../Foo/bar.pyc'>
<module 'Foo.spam' from '/home/.../Foo/spam.pyc'>

นี่คือวิธีตอบคำถามที่ถูกต้องที่สุด - มันจัดการกับไฟล์เก็บถาวร ZIP แต่ไม่เขียนinitหรือ import ดู automodinit ด้านล่าง
Niall Douglas

1
อีกอย่าง: ตัวอย่างข้างต้นไม่ได้ตรวจสอบ sys.modules เพื่อดูว่าโมดูลนั้นโหลดไปแล้วหรือไม่ โดยไม่ต้องตรวจสอบว่าข้างต้นจะโหลดโมดูลเป็นครั้งที่สอง :)
เนียลดักลาส

3
เมื่อฉันเรียกใช้ load_all_modules_from_dir ('Foo / bar') ด้วยรหัสของคุณฉันจะได้รับ "RuntimeWarning: โมดูลผู้ปกครอง 'Foo / bar' ไม่พบในขณะที่จัดการการนำเข้าแบบสัมบูรณ์" - เพื่อระงับสิ่งนี้ฉันต้องตั้งค่า full_package_name = '' dirname.split (os.path.sep) + package_name]) และยังนำเข้า Foo.bar
Alex Dupuy

บรรดาRuntimeWarningข้อความนอกจากนี้ยังสามารถหลีกเลี่ยงได้โดยไม่ได้ใช้ full_package_name importer.find_module(package_name).load_module(package_name)ที่ทั้งหมด:
Artfunkel

RuntimeWarningข้อผิดพลาดยังสามารถหลีกเลี่ยงได้ (ในทางที่น่าเกลียดเนื้อหา) โดยการนำเข้าแม่ (AKA dirname) วิธีหนึ่งในการทำเช่นนั้นคือ - if dirname not in sys.modules: pkgutil.find_loader(dirname).load_module(dirname). แน่นอนว่าใช้งานได้หากdirnameเป็นเส้นทางสัมพัทธ์ส่วนประกอบเดียว ไม่มีการทับ โดยส่วนตัวแล้วฉันชอบวิธีการของ @ Artfunkel ในการใช้ base package_name แทน
dannysauer

31

Python รวมไฟล์ทั้งหมดไว้ในไดเรกทอรี:

สำหรับมือใหม่ที่ไม่สามารถไปทำงานที่ต้องการมือของพวกเขาได้

  1. สร้างโฟลเดอร์ / home / el / foo และสร้างไฟล์main.pyภายใต้ / home / el / foo ใส่รหัสนี้ใน:

    from hellokitty import *
    spam.spamfunc()
    ham.hamfunc()
  2. ทำไดเรกทอรี /home/el/foo/hellokitty

  3. สร้างไฟล์__init__.pyภายใต้/home/el/foo/hellokittyและใส่รหัสนี้ใน:

    __all__ = ["spam", "ham"]
  4. สร้างไฟล์หลามสองไฟล์: spam.pyและham.pyใต้/home/el/foo/hellokitty

  5. กำหนดฟังก์ชั่นภายใน spam.py:

    def spamfunc():
      print("Spammity spam")
  6. กำหนดฟังก์ชั่นภายใน ham.py:

    def hamfunc():
      print("Upgrade from baloney")
  7. เรียกใช้:

    el@apollo:/home/el/foo$ python main.py 
    spammity spam
    Upgrade from baloney

1
ไม่เพียง แต่สำหรับมือใหม่ แต่ยังได้รับประสบการณ์จาก Python ที่ชอบคำตอบที่ชัดเจน ขอบคุณ
Michael Scheper

แต่การใช้งานimport *ก็ถือว่าเป็นวิธีการเขียนโค้ดที่ไม่ดี คุณจะทำสิ่งนี้โดยปราศจากสิ่งนั้นได้อย่างไร
Rachael Blake

16

ฉันเบื่อกับปัญหานี้ด้วยตัวเองดังนั้นฉันจึงเขียนแพคเกจชื่อ automodinit เพื่อแก้ไข คุณสามารถรับได้จากhttp://pypi.python.org/pypi/automodinit/ http://pypi.python.org/pypi/automodinit/

การใช้งานเป็นเช่นนี้:

  1. รวมautomodinitแพ็คเกจไว้ในการsetup.pyอ้างอิงของคุณ
  2. แทนที่ไฟล์ __init__.py ทั้งหมดดังนี้:
__all__ = ["ฉันจะได้รับการเขียนใหม่"]
# อย่าแก้ไขบรรทัดด้านบนหรือบรรทัดนี้!
นำเข้าเครื่องอัตโนมัติ
automodinit.automodinit (__ name__, __file__, globals ())
del automodinit
# อะไรก็ได้ที่คุณต้องการสามารถไปได้หลังจากที่นี่มันจะไม่ถูกแก้ไข

แค่นั้นแหละ! นับจากนี้ไปการนำเข้าโมดูลจะตั้งค่า __all__ เป็นรายการของไฟล์. py [co] ในโมดูลและจะนำเข้าแต่ละไฟล์เหล่านั้นราวกับว่าคุณได้พิมพ์:

for x in __all__: import x

ดังนั้นผลกระทบของ "จาก M import *" จึงตรงกับ "import M"

automodinit มีความสุขในการทำงานจากภายในคลัง ZIP และดังนั้นจึงปลอดภัย ZIP

เนียล


pip ไม่สามารถดาวน์โหลด automodinit ได้เนื่องจากไม่มีสิ่งใดอัปโหลดใน pypi สำหรับมัน
kanzure

ขอบคุณสำหรับรายงานข้อผิดพลาดเกี่ยวกับ GitHub ฉันได้แก้ไขสิ่งนี้ใน v0.13 Niall
Niall Douglas

11

ฉันรู้ว่าฉันกำลังอัปเดตโพสต์ค่อนข้างเก่าและฉันลองใช้automodinitแต่พบว่ากระบวนการติดตั้งใช้งานไม่ได้กับ python3 ดังนั้นตามคำตอบของ Luca ฉันจึงได้คำตอบที่ง่ายขึ้นซึ่งอาจใช้ไม่ได้กับ. zip สำหรับปัญหานี้ดังนั้นฉันจึงคิดว่าฉันควรแชร์ที่นี่:

ภายใน__init__.pyโมดูลจากyourpackage:

#!/usr/bin/env python
import os, pkgutil
__all__ = list(module for _, module, _ in pkgutil.iter_modules([os.path.dirname(__file__)]))

และภายในแพ็คเกจอื่นด้านล่างyourpackage:

from yourpackage import *

จากนั้นคุณจะมีโมดูลทั้งหมดที่อยู่ในแพ็คเกจที่โหลดและถ้าคุณเขียนโมดูลใหม่มันจะถูกนำเข้าโดยอัตโนมัติเช่นกัน แน่นอนว่าใช้สิ่งต่าง ๆ ด้วยความระมัดระวังด้วยพลังอันยิ่งใหญ่มาพร้อมความรับผิดชอบที่ยิ่งใหญ่



5

ฉันยังพบปัญหานี้และนี่เป็นวิธีแก้ปัญหาของฉัน:

import os

def loadImports(path):
    files = os.listdir(path)
    imps = []

    for i in range(len(files)):
        name = files[i].split('.')
        if len(name) > 1:
            if name[1] == 'py' and name[0] != '__init__':
               name = name[0]
               imps.append(name)

    file = open(path+'__init__.py','w')

    toWrite = '__all__ = '+str(imps)

    file.write(toWrite)
    file.close()

ฟังก์ชั่นนี้สร้างไฟล์ (ในโฟลเดอร์ที่ให้ไว้) __init__.pyซึ่งมีชื่อซึ่งมี__all__ตัวแปรที่เก็บทุกโมดูลในโฟลเดอร์

ตัวอย่างเช่นฉันมีชื่อโฟลเดอร์Test ซึ่งประกอบด้วย:

Foo.py
Bar.py

ดังนั้นในสคริปต์ฉันต้องการให้โมดูลถูกนำเข้าไปยังฉันจะเขียน:

loadImports('Test/')
from Test import *

สิ่งนี้จะนำเข้าทุกอย่างจากTestและ__init__.pyไฟล์Testจะมี:

__all__ = ['Foo','Bar']

สมบูรณ์แบบสิ่งที่ฉันกำลังมองหา
uma mahesh

4

ตัวอย่างของ Anurag พร้อมการแก้ไขสองประการ:

import os, glob

modules = glob.glob(os.path.join(os.path.dirname(__file__), "*.py"))
__all__ = [os.path.basename(f)[:-3] for f in modules if not f.endswith("__init__.py")]

4

ตอบ Anurag Uniyalพร้อมการปรับปรุงที่แนะนำ!

#!/usr/bin/python
# -*- encoding: utf-8 -*-

import os
import glob

all_list = list()
for f in glob.glob(os.path.dirname(__file__)+"/*.py"):
    if os.path.isfile(f) and not os.path.basename(f).startswith('_'):
        all_list.append(os.path.basename(f)[:-3])

__all__ = all_list  

3

เห็นว่าคุณกำหนด__init__.py โมดูล - แพคเกจ doc กล่าวว่า__all__

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

...

ทางออกเดียวสำหรับผู้สร้างแพ็คเกจเพื่อจัดทำดัชนีที่ชัดเจนของแพ็คเกจ คำสั่งการนำเข้าใช้การประชุมดังต่อไปนี้: หาก__init__.pyรหัสของแพ็คเกจกำหนดรายการชื่อ__all__จะถูกนำไปเป็นรายการชื่อโมดูลที่ควรนำเข้าเมื่อพบแพ็คเกจจากการนำเข้า * ขึ้นอยู่กับผู้สร้างแพ็คเกจเพื่อให้รายการนี้เป็นปัจจุบันเมื่อมีการออกแพ็คเกจใหม่ ผู้เขียนแพ็คเกจอาจตัดสินใจที่จะไม่สนับสนุนหากพวกเขาไม่เห็นการใช้งานเพื่อนำเข้า * จากแพ็คเกจของพวกเขา ตัวอย่างเช่นไฟล์sounds/effects/__init__.pyอาจมีรหัสต่อไปนี้:

__all__ = ["echo", "surround", "reverse"]

นี่หมายความว่าfrom sound.effects import *จะนำเข้าชุดข้อมูลย่อยที่มีชื่อสามชุด


3

นี่เป็นวิธีที่ดีที่สุดที่ฉันค้นพบ:

from os.path import dirname, join, isdir, abspath, basename
from glob import glob
pwd = dirname(__file__)
for x in glob(join(pwd, '*.py')):
    if not x.startswith('__'):
        __import__(basename(x)[:-3], globals(), locals())

2

ใช้importlibสิ่งเดียวที่คุณต้องเพิ่มคือ

from importlib import import_module
from pathlib import Path

__all__ = [
    import_module(f".{f.stem}", __package__)
    for f in Path(__file__).parent.glob("*.py")
    if "__" not in f.stem
]
del import_module, Path

นำไปสู่การนี้จะเป็นปัญหาที่ถูกต้องตามกฎหมาย error: Type of __all__ must be "Sequence[str]", not "List[Module]"mypy: การกำหนด__all__ไม่จำเป็นถ้าimport_moduleใช้วิธีการตามนี้
คิวเมนตัส

1

ดูโมดูล pkgutil จากไลบรารีมาตรฐาน มันจะช่วยให้คุณทำสิ่งที่คุณต้องการตราบใดที่คุณมี__init__.pyไฟล์ในไดเรกทอรี __init__.pyไฟล์สามารถเป็นที่ว่างเปล่า


1

ฉันได้สร้างโมดูลสำหรับสิ่งนั้นซึ่งไม่พึ่งพา__init__.py(หรือไฟล์เสริมอื่น ๆ ) และทำให้ฉันพิมพ์เพียงสองบรรทัดต่อไปนี้:

import importdir
importdir.do("Foo", globals())

รู้สึกอิสระที่จะนำกลับมาใช้ใหม่หรือมีส่วนร่วม: http://gitlab.com/aurelien-lourot/importdir


0

เพียงแค่นำเข้าพวกเขาโดยimportlibและเพิ่มลงใน__all__( addการกระทำเป็นตัวเลือก) ในการ__init__.pyบรรจุในแพ็คเกจ

/Foo
    bar.py
    spam.py
    eggs.py
    __init__.py

# __init__.py
import os
import importlib
pyfile_extes = ['py', ]
__all__ = [importlib.import_module('.%s' % filename, __package__) for filename in [os.path.splitext(i)[0] for i in os.listdir(os.path.dirname(__file__)) if os.path.splitext(i)[1] in pyfile_extes] if not filename.startswith('__')]
del os, importlib, pyfile_extes

pyfile_extes ถูกกำหนดไว้ที่ไหน?
Jeppe

ขออภัยที่พลาดมันได้รับการแก้ไขแล้ว มันเป็นส่วนเสริมของไฟล์ไพ ธ อนที่คุณต้องการนำเข้าโดยปกติแล้วเท่านั้นpy
Cheney

0

เมื่อfrom . import *ไม่เพียงพอที่ดีนี้คือการปรับปรุงมากกว่าคำตอบโดยเท็ด โดยเฉพาะการใช้__all__ไม่จำเป็นต้องใช้วิธีการนี้

"""Import all modules that exist in the current directory."""
# Ref https://stackoverflow.com/a/60861023/
from importlib import import_module
from pathlib import Path

for f in Path(__file__).parent.glob("*.py"):
    module_name = f.stem
    if (not module_name.startswith("_")) and (module_name not in globals()):
        import_module(f".{module_name}", __package__)
    del f, module_name
del import_module, Path

โปรดทราบว่าmodule_name not in globals()มีวัตถุประสงค์เพื่อหลีกเลี่ยงการนำเข้าโมดูลอีกครั้งหากนำเข้ามาแล้วเนื่องจากอาจเสี่ยงต่อการนำเข้าแบบวนซ้ำ

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