ควรนำเข้าข้อความสั่งที่ด้านบนของโมดูลเสมอหรือไม่


403

PEP 08ระบุ:

การนำเข้าจะถูกวางไว้ที่ด้านบนของไฟล์เสมอหลังจากที่โมดูลข้อคิดเห็นและเอกสารใด ๆ และก่อนโมดูลกลมและค่าคงที่

อย่างไรก็ตามหากคลาส / วิธี / ฟังก์ชั่นที่ฉันนำเข้าใช้เฉพาะในกรณีที่หายากแน่นอนว่ามันมีประสิทธิภาพมากกว่าในการนำเข้าเมื่อจำเป็น?

นี่ไม่ใช่:

class SomeClass(object):

    def not_often_called(self)
        from datetime import datetime
        self.datetime = datetime.now()

มีประสิทธิภาพมากกว่านี้ไหม?

from datetime import datetime

class SomeClass(object):

    def not_often_called(self)
        self.datetime = datetime.now()

คำตอบ:


283

การนำเข้าโมดูลค่อนข้างเร็ว แต่ไม่ทันที ซึ่งหมายความว่า:

  • การนำเข้าที่ด้านบนของโมดูลนั้นดีเพราะมันมีค่าใช้จ่ายเล็กน้อยที่จ่ายเพียงครั้งเดียว
  • การนำเข้าภายในฟังก์ชันจะทำให้การเรียกใช้ฟังก์ชันนั้นใช้เวลานานขึ้น

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


เหตุผลที่ดีที่สุดที่ฉันเคยเห็นเพื่อดำเนินการนำเข้าขี้เกียจคือ:

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

17
จอห์นนี่เป็นคำถามเชิงทฤษฎีอย่างสมบูรณ์ดังนั้นฉันจึงไม่มีรหัสไปยังโปรไฟล์ ในอดีตที่ผ่านมาฉันเคยติดตาม PEP มาตลอด แต่ฉันเขียนโค้ดบางส่วนในวันนี้ซึ่งทำให้ฉันสงสัยว่านี่เป็นสิ่งที่ถูกต้องหรือไม่ ขอบคุณสำหรับความช่วยเหลือของคุณ.
Adam J. Forster

43
> การใส่การนำเข้าภายในฟังก์ชั่นจะทำให้การเรียกใช้ฟังก์ชันนั้นใช้เวลานานขึ้น ที่จริงฉันคิดว่าค่าใช้จ่ายนี้จ่ายเพียงครั้งเดียว ฉันอ่านแล้วว่า Python เก็บโมดูลที่นำเข้าไว้เพื่อให้มีค่าใช้จ่ายเพียงเล็กน้อยในการนำเข้าอีกครั้ง
moltenform

24
@halfhourhacks หลามจะไม่นำเข้าโมดูล แต่ก็ยังคงมีการดำเนินการไม่กี่คำแนะนำเพียงเพื่อดูว่าโมดูลที่มีอยู่ / อยู่ใน sys.modules / ฯลฯ
จอห์น Millikin

24
-1 การนำเข้าในฟังก์ชั่นไม่จำเป็นต้องทำให้มันใช้เวลานาน โปรดดูคำตอบของฉันในคำถามอื่น
aaronasterling

4
กรณีใช้งานอย่างเดียวคือหลีกเลี่ยงการนำเข้าแบบวงกลม (โดยปกติจะไม่สมเหตุสมผล แต่บางครั้งก็เหมาะสม) บางครั้งคลาส A ในโมดูล m1 เรียกใช้เมธอดบนคลาส B ในโมดูล m2 ซึ่งสร้างอินสแตนซ์อื่นของคลาส A หากเมธอดในคลาส B ที่สร้างอินสแตนซ์ของคลาส A นั้นมีการนำเข้าจะทำงานเมื่อดำเนินการฟังก์ชันที่สร้างอินสแตนซ์เท่านั้น หลีกเลี่ยงการนำเข้าแบบวงกลม
Sam Svenbjorgchristiensensen

80

