ใครสามารถอธิบาย __all__ ใน Python ได้บ้าง


983

ฉันใช้ Python มากขึ้นเรื่อย ๆ และฉันก็เห็นการ__all__ตั้งค่าตัวแปรใน__init__.pyไฟล์ต่างๆ มีใครช่วยอธิบายสิ่งนี้ได้บ้าง

คำตอบ:


566

import *มันเป็นรายการของวัตถุสาธารณะของโมดูลที่เป็นตีความโดย มันแทนที่ค่าเริ่มต้นของการซ่อนทุกอย่างที่ขึ้นต้นด้วยขีดล่าง


146
วัตถุที่ขึ้นต้นด้วยขีดเส้นใต้หรือไม่ได้กล่าวถึงใน__all__กรณีที่__all__มีอยู่จะไม่ถูกซ่อนอย่างแน่นอน พวกเขาสามารถเห็นและเข้าถึงได้อย่างสมบูรณ์แบบตามปกติถ้าคุณรู้ชื่อของพวกเขา มันเป็นเพียงในกรณีของ "นำเข้า *" ซึ่งไม่แนะนำต่อไปว่าความแตกต่างมีน้ำหนักใด ๆ
Brandon Rhodes

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

12
ส่วนต่อประสานสาธารณะและภายใน - python.org/dev/peps/pep-0008/#id50เพื่อการสนับสนุนการวิปัสสนาที่ดียิ่งขึ้นโมดูลควรประกาศชื่อใน API สาธารณะอย่างชัดเจนโดยใช้แอตทริบิวต์ __all__ การตั้งค่า __all__ เป็นรายการว่างแสดงว่าโมดูลนั้นไม่มี API สาธารณะ
แก้ปัญหา

ผมไม่แน่ใจว่าถ้าtkได้รับการปล่อยตัวในวันนี้ (หรือในปี 2012 แม้) from tk import *การปฏิบัติที่แนะนำจะใช้ ฉันคิดว่าการฝึกเป็นที่ยอมรับเนื่องจากความเฉื่อยไม่ใช่การออกแบบโดยเจตนา
chepner

เนื่องจาก BrandonRhodes ชี้ให้เห็นว่าสิ่งนี้ไม่ถูกต้อง
duhaime

947

เชื่อมโยงกับ แต่ไม่ได้กล่าวถึงอย่างชัดเจนที่นี่คือเมื่อ__all__มีการใช้งาน มันคือรายการของสตริงที่กำหนดว่าสัญลักษณ์ใดในโมดูลจะถูกส่งออกเมื่อfrom <module> import *ใช้กับโมดูล

ตัวอย่างเช่นรหัสต่อไปนี้ในการfoo.pyส่งออกสัญลักษณ์barและbaz:

__all__ = ['bar', 'baz']

waz = 5
bar = 10
def baz(): return 'baz'

สัญลักษณ์เหล่านี้สามารถนำเข้าได้เช่น:

from foo import *

print(bar)
print(baz)

# The following will trigger an exception, as "waz" is not exported by the module
print(waz)

หาก__all__มีการใส่ความคิดเห็นด้านบนรหัสนี้จะดำเนินการให้เสร็จสมบูรณ์เนื่องจากพฤติกรรมเริ่มต้นของimport *คือการนำเข้าสัญลักษณ์ทั้งหมดที่ไม่ได้เริ่มต้นด้วยการขีดเส้นใต้จากเนมสเปซที่กำหนด

การอ้างอิง: https://docs.python.org/tutorial/modules.html#importing-from-a-package

หมายเหตุ: __all__มีผลกับfrom <module> import *พฤติกรรมเท่านั้น สมาชิกที่ไม่ได้กล่าวถึงในยังคงสามารถเข้าถึงจากภายนอกโมดูลและสามารถนำเข้าด้วย__all__from <module> import <member>


1
เราไม่ควรพิมพ์บาซเป็นprint(baz())?
John Cole

@JohnCole baz เป็นฟังก์ชันของวัตถุและ baz () จะเรียกใช้ฟังก์ชันของฟังก์ชัน
Bhanu Tez

@BhanuTez อย่างแน่นอน ดังนั้นprint(baz)พิมพ์บางอย่างเช่น<function baz at 0x7f32bc363c10>ในขณะที่print(baz())พิมพ์baz
John Cole

