Python: การนำเข้าแพ็คเกจย่อยหรือโมดูลย่อย


93

หลังจากใช้แพ็กเกจแบบแบนแล้วฉันไม่ได้คาดหวังว่าจะพบปัญหากับแพ็กเกจที่ซ้อนกัน ที่นี่คือ…

เค้าโครงไดเรกทอรี

dir
 |
 +-- test.py
 |
 +-- package
      |
      +-- __init__.py
      |
      +-- subpackage
           |
           +-- __init__.py
           |
           +-- module.py

เนื้อหาของinit .py

ทั้งสองpackage/__init__.pyและpackage/subpackage/__init__.pyว่างเปล่า

เนื้อหาของ module.py

# file `package/subpackage/module.py`
attribute1 = "value 1"
attribute2 = "value 2"
attribute3 = "value 3"
# and as many more as you want...

เนื้อหาของtest.py(3 เวอร์ชัน)

เวอร์ชัน 1

# file test.py
from package.subpackage.module import *
print attribute1 # OK

นั่นเป็นวิธีการนำเข้าที่ไม่ดีและไม่ปลอดภัย (นำเข้าทั้งหมดในจำนวนมาก) แต่ได้ผล

เวอร์ชัน 2

# file test.py
import package.subpackage.module
from package.subpackage import module # Alternative
from module import attribute1

วิธีที่ปลอดภัยกว่าในการนำเข้าทีละรายการ แต่ล้มเหลว Python ไม่ต้องการสิ่งนี้: ล้มเหลวด้วยข้อความ: "No module named module" อย่างไรก็ตาม…

# file test.py
import package.subpackage.module
from package.subpackage import module # Alternative
print module # Surprise here

…พูดว่า<module 'package.subpackage.module' from '...'>. นั่นคือโมดูล แต่นั่นไม่ใช่โมดูล / -P 8-O ... เอ่อ

เวอร์ชัน 3

# file test.py v3
from package.subpackage.module import attribute1
print attribute1 # OK

อันนี้ใช้ได้เลย ดังนั้นคุณอาจถูกบังคับให้ใช้คำนำหน้า overkill ตลอดเวลาหรือใช้วิธีที่ไม่ปลอดภัยเหมือนในเวอร์ชัน # 1 และ Python ไม่อนุญาตให้ใช้วิธีที่ปลอดภัยหรือไม่? วิธีที่ดีกว่าซึ่งปลอดภัยและหลีกเลี่ยงคำนำหน้าแบบยาวที่ไม่จำเป็นคือสิ่งเดียวที่ Python ปฏิเสธ? นี่เป็นเพราะมันรักimport *หรือเพราะมันชอบคำนำหน้ามากเกินไป (ซึ่งไม่ได้ช่วยในการบังคับใช้การปฏิบัตินี้)?

ขอโทษสำหรับคำพูดที่ยาก แต่นั่นเป็นสองวันที่ฉันพยายามแก้ไขพฤติกรรมโง่ ๆ แบบนี้ ถ้าฉันไม่ได้ทำผิดที่ใดที่หนึ่งโดยสิ้นเชิงสิ่งนี้จะทำให้ฉันรู้สึกว่ามีบางอย่างที่ผิดปกติในโมเดลแพ็คเกจและแพ็คเกจย่อยของ Python

หมายเหตุ

  • ฉันไม่ต้องการพึ่งพาsys.pathเพื่อหลีกเลี่ยงผลข้างเคียงระดับโลกหรือใน*.pthไฟล์ซึ่งเป็นอีกวิธีหนึ่งในการเล่นsys.pathกับผลกระทบระดับโลกเดียวกัน สำหรับวิธีการแก้ปัญหาที่สะอาดจะต้องมีเฉพาะในท้องถิ่นเท่านั้น Python ทั้งสองตัวสามารถจัดการกับแพ็กเกจย่อยได้หรือไม่ แต่ก็ไม่ควรเล่นกับการกำหนดค่าส่วนกลางเพื่อให้สามารถจัดการกับสิ่งต่างๆในเครื่องได้
  • ฉันลองใช้การนำเข้าpackage/subpackage/__init__.pyด้วย แต่มันไม่ได้แก้ไขอะไรเลยมันทำเหมือนเดิมและบ่นว่าsubpackageไม่ใช่โมดูลที่รู้จักในขณะที่print subpackageบอกว่าเป็นโมดูล (พฤติกรรมแปลก ๆ อีกแล้ว)

อาจเป็นเพราะฉันคิดผิดอย่างสิ้นเชิง (ตัวเลือกที่ฉันต้องการ) แต่สิ่งนี้ทำให้ฉันรู้สึกผิดหวังมากเกี่ยวกับ Python