การวางคำสั่งการนำเข้าไว้ในฟังก์ชั่นสามารถป้องกันการขึ้นต่อกันแบบวงกลม ตัวอย่างเช่นหากคุณมี 2 โมดูลคือ X.py และ Y.py และทั้งคู่จำเป็นต้องนำเข้าซึ่งจะทำให้การพึ่งพาแบบวงกลมเมื่อคุณนำเข้าหนึ่งในโมดูลที่ก่อให้เกิดการวนซ้ำไม่สิ้นสุด หากคุณย้ายคำสั่งการนำเข้าในโมดูลใดโมดูลหนึ่งจะไม่พยายามนำเข้าโมดูลอื่นจนกว่าจะเรียกใช้ฟังก์ชันและโมดูลนั้นจะถูกนำเข้าแล้วจึงไม่มีการวนซ้ำไม่สิ้นสุด อ่านเพิ่มเติมได้ที่นี่ - effbot.org/zone/import-confusion.htm


3
ใช่ แต่ใคร ๆ ก็สามารถเข้าไปสู่ขุมนรกได้
eigenein

8
หากสองโมดูลจำเป็นต้องนำเข้าซึ่งกันและกันมีบางอย่างผิดปกติกับรหัส
แอนนา

การเขียนโปรแกรมเชิงวัตถุมักทำให้ฉันมีการพึ่งพาแบบวงกลม คลาสอ็อบเจ็กต์ที่สำคัญอาจถูกอิมพอร์ตเข้าในหลายโมดูล เพื่อให้วัตถุนี้ทำงานของตัวเองมันอาจจำเป็นต้องเข้าถึงโมดูลเหล่านั้นอย่างน้อยหนึ่งโมดูล มีวิธีการหลีกเลี่ยงเช่นการส่งข้อมูลไปยังวัตถุผ่านฟังก์ชั่น args เพื่อให้สามารถเข้าถึงโมดูลอื่นได้ แต่มีบางครั้งที่การทำเช่นนี้ทำให้รู้สึกว่า OOP ตอบโต้ได้ง่ายมาก (โลกภายนอกไม่จำเป็นต้องรู้ว่าการทำงานในหน้าที่นั้นสำเร็จได้อย่างไร)
Robert

4
เมื่อ X ต้องการ Y และ Y ต้องการ X พวกเขาทั้งสองส่วนของความคิดเดียวกัน (เช่นควรกำหนดร่วมกัน) หรือมีนามธรรมที่ขาดหายไป
GLRoman

59

ฉันได้นำแนวปฏิบัติในการวางการนำเข้าทั้งหมดในฟังก์ชั่นที่ใช้พวกเขาแทนที่จะอยู่ด้านบนของโมดูล

ประโยชน์ที่ฉันได้รับคือความสามารถในการปรับสภาพให้น่าเชื่อถือยิ่งขึ้น เมื่อฉันย้ายฟังก์ชั่นจากโมดูลหนึ่งไปยังอีกโมดูลฉันรู้ว่าฟังก์ชั่นจะยังคงทำงานกับมรดกทั้งหมดของการทดสอบเหมือนเดิม หากฉันมีการนำเข้าของฉันที่ด้านบนของโมดูลเมื่อฉันย้ายฟังก์ชั่นฉันพบว่าฉันใช้เวลาส่วนใหญ่ในการนำเข้าโมดูลใหม่ที่สมบูรณ์และน้อยที่สุด refactoring IDE อาจทำให้สิ่งนี้ไม่เกี่ยวข้อง

มีโทษความเร็วตามที่กล่าวไว้ในที่อื่น ฉันวัดสิ่งนี้ในใบสมัครของฉันและพบว่าไม่มีนัยสำคัญต่อวัตถุประสงค์ของฉัน

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

ฉันมักจะนำเข้าจากsysภายในif __name__=='__main__'เช็คแล้วผ่านข้อโต้แย้ง (เช่นsys.argv[1:]) ไปยังmain()ฟังก์ชั่น สิ่งนี้ทำให้ฉันสามารถใช้mainในบริบทที่sysไม่ได้นำเข้า


4
IDEs จำนวนมากทำให้การปรับเปลี่ยนรหัสทำได้ง่ายขึ้นโดยการปรับและนำเข้าโมดูลที่จำเป็นเข้าสู่ไฟล์ของคุณโดยอัตโนมัติ ในกรณีส่วนใหญ่ PyCharm และ Eclipse ได้ทำการตัดสินใจที่ถูกต้องสำหรับฉัน ฉันจะเดิมพันมีวิธีการรับพฤติกรรมเดียวกันใน emacs หรือเป็นกลุ่ม
brent.payne