223

อธิบาย __all__ ใน Python ได้ไหม

ฉันเห็นตัวแปรที่__all__ตั้งไว้ใน__init__.pyไฟล์ที่แตกต่างกัน

สิ่งนี้ทำอะไร

อะไร__all__ทำอย่างไร

มันประกาศชื่อ "สาธารณะ" ความหมายจากโมดูล หากมีชื่ออยู่__all__ผู้ใช้จะถูกคาดหวังให้ใช้และพวกเขาสามารถคาดหวังว่ามันจะไม่เปลี่ยนแปลง

นอกจากนี้ยังจะมีผลกับโปรแกรม:

import *

__all__ในโมดูลเช่นmodule.py:

__all__ = ['foo', 'Bar']

หมายความว่าเมื่อคุณimport *จากโมดูลเฉพาะชื่อเหล่านั้นใน__all__การนำเข้า:

from module import *               # imports foo and Bar

เครื่องมือเอกสาร

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

__init__.py ทำให้ไดเรกทอรีเป็นแพ็คเกจ Python

จากเอกสาร :

__init__.pyไฟล์จะต้องทำให้งูหลามรักษาไดเรกทอรีที่เป็นแพคเกจที่มี; สิ่งนี้ถูกทำขึ้นเพื่อป้องกันไดเรกทอรีที่มีชื่อสามัญเช่นสตริงจากการซ่อนโมดูลที่ถูกต้องโดยไม่ตั้งใจซึ่งเกิดขึ้นในภายหลังบนเส้นทางการค้นหาโมดูล

ในกรณีที่ง่ายที่สุด__init__.pyสามารถเป็นไฟล์ที่ว่างเปล่าได้ แต่สามารถรันโค้ดการเริ่มต้นสำหรับแพ็คเกจหรือตั้งค่า__all__ตัวแปรได้

ดังนั้น__init__.pyสามารถประกาศ__all__สำหรับแพคเกจ

การจัดการ API:

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

package
├── __init__.py
├── module_1.py
└── module_2.py

มาสร้างไฟล์เหล่านี้ด้วย Python เพื่อให้คุณติดตามได้ - คุณสามารถวางสิ่งต่อไปนี้ลงใน Python 3 shell:

from pathlib import Path

package = Path('package')
package.mkdir()

(package / '__init__.py').write_text("""
from .module_1 import *
from .module_2 import *
""")

package_module_1 = package / 'module_1.py'
package_module_1.write_text("""
__all__ = ['foo']
imp_detail1 = imp_detail2 = imp_detail3 = None
def foo(): pass
""")

package_module_2 = package / 'module_2.py'
package_module_2.write_text("""
__all__ = ['Bar']
imp_detail1 = imp_detail2 = imp_detail3 = None
class Bar: pass
""")

และตอนนี้คุณได้แสดง API ที่สมบูรณ์ที่บุคคลอื่นสามารถใช้เมื่อพวกเขานำเข้าแพคเกจของคุณเช่น:

import package
package.foo()
package.Bar()

และแพ็คเกจจะไม่มีรายละเอียดการใช้งานอื่น ๆ ทั้งหมดที่คุณใช้เมื่อสร้างโมดูลของคุณที่ยุ่งเหยิงในpackageเนมสเปซ

__all__ ใน __init__.py

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

package
├── __init__.py
├── module_1
│   ├── foo_implementation.py
│   └── __init__.py
└── module_2
    ├── Bar_implementation.py
    └── __init__.py

ก่อนอื่นให้สร้างไดเร็กทอรีย่อยด้วยชื่อเดียวกับโมดูล:

subpackage_1 = package / 'module_1'
subpackage_1.mkdir()
subpackage_2 = package / 'module_2'
subpackage_2.mkdir()

ย้ายการใช้งาน:

package_module_1.rename(subpackage_1 / 'foo_implementation.py')
package_module_2.rename(subpackage_2 / 'Bar_implementation.py')

สร้าง__init__.pys สำหรับแพ็คเกจย่อยที่ประกาศ__all__สำหรับแต่ละ:

(subpackage_1 / '__init__.py').write_text("""
from .foo_implementation import *
__all__ = ['foo']
""")
(subpackage_2 / '__init__.py').write_text("""
from .Bar_implementation import *
__all__ = ['Bar']
""")

