Tại sao nhập khẩu * * xấu?


153

Không nên sử dụng import *trong Python.

Bất cứ ai có thể xin vui lòng chia sẻ lý do cho điều đó, để tôi có thể tránh nó làm lần sau?



2
nó phụ thuộc nếu bạn đang viết kịch bản hoặc viết mã bạn cần sử dụng lại. đôi khi nó trả tiền để bỏ qua các tiêu chuẩn mã. "Nhập *" cũng có thể ổn nếu bạn có quy ước đặt tên cho biết rõ nội dung đến từ đâu. ví dụ: "từ mèo nhập khẩu *; TabbyCat; MaineCoonCat; CalicoCat;"
gatoatigrado

3
import *không hoạt động với tôi ở vị trí đầu tiên trong Python 2 hoặc 3.
joshreesjones 16/07/2015

1
Điều này có trả lời câu hỏi của bạn không? Chính xác thì nhập "nhập *" là gì?
AMC

Câu trả lời:


223
  • Bởi vì nó đặt rất nhiều thứ vào không gian tên của bạn (có thể che khuất một số đối tượng khác từ lần nhập trước và bạn sẽ không biết về nó).

  • Bởi vì bạn không biết chính xác những gì được nhập và không thể dễ dàng tìm thấy từ mô-đun nào một thứ nhất định đã được nhập (khả năng đọc).

  • Bởi vì bạn không thể sử dụng các công cụ tuyệt vời như pyflakesđể phát hiện tĩnh các lỗi trong mã của mình.


2
Vâng, tôi thực sự ghét công việc của mình khi ai đó sử dụng * nhập khẩu, bởi vì sau đó tôi không thể chạy pyflakes và hạnh phúc, mà phải sửa chữa những lần nhập đó. Mặc dù vậy, thật tuyệt, với những chiếc bánh đó giúp tôi :-)
gruszczy

7
Một ví dụ cụ thể, nhiều người dùng NumPy đã bị cắn bởi numpy.anybóng anykhi họ làm from numpy import *hoặc một công cụ "hữu ích" làm điều đó cho họ.
user2357112 hỗ trợ Monica

1
Tôi có nên tránh sử dụng khóa --pylab cho IPython vì những lý do tương tự không?
hồ bấm giờ

6
Để làm nổi bật rủi ro mà tôi chưa từng nghĩ đến trước khi đọc điều này ("có thể che giấu một số đối tượng khác từ lần nhập trước"): import *làm cho thứ tự của các importcâu lệnh trở nên quan trọng ... ngay cả đối với các mô-đun thư viện tiêu chuẩn thường không quan tâm đến thứ tự nhập . Một cái gì đó ngây thơ như bảng chữ cái các importtuyên bố của bạn có thể phá vỡ kịch bản của bạn khi một nạn nhân trước đây của cuộc chiến nhập khẩu trở thành người sống sót duy nhất. (Ngay cả khi tập lệnh của bạn hoạt động ngay bây giờ và không bao giờ thay đổi, đôi khi nó có thể bị lỗi nếu mô-đun được nhập giới thiệu một tên mới thay thế cho tên bạn đang dựa vào.)
Kevin J. Chase

49

Theo Zen của Python :

Rõ ràng là tốt hơn so với ngầm.

... chắc chắn không thể tranh luận với điều đó, chắc chắn?


29
Trên thực tế, bạn có thể tranh luận với điều đó. Nó cũng hoàn toàn không nhất quán, cho rằng bạn không khai báo các biến rõ ràng trong Python, chúng chỉ xuất hiện khi bạn gán cho chúng.
Konrad Rudolph

7
@gruszczy: khai báo các biến là dư thừa để làm ? Phân công? Không, đó là hai khái niệm riêng biệt và tuyên bố một cái gì đó truyền tải một thông tin rất riêng biệt và quan trọng. Dù sao, nhà thám hiểm luôn có phần nào liên quan đến sự dư thừa, họ là hai khuôn mặt của cùng một đồng tiền.
Konrad Rudolph