3
การนำเข้าภายในของคำสั่ง if ใน namespace ส่วนกลางยังคงเป็นการนำเข้าทั่วโลก สิ่งนี้จะพิมพ์อาร์กิวเมนต์ (โดยใช้ Python 3): def main(): print(sys.argv); if True: import sys; main();คุณจะต้องตัดคำif __name__=='__main__'ในฟังก์ชันเพื่อสร้างเนมสเปซใหม่
Darcinon

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

@ algal ข้อเสียคือคนไพ ธ อนหลายคนเกลียดสิ่งนี้เพราะคุณละเมิด pep codex คุณต้องโน้มน้าวใจสมาชิกในทีมของคุณ โทษประสิทธิภาพน้อยที่สุด บางครั้งก็เป็นได้เร็วยิ่งขึ้นดูstackoverflow.com/a/4789963/362951
mit

ฉันพบว่ามีประโยชน์อย่างยิ่งสำหรับการปรับโครงสร้างใหม่เพื่อให้การนำเข้าใกล้กับที่ฉันใช้ ไม่จำเป็นต้องเลื่อนไปที่ด้านบนและด้านหลังอีกต่อไป ฉันใช้ IDEs เช่น pycharm หรือวิงวิงและใช้การรีแฟคเตอร์ของพวกเขา แต่ฉันไม่ต้องการพึ่งพาพวกเขาเสมอไป การย้ายฟังก์ชั่นไปยังโมดูลอื่นกลายเป็นเรื่องง่ายขึ้นด้วยรูปแบบการนำเข้าทางเลือกนี้ซึ่งทำให้ฉันปรับโครงสร้างได้มากขึ้น
mit

39

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

ประการแรกคุณสามารถมีโมดูลที่มีการทดสอบหน่วยของแบบฟอร์ม:

if __name__ == '__main__':
    import foo
    aa = foo.xyz()         # initiate something for the test

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

if [condition]:
    import foo as plugin_api
else:
    import bar as plugin_api
xx = plugin_api.Plugin()
[...]

อาจมีสถานการณ์อื่น ๆ ที่คุณอาจนำเข้าในส่วนอื่น ๆ ในรหัส


14

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

แต่มีเหตุผลอื่นนอกเหนือจากประสิทธิภาพว่าทำไมคุณถึงชอบอีกอันหนึ่ง วิธีการหนึ่งที่ทำให้ผู้ใช้ที่อ่านรหัสนั้นเข้าใจได้ง่ายขึ้นว่าโมดูลนี้มีอะไรบ้าง พวกเขายังมีลักษณะความล้มเหลวที่แตกต่างกันมาก - อย่างแรกจะล้มเหลวในเวลาโหลดถ้าไม่มีโมดูล "datetime" ในขณะที่สองจะไม่ล้มเหลวจนกว่าจะมีการเรียกวิธีการ

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


1
มันไม่ได้เป็นความจริงที่ว่าคนแรกที่ดำเนินการที่ดีกว่า: wiki.python.org/moin/PythonSpeed/...
เจสันเบเกอร์

มันจะทำงานได้ดีขึ้นถ้าวิธีไม่เคยถูกเรียกเพราะการนำเข้าไม่เคยเกิดขึ้น
Curt Hagenlocher

จริง แต่มันทำงานได้แย่กว่านั้นถ้าวิธีนั้นถูกเรียกมากกว่าหนึ่งครั้ง และประโยชน์ที่ได้รับจากการใช้งานที่คุณจะได้รับจากการไม่นำเข้าโมดูลในทันทีนั้นเป็นเรื่องเล็กน้อย ข้อยกเว้นคือถ้าโมดูลมีขนาดใหญ่มากหรือมีฟังก์ชั่นประเภทนี้มากมาย
Jason Baker

ในโลก IronPython การนำเข้าเริ่มต้นมีราคาแพงกว่า CPython;) ตัวอย่าง "การนำเข้าขี้เกียจ" ในลิงก์ของคุณอาจเป็นวิธีแก้ปัญหาทั่วไปโดยรวมที่ดีที่สุด
Curt Hagenlocher

