เหตุใด“ การนำเข้า *” จึงไม่ดี


153

แนะนำให้ไม่ใช้import *ใน Python

ใครช่วยกรุณาแบ่งปันเหตุผลเพื่อที่ฉันสามารถหลีกเลี่ยงได้ในครั้งต่อไป?



2
มันขึ้นอยู่กับว่าคุณกำลังเขียนสคริปต์หรือเขียนโค้ดที่คุณต้องนำมาใช้ใหม่ บางครั้งมันจ่ายเพื่อละเว้นมาตรฐานรหัส "import *" สามารถใช้ได้หากคุณมีแบบแผนการตั้งชื่อที่ทำให้ชัดเจนว่ามาจากที่ใด เช่น "จาก Cats import *; TabbyCat; MaineCoonCat; CalicoCat;"
gatoatigrado

3
import *ไม่ทำงานสำหรับฉันตั้งแต่แรกใน Python 2 หรือ 3
joshreesjones

1
สิ่งนี้ตอบคำถามของคุณหรือไม่ "นำเข้า *" นำเข้าอะไร
AMC

คำตอบ:


223
  • เพราะมันทำให้สิ่งต่าง ๆ มากมายใน namespace ของคุณ (อาจเงาวัตถุอื่น ๆ จากการนำเข้าก่อนหน้านี้และคุณจะไม่ทราบเกี่ยวกับมัน)

  • เนื่องจากคุณไม่ทราบว่าสิ่งใดที่นำเข้ามาและไม่สามารถหาได้ง่ายว่าโมดูลใดที่นำเข้าบางสิ่ง (อ่านได้)

  • เพราะคุณไม่สามารถใช้เครื่องมือเจ๋ง ๆ เช่นpyflakesตรวจจับข้อผิดพลาดในรหัสของคุณได้


2
ใช่ฉันเกลียดงานของฉันจริงๆเมื่อมีคนใช้การนำเข้า * เพราะฉันไม่สามารถเรียกใช้ pyflakes และมีความสุขได้ แต่ต้องซ่อมแซมการนำเข้าเหล่านั้น แม้ว่าจะเป็นเรื่องที่ดี แต่ด้วย pyflakes นั้นช่วยให้ฉัน :-)
gruszczy

7
ตัวอย่างที่เป็นรูปธรรมผู้ใช้ NumPy จำนวนมากถูกกัดโดยnumpy.anyเงาanyเมื่อพวกเขาทำfrom numpy import *หรือเครื่องมือ "มีประโยชน์" ทำเพื่อพวกเขา
user2357112 รองรับ Monica

1
ฉันควรหลีกเลี่ยงการใช้สวิตช์ --pylab สำหรับ IPython ด้วยเหตุผลเดียวกันหรือไม่
timgeb

6
เพื่อเน้นถึงความเสี่ยงที่ฉันไม่เคยคิดมาก่อนอ่านสิ่งนี้ ("อาจทำเงาวัตถุอื่น ๆ จากการนำเข้าก่อนหน้า"): import *ทำให้ลำดับของimportข้อความสั่งสำคัญ ... แม้สำหรับโมดูลไลบรารีมาตรฐานที่ไม่สนใจสั่งนำเข้า . บางสิ่งที่ไร้เดียงสาเหมือนการเรียงimportข้อความของคุณอาจทำให้สคริปต์ของคุณเสียหายเมื่ออดีตผู้บาดเจ็บจากสงครามการนำเข้ากลายเป็นผู้รอดชีวิตเพียงคนเดียว (แม้ว่าสคริปต์ของคุณจะทำงานได้ในขณะนี้และไม่เคยเปลี่ยนแปลง แต่มันก็อาจล้มเหลวได้ในภายหลังหากโมดูลที่นำเข้ามีชื่อใหม่ซึ่งมาแทนที่ชื่อที่คุณใช้)
Kevin J. Chase

49

อ้างอิงจากZen of Python :

ชัดเจนดีกว่าโดยปริยาย

... ไม่สามารถโต้เถียงกับเรื่องนี้ได้อย่างแน่นอน?