และตอนนี้คุณยังมี API ที่กำหนดไว้ในระดับแพ็คเกจ:

>>> import package
>>> package.foo()
>>> package.Bar()
<package.module_2.Bar_implementation.Bar object at 0x7f0c2349d210>

และคุณสามารถเพิ่มสิ่งต่าง ๆ ลงใน API ของคุณที่คุณสามารถจัดการได้ที่ระดับแพ็คเกจย่อยแทนระดับโมดูลของแพ็คเกจย่อย หากคุณต้องการเพิ่มชื่อใหม่ให้กับ API คุณเพียงอัปเดต__init__.pyเช่นใน module_2:

from .Bar_implementation import *
from .Baz_implementation import *
__all__ = ['Bar', 'Baz']

และหากคุณไม่พร้อมที่จะเผยแพร่Bazใน API ระดับบนสุดในระดับสูงสุดของ__init__.pyคุณคุณสามารถมี:

from .module_1 import *       # also constrained by __all__'s
from .module_2 import *       # in the __init__.py's
__all__ = ['foo', 'Bar']     # further constraining the names advertised

และหากผู้ใช้ของคุณทราบถึงความพร้อมของBazพวกเขาก็สามารถใช้งานได้:

import package
package.Baz()

แต่ถ้าพวกเขาไม่รู้เกี่ยวกับมันเครื่องมืออื่น ๆ (เช่นpydoc ) จะไม่แจ้งให้ทราบ

คุณสามารถเปลี่ยนแปลงได้ในภายหลังว่าเมื่อBazพร้อมสำหรับเวลาที่ดี

from .module_1 import *
from .module_2 import *
__all__ = ['foo', 'Bar', 'Baz']

คำนำหน้า_กับ__all__:

_โดยค่าเริ่มต้นงูใหญ่จะส่งออกชื่อทั้งหมดที่ไม่ได้เริ่มต้นด้วย แน่นอนคุณสามารถพึ่งพากลไกนี้ แพคเกจบางอย่างใน Python standard library จริง ๆแล้วพึ่งพาสิ่งนี้ แต่เมื่อต้องการทำเช่นนั้นพวกมันแทน alias import ของพวกเขาตัวอย่างเช่นในctypes/__init__.py:

import os as _os, sys as _sys

การใช้_แบบแผนอาจดูสง่างามกว่าเพราะจะลบความซ้ำซ้อนของการตั้งชื่ออีกครั้ง แต่มันเพิ่มความซ้ำซ้อนสำหรับการนำเข้า (ถ้าคุณมีจำนวนมาก) และมันง่ายที่จะลืมที่จะทำอย่างสม่ำเสมอ - และสิ่งสุดท้ายที่คุณต้องการคือการสนับสนุนสิ่งที่คุณตั้งใจจะมีเพียงรายละเอียดการใช้งานอย่างไม่มีกำหนด เพราะคุณลืมคำนำหน้า_เมื่อตั้งชื่อฟังก์ชั่น

ฉันเขียน__all__วงจรการพัฒนาของตัวเองแต่เนิ่นๆสำหรับโมดูลเพื่อให้ผู้อื่นที่อาจใช้รหัสของฉันรู้ว่าควรใช้อะไรและไม่ควรใช้

แพ็คเกจส่วนใหญ่ในไลบรารีมาตรฐานก็ใช้__all__เช่นกัน

เมื่อหลีกเลี่ยง__all__ทำให้รู้สึก

มันเหมาะสมที่จะยึดติดกับ_คำนำหน้าแทน__all__เมื่อ:

  • คุณยังอยู่ในโหมดเริ่มต้นพัฒนาและไม่มีผู้ใช้และกำลังปรับแต่ง API ของคุณอย่างต่อเนื่อง
  • บางทีคุณอาจมีผู้ใช้ แต่คุณมี unittests ที่ครอบคลุม API และคุณยังคงเพิ่มไปยัง API และปรับปรุงในการพัฒนา

exportมัณฑนากร

ข้อเสียของการใช้__all__คือคุณต้องเขียนชื่อของฟังก์ชั่นและคลาสที่จะถูกส่งออกสองครั้ง - และข้อมูลจะถูกเก็บแยกต่างหากจากคำจำกัดความ เราสามารถใช้มัณฑนากรเพื่อแก้ปัญหานี้