ฉันหวังว่าคุณจะไม่รังเกียจ แต่ฉันได้แก้ไขมันในโพสต์ของคุณ นั่นเป็นข้อมูลที่เป็นประโยชน์ที่จะรู้
Jason Baker

9

Curt ทำให้เป็นจุดที่ดี: รุ่นที่สองนั้นชัดเจนและจะล้มเหลวในเวลาโหลดมากกว่าในภายหลังและโดยไม่คาดคิด

ปกติฉันไม่ต้องกังวลเกี่ยวกับประสิทธิภาพของการโหลดโมดูลเนื่องจากเป็น (ก) ค่อนข้างเร็วและ (b) ส่วนใหญ่เกิดขึ้นเมื่อเริ่มต้นเท่านั้น

หากคุณต้องโหลดโมดูลเฮฟวี่เวทในเวลาที่ไม่คาดคิดมันอาจเหมาะสมกว่าที่จะโหลดโมดูลเหล่านั้นพร้อม__import__ฟังก์ชั่นแบบไดนามิกและตรวจสอบให้แน่ใจว่าได้รับการImportErrorยกเว้นและจัดการกับมันในลักษณะที่สมเหตุสมผล


8

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

ในกรณีส่วนใหญ่คุณต้องการโหลดโมดูลที่ด้านบนของไฟล์ต้นฉบับ สำหรับใครบางคนที่อ่านโค้ดของคุณมันทำให้ง่ายต่อการบอกฟังก์ชั่นหรือวัตถุที่มาจากโมดูลอะไร

เหตุผลหนึ่งที่ดีในการนำเข้าโมดูลที่อื่นในรหัสคือถ้ามันถูกใช้ในคำสั่งการดีบัก

ตัวอย่างเช่น:

do_something_with_x(x)

ฉันสามารถแก้ปัญหานี้ด้วย:

from pprint import pprint
pprint(x)
do_something_with_x(x)

แน่นอนเหตุผลอื่น ๆ ในการนำเข้าโมดูลที่อื่นในรหัสคือถ้าคุณต้องการนำเข้าแบบไดนามิก นี่เป็นเพราะคุณค่อนข้างไม่มีทางเลือก

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


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

6

มันเป็นข้อเสียที่มี แต่โปรแกรมเมอร์เท่านั้นที่สามารถตัดสินใจได้

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

กรณีที่ 2 บันทึกบางเวลาดำเนินการและแฝงด้วยการนำเข้า datetime ก่อนเพื่อให้ not_often_called () จะกลับมาได้รวดเร็วยิ่งขึ้นเมื่อมันถูกเรียกว่าและยังไม่ได้เกิดขึ้นโดยค่าใช้จ่ายในการนำเข้าในทุกสาย

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

โดยส่วนตัวแล้วฉันจะติดตาม PEP ยกเว้นสิ่งต่าง ๆ เช่นการทดสอบหน่วยและฉันไม่ต้องการให้โหลดเสมอเพราะฉันรู้ว่าพวกเขาจะไม่ถูกใช้ยกเว้นรหัสทดสอบ


2
-1 ค่าใช้จ่ายหลักของการนำเข้าจะเกิดขึ้นในครั้งแรกเท่านั้น ค่าใช้จ่ายในการค้นหาโมดูลsys.modulesสามารถชดเชยได้อย่างง่ายดายด้วยการประหยัดเพียงแค่ต้องค้นหาชื่อท้องถิ่นแทนที่จะเป็นชื่อทั่วโลก
aaronasterling

6

นี่คือตัวอย่างที่การนำเข้าทั้งหมดอยู่ด้านบนสุด (นี่เป็นครั้งเดียวที่ฉันต้องทำสิ่งนี้) ฉันต้องการที่จะยุติกระบวนการย่อยในทั้ง Un * x และ Windows

import os
# ...
try:
    kill = os.kill  # will raise AttributeError on Windows
    from signal import SIGTERM
    def terminate(process):
        kill(process.pid, SIGTERM)
except (AttributeError, ImportError):
    try:
        from win32api import TerminateProcess  # use win32api if available
        def terminate(process):
            TerminateProcess(int(process._handle), -1)
    except ImportError:
        def terminate(process):
            raise NotImplementedError  # define a dummy function

(ในการตรวจสอบ: สิ่งที่John Millikinพูด)


6

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

from foo import bar
from baz import qux
# Note: datetime is imported in SomeClass below

