__Init__.py để làm gì?


Câu trả lời:


1454

Nó từng là một phần bắt buộc của một gói ( cũ, trước "gói thông thường" 3,3 , không phải là gói 3,3+ "không gian tên" mới hơn ).

Đây là tài liệu.

Python định nghĩa hai loại gói, gói thông thường và gói không gian tên. Các gói thông thường là các gói truyền thống khi chúng tồn tại trong Python 3.2 trở về trước. Một gói thông thường thường được thực hiện như một thư mục chứa một __init__.pytệp. Khi một gói thông thường được nhập, __init__.pytệp này được thực thi ngầm và các đối tượng mà nó xác định được liên kết với các tên trong không gian tên của gói. Các __init__.pytập tin có thể chứa mã Python tương tự mà bất kỳ thành phần khác có thể chứa, và Python sẽ bổ sung thêm một số thuộc tính bổ sung cho các mô-đun khi nó được nhập khẩu.

Nhưng chỉ cần nhấp vào liên kết, nó chứa một ví dụ, thêm thông tin và giải thích về các gói không gian tên, loại gói không có __init__.py.


187
Điều này có nghĩa là gì: "điều này được thực hiện để ngăn các thư mục có tên chung, chẳng hạn như chuỗi, vô tình che giấu các mô-đun hợp lệ xảy ra sau này trên đường tìm kiếm mô-đun"?
Carl G

97
@CarlG Python tìm kiếm danh sách các thư mục để phân giải tên, ví dụ: nhập câu lệnh. Vì đây có thể là bất kỳ thư mục nào và người dùng cuối có thể thêm tùy ý, nên các nhà phát triển phải lo lắng về các thư mục xảy ra để chia sẻ tên với mô-đun Python hợp lệ, chẳng hạn như 'chuỗi' trong ví dụ về tài liệu. Để giảm bớt điều này, nó bỏ qua các thư mục không chứa tệp có tên _ _ init _ _.py (không có dấu cách), ngay cả khi nó trống.
Nhà giả kim hai bit

186
@CarlG Hãy thử điều này. Tạo một thư mục có tên 'datetime' và trong đó tạo hai tệp trống, tệp init.py (có dấu gạch dưới) và datetime.py. Bây giờ hãy mở một trình thông dịch, nhập sys và phát hành sys.path.insert(0, '/path/to/datetime'), thay thế đường dẫn đó bằng đường dẫn đến bất kỳ thư mục nào bạn vừa thực hiện. Bây giờ hãy thử một cái gì đó như from datetime import datetime;datetime.now(). Bạn sẽ nhận được một AttributionError (vì hiện tại nó đang nhập tệp trống của bạn). Nếu bạn đã lặp lại các bước này mà không tạo tệp init trống, điều này sẽ không xảy ra. Đó là những gì nó dự định ngăn chặn.
Nhà giả kim hai bit

4
@ DarekNędza Bạn đã thiết lập một cái gì đó không chính xác nếu bạn không thể mở trình thông dịch Python và from datetime import datetimekhông có lỗi. Đó là cách tốt để trở lại phiên bản 2.3!
Nhà giả kim hai bit