ฉันมีความคิดสำหรับมัณฑนากรส่งออกจากการพูดคุยของ David Beazley บนบรรจุภัณฑ์ การใช้งานนี้ดูเหมือนจะทำงานได้ดีในผู้นำเข้าดั้งเดิมของ CPython หากคุณมีเบ็ดหรือระบบการนำเข้าพิเศษฉันไม่รับประกัน แต่ถ้าคุณนำมาใช้มันเป็นเรื่องที่ค่อนข้างง่ายที่จะถอยออก - คุณจะต้องเพิ่มชื่อกลับเข้าไปใน__all__

ดังนั้นในตัวอย่างเช่นยูทิลิตี้ไลบรารี่คุณจะต้องกำหนดมัณฑนากร:

import sys

def export(fn):
    mod = sys.modules[fn.__module__]
    if hasattr(mod, '__all__'):
        mod.__all__.append(fn.__name__)
    else:
        mod.__all__ = [fn.__name__]
    return fn

จากนั้นคุณจะกำหนดที่__all__คุณทำสิ่งนี้:

$ cat > main.py
from lib import export
__all__ = [] # optional - we create a list if __all__ is not there.

@export
def foo(): pass

@export
def bar():
    'bar'

def main():
    print('main')

if __name__ == '__main__':
    main()

และใช้งานได้ดีไม่ว่าจะเรียกใช้เป็นหลักหรือนำเข้าโดยฟังก์ชันอื่น

$ cat > run.py
import main
main.main()

$ python run.py
main

และการจัดสรร API ด้วยimport *ก็จะใช้ได้เช่นกัน:

$ cat > run.py
from main import *
foo()
bar()
main() # expected to error here, not exported

$ python run.py
Traceback (most recent call last):
  File "run.py", line 4, in <module>
    main() # expected to error here, not exported
NameError: name 'main' is not defined

1
การอ้างอิงโยง: ฉันพูดถึงมัณฑนากรของคุณในCW นี้ตอบคำถามของวิธีเขียน@exportมัณฑนากร
MvG

13
นี่เป็นคำตอบที่เป็นประโยชน์มากที่สุดที่ฉันเคยเห็นเกี่ยวกับการช่วยให้นักพัฒนางูใหญ่ตัวใหม่เข้าใจกระบวนการนำเข้าโมดูล / แพ็คเกจด้วย__init__.pyและการใช้งาน__all__
Brett Reinhard

สิ่งนี้ช่วยฉันได้มาก ปัญหาของฉันคือสรรพสิ่งที่ฉันต้องการนำเข้าเป็นไฟล์ที่สร้างขึ้นโดยมี cruft มากมายในสัญลักษณ์ของพวกเขาที่ฉันต้องการดึงออกโดยไม่ต้องตรวจสอบให้__all__ถูกต้องด้วยตนเอง
Mike C

@MikeC บางทีคุณควรสร้าง__all__ด้วย - แต่ฉันจะบอกว่าคุณมี API ที่ไม่เสถียร ... นี่เป็นสิ่งที่จะต้องมีการทดสอบการยอมรับที่ครอบคลุม
Aaron Hall

@AaronHall "พวกเขาจะไม่มีชื่ออื่นทั้งหมด ... ที่ทำให้แพ็คเกจเนมสเปซแน่น" แต่พวกเขาก็จะมีชื่อmodule_1และmodule_2; มันมีการตกลงที่จะรวมอย่างชัดเจนdel module_1ใน__init__.py? ฉันผิดที่คิดว่าสิ่งนี้มีค่าหรือไม่
Mike C

176

ฉันแค่เพิ่มสิ่งนี้เพื่อความแม่นยำ:

คำตอบอื่น ๆ ทั้งหมดอ้างอิงถึงโมดูล คำถามเดิมกล่าวถึง explicitely __all__ใน__init__.pyไฟล์ดังนั้นนี้เป็นเรื่องเกี่ยวกับงูหลามแพคเกจ

โดยทั่วไป__all__จะเข้ามาเล่นเมื่อมีการใช้from xxx import *ชุดตัวเลือกของimportคำสั่ง สิ่งนี้ใช้กับแพ็คเกจรวมถึงโมดูล