3
@kriss đúng, nhưng đó không phải là quan điểm của tôi. Quan điểm của tôi là việc không tuyên bố rõ ràng một biến dẫn đến lỗi. Bạn nói rằng "chuyển nhượng mà không có [khai báo] là không thể". Nhưng điều đó là sai, toàn bộ quan điểm của tôi là Python không may làm điều đó chính xác.
Konrad Rudolph

3
@kriss Một phần thông tin khác được cung cấp cho trình biên dịch bởi khai báo là thực tế là bạn thực sự có ý định khai báo một biến mới. Đó là một thông tin quan trọng cho hệ thống loại. Bạn nói rằng các IDE hiện đại giải quyết vấn đề nhầm lẫn nhưng điều đó đơn giản là sai và trên thực tế đây là một vấn đề đáng kể trong các ngôn ngữ không được biên dịch tĩnh, đó là lý do Perl thêm vào use strict(JavaScript var). Bên cạnh, dĩ nhiên Python không phảilỗi chính tả (thực tế nó được gõ rất mạnh). Dù sao, ngay cả khi bạn đã đúng, điều này vẫn sẽ mâu thuẫn với Zen của Python, được trích dẫn trong câu trả lời này.
Konrad Rudolph

3
@kriss Bạn đã nhầm: việc sử dụng lại cùng một tên biến không phải là vấn đề - sử dụng lại cùng một biến là (tức là cùng tên trong cùng một phạm vi). Tuyên bố rõ ràng sẽ ngăn chặn chính xác sai lầm này (và các lỗi khác, dựa trên sự nhầm lẫn đơn giản, như tôi đã nói, thực sự là một vấn đề cực kỳ phổ biến và tốn thời gian, mặc dù bạn nói đúng là vấn đề lớn hơn ở Perl ngôn ngữ). Và mâu thuẫn mà tôi ám chỉ là yêu cầu của Zen cho nhân chứng, được ném ra khỏi cửa sổ ở đây.
Konrad Rudolph

40

Bạn không vượt qua **locals()chức năng, phải không?

Kể từ khi Python thiếu một "bao gồm" tuyên bố, các selftham số là rõ ràng, nguyên tắc xác định phạm vi khá đơn giản, nó thường là rất dễ dàng để chỉ một ngón tay vào một biến và cho biết nơi đối tượng đó đến từ - mà không đọc các module khác và không có bất cứ loại nào của IDE (dù sao cũng bị giới hạn trong cách hướng nội, bởi thực tế ngôn ngữ rất năng động).

Sự import *phá vỡ tất cả những điều đó.

Ngoài ra, nó có một khả năng cụ thể để che giấu lỗi.

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

Bây giờ, nếu mô-đun thanh có bất kỳ thuộc tính " os", " mystuff", v.v ..., chúng sẽ ghi đè lên các thuộc tính được nhập rõ ràng và có thể chỉ ra những thứ rất khác nhau. Xác định __all__trong thanh thường là khôn ngoan - điều này nói rõ những gì sẽ được nhập hoàn toàn - nhưng vẫn khó theo dõi các đối tượng đến từ đâu, mà không đọc và phân tích mô-đun thanh và theo dõi nhập khẩu của nó . Một mạng lưới import *là điều đầu tiên tôi sửa chữa khi tôi sở hữu một dự án.

Đừng hiểu lầm tôi: nếu import *mất tích, tôi sẽ khóc để có nó. Nhưng nó phải được sử dụng cẩn thận. Một trường hợp sử dụng tốt là cung cấp giao diện mặt tiền trên một mô-đun khác. Tương tự như vậy, việc sử dụng các câu lệnh nhập có điều kiện hoặc nhập bên trong các không gian tên hàm / lớp, đòi hỏi một chút kỷ luật.

Tôi nghĩ trong các dự án từ trung bình đến lớn, hoặc các dự án nhỏ có nhiều người đóng góp, cần tối thiểu vệ sinh về mặt phân tích thống kê - chạy ít nhất là pyflakes hoặc thậm chí tốt hơn là một pylint được cấu hình đúng - để bắt một số loại lỗi trước đó chúng xảy ra