4

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

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


4

เพียงทำคำตอบของ Moeให้สมบูรณ์และคำถามเดิม:

เมื่อเราต้องจัดการกับการพึ่งพาอาศัยกันเป็นวงกลมเราสามารถทำ "เทคนิค" บางอย่าง สมมติว่าเรากำลังทำงานกับโมดูลa.pyและb.pyที่มีx()และ b y()ตามลำดับ แล้ว:

  1. เราสามารถย้ายหนึ่งในfrom importsที่ด้านล่างของโมดูล
  2. เราสามารถย้ายหนึ่งfrom importsในฟังก์ชั่นหรือวิธีการที่ต้องการนำเข้าจริง ๆ (นี่อาจเป็นไปไม่ได้เสมอไปเนื่องจากคุณอาจใช้จากหลาย ๆ ที่)
  3. เราสามารถเปลี่ยนหนึ่งในสองรายการfrom importsให้เป็นการนำเข้าที่มีลักษณะดังนี้:import a

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


4

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

ปัญหานี้เกิดขึ้นบ่อยครั้งใน Python API ของ Apache Spark ซึ่งคุณต้องเริ่มต้น SparkContext ก่อนที่จะนำเข้าแพ็คเกจ pyspark หรือโมดูลใด ๆ เป็นการดีที่สุดที่จะวางการนำเข้า pyspark ในขอบเขตที่รับประกัน SparkContext


4

ฉันประหลาดใจที่ไม่เห็นตัวเลขค่าใช้จ่ายจริงสำหรับการตรวจสอบการโหลดซ้ำแล้วที่โพสต์แล้วแม้ว่าจะมีคำอธิบายที่ดีมากมายเกี่ยวกับสิ่งที่คาดหวัง

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