29
ที่จริงคุณสามารถโต้แย้งกับที่ นอกจากนี้ยังไม่สอดคล้องกันโดยสิ้นเชิงเนื่องจากคุณไม่ได้ประกาศตัวแปรอย่างชัดเจนใน Python พวกเขาเพิ่งปรากฏขึ้นมาเมื่อคุณกำหนดให้กับพวกเขา
Konrad Rudolph

7
@gruszczy: ประกาศตัวแปรไม่ซ้ำซ้อนสิ่งที่ ? การกำหนด? ไม่นั่นเป็นแนวคิดสองข้อที่แยกจากกันและประกาศบางสิ่งที่สื่อถึงข้อมูลที่แตกต่างและสำคัญมาก อย่างไรก็ตามความชัดเจนมักเชื่อมโยงกับความซ้ำซ้อนอยู่เสมอพวกเขาทั้งสองหน้าของเหรียญเดียวกัน
Konrad Rudolph

3
@ Kriss ถูกต้อง แต่นั่นไม่ใช่ประเด็นของฉัน ประเด็นของฉันคือความล้มเหลวในการประกาศตัวแปรนำไปสู่ข้อผิดพลาดอย่างชัดเจน คุณบอกว่า "การมอบหมายโดยไม่มี [การประกาศ] เป็นไปไม่ได้" แต่นั่นเป็นสิ่งที่ผิดจุดประสงค์ทั้งหมดของฉันคือ Python ทำให้มันเป็นไปได้
Konrad Rudolph

3
@kriss ข้อมูลอีกชิ้นที่ให้กับคอมไพเลอร์โดยการประกาศคือความจริงที่ว่าคุณตั้งใจจะประกาศตัวแปรใหม่ นั่นเป็นข้อมูลที่สำคัญสำหรับระบบพิมพ์ คุณบอกว่า IDEs ที่ทันสมัยแก้ปัญหาการพิมพ์ผิดพลาด แต่มันก็ผิดและในความเป็นจริงนี่เป็นปัญหาที่สำคัญในภาษาที่ไม่ได้รวบรวมแบบคงที่ซึ่งเป็นเหตุผลที่เพิ่ม Perl use strict(JavaScript var) แน่นอนว่า Python นั้นไม่ได้เป็นแบบพิมพ์ดีด อย่างไรก็ตามแม้ถ้าคุณมีสิทธินี้จะยังคงขัดแย้งกับเซนของงูใหญ่, อ้างในคำตอบนี้
Konrad Rudolph

3
@kriss คุณเข้าใจผิด: การนำชื่อตัวแปรเดิมมาใช้ใหม่ไม่ใช่ปัญหา - การนำตัวแปรเดียวกันมาใช้ซ้ำคือ (เช่นชื่อเดียวกันในขอบเขตเดียวกัน) การประกาศอย่างชัดแจ้งจะป้องกันความผิดพลาดนี้ได้อย่างแน่นอน (และอื่น ๆ ซึ่งอิงจากการพิมพ์ผิดอย่างง่ายซึ่งอย่างที่ฉันได้กล่าวไปแล้วนั้นเป็นปัญหาที่พบได้บ่อยและใช้เวลานานมากถึงแม้ว่าคุณจะพูดถูกก็ตาม ภาษา) และความขัดแย้งที่ฉันหมายถึงคือความต้องการของเซนสำหรับการอธิบายอย่างชัดเจนซึ่งถูกโยนออกไปนอกหน้าต่างที่นี่
Konrad Rudolph

40

คุณไม่ผ่าน**locals()ฟังก์ชั่นใช่มั้ย

เนื่องจากงูใหญ่ขาด "รวม" คำสั่งและselfพารามิเตอร์ที่ชัดเจนและกฎระเบียบที่กำหนดขอบเขตค่อนข้างง่ายก็มักจะง่ายมากที่จะชี้ที่ตัวแปรและบอกที่วัตถุที่มาจาก - โดยไม่ต้องอ่านโมดูลอื่น ๆ และไม่มีการใด ๆ ของ IDE (ซึ่งมีข้อ จำกัด ในทางของการวิปัสสนาต่อไปโดยความจริงแล้วภาษาเป็นแบบไดนามิกมาก)