Tất nhiên vì đây là python - hãy thoải mái phá vỡ các quy tắc và khám phá - nhưng hãy cảnh giác với các dự án có thể tăng gấp 10 lần, nếu mã nguồn bị thiếu kỷ luật thì đó sẽ là một vấn đề.


6
Python 2.x không có câu lệnh "bao gồm". Nó được gọi là execfile(). May mắn thay, nó hiếm khi được sử dụng và đi trong 3.x.
Sven Marnach

Làm thế nào về **vars()bao gồm toàn cầu nếu chức năng được gọi là trong một tập tin khác? : P
Solomon Ucko

16

Nó là OK để làm from ... import *trong một phiên tương tác.


Làm thế nào về bên trong một doctestchuỗi? Có import *được giải thích bên trong một "hộp cát" trong trường hợp này? Cảm ơn.
PatrickT

16

Đó là bởi vì bạn đang làm ô nhiễm không gian tên. Bạn sẽ nhập tất cả các hàm và các lớp trong không gian tên của riêng bạn, có thể xung đột với các hàm bạn tự xác định.

Hơn nữa, tôi nghĩ rằng việc sử dụng một tên đủ điều kiện rõ ràng hơn cho nhiệm vụ bảo trì; bạn thấy trên chính dòng mã nơi một hàm xuất phát, vì vậy bạn có thể kiểm tra các tài liệu dễ dàng hơn nhiều.

Trong mô-đun foo:

def myFunc():
    print 1

Trong mã của bạn:

from foo import *

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

def myFunc():
    print 2


9

Giả sử bạn có đoạn mã sau trong một mô-đun gọi là foo:

import ElementTree as etree

và sau đó trong mô-đun của riêng bạn, bạn có:

from lxml import etree
from foo import *

Bây giờ bạn có một mô-đun khó gỡ lỗi, có vẻ như nó có etree của lxml, nhưng thực sự có ElementTree thay thế.


7

Đây là tất cả các câu trả lời tốt. Tôi sẽ nói thêm rằng khi dạy người mới viết mã bằng Python, việc xử lý import *rất khó khăn. Ngay cả khi bạn hoặc họ không viết mã, nó vẫn là một vấp ngã.

Tôi dạy trẻ em (khoảng 8 tuổi) lập trình bằng Python để thao túng Minecraft. Tôi muốn cung cấp cho họ một môi trường mã hóa hữu ích để làm việc với ( Trình soạn thảo nguyên tử ) và dạy phát triển dựa trên REPL (thông qua bpython ). Trong Atom tôi thấy rằng các gợi ý / hoàn thành hoạt động hiệu quả như bpython. May mắn thay, không giống như một số công cụ phân tích thống kê khác, Atom không bị lừa import *.

Tuy nhiên, hãy lấy ví dụ này ... Trong trình bao bọc này, chúng có from local_module import *một mô-đun bao gồm danh sách các khối này . Hãy bỏ qua nguy cơ va chạm không gian tên. Bằng cách from mcpi.block import *họ làm cho toàn bộ danh sách các loại khối tối nghĩa này một cái gì đó mà bạn phải nhìn vào để biết những gì có sẵn. Nếu họ đã sử dụng thay vào đó from mcpi import block, thì bạn có thể nhập walls = block.và sau đó một danh sách tự động hoàn thành sẽ bật lên. Ảnh chụp màn hình Atom.io


6

Hiểu những điểm hợp lệ mọi người đặt ở đây. Tuy nhiên, tôi có một lập luận rằng, đôi khi, "nhập sao" có thể không phải lúc nào cũng là một thực tiễn tồi:

  • Khi tôi muốn cấu trúc mã của mình theo cách mà tất cả các hằng số đi đến một mô-đun có tên const.py:
    • Nếu tôi làm import const, thì với mỗi hằng số, tôi phải gọi nó là const.SOMETHING, đó có lẽ không phải là cách thuận tiện nhất.
    • Nếu tôi làm from const import SOMETHING_A, SOMETHING_B ..., thì rõ ràng nó quá dài dòng và đánh bại mục đích của cấu trúc.
    • Vì vậy, tôi cảm thấy trong trường hợp này, làm một from const import *có thể là một lựa chọn tốt hơn.