อธิบายพฤติกรรมของโมดูลในคำตอบอื่น ๆ พฤติกรรมที่แน่นอนสำหรับแพคเกจอธิบายไว้ที่นี่ในรายละเอียด

กล่าวโดยย่อ__all__ในระดับแพ็คเกจจะทำสิ่งเดียวกันกับโมดูลยกเว้นจะเกี่ยวข้องกับโมดูลภายในแพ็คเกจ (ตรงกันข้ามกับการระบุชื่อภายในโมดูล ) ดังนั้น__all__ระบุโมดูลทั้งหมดนั้นจะต้องถูกโหลดและนำเข้ามาใน namespace from package import *ปัจจุบันเมื่อเราใช้

ความแตกต่างใหญ่คือเมื่อคุณละเว้นการประกาศ__all__ในแพคเกจ__init__.pyคำสั่งfrom package import *จะไม่นำเข้าอะไรเลย (มีข้อยกเว้นอธิบายไว้ในเอกสารประกอบให้ดูลิงค์ด้านบน)

ในทางกลับกันหากคุณไม่ใช้__all__งานโมดูล "นำเข้าที่ติดดาว" จะนำเข้าชื่อทั้งหมด (ไม่เริ่มต้นด้วยขีดล่าง) ที่กำหนดไว้ในโมดูล


29
from package import *จะยังคงนำเข้าทุกอย่างที่กำหนดไว้ในแม้ว่าจะไม่มี__init__.py allความแตกต่างที่สำคัญคือถ้าไม่มี__all__มันจะไม่นำเข้าโมดูลใด ๆ ที่กำหนดไว้ในไดเรกทอรีของแพ็คเกจโดยอัตโนมัติ
Nikratio

เมื่อทั้งหมดมี [foo, bar] และในไฟล์ test.py ถ้าเราใช้: จาก package import * ดังนั้น foo และ bar จะได้รับการนำเข้าในเนมสเปซในท้องถิ่นของ test.py หรือใน namespace ของ foo และ bar เองหรือไม่
ตัวแปร

87

นอกจากนี้ยังเปลี่ยนแปลงสิ่งที่ pydoc จะแสดง:

module1.py

a = "A"
b = "B"
c = "C"

module2.py

__all__ = ['a', 'b']

a = "A"
b = "B"
c = "C"

$ pydoc module1

ช่วยในโมดูล module1:

ชื่อ
    module1

ไฟล์
    module1.py

DATA 
    a = 'A'
     b = 'B'
     c = 'C'

$ pydoc module2

ช่วยในโมดูล module2:

ชื่อ
    module2

ไฟล์
    module2.py

DATA 
    __all__ = ['a', 'b']
     a = 'A'
     b = 'B'

ฉันประกาศ__all__ในโมดูลทั้งหมดของฉันรวมถึงเน้นรายละเอียดภายในสิ่งเหล่านี้ช่วยได้มากเมื่อใช้สิ่งที่คุณไม่เคยใช้มาก่อนในเซสชันล่ามแบบสด


54

__all__ปรับแต่ง*ในfrom <module> import *

__all__ปรับแต่ง*ในfrom <package> import *


โมดูลเป็น.pyไฟล์หมายความว่าจะต้องนำเข้า

แพคเกจเป็นไดเรกทอรีกับเป็น__init__.pyไฟล์ แพคเกจมักจะมีโมดูล


โมดูล

""" cheese.py - an example module """

__all__ = ['swiss', 'cheddar']

swiss = 4.99
cheddar = 3.99
gouda = 10.99

__all__ช่วยให้มนุษย์รู้ว่าคุณสมบัติ "สาธารณะ" ของโมดูล [ @AaronHall ] นอกจากนี้pydocยังจดจำพวกมันได้ [ @Longpoke ]

จากการนำเข้าโมดูล *

ดูวิธีการswissและcheddarนำมาไว้ในเนมสเปซท้องถิ่น แต่ไม่ใช่gouda:

>>> from cheese import *
>>> swiss, cheddar
(4.99, 3.99)
>>> gouda
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'gouda' is not defined

หากไม่มี__all__สัญลักษณ์ใด ๆ (ที่ไม่ได้ขึ้นต้นด้วยเครื่องหมายขีดเส้นใต้) จะสามารถใช้ได้