การimport *แบ่งทั้งหมดนั้น

นอกจากนี้ยังมีความเป็นไปได้ที่จะซ่อนข้อบกพร่องอย่างเป็นรูปธรรม

import os, sys, foo, sqlalchemy, mystuff
from bar import *

ตอนนี้หากโมดูลบาร์มีแอตทริบิวต์" os", " mystuff", ฯลฯ ... ใด ๆ พวกเขาจะแทนที่คนที่นำเข้าอย่างชัดเจนและอาจชี้ไปที่สิ่งที่แตกต่างกันมาก กำหนด__all__ในแถบมักจะฉลาด - รัฐนี้สิ่งปริยายจะถูกนำเข้า - แต่ก็ยังยากที่จะติดตามวัตถุที่มาจากโดยไม่ต้องอ่านและแยกโมดูลบาร์และต่อไปนี้ของการนำเข้า เครือข่ายของimport *เป็นสิ่งแรกที่ฉันแก้ไขเมื่อฉันเป็นเจ้าของโครงการ

อย่าเข้าใจฉันผิด: ถ้าimport *คนที่หายไปฉันจะร้องไห้เพื่อที่จะได้มัน แต่จะต้องมีการใช้อย่างระมัดระวัง กรณีการใช้งานที่ดีคือการจัดหาส่วนต่อประสานส่วนหน้าผ่านโมดูลอื่น ในทำนองเดียวกันการใช้คำสั่งนำเข้าแบบมีเงื่อนไขหรืออิมพอร์ตภายในเนมสเปซฟังก์ชัน / คลาสต้องใช้วินัยเล็กน้อย

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

แน่นอนเพราะนี่คือ python อย่าลังเลที่จะทำลายกฎและสำรวจ - แต่ระวังโครงการที่อาจเติบโตเป็นสิบเท่าหากซอร์สโค้ดไม่มีระเบียบวินัยมันจะเป็นปัญหา


6
งูหลาม 2.x ไม่มี "รวมถึง" คำสั่ง execfile()มันเรียกว่า โชคดีที่มันไม่ค่อยได้ใช้และหายไปใน 3.x
Sven Marnach

วิธีการเกี่ยวกับ**vars()ที่จะรวม Globals ถ้าฟังก์ชั่นที่เรียกว่าอยู่ในแฟ้มอื่นได้หรือไม่ : P
โซโลมอน Ucko

16

ไม่เป็นไรที่จะทำfrom ... import *ในเซสชันโต้ตอบ


ภายในdoctestสตริง? การimport *ตีความถูกตีความใน "กล่องทราย" ในกรณีนี้หรือไม่? ขอบคุณ
PatrickT

16

นั่นเป็นเพราะคุณกำลังสร้างมลภาวะให้กับเนมสเปซ คุณจะนำเข้าฟังก์ชั่นและคลาสทั้งหมดในเนมสเปซของคุณเองซึ่งอาจขัดแย้งกับฟังก์ชันที่คุณกำหนดเอง

นอกจากนี้ฉันคิดว่าการใช้ชื่อที่ผ่านการรับรองมีความชัดเจนมากขึ้นสำหรับงานบำรุงรักษา คุณเห็นบรรทัดโค้ดที่มีฟังก์ชันมาจากคุณจึงสามารถตรวจสอบเอกสารได้ง่ายขึ้น

ในโมดูลฟู:

def myFunc():
    print 1

ในรหัสของคุณ:

from foo import *

def doThis():
    myFunc() # Which myFunc is called?

def myFunc():
    print 2


9

สมมติว่าคุณมีรหัสต่อไปนี้ในโมดูลที่ชื่อว่า foo:

import ElementTree as etree

และจากนั้นในโมดูลของคุณเองคุณมี:

from lxml import etree
from foo import *

ตอนนี้คุณมีโมดูลที่ยากต่อการดีบักซึ่งดูเหมือนว่ามันมี etree ของ lxml แต่มี ElementTree แทน


7

