__future__ นำเข้า absolute_import ทำอะไรได้จริง


164

ฉันได้ตอบคำถามเกี่ยวกับการนำเข้าแบบสัมบูรณ์ใน Python ซึ่งฉันคิดว่าฉันเข้าใจโดยอ้างอิงจากการอ่านPython 2.5 changelogและPEP ที่มาพร้อมกัน อย่างไรก็ตามเมื่อติดตั้ง Python 2.5 และพยายามสร้างตัวอย่างของการใช้อย่างถูกต้องfrom __future__ import absolute_importฉันรู้ว่าสิ่งต่าง ๆ ไม่ชัดเจนนัก

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

สมมติว่าคุณมีไดเรกทอรีแพ็คเกจเช่นนี้:

pkg/
pkg/__init__.py
pkg/main.py
pkg/string.py

สิ่งนี้จะกำหนดแพ็คเกจpkgที่มีชื่อpkg.mainและpkg.stringsubmodules

พิจารณารหัสในโมดูล main.py จะเกิดอะไรขึ้นถ้ามันประมวลผลคำสั่งimport string? ใน Python 2.4 และรุ่นก่อนหน้ามันจะดูในไดเรกทอรีของแพ็คเกจเพื่อดำเนินการนำเข้าแบบสัมพัทธ์ค้นหา pkg / string.py นำเข้าเนื้อหาของไฟล์นั้นเป็นpkg.stringโมดูลและโมดูลนั้นถูกผูกไว้กับชื่อ"string"ในpkg.mainเนมสเปซของโมดูล

ดังนั้นฉันจึงสร้างโครงสร้างไดเรกทอรีที่แน่นอนนี้:

$ ls -R
.:
pkg/

./pkg:
__init__.py  main.py  string.py

__init__.pyและstring.pyว่างเปล่า main.pyมีรหัสต่อไปนี้:

import string
print string.ascii_uppercase

ตามที่คาดไว้การรันด้วย Python 2.5 จะล้มเหลวด้วยAttributeError:

$ python2.5 pkg/main.py
Traceback (most recent call last):
  File "pkg/main.py", line 2, in <module>
    print string.ascii_uppercase
AttributeError: 'module' object has no attribute 'ascii_uppercase'

อย่างไรก็ตามต่อไปในการเปลี่ยนแปลง 2.5 เราพบสิ่งนี้ (เน้นการเพิ่ม):

ใน Python 2.5 คุณสามารถสลับimportการทำงานเป็นการนำเข้าแบบสัมบูรณ์โดยใช้from __future__ import absolute_importคำสั่ง พฤติกรรมการนำเข้าแบบสัมบูรณ์นี้จะกลายเป็นค่าเริ่มต้นในเวอร์ชันในอนาคต (อาจเป็น Python 2.7) เมื่อการนำเข้าแบบสัมบูรณ์เป็นค่าเริ่มต้นimport stringจะค้นหาเวอร์ชันของไลบรารีมาตรฐานเสมอ

ฉันสร้างขึ้นpkg/main2.pyเหมือนกันmain.pyแต่มีคำสั่งการนำเข้าเพิ่มเติมในอนาคต ตอนนี้ดูเหมือนว่านี้:

from __future__ import absolute_import
import string
print string.ascii_uppercase

รันด้วย Python 2.5 อย่างไรก็ตาม ... ล้มเหลวด้วยAttributeError:

$ python2.5 pkg/main2.py
Traceback (most recent call last):
  File "pkg/main2.py", line 3, in <module>
    print string.ascii_uppercase
AttributeError: 'module' object has no attribute 'ascii_uppercase'

นี้สวยอย่างเด็ดขาดขัดแย้งกับคำสั่งที่import stringจะเสมอหารุ่น STD-lib กับการนำเข้าการเปิดใช้งานแน่นอน แม้จะมีคำเตือนว่าการนำเข้าสัมบูรณ์นั้นถูกกำหนดให้เป็นพฤติกรรม "เริ่มต้นใหม่" แต่ฉันก็พบปัญหาเดียวกันนี้โดยใช้ Python 2.7 ทั้งที่มีหรือไม่มี__future__คำสั่ง:

$ python2.7 pkg/main.py
Traceback (most recent call last):
  File "pkg/main.py", line 2, in <module>
    print string.ascii_uppercase