การนำเข้าที่ไม่มี*จะไม่ได้รับผลกระทบจาก__all__


โมดูลนำเข้า

>>> import cheese
>>> cheese.swiss, cheese.cheddar, cheese.gouda
(4.99, 3.99, 10.99)

จากชื่อการนำเข้าโมดูล

>>> from cheese import swiss, cheddar, gouda
>>> swiss, cheddar, gouda
(4.99, 3.99, 10.99)

โมดูลนำเข้าเป็นชื่อท้องถิ่น

>>> import cheese as ch
>>> ch.swiss, ch.cheddar, ch.gouda
(4.99, 3.99, 10.99)

แพ็คเกจ

ใน__init__.pyไฟล์ของแพ็คเกจ __all__เป็นรายการสตริงที่มีชื่อของโมดูลสาธารณะหรือวัตถุอื่น ๆ คุณลักษณะเหล่านั้นพร้อมใช้งานเพื่อนำเข้าสัญลักษณ์แทน เช่นเดียวกับโมดูล__all__กำหนด*เมื่อสัญลักษณ์แทนการนำเข้าจากแพคเกจ [ @MartinStettner ]

นี่เป็นข้อความที่ตัดตอนมาจากตัวเชื่อมต่อ Python MySQL __init__.py :

__all__ = [
    'MySQLConnection', 'Connect', 'custom_error_exception',

    # Some useful constants
    'FieldType', 'FieldFlag', 'ClientFlag', 'CharacterSet', 'RefreshOption',
    'HAVE_CEXT',

    # Error handling
    'Error', 'Warning',

    ...etc...

    ]

กรณีเริ่มต้นเครื่องหมายดอกจันที่ไม่มี__all__สำหรับแพ็คเกจมีความซับซ้อนเนื่องจากพฤติกรรมที่เห็นได้ชัดอาจมีราคาแพง: การใช้ระบบไฟล์เพื่อค้นหาโมดูลทั้งหมดในแพ็คเกจ ในการอ่านเอกสารของฉัน__init__.pyมีการนำเข้าเฉพาะวัตถุที่กำหนดไว้เท่านั้น:

หาก__all__ไม่ได้กำหนดไว้คำสั่งfrom sound.effects import *จะไม่นำเข้า submodules ทั้งหมดจากแพ็คเกจsound.effectsไปยัง namespace ปัจจุบัน มันช่วยให้มั่นใจได้ว่าแพคเกจsound.effectsนั้นได้ถูกนำเข้าแล้ว (อาจเรียกใช้รหัสการเริ่มต้นใด ๆ ใน__init__.py) จากนั้นจึงนำเข้าชื่อใด ๆ ซึ่งรวมถึงชื่อใด ๆ ที่กำหนดไว้ (และ submodules โหลดอย่างชัดเจน) __init__.pyโดย นอกจากนี้ยังรวมถึง submodules ใด ๆ ของแพคเกจที่โหลดอย่างชัดเจนโดยงบการนำเข้าก่อนหน้านี้


สัญลักษณ์แทนการนำเข้า ... ควรหลีกเลี่ยงเนื่องจากผู้อ่าน [สับสน] และเครื่องมืออัตโนมัติจำนวนมาก

[ PEP 8 , @ToolmakerSteve]


2
ผมชอบคำตอบนี้ แต่ฉันขาดข้อมูลเกี่ยวกับสิ่งที่พฤติกรรมเริ่มต้นสำหรับการfrom <package> import *ได้โดยไม่ต้อง__all__อยู่ใน__init__.pyที่ไม่ได้นำเข้าใด ๆ ของโมดูล
radzak

ขอบคุณ @Jatimir ฉันชี้แจงอย่างดีที่สุดเท่าที่จะทำได้โดยไม่ต้องทำการทดลอง ฉันเกือบจะอยากจะบอกว่ากรณีนี้ (ดอกจันโดยไม่ต้องทั้งหมดสำหรับแพคเกจ) มีลักษณะการทำงานที่เหมือนกันเช่นถ้า__init__.pyเป็นโมดูล แต่ฉันไม่แน่ใจว่าถูกต้องหรือโดยเฉพาะอย่างยิ่งถ้าวัตถุที่ขีดเส้นใต้คำนำหน้าถูกยกเว้น นอกจากนี้ฉันแยกส่วนต่าง ๆ อย่างชัดเจนเกี่ยวกับโมดูลและแพ็คเกจ ความคิดของคุณ?
Bob Stein