ทั้งหมดนี้เป็นคำตอบที่ดี ฉันจะเพิ่มว่าเมื่อสอนคนใหม่ให้เขียนโค้ดใน Python การจัดการกับimport *มันยากมาก แม้ว่าคุณหรือพวกเขาไม่ได้เขียนรหัสก็ยังคงเป็นบล็อกสะดุด

ฉันสอนเด็ก ๆ (อายุประมาณ 8 ปี) ให้เข้าร่วมโปรแกรมใน Python เพื่อจัดการ Minecraft ฉันต้องการให้สภาพแวดล้อมการเข้ารหัสที่เป็นประโยชน์แก่พวกเขาในการทำงานกับ ( Atom Editor ) และสอนการพัฒนาที่ขับเคลื่อนด้วย REPL (ผ่านbpython ) ใน Atom ฉันพบว่าคำใบ้ / ความสำเร็จนั้นทำงานได้อย่างมีประสิทธิภาพเหมือนกับ bpython โชคดีที่ไม่เหมือนที่อื่นเครื่องมือในการวิเคราะห์บางสถิติ Atom import *จะไม่หลงกลโดย

แต่จะช่วยให้ตัวอย่างนี้ ... ในนี้เสื้อคลุมพวกเขาfrom local_module import *โมดูลพวงรวมทั้งรายชื่อของบล็อกนี้ ลองเพิกเฉยต่อความเสี่ยงของการชนกันของ namespace การทำเช่นfrom mcpi.block import *นี้ทำให้รายการทั้งหมดของประเภทบล็อกที่ไม่ชัดเจนซึ่งคุณต้องดูเพื่อดูว่ามีอะไรบ้าง หากพวกเขาใช้แทนคุณfrom mcpi import blockสามารถพิมพ์walls = block.แล้วรายการเติมข้อความอัตโนมัติจะปรากฏขึ้น ภาพหน้าจอ Atom.io


6

ทำความเข้าใจกับคะแนนที่ถูกต้องที่ผู้คนใส่ไว้ที่นี่ อย่างไรก็ตามฉันมีอาร์กิวเมนต์หนึ่งที่บางครั้ง "การนำเข้าดาว" อาจไม่ใช่วิธีที่ไม่ดีเสมอไป:

  • เมื่อฉันต้องการโครงสร้างรหัสของฉันในลักษณะที่ค่าคงที่ทั้งหมดไปที่โมดูลที่เรียกว่าconst.py:
    • ถ้าฉันทำเช่นimport constนั้นสำหรับทุกค่าคงที่ฉันต้องอ้างอิงมันconst.SOMETHINGซึ่งอาจไม่ใช่วิธีที่สะดวกที่สุด
    • ถ้าฉันทำfrom const import SOMETHING_A, SOMETHING_B ...แล้วเห็นได้ชัดว่ามันเป็นวิธีการ verbose เกินไปและเอาชนะวัตถุประสงค์ของการสร้าง
    • ดังนั้นฉันรู้สึกในกรณีนี้การทำfrom const import *อาจเป็นทางเลือกที่ดีกว่า

4

มันเป็นวิธีปฏิบัติที่ไม่ดีมากด้วยเหตุผลสองประการ:

  1. การอ่านรหัส
  2. ความเสี่ยงของการแทนที่ตัวแปร / ฟังก์ชั่น ฯลฯ

สำหรับจุดที่ 1 : มาดูตัวอย่างของสิ่งนี้:

from module1 import *
from module2 import *
from module3 import *

a = b + c - d

นี่เห็นรหัสไม่มีใครจะได้รับความคิดเกี่ยวกับการที่โมดูลb, cและdจริงเป็น

ในทางกลับกันถ้าคุณทำเช่นนั้น:

#                   v  v  will know that these are from module1
from module1 import b, c   # way 1
import module2             # way 2

a = b + c - module2.d
#            ^ will know it is from module2

มันสะอาดกว่าสำหรับคุณและคนใหม่ที่เข้าร่วมทีมของคุณจะมีความคิดที่ดีกว่า

สำหรับจุดที่ 2 : ให้พูดทั้งสองmodule1และมีตัวแปรmodule2 bเมื่อฉันทำ:

from module1 import *
from module2 import *