4

Nó là một thực hành BAD rất vì hai lý do:

  1. Mã dễ đọc
  2. Rủi ro ghi đè các biến / hàm vv

Đối với điểm 1 : Chúng ta hãy xem một ví dụ về điều này:

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

a = b + c - d

Ở đây, khi nhìn thấy mã, sẽ không ai có ý tưởng về mô-đun nào b , cdthực sự thuộc về.

Mặt khác, nếu bạn làm như thế:

#                   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

Nó sạch sẽ hơn nhiều đối với bạn, và người mới tham gia nhóm của bạn sẽ có ý tưởng tốt hơn.

Đối với điểm 2 : Hãy nói cả hai module1module2có biến là b. Khi tôi làm:

from module1 import *
from module2 import *

print b  # will print the value from module2

Ở đây giá trị từ module1bị mất. Sẽ khó gỡ lỗi tại sao mã không hoạt động ngay cả khi bđược khai báo trongmodule1 và tôi đã viết mã mong mã của tôi sử dụngmodule1.b

Nếu bạn có cùng một biến trong các mô-đun khác nhau và bạn không muốn nhập toàn bộ mô-đun, bạn thậm chí có thể làm:

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

2

Để thử nghiệm, tôi đã tạo một mô-đun test.txt với 2 chức năng A và B, tương ứng in "A 1" và "B 1". Sau khi nhập test.txt với:

import test

. . . Tôi có thể chạy 2 hàm là test.A () và test.B () và "test" hiển thị dưới dạng một mô-đun trong không gian tên, vì vậy nếu tôi chỉnh sửa test.txt tôi có thể tải lại bằng:

import importlib
importlib.reload(test)

Nhưng nếu tôi làm như sau:

from test import *

không có tham chiếu đến "kiểm tra" trong không gian tên, vì vậy không có cách nào để tải lại nó sau khi chỉnh sửa (theo như tôi có thể nói), đây là một vấn đề trong phiên tương tác. Trong khi một trong những điều sau đây:

import test
import test as tt

sẽ thêm "test" hoặc "tt" (tương ứng) làm tên mô-đun trong không gian tên, cho phép tải lại.

Nếu tôi làm:

from test import *

tên "A" và "B" hiển thị trong không gian tên dưới dạng hàm . Nếu tôi chỉnh sửa test.txt và lặp lại lệnh trên, các phiên bản sửa đổi của các chức năng sẽ không được tải lại.

Và lệnh sau gợi ra một thông báo lỗi.

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

Nếu ai đó biết cách tải lại mô-đun được tải bằng "từ nhập mô-đun *", vui lòng gửi. Nếu không, đây sẽ là một lý do khác để tránh hình thức:

from module import *

2

Như được đề xuất trong các tài liệu, bạn nên (hầu như) không bao giờ sử dụng import * trong mã sản xuất.

Trong khi nhập *từ một mô-đun là xấu, nhập * từ một gói thậm chí còn tồi tệ hơn. Theo mặc định, from package import *nhập bất kỳ tên nào được xác định bởi gói __init__.py, bao gồm mọi mô hình con của gói đã được tải trước đóimport câu lệnh .

Tuy nhiên, nếu __init__.pymã của gói xác định một danh sách có tên __all__, thì nó được coi là danh sách các tên mô hình con nên được nhập khi from package import *gặp phải.

Xem xét ví dụ này (giả sử không có __all__định nghĩa trong 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 *

Câu lệnh cuối cùng sẽ nhập các mô-đun echosurroundmô-đun vào không gian tên hiện tại (có thể ghi đè các định nghĩa trước đó) vì chúng được định nghĩa trong sound.effectsgói khi importcâu lệnh được thi hành.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.