หากคุณนำเข้าภายในฟังก์ชั่นคุณจะต้องกดปุ่มเพื่อโหลดถ้าและเมื่อมีการเรียกใช้ฟังก์ชันใดฟังก์ชันหนึ่งก่อน ดังที่หลายคนชี้ให้เห็นหากสิ่งนั้นไม่เกิดขึ้นเลยคุณสามารถประหยัดเวลาในการโหลดได้ แต่ถ้าฟังก์ชั่น (s) ได้รับการเรียกมากคุณจะซ้ำ แต่ตีมีขนาดเล็กมาก (สำหรับการตรวจสอบว่าได้รับการโหลด; ไม่ได้สำหรับจริงอีกครั้งโหลด) ในทางตรงกันข้าม @aaronasterling ชี้ให้เห็นว่าคุณประหยัดอีกเล็กน้อยเพราะการนำเข้าภายในฟังก์ชั่นทำให้ฟังก์ชั่นใช้การค้นหาตัวแปรเฉพาะที่เร็วขึ้นเล็กน้อยเพื่อระบุชื่อภายหลัง ( http://stackoverflow.com/questions/477096/python-) import-coding-style / 4789963 # 4789963 )

นี่คือผลลัพธ์ของการทดสอบอย่างง่ายที่นำเข้าบางสิ่งจากภายในฟังก์ชั่น เวลาที่รายงาน (ใน Python 2.7.14 ใน 2.3 GHz Intel Core i7) แสดงไว้ด้านล่าง (การโทรสายที่สองที่ใช้มากกว่าการโทรในภายหลังดูเหมือนจะสอดคล้องกัน แต่ฉันไม่รู้ว่าทำไม)

 0 foo:   14429.0924 µs
 1 foo:      63.8962 µs
 2 foo:      10.0136 µs
 3 foo:       7.1526 µs
 4 foo:       7.8678 µs
 0 bar:       9.0599 µs
 1 bar:       6.9141 µs
 2 bar:       7.1526 µs
 3 bar:       7.8678 µs
 4 bar:       7.1526 µs

รหัส:

from __future__ import print_function
from time import time

def foo():
    import collections
    import re
    import string
    import math
    import subprocess
    return

def bar():
    import collections
    import re
    import string
    import math
    import subprocess
    return

t0 = time()
for i in xrange(5):
    foo()
    t1 = time()
    print("    %2d foo: %12.4f \xC2\xB5s" % (i, (t1-t0)*1E6))
    t0 = t1
for i in xrange(5):
    bar()
    t1 = time()
    print("    %2d bar: %12.4f \xC2\xB5s" % (i, (t1-t0)*1E6))
    t0 = t1

การเปลี่ยนแปลงในรันไทม์มีแนวโน้มเนื่องจากการปรับขนาดความถี่ CPU เพื่อตอบสนองต่อการโหลด เป็นการดีกว่าที่จะเริ่มการทดสอบความเร็วกับงานที่ยุ่งเป็นอันดับที่สองเพื่อรับความเร็วสัญญาณนาฬิกาของ CPU เพื่อขยาย
Han-Kwang Nienhuys

3

ฉันไม่ปรารถนาที่จะให้คำตอบที่สมบูรณ์เพราะคนอื่นทำได้ดีมาก ฉันแค่ต้องการพูดถึงกรณีการใช้งานเมื่อฉันพบว่ามีประโยชน์อย่างยิ่งในการนำเข้าโมดูลภายในฟังก์ชั่น แอปพลิเคชันของฉันใช้แพคเกจหลามและโมดูลที่จัดเก็บในบางตำแหน่งเป็นปลั๊กอิน ในระหว่างการเริ่มต้นแอปพลิเคชันแอปพลิเคชันจะเดินผ่านโมดูลทั้งหมดในสถานที่และนำเข้าจากนั้นจะดูภายในโมดูลและหากพบจุดยึดบางอย่างสำหรับปลั๊กอิน (ในกรณีของฉันมันเป็นคลาสย่อยของคลาสฐานที่แน่นอน ID) มันลงทะเบียนพวกเขา จำนวนปลั๊กอินมีขนาดใหญ่ (ตอนนี้มีหลายสิบ แต่อาจเป็นร้อยในอนาคต) และแต่ละปลั๊กอินมีการใช้งานค่อนข้างน้อย การมีการนำเข้าไลบรารีบุคคลที่สามที่ด้านบนสุดของโมดูลปลั๊กอินของฉันเป็นโทษเล็กน้อยระหว่างการเริ่มต้นแอปพลิเคชัน โดยเฉพาะอย่างยิ่งห้องสมุดของบุคคลที่สามบางแห่งที่มีการนำเข้าอย่างหนัก (เช่นการนำเข้าของพล็อตแม้พยายามเชื่อมต่ออินเทอร์เน็ต ด้วยการเพิ่มประสิทธิภาพการนำเข้า (เรียกพวกเขาเฉพาะในฟังก์ชั่นที่ใช้) ในปลั๊กอินที่ฉันจัดการเพื่อลดการเริ่มต้นจาก 10 วินาทีถึงบาง 2 วินาที นั่นเป็นข้อแตกต่างที่สำคัญสำหรับผู้ใช้ของฉัน

ดังนั้นคำตอบของฉันคือไม่ไม่ต้องนำเข้าที่ด้านบนของโมดูลของคุณ


3

เป็นที่น่าสนใจว่าไม่ใช่คำตอบเดียวที่กล่าวถึงการประมวลผลแบบขนานซึ่งอาจจำเป็นต้องมีการนำเข้ามาในฟังก์ชั่นเมื่อรหัสฟังก์ชั่นต่อเนื่องเป็นสิ่งที่ถูกผลักไปรอบ ๆ แกนอื่น ๆ เช่นในกรณีของ ipyparallel


1

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

test.py

X=10
Y=11
Z=12
def add(i):
  i = i + 10

runlocal.py

from test import add, X, Y, Z

    def callme():
      x=X
      y=Y
      z=Z
      ladd=add 
      for i  in range(100000000):
        ladd(i)
        x+y+z

    callme()

run.py

from test import add, X, Y, Z

def callme():
  for i in range(100000000):
    add(i)
    X+Y+Z

callme()

เวลาบน Linux แสดงให้เห็นถึงการเพิ่มขึ้นเล็กน้อย

/usr/bin/time -f "\t%E real,\t%U user,\t%S sys" python run.py 
    0:17.80 real,   17.77 user, 0.01 sys
/tmp/test$ /usr/bin/time -f "\t%E real,\t%U user,\t%S sys" python runlocal.py 
    0:14.23 real,   14.22 user, 0.01 sys

จริงเป็นนาฬิกาแขวน ผู้ใช้คือเวลาในโปรแกรม sys เป็นเวลาสำหรับการโทรของระบบ

https://docs.python.org/3.5/reference/executionmodel.html#resolution-of-names


1

การอ่าน

นอกเหนือจากประสิทธิภาพการเริ่มต้นแล้วยังมีอาร์กิวเมนต์การอ่านที่ต้องทำสำหรับimportคำสั่งการแปล ตัวอย่างเช่นใช้หมายเลขบรรทัดหลาม 1283 ถึง 1296 ในโครงการงูหลามแรกของฉันในปัจจุบัน:

listdata.append(['tk font version', font_version])
listdata.append(['Gtk version', str(Gtk.get_major_version())+"."+
                 str(Gtk.get_minor_version())+"."+
                 str(Gtk.get_micro_version())])

import xml.etree.ElementTree as ET

xmltree = ET.parse('/usr/share/gnome/gnome-version.xml')
xmlroot = xmltree.getroot()
result = []
for child in xmlroot:
    result.append(child.text)
listdata.append(['Gnome version', result[0]+"."+result[1]+"."+
                 result[2]+" "+result[3]])

หากimportคำสั่งนั้นอยู่ด้านบนของไฟล์ฉันจะต้องเลื่อนดูให้ไกลหรือกดHomeเพื่อดูว่าETเกิดอะไรขึ้น จากนั้นฉันจะต้องนำทางกลับไปที่บรรทัด 1283 เพื่ออ่านโค้ดต่อไป

ถึงแม้ว่าimportข้อความจะอยู่ด้านบนสุดของฟังก์ชั่น (หรือคลาส) ที่คนส่วนใหญ่จะวางไว้ก็จะต้องมีการเพจและกลับลงมา

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


0

ฉันต้องการพูดถึง usecase ของฉันคล้ายกับที่กล่าวถึงโดย @John Millikin และ @VK:

ตัวเลือกการนำเข้า

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

ด้วยวิธีนี้ฉันสามารถทำ "restart-and-run-all" โดยไม่ต้องรอการนำเข้าหรือต้องดำเนินการส่วนที่เหลือของเซลล์ต่อเมื่อมันล้มเหลว


0

นี่คือการสนทนาที่น่าสนใจ เช่นเดียวกับคนอื่น ๆ ฉันไม่เคยคิดเรื่องนี้มาก่อนเลย ฉันได้รับมุมในการมีการนำเข้าในฟังก์ชั่นเพราะต้องการใช้ Django ORM ในห้องสมุดของฉัน ฉันต้องโทรหาdjango.setup()ก่อนที่จะอิมพอร์ตคลาสโมเดลของฉันและเนื่องจากนี่คือส่วนบนสุดของไฟล์จึงถูกลากไปยังโค้ดไลบรารีที่ไม่ใช่ Django อย่างสมบูรณ์เนื่องจากการสร้าง IoC injector

ฉันถูกแฮ็ครอบ ๆ และจบลงด้วยการวางdjango.setup()ในคอนสตรัคชันเดี่ยวและการนำเข้าที่เกี่ยวข้องที่ด้านบนของแต่ละวิธีการเรียน ตอนนี้ใช้งานได้ดี แต่ทำให้ฉันไม่สบายใจเพราะการนำเข้าไม่ได้อยู่ด้านบนและฉันก็เริ่มกังวลเกี่ยวกับการนำเข้าที่เพิ่มขึ้นในช่วงเวลาพิเศษ จากนั้นฉันก็มาที่นี่และอ่านด้วยความสนใจอย่างมาก

ฉันมีพื้นหลัง C ++ ที่ยาวและตอนนี้ใช้ Python / Cython สิ่งที่ฉันทำในเรื่องนี้คือทำไมไม่นำเข้าในฟังก์ชั่นเว้นแต่จะทำให้คุณเป็นคอขวดประวัติ มันเหมือนกับการประกาศพื้นที่สำหรับตัวแปรก่อนที่คุณต้องการ ปัญหาคือฉันมีรหัสหลายพันบรรทัดด้วยการนำเข้าทั้งหมดที่ด้านบน! ดังนั้นฉันคิดว่าฉันจะทำต่อจากนี้และเปลี่ยนไฟล์คี่ที่นี่และที่นั่นเมื่อฉันผ่านและมีเวลา

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