print b  # will print the value from module2

ค่าที่นี่module1สูญเสียไป มันจะยากที่จะแก้ปัญหาว่าทำไมรหัสไม่ทำงานแม้ว่าbจะมีการประกาศในmodule1และฉันได้เขียนรหัสคาดว่ารหัสของฉันที่จะใช้module1.b

หากคุณมีตัวแปรเดียวกันในโมดูลที่ต่างกันและคุณไม่ต้องการนำเข้าทั้งโมดูลคุณอาจทำได้:

from module1 import b as mod1b
from module2 import b as mod2b

2

เป็นการทดสอบฉันสร้างโมดูล test.py ด้วย 2 ฟังก์ชัน A และ B ซึ่งพิมพ์ "A 1" และ "B 1" ตามลำดับ หลังจากนำเข้า test.py ด้วย:

import test

. . . ฉันสามารถเรียกใช้ฟังก์ชัน 2 อย่างเป็น test.A () และ test.B () และ "test" แสดงเป็นโมดูลในเนมสเปซดังนั้นหากฉันแก้ไข test.py ฉันสามารถโหลดซ้ำได้ด้วย:

import importlib
importlib.reload(test)

แต่ถ้าฉันทำต่อไปนี้:

from test import *

ไม่มีการอ้างอิงถึง "ทดสอบ" ในเนมสเปซดังนั้นจึงไม่มีวิธีการโหลดซ้ำหลังจากแก้ไข (เท่าที่ฉันสามารถบอกได้) ซึ่งเป็นปัญหาในเซสชันแบบโต้ตอบ ในขณะที่อย่างใดอย่างหนึ่งต่อไปนี้:

import test
import test as tt

จะเพิ่ม "test" หรือ "tt" (ตามลำดับ) เป็นชื่อโมดูลในเนมสเปซซึ่งจะอนุญาตให้โหลดซ้ำได้

ถ้าฉันทำ:

from test import *

ชื่อ "A" และ "B" จะปรากฏในเนมสเปซเป็นฟังก์ชันฟังก์ชั่นถ้าฉันแก้ไข test.py และทำซ้ำคำสั่งข้างต้นฟังก์ชั่นเวอร์ชั่นที่แก้ไขจะไม่ได้รับการโหลดซ้ำ

และคำสั่งต่อไปนี้จะแสดงข้อความแสดงข้อผิดพลาด

importlib.reload(test)    # Error - name 'test' is not defined

หากมีคนรู้วิธีโหลดโมดูลที่โหลดด้วย "จากโมดูลนำเข้า *" โปรดโพสต์ มิฉะนั้นจะเป็นอีกเหตุผลหนึ่งที่หลีกเลี่ยงฟอร์ม:

from module import *

2

ตามที่แนะนำในเอกสารคุณไม่ควรใช้ (เกือบ) import *ในรหัสการผลิต

ในขณะที่การนำเข้า*จากโมดูลไม่ดีการนำเข้า * จากแพคเกจยิ่งแย่กว่านั้น โดยค่าเริ่มต้นfrom package import *การนำเข้าชื่อใดก็ตามที่กำหนดโดยแพ็คเกจ__init__.pyรวมถึง submodules ของแพ็คเกจที่โหลดโดยimportคำสั่งก่อนหน้า

อย่างไรก็ตามหาก__init__.pyรหัสของแพคเกจกำหนดรายการชื่อ__all__มันจะถูกนำไปเป็นรายการของชื่อ submodule ที่ควรนำเข้าเมื่อfrom package import *พบ

ลองพิจารณาตัวอย่างนี้ (สมมติว่าไม่มีการ__all__กำหนดไว้sound/effects/__init__.py):

# anywhere in the code before import *
import sound.effects.echo
import sound.effects.surround

# in your module
from sound.effects import *

คำสั่งสุดท้ายจะนำเข้าechoและsurroundโมดูลลงใน namespace ปัจจุบัน (อาจแทนที่คำนิยามก่อนหน้า) เพราะพวกเขาจะถูกกำหนดในsound.effectsแพคเกจเมื่อimportคำสั่งจะถูกดำเนินการ

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