AttributeError: 'module' object has no attribute 'ascii_uppercase'

$ python2.7 pkg/main2.py
Traceback (most recent call last):
  File "pkg/main2.py", line 3, in <module>
    print string.ascii_uppercase
AttributeError: 'module' object has no attribute 'ascii_uppercase'

เช่นเดียวกับ Python 3.5 โดยมีหรือไม่มี (สมมติว่าprintคำสั่งเปลี่ยนไปในทั้งสองไฟล์):

$ python3.5 pkg/main.py
Traceback (most recent call last):
  File "pkg/main.py", line 2, in <module>
    print(string.ascii_uppercase)
AttributeError: module 'string' has no attribute 'ascii_uppercase'

$ python3.5 pkg/main2.py
Traceback (most recent call last):
  File "pkg/main2.py", line 3, in <module>
    print(string.ascii_uppercase)
AttributeError: module 'string' has no attribute 'ascii_uppercase'

ฉันได้ทดสอบรูปแบบอื่น ๆ ของสิ่งนี้แล้ว แทนที่จะเป็นstring.pyฉันได้สร้างโมดูลว่าง - ไดเรกทอรีstringที่มีเพียงว่าง__init__.py- และแทนที่จะออกจากการนำเข้าmain.pyฉันมีcd'ไปที่pkgและเรียกใช้การนำเข้าโดยตรงจาก REPL การเปลี่ยนแปลงเหล่านี้ (หรือการรวมกันของทั้งสองอย่าง) ไม่ได้เปลี่ยนผลลัพธ์ข้างต้น ฉันไม่สามารถกระทบยอดกับสิ่งที่ฉันได้อ่านเกี่ยวกับ__future__การนำเข้าคำสั่งและแบบสัมบูรณ์

ดูเหมือนว่าสำหรับฉันแล้วสิ่งนี้สามารถอธิบายได้อย่างง่ายดายโดยสิ่งต่อไปนี้ (นี่คือจากเอกสาร Python 2 แต่ข้อความนี้ยังคงไม่เปลี่ยนแปลงในเอกสารเดียวกันสำหรับ Python 3):

sys.path

( ... )

ตามที่เริ่มต้นเมื่อเริ่มต้นโปรแกรมรายการแรกของรายการนี้path[0]เป็นไดเรกทอรีที่มีสคริปต์ที่ใช้ในการเรียก Python interpreter หากไดเรกทอรีของสคริปต์ไม่พร้อมใช้งาน (เช่นถ้าล่ามถูกเรียกใช้แบบโต้ตอบหรือถ้าสคริปต์ถูกอ่านจากอินพุตมาตรฐาน) path[0]เป็นสตริงว่างซึ่งนำ Python ไปยังโมดูลการค้นหาในไดเรกทอรีปัจจุบันก่อน

แล้วฉันจะพลาดอะไรไป? เหตุใดข้อความดังกล่าวจึงไม่__future__ปรากฏตามที่กล่าวและความละเอียดของความขัดแย้งระหว่างเอกสารทั้งสองส่วนนี้รวมถึงพฤติกรรมที่อธิบายและที่เกิดขึ้นจริงคืออะไร


ดูเพิ่มเติมที่: docs.python.org/2.5/whatsnew/pep-328.html
dreftymac

คำตอบ:


104

changelog มีการใช้คำพูดอย่างเลอะเทอะ from __future__ import absolute_importไม่สนใจว่าบางสิ่งบางอย่างเป็นส่วนหนึ่งของไลบรารีมาตรฐานหรือimport stringไม่และจะไม่ให้โมดูลห้องสมุดมาตรฐานที่มีการนำเข้าแน่นอน

from __future__ import absolute_importหมายความว่าถ้าคุณimport stringPython จะมองหาstringโมดูลระดับบนสุดcurrent_package.stringเสมอ อย่างไรก็ตามมันไม่ได้ส่งผลกระทบต่อตรรกะ Python ใช้ในการตัดสินใจว่าไฟล์คือstringโมดูล เมื่อคุณทำ

python pkg/script.py