49

จาก(อย่างไม่เป็นทางการ) Python อ้างอิง Wiki :

ชื่อสาธารณะที่กำหนดโดยโมดูลจะถูกกำหนดโดยการตรวจสอบ namespace ของโมดูลสำหรับตัวแปรชื่อ__all__; หากกำหนดไว้จะต้องเป็นลำดับของสตริงที่กำหนดชื่อหรือนำเข้าโดยโมดูลนั้น ชื่อที่ให้ไว้เป็นชื่อ__all__สาธารณะทั้งหมดและจำเป็นต้องมีอยู่จริง หาก__all__ไม่ได้กำหนดไว้ชุดของชื่อสาธารณะจะรวมชื่อทั้งหมดที่พบในเนมสเปซของโมดูลซึ่งไม่ได้ขึ้นต้นด้วยอักขระขีดล่าง ("_") __all__ควรมี API สาธารณะทั้งหมด มีวัตถุประสงค์เพื่อหลีกเลี่ยงการส่งออกรายการที่ไม่ได้เป็นส่วนหนึ่งของ API โดยไม่ตั้งใจ (เช่นโมดูลห้องสมุดที่นำเข้าและใช้ภายในโมดูล)


ลิงค์ที่ระบุไว้นั้นตาย แต่พบข้อความคำต่อคำในvdocuments.net/ … & ที่นี่: dokumen.tips/documents/reference-567bab8d6118a.html
JayRizzo

8

__all__ใช้เพื่อทำเอกสาร API สาธารณะของโมดูล Python แม้ว่ามันจะเป็นตัวเลือก__all__ควรใช้

นี่คือข้อความที่ตัดตอนมาที่เกี่ยวข้องจากการอ้างอิงภาษา Python :

ชื่อสาธารณะที่กำหนดโดยโมดูลจะถูกกำหนดโดยการตรวจสอบ namespace ของโมดูลสำหรับตัวแปรชื่อ__all__; หากกำหนดไว้จะต้องเป็นลำดับของสตริงที่กำหนดชื่อหรือนำเข้าโดยโมดูลนั้น ชื่อที่ให้ไว้เป็นชื่อ__all__สาธารณะทั้งหมดและจำเป็นต้องมีอยู่จริง หาก__all__ไม่ได้กำหนดไว้ชุดของชื่อสาธารณะจะรวมชื่อทั้งหมดที่พบในเนมสเปซของโมดูลซึ่งไม่ได้ขึ้นต้นด้วยอักขระขีดล่าง ('_') __all__ควรมี API สาธารณะทั้งหมด มีวัตถุประสงค์เพื่อหลีกเลี่ยงการส่งออกรายการที่ไม่ได้เป็นส่วนหนึ่งของ API โดยไม่ตั้งใจ (เช่นโมดูลห้องสมุดที่นำเข้าและใช้ภายในโมดูล)

PEP 8ใช้ถ้อยคำที่คล้ายกันแม้ว่าจะชัดเจนว่าชื่อที่นำเข้านั้นไม่ได้เป็นส่วนหนึ่งของ API สาธารณะเมื่อ__all__ไม่อยู่:

เพื่อรองรับการวิปัสสนาที่ดีขึ้นโมดูลควรประกาศชื่ออย่างชัดเจนใน API สาธารณะโดยใช้__all__แอตทริบิวต์ การตั้งค่า__all__เป็นรายการว่างบ่งชี้ว่าโมดูลไม่มี API สาธารณะ

[ ... ]

ชื่อที่นำเข้าควรได้รับการพิจารณารายละเอียดการใช้งาน โมดูลอื่น ๆ จะต้องไม่พึ่งพาการเข้าถึงโดยอ้อมกับชื่อที่นำเข้าดังกล่าวเว้นแต่ว่าจะเป็นส่วนที่มีการบันทึกไว้อย่างชัดเจนของ API ของโมดูลที่มีเช่นos.pathหรือ__init__โมดูลของแพคเกจที่เปิดเผยการทำงานจาก submodules