วิธีอื่นที่เป็นที่รู้จักนอกเหนือจากทั้งสามที่ฉันลอง? บางสิ่งที่ฉันไม่รู้เกี่ยวกับ?

(ถอนหายใจ)

-----% <----- แก้ไข ----->% -----

สรุปแล้ว (หลังจากความคิดเห็นของผู้คน)

ไม่มีอะไรที่เหมือนกับแพ็กเกจย่อยจริงใน Python เนื่องจากการอ้างอิงแพ็คเกจทั้งหมดไปที่พจนานุกรมทั่วโลกเท่านั้นซึ่งหมายความว่าไม่มีพจนานุกรมในเครื่องซึ่งหมายความว่าไม่มีวิธีจัดการการอ้างอิงแพ็กเกจในเครื่อง

คุณต้องใช้คำนำหน้าแบบเต็มหรือคำนำหน้าแบบสั้นหรือนามแฝง ใน:

คำนำหน้าเวอร์ชันเต็ม

from package.subpackage.module import attribute1
# An repeat it again an again
# But after that, you can simply:
use_of (attribute1)

คำนำหน้าแบบสั้น (แต่คำนำหน้าซ้ำ)

from package.subpackage import module
# Short but then you have to do:
use_of (module.attribute1)
# and repeat the prefix at every use place

หรือรูปแบบอื่น ๆ ข้างต้น

from package.subpackage import module as m
use_of (m.attribute1)
# `m` is a shorter prefix, but you could as well
# define a more meaningful name after the context

รุ่นแยกตัวประกอบ

หากคุณไม่ทราบเกี่ยวกับการนำเข้าหลายเอนทิตีพร้อมกันในชุดงานคุณสามารถ:

from package.subpackage.module import attribute1, attribute2
# and etc.

ไม่ใช่รสชาติที่ฉันชอบเป็นอันดับแรก (ฉันชอบที่จะมีใบแจ้งยอดการนำเข้าหนึ่งรายการต่อหน่วยงานที่นำเข้า) แต่อาจเป็นสิ่งที่ฉันชอบเป็นการส่วนตัว

ปรับปรุง (2012-09-14):

ในที่สุดดูเหมือนจะใช้ได้ในทางปฏิบัติยกเว้นมีความคิดเห็นเกี่ยวกับเค้าโครง แทนที่จะใช้ข้างต้นฉันใช้:

from package.subpackage.module import (

    attribute1, 
    attribute2,
    attribute3,
    ...)  # and etc.

สิ่งต่างๆจะเป็นอย่างไรเมื่อคุณเขียน "from. import module" ลงใน "/package/subpackage/__init__.py"
Markus Unterwaditzer

"เวอร์ชันแยกตัวประกอบ" ของคุณดูเหมือนจะตรงกับสิ่งที่คุณต้องการทำ หากคุณทำบรรทัดการนำเข้าแยกต่างหากสำหรับแอตทริบิวต์ 1 และแอตทริบิวต์ 2 (ตามที่คุณ "ต้องการ") แสดงว่าคุณตั้งใจให้ตัวเองทำงานมากขึ้น ไม่มีเหตุผลที่จะทำเช่นนั้น
BrenBarn

ขออภัยฉันไม่เข้าใจสิ่งที่คุณต้องการ คุณช่วยเรียบเรียงคำถามใหม่ให้ชัดเจนยิ่งขึ้นได้ไหม คุณต้องการทำอะไรกันแน่? ฉันหมายความว่าคุณต้องการเขียนอะไรที่ไม่ได้ผลและคุณคาดหวังว่าจะได้ผลอย่างไร จากสิ่งที่ฉันอ่านฉันคิดว่าคุณความหมายของการนำเข้าเป็นอย่างไรเช่นของ Java หรืออาจรวมถึง C สิ่งสุดท้าย: คุณสามารถสร้างโมดูล "star-import" ได้อย่างปลอดภัยโดยเพิ่ม__all__ตัวแปรที่มีรายการชื่อที่ควรส่งออกเมื่อนำเข้าด้วยดาว แก้ไข: โอเคอ่านคำตอบ BrenBarn ฉันเข้าใจว่าคุณหมายถึงอะไร
Bakuriu

คำตอบ:


69

ดูเหมือนคุณจะเข้าใจผิดว่าจะimportค้นหาโมดูลอย่างไร เมื่อคุณใช้คำสั่งนำเข้าจะค้นหาเส้นทางโมดูลจริง (และ / หรือsys.modules) เสมอ ไม่ใช้ประโยชน์จากออบเจ็กต์โมดูลในเนมสเปซโลคัลที่มีอยู่เนื่องจากการนำเข้าก่อนหน้านี้ เมื่อคุณทำ:

import package.subpackage.module
from package.subpackage import module
from module import attribute1

บรรทัดที่สองค้นหาแพ็กเกจที่เรียกpackage.subpackageและอิมพอร์ตmoduleจากแพ็กเกจนั้น บรรทัดนี้ไม่มีผลกับบรรทัดที่สาม บรรทัดที่สามมองหาโมดูลที่เรียกmoduleและไม่พบโมดูล มันไม่ "ใช้ซ้ำ" วัตถุที่เรียกmoduleว่าคุณได้รับจากบรรทัดด้านบน

กล่าวอีกนัยหนึ่ง from someModule import ...ไม่ได้หมายความว่า "จากโมดูลที่เรียกว่า someModule ที่ฉันนำเข้าก่อนหน้านี้ ... " หมายความว่า "จากโมดูลชื่อ someModule ที่คุณพบใน sys.path ... " ไม่มีวิธีที่จะสร้างเส้นทางของโมดูลแบบ "เพิ่มขึ้น" โดยการนำเข้าแพ็กเกจที่นำไปสู่ คุณต้องอ้างถึงชื่อโมดูลทั้งหมดเสมอเมื่อนำเข้า

ยังไม่ชัดเจนว่าคุณกำลังพยายามบรรลุเป้าหมายอะไร หากคุณต้องการนำเข้าเฉพาะอ็อบเจ็กต์ attribute1 เพียงแค่ทำfrom package.subpackage.module import attribute1และดำเนินการกับมัน คุณไม่ต้องกังวลเกี่ยวกับความยาวpackage.subpackage.moduleเมื่อคุณนำเข้าชื่อที่คุณต้องการจากมัน

ถ้าคุณทำต้องการให้มีการเข้าถึงโมดูลเพื่อเข้าถึงชื่ออื่น ๆ ต่อมาจากนั้นคุณสามารถทำได้from package.subpackage import moduleและเป็นคุณได้เห็นแล้วคุณสามารถทำmodule.attribute1และอื่น ๆ มากที่สุดเท่าที่คุณต้องการ

หากคุณต้องการทั้งสองอย่าง --- นั่นคือถ้าคุณต้องการattribute1เข้าถึงได้โดยตรงและคุณต้องการmoduleเข้าถึงได้ให้ทำทั้งสองอย่างข้างต้น:

from package.subpackage import module
from package.subpackage.module import attribute1
attribute1 # works
module.someOtherAttribute # also works

หากคุณไม่ชอบพิมพ์package.subpackageซ้ำสองครั้งคุณสามารถสร้างการอ้างอิงภายในเครื่องไปยังแอตทริบิวต์ 1 ได้ด้วยตนเอง:

from package.subpackage import module
attribute1 = module.attribute1
attribute1 # works
module.someOtherAttribute #also works

ความคิดเห็นของคุณไปในทิศทางเดียวกับความคิดเห็นจาก Ignacio Vazquez-Abrams (ฉันแสดงความคิดเห็นข้อความของเขา) คุณเพิ่มเติมในตอนท้ายเกี่ยวกับการใช้module.attribute1เป็นสิ่งที่ฉันคิด แต่ฉันจะมีวิธีหลีกเลี่ยงความจำเป็นของคำนำหน้าทุกที่ ดังนั้นฉันจึงต้องใช้คำนำหน้าทุกที่หรือสร้างนามแฝงในเครื่องโดยใช้ชื่อซ้ำ ไม่ใช่สไตล์ที่ฉันคาดหวัง แต่ถ้าไม่มีทาง (หลังจากนั้นฉันคุ้นเคยกับ Ada ซึ่งต้องการสิ่งที่คล้ายกันกับการประกาศการเปลี่ยนชื่อ)
Hibou57

@ Hibou57: ยังไม่ชัดเจนสำหรับฉันว่าคุณพยายามทำอะไรใน "เวอร์ชัน 2" ของคุณ คุณต้องการทำอะไรที่เป็นไปไม่ได้? คุณไม่ต้องการพิมพ์ซ้ำส่วนใด ๆ ของชื่อแพ็กเกจ / โมดูล / แอ็ตทริบิวต์ แต่ยังคงนำเข้าทั้งโมดูลและแอตทริบิวต์?
BrenBarn