5
@SWang: Điều đó không chính xác: builtinsliệt kê các hàmlớp tích hợp , không phải các mô-đun tích hợp (xem docs.python.org/3/tutorial/modules.html#the-dir-feft ). Nếu bạn muốn liệt kê các mô-đun tích hợp , hãy làm import sys; print(sys.builtin_module_names)(xem docs.python.org/3/l Library / sys.html # sys.builtin_module_names ).
Maggyero

842

Các tệp có tên __init__.pyđược sử dụng để đánh dấu các thư mục trên đĩa dưới dạng các thư mục gói Python. Nếu bạn có các tập tin

mydir/spam/__init__.py
mydir/spam/module.py

mydirtrên đường dẫn của bạn, bạn có thể nhập mã module.pydưới dạng

import spam.module

hoặc là

from spam import module

Nếu bạn xóa __init__.pytệp, Python sẽ không còn tìm các mô hình con trong thư mục đó nữa, vì vậy các nỗ lực nhập mô-đun sẽ thất bại.

Các __init__.pytập tin thường được làm rỗng, nhưng có thể được sử dụng để xuất khẩu phần chọn của gói dưới tên thuận tiện hơn, các chức năng thuận tiện cho tổ chức, vv Cho ví dụ trên, nội dung của các mô-đun init có thể được truy cập như

import spam

dựa trên điều này


96
Cập nhật: Tệp __init__.pyđược yêu cầu trong Python 2.X và vẫn được yêu cầu trong Python 2.7.12 (Tôi đã kiểm tra nó) nhưng nó không còn được yêu cầu từ (được cho là) ​​Python 3.3 trở đi và không bắt buộc trong Python 3.4.3 (I đã thử nó). Xem stackoverflow.com/questions/37139786 để biết thêm chi tiết.
Rob_b Before_edits

4
Đừng sử dụng nó. Đó là gói "không gian tên", không phải là gói thông thường. gói không gian tên được sử dụng cho các trường hợp sử dụng rất hiếm. Bạn có thể không cần biết khi sử dụng nó. Chỉ cần sử dụng __init__.py.
mêtan

2
tuy nhiên nếu bạn có setup.pyvà bạn sử dụng find_packages()thì cần phải có __init__.pytrong mọi thư mục. Xem stackoverflow.com/a/56277323/7127824
techkuz

484

Ngoài việc gắn nhãn thư mục là gói Python và xác định __all__, __init__.pycho phép bạn xác định bất kỳ biến nào ở cấp gói. Làm như vậy thường thuận tiện nếu một gói xác định thứ gì đó sẽ được nhập thường xuyên, theo kiểu giống như API. Mô hình này thúc đẩy sự tuân thủ triết lý "phẳng hơn là lồng nhau" của Pythonic.

Một ví dụ

Dưới đây là một ví dụ từ một trong những dự án của tôi, trong đó tôi thường nhập một lệnh sessionmakergọi Sessionđể tương tác với cơ sở dữ liệu của mình. Tôi đã viết một gói "cơ sở dữ liệu" với một vài mô-đun:

database/
    __init__.py
    schema.py
    insertions.py
    queries.py

Của tôi __init__.pychứa mã sau đây:

import os

from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine

engine = create_engine(os.environ['DATABASE_URL'])
Session = sessionmaker(bind=engine)

Vì tôi xác định Sessionở đây, tôi có thể bắt đầu một phiên mới bằng cú pháp bên dưới. Mã này sẽ được thực hiện giống nhau từ bên trong hoặc bên ngoài thư mục gói "cơ sở dữ liệu".

from database import Session
session = Session()

Tất nhiên, đây là một sự thuận tiện nhỏ - giải pháp thay thế sẽ là xác định Sessiontrong một tệp mới như "create_session.py" trong gói cơ sở dữ liệu của tôi và bắt đầu các phiên mới bằng cách sử dụng:

from database.create_session import Session
session = Session()

đọc thêm

Có một chủ đề reddit khá thú vị bao gồm các cách sử dụng thích hợp __init__.pyở đây:

http://www.reddit.com/r/Python/comments/1bbbwk/whats_your_opinion_on_what_to_include_in_init_py/

Ý kiến ​​của đa số dường như là __init__.pycác tệp phải rất mỏng để tránh vi phạm triết lý "rõ ràng hơn là ngầm".


3
engine, sessionmaker, create_engine, Và oscũng có thể tất cả được nhập khẩu từ databasebây giờ ... có vẻ như bạn đã thực hiện một mớ hỗn độn của namespace đó.
ArtOfWarfare

9
@ArtOfWarfare, bạn có thể sử dụng __all__ = [...]để giới hạn những gì được nhập với import *. Nhưng bên cạnh đó, vâng, bạn còn lại một không gian tên cấp cao lộn xộn.
Nathan Gould

Tôi có thể biết 'URL DATABASE' là gì không? Tôi đã cố gắng sao chép điều này bằng cách kèm theo create_engine với 'mysql + mysqldb: // root: python @ localhost: 3306 / test' nhưng nó không hoạt động. Cảm ơn.
SunnyBoiz

2
Làm thế nào bạn có thể truy cập lớp 'Phiên' được xác định trong init từ bên trong gói, ví dụ quieries.py?
vldbnc

252

Có 2 lý do chính cho __init__.py

  1. Để thuận tiện: những người dùng khác sẽ không cần biết vị trí chính xác của các chức năng trong phân cấp gói của bạn.

    your_package/
      __init__.py
      file1.py
      file2.py
        ...
      fileN.py
    # in __init__.py
    from file1 import *
    from file2 import *
    ...
    from fileN import *
    # in file1.py
    def add():
        pass

    sau đó những người khác có thể gọi add () bằng

    from your_package import add

    mà không biết file1, như

    from your_package.file1 import add
  2. Nếu bạn muốn một cái gì đó được khởi tạo; ví dụ: ghi nhật ký (cần được đặt ở cấp cao nhất):

    import logging.config
    logging.config.dictConfig(Your_logging_config)

7
oh, trước khi đọc câu trả lời của bạn, tôi nghĩ gọi một chức năng rõ ràng từ vị trí của nó là một thực hành tốt.
Aerin

2
@Aerin sẽ tốt hơn nếu không xem xét các tuyên bố ngắn (hoặc, trong trường hợp này, kết luận chủ quan) là luôn luôn đúng. Nhập từ __init__.pyđôi khi có thể hữu ích, nhưng không phải tất cả các lần.
Tobias Sette

2
Các mã này được thực thi tại thời gian nhập hoặc thời gian chạy?
dùng1559897

111

Các __init__.pytập tin làm cho danh bạ trị Python chứa nó như là mô-đun.

Hơn nữa, đây là tệp đầu tiên được tải trong một mô-đun, vì vậy bạn có thể sử dụng nó để thực thi mã mà bạn muốn chạy mỗi khi mô-đun được tải hoặc chỉ định các mô hình con được xuất.


89

Vì Python 3.3, __init__.pykhông còn cần thiết để xác định các thư mục là các gói Python có thể nhập được.

Kiểm tra PEP 420: Gói không gian tên ngầm định :

Hỗ trợ riêng cho các thư mục gói không yêu cầu __init__.pytệp đánh dấu và có thể tự động trải rộng trên nhiều phân đoạn đường dẫn (lấy cảm hứng từ các cách tiếp cận khác nhau của bên thứ ba đối với các gói không gian tên, như được mô tả trong PEP 420 )

Đây là bài kiểm tra:

$ mkdir -p /tmp/test_init
$ touch /tmp/test_init/module.py /tmp/test_init/__init__.py
$ tree -at /tmp/test_init
/tmp/test_init
├── module.py
└── __init__.py
$ python3

>>> import sys
>>> sys.path.insert(0, '/tmp')
>>> from test_init import module
>>> import test_init.module

$ rm -f /tmp/test_init/__init__.py
$ tree -at /tmp/test_init
/tmp/test_init
└── module.py
$ python3

>>> import sys
>>> sys.path.insert(0, '/tmp')
>>> from test_init import module
>>> import test_init.module

tài liệu tham khảo:
https://docs.python.org/3/whatsnew/3.3.html#pep-420-implicit-namespace-packages
https://www.python.org/dev/peps/pep-0420/
Is __init__. py không cần thiết cho các gói trong Python 3?


3
Đó là gói "không gian tên". Đừng sử dụng nó cho gói thông thường.
mêtan

@methan, bạn có thể giải thích về nhận xét của bạn?
Robert Lugg


57

Trong Python định nghĩa của gói rất đơn giản. Giống như Java, cấu trúc phân cấp và cấu trúc thư mục là như nhau. Nhưng bạn phải có __init__.pytrong một gói. Tôi sẽ giải thích các __init__.pytập tin với ví dụ dưới đây:

package_x/
|--  __init__.py
|--    subPackage_a/
|------  __init__.py
|------  module_m1.py
|--    subPackage_b/
|------  __init__.py
|------  module_n1.py
|------  module_n2.py
|------  module_n3.py

__init__.pycó thể trống rỗng, miễn là nó tồn tại. Nó chỉ ra rằng thư mục nên được coi là một gói. Tất nhiên, __init__.pycũng có thể thiết lập nội dung phù hợp.

Nếu chúng ta thêm một hàm trong module_n1:

def function_X():
    print "function_X in module_n1"
    return

Sau khi chạy:

>>>from package_x.subPackage_b.module_n1 import function_X
>>>function_X()

function_X in module_n1 

Sau đó, chúng tôi theo gói phân cấp và gọi module_n1 là hàm. Chúng tôi có thể sử dụng __init__.pytrong subPackage_b như thế này:

__all__ = ['module_n2', 'module_n3']

Sau khi chạy:

>>>from package_x.subPackage_b import * 
>>>module_n1.function_X()

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: No module named module_n1

Do đó sử dụng * nhập, gói mô-đun tùy thuộc vào __init__.pynội dung.


Làm thế nào thiết lập của tôi sẽ tìm cách nhập tương tự thông qua thư viện đóng gói? from package_x.subPackage_b.module_n1 import function_X
Technazi

Vì vậy, chìa khóa mang đi ở đây là "sử dụng * nhập, gói mô-đun phải tuân theo nội dung init .py"
Minnie

54

Mặc dù Python hoạt động mà không có __init__.pytệp, bạn vẫn nên bao gồm một tệp.

Nó chỉ định một gói nên được coi là một mô-đun, do đó bao gồm nó (ngay cả khi nó trống).

Cũng có trường hợp bạn thực sự có thể sử dụng một __init__.pytệp:

Hãy tưởng tượng bạn có cấu trúc tệp sau:

main_methods 
    |- methods.py

methods.pycó chứa điều này:

def foo():
    return 'foo'

Để sử dụng, foo()bạn cần một trong những điều sau đây:

from main_methods.methods import foo # Call with foo()
from main_methods import methods # Call with methods.foo()
import main_methods.methods # Call with main_methods.methods.foo()

Có thể ở đó bạn cần (hoặc muốn) giữ methods.pybên trong main_methods(thời gian chạy / phụ thuộc chẳng hạn) nhưng bạn chỉ muốn nhập main_methods.


Nếu bạn đã thay đổi tên của methods.pyđể __init__.pysau đó bạn có thể sử dụng foo()bằng cách chỉ nhập khẩu main_methods:

import main_methods
print(main_methods.foo()) # Prints 'foo'

Điều này hoạt động vì __init__.pyđược coi là một phần của gói.


Một số gói Python thực sự làm điều này. Một ví dụ là với JSON , nơi chạy import jsonthực sự là nhập __init__.pytừ jsongói ( xem cấu trúc tệp gói ở đây ):

Mã nguồn: Lib/json/__init__.py


39

__init__.py sẽ coi thư mục của nó là một mô-đun có thể tải.

Đối với những người thích đọc mã, tôi đặt bình luận của Nhà giả kim Two-Bit ở đây.

$ find /tmp/mydir/
/tmp/mydir/
/tmp/mydir//spam
/tmp/mydir//spam/__init__.py
/tmp/mydir//spam/module.py
$ cd ~
$ python
>>> import sys
>>> sys.path.insert(0, '/tmp/mydir')
>>> from spam import module
>>> module.myfun(3)
9
>>> exit()
$ 
$ rm /tmp/mydir/spam/__init__.py*
$ 
$ python
>>> import sys
>>> sys.path.insert(0, '/tmp/mydir')
>>> from spam import module
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: No module named spam
>>> 

30

Nó tạo điều kiện nhập các tệp python khác. Khi bạn đặt tệp này trong một thư mục (nói nội dung) có chứa các tệp py khác, thì bạn có thể thực hiện một số thứ như nhập công cụ.

root\
    stuff\
         other.py

    morestuff\
         another.py

Nếu không có cái này __init__.pybên trong thư mục, bạn không thể nhập other.txt, vì Python không biết mã nguồn của thứ đó ở đâu và không thể nhận ra nó là một gói.


2
Tôi có cấu trúc tương tự trong dự án của mình (python 3.4) nhưng tôi không thể tạo một other.txt xem other.py. Làm thế nào tôi nên nhập khẩu? từ root. nhập khẩu khác? Nó hoạt động trong chế độ gỡ lỗi VSCode nhưng không phải trong dòng lệnh. Có ý kiến ​​gì không?
Rodrigorf

10

Một __init__.pytập tin làm cho nhập khẩu dễ dàng. Khi một __init__.pyhiện diện trong một gói, chức năng a()có thể được nhập từ tệp b.pynhư vậy:

from b import a

Không có nó, tuy nhiên, bạn không thể nhập trực tiếp. Bạn phải sửa đổi đường dẫn hệ thống:

import sys
sys.path.insert(0, 'path/to/b.py')

from b import a

Ý bạn là gì " chức năng a () có thể được nhập từ tệp b.py [đoạn trích] Tuy nhiên, nếu không có nó, bạn không thể nhập trực tiếp. "? Tôi có thể nhập hàm a () từ tệp b.py mà không cần __init__.py.
aderchox
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.