นอกจากนี้ตามที่ระบุไว้ในคำตอบอื่น ๆ__all__จะใช้ในการเปิดใช้งานสัญลักษณ์แทนการนำเข้าสำหรับแพ็คเกจ :

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


8

คำตอบสั้น ๆ

__all__ส่งผลกระทบต่อfrom <module> import *งบ

คำตอบที่ยาว

ลองพิจารณาตัวอย่างนี้:

foo
├── bar.py
└── __init__.py

ในfoo/__init__.py:

  • (โดยปริยาย) ถ้าเราไม่กำหนด__all__แล้วจะนำเข้าชื่อที่กำหนดไว้ในfrom foo import *foo/__init__.py

  • (Explicit) ถ้าเรากำหนด__all__ = []แล้วfrom foo import *จะนำเข้าอะไร

  • (Explicit) ถ้าเรากำหนด__all__ = [ <name1>, ... ]แล้วfrom foo import *จะนำเข้าชื่อเหล่านั้น

_ทราบว่าในกรณีนัยหลามจะไม่นำเข้าชื่อเริ่มต้นด้วย __all__อย่างไรก็ตามคุณสามารถบังคับให้นำเข้าโดยใช้ชื่อดังกล่าว

คุณสามารถดูเอกสารหลามที่นี่


5

__all__ส่งผลกระทบต่อวิธีการfrom foo import *ทำงาน

รหัสที่อยู่ภายในร่างกายโมดูล (แต่ไม่ใช่ในร่างกายของฟังก์ชั่นหรือคลาส) อาจใช้เครื่องหมายดอกจัน ( *) ในfromคำสั่ง:

from foo import *

*ขอให้ทุกแบบโมดูลfoo(ยกเว้นผู้ที่มีจุดเริ่มต้นขีด) จะผูกพันเป็นตัวแปรระดับโลกในโมดูลนำเข้า เมื่อfooมีแอททริ__all__บิวค่าของแอททริบิวจะเป็นรายการของชื่อที่ผูกพันกับfromคำสั่งประเภทนี้

ถ้าfooเป็นแพ็กเกจและ__init__.pyนิยามรายการที่มีชื่อ__all__จะถูกใช้เป็นรายการของชื่อ submodule ที่ควรอิมพอร์ตเมื่อfrom foo import *พบ หาก__all__ไม่ได้กำหนดไว้คำสั่งจะfrom foo import *อิมพอร์ตชื่อที่กำหนดไว้ในแพ็คเกจ ซึ่งรวมถึงชื่อใด ๆ ที่กำหนดไว้ (และ submodules โหลดอย่างชัดเจน) __init__.pyโดย

โปรดทราบว่า__all__ไม่จำเป็นต้องเป็นรายการ ตามเอกสารในimportคำสั่งถ้ากำหนด__all__จะต้องเป็นลำดับของสตริงซึ่งเป็นชื่อที่กำหนดหรือนำเข้าโดยโมดูล ดังนั้นคุณอาจใช้ tuple เพื่อบันทึกหน่วยความจำและรอบการทำงานของ CPU อย่าลืมเครื่องหมายจุลภาคในกรณีที่โมดูลกำหนดชื่อสาธารณะเดียว:

__all__ = ('some_name',)

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


1

สิ่งนี้กำหนดไว้ใน PEP8 ที่นี่ :

ชื่อตัวแปรส่วนกลาง

(หวังว่าตัวแปรเหล่านี้มีไว้สำหรับใช้ภายในโมดูลเดียวเท่านั้น) การประชุมมีความคล้ายคลึงกับฟังก์ชั่น

โมดูลที่ออกแบบมาเพื่อใช้งานผ่านfrom M import *ควรใช้__all__กลไกเพื่อป้องกันการส่งออก globals หรือใช้แบบแผนเก่าของ prefixing globals ดังกล่าวด้วยการขีดเส้นใต้ (ซึ่งคุณอาจต้องการทำเพื่อระบุว่า globals เหล่านี้เป็น

PEP8 จัดเตรียมข้อตกลงการเข้ารหัสสำหรับโค้ด Python ซึ่งประกอบด้วยไลบรารีมาตรฐานในการแจกแจง Python หลัก ยิ่งคุณติดตามสิ่งนี้มากเท่าไหร่คุณก็ยิ่งใกล้ชิดกับเจตนาดั้งเดิมมากขึ้นเท่านั้น

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