pkg/script.pyดูไม่เหมือนส่วนหนึ่งของแพ็คเกจของ Python ทำตามขั้นตอนปกติpkgไดเรกทอรีจะถูกเพิ่มลงในพา ธ และ.pyไฟล์ทั้งหมดในpkgไดเรกทอรีดูเหมือนโมดูลระดับบนสุด import stringพบว่าpkg/string.pyไม่ได้เพราะมันทำการนำเข้าญาติ แต่เป็นเพราะปรากฏจะเป็นโมดูลระดับบนสุดpkg/string.py stringความจริงที่ว่านี่ไม่ใช่stringโมดูลไลบรารีมาตรฐานไม่ได้เกิดขึ้น

หากต้องการเรียกใช้ไฟล์โดยเป็นส่วนหนึ่งของpkgแพ็คเกจคุณสามารถทำได้

python -m pkg.script

ในกรณีนี้pkgไดเรกทอรีจะไม่ถูกเพิ่มลงในพา ธ อย่างไรก็ตามไดเรกทอรีปัจจุบันจะถูกเพิ่มไปยังเส้นทาง

คุณยังสามารถเพิ่มส่วนสำเร็จรูปบางส่วนpkg/script.pyเพื่อให้ Python ปฏิบัติกับมันเป็นส่วนหนึ่งของpkgแพ็คเกจแม้ว่าจะทำงานเป็นไฟล์:

if __name__ == '__main__' and __package__ is None:
    __package__ = 'pkg'

sys.pathแต่นี้จะไม่ส่งผลกระทบต่อ คุณจะต้องมีการจัดการเพิ่มเติมเพื่อลบpkgไดเรกทอรีออกจากเส้นทางและหากpkgไดเรกทอรีหลักไม่ได้อยู่ในเส้นทางคุณจะต้องติดกับเส้นทางนั้นด้วย


2
โอเคฉันหมายความว่าฉันเข้าใจแล้ว นั่นเป็นพฤติกรรมที่โพสต์ของฉันกำลังบันทึกไว้ ถึงแม้ว่าจะมีสองคำถาม: (1. ) ถ้า "นั่นไม่จริง" ทำไม docs อย่างแน่ชัดว่ามันคืออะไร? และ (2) วิธีการนั้นคุณถ้าคุณตั้งใจเงามันอย่างน้อยโดยไม่ต้องรื้อค้นผ่านimport string sys.modulesสิ่งfrom __future__ import absolute_importนี้มีไว้เพื่อป้องกันหรือไม่ มันทำอะไร? (PS, ฉันไม่ใช่ผู้ลงคะแนน)
นักเล่นแร่แปรธาตุ Two-Bit

14
ใช่นั่นคือฉัน (downvote สำหรับ 'ไม่มีประโยชน์' ไม่ใช่สำหรับ 'ผิด') ชัดเจนจากส่วนล่างที่ OP เข้าใจถึงวิธีการsys.pathทำงานและคำถามที่แท้จริงยังไม่ได้รับการแก้ไขเลย นั่นคือสิ่งที่from __future__ import absolute_importจริงจะทำอย่างไร
Wim

5
@ Two-BitAlchemist: 1) การเปลี่ยนแปลงเป็นคำที่หลวมและไม่เป็นบรรทัดฐาน 2) คุณหยุดแชโดว์ แม้แต่การควงผ่านsys.modulesก็จะไม่ทำให้คุณได้รับstringโมดูลห้องสมุดมาตรฐานหากคุณสร้างมันด้วยโมดูลระดับบนของคุณเอง from __future__ import absolute_importไม่ได้หมายถึงการหยุดโมดูลระดับบนสุดจากโมดูลเงาระดับบนสุด มันควรจะหยุดโมดูลแพ็กเกจภายในจากโมดูลเงาระดับบนสุด หากคุณเรียกใช้ไฟล์เป็นส่วนหนึ่งของpkgแพ็คเกจไฟล์ภายในของแพ็คเกจจะหยุดแสดงเป็นระดับบนสุด
user2357112 รองรับ Monica

@ Two-BitAlchemist: แก้ไขคำตอบแล้ว รุ่นนี้มีประโยชน์มากขึ้นหรือไม่
user2357112 รองรับ Monica

1
@storen: สมมติว่าเป็นแพคเกจในเส้นทางการค้นหานำเข้าที่ควรจะเป็นpkg ต้องการชื่อโมดูลไม่ใช่พา ธ ไฟล์ python -m pkg.main-m
user2357112 รองรับ Monica

44