ฉันต้องการมีการอ้างอิงแพคเกจในเครื่องเช่นเดียวกับวิธีที่คุณสามารถมีการอ้างอิงวัตถุในเครื่อง ดูเหมือนว่าในที่สุดก็มีการอ้างอิงโมดูลในเครื่อง แต่คุณไม่สามารถนำเข้าจากสิ่งเหล่านี้ได้ เป็นการผสมผสานระหว่างท้องถิ่นและระดับโลกที่มีรสชาติตลก (ของบางอย่างอาจเป็นของท้องถิ่นบางอย่างต้องเป็นระดับโลกฉันไม่ชอบ แต่ฉันก็โอเคตราบเท่าที่ฉันเข้าใจวิธีการทำงานดีขึ้น) ขอบคุณสำหรับข้อความของคุณโดยวิธีการ
Hibou57

1
ฉันไม่แน่ใจว่าคุณยังเข้าใจวิธีการทำงานอยู่ หรือที่คุณทำในปี 2012 ไม่ว่าในกรณีใด ๆ
Hejazzman

1
ทุกครั้งที่ฉันกลับไปที่ Python หลังจากเลิกจ้าง 6 เดือนฉันก็จบลงที่นี่ หากฉันสามารถโหวตได้ทุกครั้งที่เยี่ยมชมหน้านี้! ฉันจะทำโปสเตอร์ขนาดมหึมาด้วยประโยคนี้: "ไม่มีทาง" เพิ่มขึ้น "สร้างเส้นทางของโมดูลโดยการนำเข้าแพ็กเกจที่นำไปสู่มัน"
PatrickT

10

สาเหตุที่ # 2 ล้มเหลวเนื่องจากsys.modules['module']ไม่มีอยู่ (รูทีนการนำเข้ามีขอบเขตของตัวเองและไม่สามารถมองเห็นmoduleชื่อโลคัล) และไม่มีmoduleโมดูลหรือแพ็กเกจบนดิสก์ โปรดทราบว่าคุณสามารถแยกชื่อที่นำเข้าหลายชื่อโดยใช้ลูกน้ำ

from package.subpackage.module import attribute1, attribute2, attribute3

นอกจากนี้:

from package.subpackage import module
print module.attribute1

การอ้างอิงของคุณsys.modules['name']ซึ่งฉันไม่รู้จนถึงตอนนี้ทำให้ฉันคิดว่านั่นคือสิ่งที่ฉันกลัว (และ BrenBarn ยืนยัน): ไม่มีอะไรที่เหมือนกับแพ็คเกจย่อยจริงใน Python sys.modulesตามชื่อที่แนะนำนั้นเป็นแบบสากลและหากการอ้างอิงถึงโมดูลทั้งหมดขึ้นอยู่กับสิ่งนี้ก็จะไม่มีอะไรเหมือนกับการอ้างอิงภายในกับโมดูล (อาจมาพร้อมกับ Python 3.x?)
Hibou57

การใช้ "อ้างอิง" ของคุณมีความคลุมเครือ ครั้งแรกimportใน # 2 สร้างการอ้างอิงในท้องถิ่นที่จะผูกไว้กับpackage.subpackage.module module
Ignacio Vazquez-Abrams

ใช่ แต่นั่นคือ "โมดูล" ที่ฉันไม่สามารถนำเข้าจาก ;-)
Hibou57

0

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

ในเวอร์ชัน 2 แทนที่จะเป็น

from module import attribute1

คุณทำได้

attribute1 = module.attribute1

attribute1 = module.attribute1เป็นเพียงการตั้งชื่อซ้ำโดยไม่มีมูลค่าเพิ่ม ฉันรู้ว่ามันใช้ได้ แต่ฉันไม่ชอบสไตล์นี้ (ซึ่งไม่ได้หมายความว่าฉันไม่ชอบคำตอบของคุณ)
Hibou57

2
ฉันเดาว่าฉันเหมือนทุกคนที่แสดงความคิดเห็นที่นี่ไม่เข้าใจว่าคุณต้องการทำอะไร ในตัวอย่างทั้งหมดที่คุณให้มาดูเหมือนว่าคุณต้องการลงท้ายด้วยสัญลักษณ์จากแพ็กเกจย่อยในเนมสเปซของคุณ ตัวอย่างที่ไม่ทำงานของคุณ (ตัวอย่างที่ 2) ต้องการทำโดยการนำเข้าโมดูลย่อยจากแพ็กเกจจากนั้นจึงนำเข้าสัญลักษณ์จากโมดูลย่อยนั้น ฉันไม่รู้ว่าทำไมคุณถึงต้องการทำในสองขั้นตอนแทนที่จะเป็นขั้นตอนเดียว อาจอธิบายเพิ่มเติมว่าทางออกที่ดีของคุณคืออะไรและเพราะเหตุใด
Thomas Vander Stichele
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.