ความแตกต่างระหว่างการนำเข้าแบบสัมบูรณ์และแบบสัมพัทธ์มาลงเล่นเมื่อคุณนำเข้าโมดูลจากแพ็คเกจและโมดูลนั้นจะนำเข้า submodule อื่นจากแพ็คเกจนั้น ดูความแตกต่าง:

$ mkdir pkg
$ touch pkg/__init__.py
$ touch pkg/string.py
$ echo 'import string;print(string.ascii_uppercase)' > pkg/main1.py
$ python2
Python 2.7.9 (default, Dec 13 2014, 18:02:08) [GCC] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pkg.main1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "pkg/main1.py", line 1, in <module>
    import string;print(string.ascii_uppercase)
AttributeError: 'module' object has no attribute 'ascii_uppercase'
>>> 
$ echo 'from __future__ import absolute_import;import string;print(string.ascii_uppercase)' > pkg/main2.py
$ python2
Python 2.7.9 (default, Dec 13 2014, 18:02:08) [GCC] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pkg.main2
ABCDEFGHIJKLMNOPQRSTUVWXYZ
>>> 

โดยเฉพาะอย่างยิ่ง:

$ python2 pkg/main2.py
Traceback (most recent call last):
  File "pkg/main2.py", line 1, in <module>
    from __future__ import absolute_import;import string;print(string.ascii_uppercase)
AttributeError: 'module' object has no attribute 'ascii_uppercase'
$ python2
Python 2.7.9 (default, Dec 13 2014, 18:02:08) [GCC] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pkg.main2
ABCDEFGHIJKLMNOPQRSTUVWXYZ
>>> 
$ python2 -m pkg.main2
ABCDEFGHIJKLMNOPQRSTUVWXYZ

โปรดทราบว่าpython2 pkg/main2.pyมีพฤติกรรมที่แตกต่างจากนั้นเปิดตัวpython2แล้วนำเข้าpkg.main2(ซึ่งเทียบเท่ากับการใช้-mสวิตช์)

หากคุณต้องการเรียกใช้ submodule ของแพ็คเกจให้ใช้-mสวิตช์ซึ่งป้องกัน interpreter สำหรับการโยงsys.pathรายการและจัดการความหมายของ submodule อย่างถูกต้อง

นอกจากนี้ฉันชอบที่จะใช้การนำเข้าที่สัมพันธ์กันอย่างชัดเจนสำหรับแพคเกจ submodules เนื่องจากมีความหมายมากขึ้นและข้อความแสดงข้อผิดพลาดที่ดีขึ้นในกรณีที่ล้มเหลว


ดังนั้นโดยหลักแล้วมันจะทำงานเฉพาะกับกรณีที่แคบซึ่งคุณหลีกเลี่ยงปัญหา "ไดเรกทอรีปัจจุบัน" ได้หรือไม่ สิ่งนี้ดูเหมือนจะเป็นการใช้งานที่อ่อนแอกว่าที่อธิบายไว้โดย PEP 328 และ 2.5 การเปลี่ยนแปลง คุณเชื่อว่าเอกสารไม่ถูกต้องหรือไม่?
นักเล่นแร่แปรธาตุ Two-Bit

@ Two-BitAlchemist ที่จริงแล้วสิ่งที่คุณกำลังทำคือ "ตัวพิมพ์เล็ก" คุณเรียกใช้ไฟล์หลามไฟล์เดียวที่จะดำเนินการ แต่อาจทำให้มีการนำเข้าหลายร้อยรายการ โมดูลย่อยของแพ็คเกจไม่ควรถูกเรียกใช้งานนั่นคือทั้งหมด
Bakuriu

เหตุใดจึง python2 pkg/main2.pyมีพฤติกรรมที่แตกต่างจากนั้นเปิดตัว python2 และนำเข้า pkg.main2
59

1
@storen นั่นเป็นเพราะพฤติกรรมที่มีการเปลี่ยนแปลงการนำเข้าที่เกี่ยวข้อง เมื่อคุณเปิดใช้งานpkg/main2.pypython (เวอร์ชัน 2) จะไม่ถือว่าpkgเป็นแพคเกจ ในขณะที่ใช้python2 -m pkg.main2หรือนำเข้าจะคำนึงถึงว่าpkgเป็นแพคเกจ
Bakuriu
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.