ฉันจะยกเลิกการโหลด (โหลด) โมดูลได้อย่างไร


797

ฉันมีเซิร์ฟเวอร์ Python ที่ใช้งานมานานและต้องการที่จะสามารถอัพเกรดบริการได้โดยไม่ต้องรีสตาร์ทเซิร์ฟเวอร์ วิธีที่ดีที่สุดในการทำเช่นนี้คืออะไร?

if foo.py has changed:
    unimport foo  <-- How do I do this?
    import foo
    myfoo = foo.Foo()

53
เคล็ดลับบันทึก: "นำเข้า" ไม่ได้แปลว่า "โหลด" แต่หมายถึง "โหลดถ้ายังไม่โหลดจากนั้นจึงนำเข้าสู่ namespace"
Kos

3
คำถามไม่ควรรวม 'unload' เนื่องจากไม่สามารถทำได้ใน python แต่การโหลดซ้ำเป็นกระบวนทัศน์ที่เป็นที่รู้จักดังคำตอบด้านล่าง
robertmoggach

ฉันมีปัญหาเดียวกันเมื่อใช้โมดูลแบบไดนามิกในแอพ py2exe ในฐานะที่เป็น py2exe เก็บ bytecode ในการโหลดไดเรกทอรี zip ไม่ทำงาน แต่ฉันพบวิธีแก้ปัญหาการทำงานโดยใช้โมดูล import_file ตอนนี้ใบสมัครของฉันทำงานได้ดี
Pritam Pan

2
ถ้าคุณต้องการ "ยกเลิกการโหลด" เนื่องจากพยายามที่จะลบไฟล์. pyc กำลังถูกใช้งานโดยรหัส?
darkgaze

คำตอบ:


789

คุณสามารถโหลดโมดูลใหม่เมื่อนำเข้าแล้วโดยใช้reloadฟังก์ชัน builtin (Python 3.4+ เท่านั้น) :

from importlib import reload  
import foo

while True:
    # Do some things.
    if is_changed(foo):
        foo = reload(foo)

ใน Python 3 reloadถูกย้ายไปที่impโมดูล ใน 3.4 impถูกคัดค้านimportlibและreloadถูกเพิ่มเข้ามาในภายหลัง เมื่อกำหนดเป้าหมาย 3 หรือใหม่กว่าให้อ้างอิงโมดูลที่เหมาะสมเมื่อโทรreloadหรือนำเข้า

ฉันคิดว่านี่คือสิ่งที่คุณต้องการ เว็บเซิร์ฟเวอร์เช่นเซิร์ฟเวอร์การพัฒนาของ Django ใช้สิ่งนี้เพื่อให้คุณสามารถเห็นผลกระทบของการเปลี่ยนแปลงรหัสของคุณโดยไม่ต้องรีสตาร์ทกระบวนการเซิร์ฟเวอร์เอง

อ้างจากเอกสาร:

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

ดังที่คุณจดไว้ในคำถามของคุณคุณจะต้องสร้างFooวัตถุใหม่ถ้าFooชั้นเรียนอยู่ในfooโมดูล


10
จริงที่ Django dev เซิร์ฟเวอร์เตะตัวเองเมื่อคุณเปลี่ยนไฟล์ .. (รีสตาร์ทเซิร์ฟเวอร์ที่ไม่เพียง แต่โหลดโมดูล)
Hasen

25
ฟังก์ชัน "is_changed" นี้มาจากไหน ฉันไม่เห็นเอกสารเกี่ยวกับมันและมันไม่ทำงานในสภาพแวดล้อม Python 3.1.3 ของฉันและไม่ทำงานใน 2.6.4
jedmao

5
ไม่มี cdleary, Django ไม่สามารถใช้รีโหลดได้: pyunit.sourceforge.net/notes/reloading.html
raylu

14
@BartoszKP หากXไม่ใช่โมดูลคุณสามารถimport sys; reload(sys.modules[X.__module__])
drevicko

5
@jedmao @JamesDraper ฉันค่อนข้างมั่นใจว่าis_changedฟังก์ชั่นเป็นเพียงฟังก์ชั่นที่คุณต้องเขียน; มันไม่ใช่ในตัว ตัวอย่างเช่นมันอาจจะเปิดไฟล์ที่สอดคล้องกับโมดูลที่คุณกำลังนำเข้าและ diff กับรุ่นแคชเพื่อดูว่ามันมีการเปลี่ยนแปลง
James Mchugh

252

ใน Python 3.0–3.3 คุณจะใช้: imp.reload(module)

BDFLได้ตอบคำถามนี้

อย่างไรก็ตาม impถูกคัดค้านใน 3.4 ตามimportlib (ขอบคุณ@Stefan! )

ฉันคิดว่าดังนั้นตอนนี้คุณจะใช้importlib.reload(module)แม้ว่าฉันไม่แน่ใจ


23
มือใหม่ที่จริงจังจะขอบคุณที่ได้เรียนรู้เกี่ยวกับความแตกต่างที่สำคัญระหว่าง Python 2 และ 3
Smandoli

24
Imp.reload (imp) ถูกต้องหรือไม่
Loïc Faure-Lacroix

2
@ LoïcFaure-Lacroix ในลักษณะเดียวกันreload(__builtins__)มีผลบังคับใช้ใน 2.x
JBernardo

1
@Tarrasch: เป็นโมดูล Python ที่คุณต้องการจะโหลดซ้ำอีกครั้งตัวอย่างเช่นในคำถาม
Paul D. Waite

3
@ LoïcFaure-Lacroix ใช่แล้วสามารถโหลดซ้ำได้
Devyn Collier Johnson

92

มันอาจเป็นเรื่องยากโดยเฉพาะอย่างยิ่งในการลบโมดูลถ้ามันไม่ได้เป็นงูหลามบริสุทธิ์

นี่คือข้อมูลบางส่วนจาก: ฉันจะลบโมดูลที่นำเข้าได้อย่างไร

คุณสามารถใช้ sys.getrefcount () เพื่อค้นหาจำนวนการอ้างอิงจริง

>>> import sys, empty, os
>>> sys.getrefcount(sys)
9
>>> sys.getrefcount(os)
6
>>> sys.getrefcount(empty)
3

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

>>> del sys.modules["empty"]
>>> del empty

เนื่องจากการอ้างอิงที่สามเป็นสิ่งประดิษฐ์ของฟังก์ชัน getrefcount ()


4
ฉันเพิ่งค้นพบว่าถ้าโมดูลเป็นส่วนหนึ่งของแพ็กเกจคุณต้องลบมันออกด้วย:setattr(package, "empty", None)
u0b34a0f6ae

6
นี่เป็นทางออกที่ถูกต้องโดยเฉพาะถ้าคุณมีแพ็คเกจที่มีโมดูลซ้อนอยู่ reload()โหลดเฉพาะโมดูลที่ใหม่ที่สุดเท่านั้นและสิ่งใดก็ตามที่อยู่ภายในจะไม่ถูกโหลดซ้ำเว้นแต่คุณจะลบออกจาก sys.modules เป็นครั้งแรก
Cerin

73

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

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

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

วิธีเดียวที่จะทำได้ในกรณีนี้คือการแฮ็sys.modulesคซึ่งไม่ได้รับการสนับสนุน คุณจะต้องผ่านและลบแต่ละsys.modulesรายการที่คุณต้องการที่จะโหลดใหม่ในการนำเข้าต่อไปและลบรายการที่มีค่าที่Noneจะจัดการกับปัญหาการใช้งานจะทำอย่างไรกับการแคชล้มเหลวในการนำเข้าญาติ มันไม่ได้ดีมากนัก แต่ตราบใดที่คุณมีชุดอ้างอิงที่สมบูรณ์แบบในตัวเองที่ไม่ทิ้งการอ้างอิงไว้นอก codebase มันสามารถใช้งานได้

อาจเป็นการดีที่สุดที่จะรีสตาร์ทเซิร์ฟเวอร์ :-)


1
ไม่ใช่ dreload โดยเฉพาะสำหรับสถานการณ์นั้นใช่ไหม
Josh

@ จอช: ไม่มันใช้สำหรับโหลดทรีแพ็กเกจและแม้จะใช้งานได้ตราบใดที่แพคเกจไม่มีการอ้างอิงภายนอก / แบบวงกลม
bobince

1
คุณช่วยอธิบายรายละเอียดของชิ้นส่วนที่มีNoneค่าได้ไหมเพราะฉันพบปัญหานี้อย่างแน่นอน: ฉันกำลังลบรายการจากsys.modulesและหลังจากนำเข้าการอ้างอิงที่นำเข้ามาNoneอีกครั้ง
schlamar

@shclamar: ดูstackoverflow.com/questions/1958417/… (และลิงก์จากที่นั่น) สำหรับพื้นหลัง มันไม่ชัดเจนสำหรับฉัน (แม้แต่การดูรหัส import.c) วิธีการที่Noneรายการจัดการเพื่อย้อนกลับผ่านกลไกการนำเข้าเมื่อรายการ 'ของจริง' ถูกลบไปแล้วและฉันไม่สามารถทำให้มันเกิดขึ้นได้ใน 2.7; ในอนาคตแน่นอนว่ามันจะไม่มีปัญหาอีกต่อไปเนื่องจากการนำเข้าญาติโดยนัยหายไป ในระหว่างนี้การลบรายการทั้งหมดที่มีNoneมูลค่าดูเหมือนจะแก้ไขได้
bobince

1
@Eliethesaiyan: คุณหมายถึงreloadฟังก์ชั่นหรือไม่ มันมีอยู่แล้วภายในคุณไม่ต้องนำเข้าห้องสมุดใด ๆ
bobince

63
if 'myModule' in sys.modules:  
    del sys.modules["myModule"]

3
+1 เป้าหมายของฉันคือทำการทดสอบจมูกภายในหลาม หลังจากที่ฉันโหลดโมดูลและเปลี่ยนชื่อฟังก์ชั่นบางอย่างชื่อเก่ายังคงอยู่เมื่อมีการโทรnose.run()ถึงแม้reload(my_module) %run my_module
Peter D

5
หากโมดูลของคุณนำเข้า submodules เป็นของตัวเองคุณอาจต้องลบสิ่งเหล่านั้นด้วย [del(sys.modules[mod] for mod in sys.modules.keys() if mod.startswith('myModule.')]สิ่งที่ชอบ
drevicko

ฉันไม่คิดว่าจะยกเลิกการโหลดโมดูล ใน Python 3.8: import sys; import json; del sys.modules['json']; print(json.dumps([1]))และโมดูล json ยังคงทำงานแม้ว่ามันจะไม่ได้อยู่ใน sys.modules อีกต่อไป
Seperman

60

สำหรับ Python 2 ให้ใช้ฟังก์ชันรีโหลดในตัว () :

reload(module)

สำหรับ Python 2 และ 3.2–3.3 ให้ใช้การโหลดซ้ำจากโมดูล imp :

import imp
imp.reload(module)

แต่imp เลิกใช้แล้วตั้งแต่เวอร์ชัน 3.4 เพื่อใช้กับimportlibดังนั้นใช้:

import importlib
importlib.reload(module)

หรือ

from importlib import reload
reload(module)

2
เพื่อจัดการกับกรณีใด ๆ เหล่านี้: from six import reload_module(ต้องเรียนpip install sixก่อน)
Anentropic

@Anentropic: มันเป็นคำแนะนำที่ดีที่จะแนะนำให้ใช้หกแพ็คเกจ แต่ไวยากรณ์คือfrom six.moves import reload_module( doc )
x0s

23

รหัสต่อไปนี้ช่วยให้คุณสามารถใช้งาน Python 2/3 ได้:

try:
    reload
except NameError:
    # Python 3
    from imp import reload

คุณสามารถใช้มันreload()ในทั้งสองเวอร์ชันซึ่งทำให้สิ่งต่าง ๆ ง่ายขึ้น


18

คำตอบที่ยอมรับไม่ได้จัดการกรณี X จากการนำเข้า Y รหัสนี้จัดการกับมันและกรณีนำเข้ามาตรฐานเช่นกัน:

def importOrReload(module_name, *names):
    import sys

    if module_name in sys.modules:
        reload(sys.modules[module_name])
    else:
        __import__(module_name, fromlist=names)

    for name in names:
        globals()[name] = getattr(sys.modules[module_name], name)

# use instead of: from dfly_parser import parseMessages
importOrReload("dfly_parser", "parseMessages")

ในกรณีที่โหลดใหม่เรากำหนดชื่อระดับสูงสุดให้กับค่าที่เก็บไว้ในโมดูลที่โหลดใหม่ซึ่งจะอัปเดต


พบปัญหา globals () หมายถึงโมดูลที่คุณกำหนดฟังก์ชั่นนี้ดังนั้นหากคุณกำหนดไว้ในโมดูลที่แตกต่างจากที่คุณเรียกมันว่าสิ่งนี้ไม่ทำงาน
Joseph Garvin

สำหรับการโต้ตอบหลังจาก>>> from X import Yโหลดใหม่ทำแล้ว>>> __import__('X', fromlist='Y')
Bob Stein

@ BobStein-VisiBone มีวิธีทำให้ทำงานเมื่อfromlist='*'ใด
Mike C

เป็นคำถามที่ดีไม่รู้จัก @MikeC โดยวิธีที่ฉันมีแนวโน้มที่จะหยุดการใช้งานfromในงบการนำเข้าเกือบทั้งหมด เพียงสิ้นเชิงimport <package>และ package.symbol ชัดเจนในรหัส ตระหนักว่าสิ่งนี้อาจไม่เป็นไปได้หรือเป็นที่ต้องการเสมอไป (นี่คือข้อยกเว้นหนึ่งข้อ: จากการนำเข้า print_function ในอนาคต)
Bob Stein

Mike C: อะไรที่เหมาะกับฉันคือfoo = reload(foo); from foo import *
rampion

16

นี่เป็นวิธีที่ทันสมัยในการโหลดโมดูล:

from importlib import reload

ถ้าคุณต้องการสนับสนุน Python เวอร์ชันที่เก่ากว่า 3.5 ลองสิ่งนี้:

from sys import version_info
if version_info[0] < 3:
    pass # Python 2 has built in reload
elif version_info[0] == 3 and version_info[1] <= 4:
    from imp import reload # Python 3.0 - 3.4 
else:
    from importlib import reload # Python 3.5+

หากต้องการใช้งานให้เรียกใช้reload(MODULE)แทนที่MODULEด้วยโมดูลที่คุณต้องการโหลดซ้ำ

ตัวอย่างเช่นreload(math)จะโหลดmathโมดูลอีกครั้ง


4
from importlib import reloadหรือเพียงแค่ทำ reload(MODULE_NAME)จากนั้นคุณสามารถทำได้ ไม่จำเป็นต้องใช้ฟังก์ชั่นนี้
pault

ฉันเชื่อว่าmodulereload(MODULE_NAME)สามารถอธิบายตนเองได้มากกว่าเพียงแค่reload(MODULE_NAME)และมีโอกาสน้อยกว่าที่จะขัดแย้งกับหน้าที่อื่น ๆ
Richie Bendall

@RichieBendall ขออภัย แต่คำตอบนี้ผิดอย่างสมบูรณ์ ฟังก์ชั่นโหลด () ใช้วัตถุโมดูลไม่ใช่ชื่อโมดูล ... อ่านเอกสาร: docs.python.org/3/library/importlib.html#importlib.reloadและฉันเห็นด้วยกับ @ pault - นี่ "เป็น modulereload" เป็นฟุ่มเฟือย .
mbdevpl

ฉันเปลี่ยนคำตอบเพื่อแสดงความคิดเห็นของคุณแล้ว
Richie Bendall

13

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

ขั้นแรกให้แน่ใจว่าคุณใช้เปลือก IPython ที่ยอดเยี่ยมจากโครงการ Jupyter Notebook หลังจากติดตั้ง Jupyter คุณสามารถเริ่มต้นด้วยipythonหรือjupyter consoleหรือดียิ่งขึ้นjupyter qtconsoleซึ่งจะให้คอนโซลที่มีสีสันดีพร้อมโค้ดที่สมบูรณ์ในระบบปฏิบัติการใด ๆ

ตอนนี้ในเชลล์ของคุณพิมพ์:

%load_ext autoreload
%autoreload 2

ตอนนี้ทุกครั้งที่คุณเรียกใช้สคริปต์โมดูลของคุณจะถูกโหลดใหม่

นอกเหนือจาก2นั้นยังมีตัวเลือกอื่น ๆของเวทมนตร์โหลดอัตโนมัติ :

%autoreload
Reload all modules (except those excluded by %aimport) automatically now.

%autoreload 0
Disable automatic reloading.

%autoreload 1
Reload all modules imported with %aimport every time before executing the Python code typed.

%autoreload 2
Reload all modules (except those excluded by %aimport) every time before
executing the Python code typed.

7

สำหรับคนอย่างฉันที่ต้องการยกเลิกการโหลดโมดูลทั้งหมด (เมื่อทำงานใน Python interpreter ภายใต้Emacs ):

   for mod in sys.modules.values():
      reload(mod)

ข้อมูลเพิ่มเติมในโมดูลโหลดหลาม


ที่จริงแล้วดูเหมือนจะไม่สามารถทำงานได้อย่างน่าเชื่อถือ (ใน 2.6) เพราะไม่ใช่ทุกสิ่งในsys.modules.values()นั้นเป็นโมดูล ตัวอย่างเช่น: >>> type (sys.modules.values ​​() [1]) <class 'email.LazyImporter'> ดังนั้นถ้าฉันพยายามเรียกใช้รหัสนั้นมันจะตก (ฉันรู้ว่ามันไม่ได้เป็นวิธีการแก้ปัญหาเพียงแค่ ชี้ไปที่นั้น)
ฟรานซิสดาวี่

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

2
ทำงานได้ดีในหลาม 2.7 หลังจากการปรับเปลี่ยนบางอย่างif mod and mod.__name__ != "__main__": imp.reload(mod)
Czarek Tomczak

2
วิธีนี้ใช้ได้ผลดีสำหรับฉัน: นำเข้า imp [reload (m) สำหรับ m ใน sys.modules.values ​​() ถ้า m และไม่ใช่ " " ใน m .__ ชื่อและไม่ใช่ imp.is_builtin (m .__ name__)]
Patrick Wolf

5

คุณลักษณะความคิดมีโมดูลที่ทำงานค่อนข้างดีสำหรับเรื่องนี้ https://traits.readthedocs.org/en/4.3.0/_modules/traits/util/refresh.html

มันจะโหลดโมดูลใด ๆ ที่มีการเปลี่ยนแปลงและอัปเดตโมดูลอื่น ๆ และวัตถุที่ไม่ได้ใช้ที่กำลังใช้งานอยู่ มันใช้งานไม่ได้กับ__very_private__วิธีการส่วนใหญ่และสามารถทำให้หายใจไม่ออกในชั้นเรียนได้ แต่มันช่วยประหยัดเวลาได้อย่างมากที่ฉันไม่ต้องรีสตาร์ทแอปพลิเคชั่นโฮสต์เมื่อเขียน PyQt guis หรือสิ่งที่ทำงานภายในโปรแกรมเช่น Maya หรือ Nuke อาจไม่ได้ผลประมาณ 20-30% แต่ก็ยังมีประโยชน์อย่างไม่น่าเชื่อ

แพคเกจของแม้จะไม่โหลดไฟล์ทันทีที่มีการเปลี่ยนแปลง - คุณต้องเรียกมันอย่างชัดเจน - แต่มันไม่ควรที่จะนำไปใช้ทั้งหมดหากคุณต้องการมันจริงๆ


5

ผู้ที่ใช้ python 3 และโหลดซ้ำจาก importlib

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



3

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

หากคุณติดตั้งIPythonหรือJupyterคุณสามารถใช้ฟังก์ชั่นในการรีโหลด libs ทั้งหมด:

from IPython.lib.deepreload import reload as dreload
dreload(foo)

หากคุณไม่มี Jupyter ติดตั้งด้วยคำสั่งนี้ในเปลือกของคุณ:

pip3 install jupyter

ทั้งสองนี้ dreload IPython และโหลด () จาก importlib reload() argument must be moduleไม่บ่นกับ ฉันใช้การนำเข้าฟังก์ชั่นที่กำหนดเองและดูเหมือนจะไม่ทำงาน การใช้โมดูลในตัวใช้งานได้ :-( เป็นการเสียเวลาโหลด iPython สำหรับการเปลี่ยนแปลงเล็ก ๆ น้อย ๆ ทุกครั้งที่ฉันทำกับโค้ดของฉัน ...
m3nda

2

แก้ไข (คำตอบ V2)

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

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

รหัสใหม่:

import importlib
import inspect
import gc
from weakref import ref


def reset_module(module, inner_modules_also=True):
    """
    This function is a stronger form of importlib's `reload` function. What it does, is that aside from reloading a
    module, it goes to the old instance of the module, and sets all the (not read-only) attributes, functions and classes
    to be the reloaded-module's
    :param module: The module to reload (module reference, not the name)
    :param inner_modules_also: Whether to treat ths module as a package as well, and reload all the modules within it.
    """

    # For the case when the module is actually a package
    if inner_modules_also:
        submods = {submod for _, submod in inspect.getmembers(module)
                   if (type(submod).__name__ == 'module') and (submod.__package__.startswith(module.__name__))}
        for submod in submods:
            reset_module(submod, True)

    # First, log all the references before reloading (because some references may be changed by the reload operation).
    module_tree = _get_tree_references_to_reset_recursively(module, module.__name__)

    new_module = importlib.reload(module)
    _reset_item_recursively(module, module_tree, new_module)


def _update_referrers(item, new_item):
    refs = gc.get_referrers(item)

    weak_ref_item = ref(item)
    for coll in refs:
        if type(coll) == dict:
            enumerator = coll.keys()
        elif type(coll) == list:
            enumerator = range(len(coll))
        else:
            continue

        for key in enumerator:

            if weak_ref_item() is None:
                # No refs are left in the GC
                return

            if coll[key] is weak_ref_item():
                coll[key] = new_item

def _get_tree_references_to_reset_recursively(item, module_name, grayed_out_item_ids = None):
    if grayed_out_item_ids is None:
        grayed_out_item_ids = set()

    item_tree = dict()
    attr_names = set(dir(item)) - _readonly_attrs
    for sub_item_name in attr_names:

        sub_item = getattr(item, sub_item_name)
        item_tree[sub_item_name] = [sub_item, None]

        try:
            # Will work for classes and functions defined in that module.
            mod_name = sub_item.__module__
        except AttributeError:
            mod_name = None

        # If this item was defined within this module, deep-reset
        if (mod_name is None) or (mod_name != module_name) or (id(sub_item) in grayed_out_item_ids) \
                or isinstance(sub_item, EnumMeta):
            continue

        grayed_out_item_ids.add(id(sub_item))
        item_tree[sub_item_name][1] = \
            _get_tree_references_to_reset_recursively(sub_item, module_name, grayed_out_item_ids)

    return item_tree


def _reset_item_recursively(item, item_subtree, new_item):

    # Set children first so we don't lose the current references.
    if item_subtree is not None:
        for sub_item_name, (sub_item, sub_item_tree) in item_subtree.items():

            try:
                new_sub_item = getattr(new_item, sub_item_name)
            except AttributeError:
                # The item doesn't exist in the reloaded module. Ignore.
                continue

            try:
                # Set the item
                _reset_item_recursively(sub_item, sub_item_tree, new_sub_item)
            except Exception as ex:
                pass

    _update_referrers(item, new_item)

คำตอบเดิม

ดังที่เขียนไว้ในคำตอบของ @ bobince หากมีการอ้างอิงไปยังโมดูลนั้นในโมดูลอื่นแล้ว (โดยเฉพาะถ้านำเข้าด้วยasคำหลักที่ชอบimport numpy as np) อินสแตนซ์นั้นจะไม่ถูกเขียนทับ

สิ่งนี้พิสูจน์ให้เห็นแล้วว่าเป็นปัญหาสำหรับฉันเมื่อใช้การทดสอบที่ต้องการสถานะ "clean-slate" ของโมดูลการกำหนดค่าดังนั้นฉันจึงเขียนฟังก์ชันชื่อreset_moduleที่ใช้ฟังก์ชั่นimportlibของreloadฟังก์ชั่นและเขียนทับคุณลักษณะทั้งหมดของโมดูลที่ประกาศ มันได้รับการทดสอบด้วย Python เวอร์ชั่น 3.6

import importlib
import inspect
from enum import EnumMeta

_readonly_attrs = {'__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__',
               '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__func__', '__ge__', '__get__',
               '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__',
               '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__',
               '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__sizeof__', '__str__',
               '__subclasshook__', '__weakref__', '__members__', '__mro__', '__itemsize__', '__isabstractmethod__',
               '__basicsize__', '__base__'}


def reset_module(module, inner_modules_also=True):
    """
    This function is a stronger form of importlib's `reload` function. What it does, is that aside from reloading a
    module, it goes to the old instance of the module, and sets all the (not read-only) attributes, functions and classes
    to be the reloaded-module's
    :param module: The module to reload (module reference, not the name)
    :param inner_modules_also: Whether to treat ths module as a package as well, and reload all the modules within it.
    """

    new_module = importlib.reload(module)

    reset_items = set()

    # For the case when the module is actually a package
    if inner_modules_also:
        submods = {submod for _, submod in inspect.getmembers(module)
                   if (type(submod).__name__ == 'module') and (submod.__package__.startswith(module.__name__))}
        for submod in submods:
            reset_module(submod, True)

    _reset_item_recursively(module, new_module, module.__name__, reset_items)


def _reset_item_recursively(item, new_item, module_name, reset_items=None):
    if reset_items is None:
        reset_items = set()

    attr_names = set(dir(item)) - _readonly_attrs

    for sitem_name in attr_names:

        sitem = getattr(item, sitem_name)
        new_sitem = getattr(new_item, sitem_name)

        try:
            # Set the item
            setattr(item, sitem_name, new_sitem)

            try:
                # Will work for classes and functions defined in that module.
                mod_name = sitem.__module__
            except AttributeError:
                mod_name = None

            # If this item was defined within this module, deep-reset
            if (mod_name is None) or (mod_name != module_name) or (id(sitem) in reset_items) \
                    or isinstance(sitem, EnumMeta):  # Deal with enums
                continue

            reset_items.add(id(sitem))
            _reset_item_recursively(sitem, new_sitem, module_name, reset_items)
        except Exception as ex:
            raise Exception(sitem_name) from ex

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


1

สำหรับฉันสำหรับกรณีของ Abaqus มันเป็นวิธีการทำงาน ลองนึกภาพไฟล์ของคุณคือ Class_VerticesEdges.py

sys.path.append('D:\...\My Pythons')
if 'Class_VerticesEdges' in sys.modules:  
    del sys.modules['Class_VerticesEdges']
    print 'old module Class_VerticesEdges deleted'
from Class_VerticesEdges import *
reload(sys.modules['Class_VerticesEdges'])

คำตอบนี้เป็นสำเนาโดยตรงจากที่นี่: ebanshi.cc/questions/1942/…
SiHa

0

ฉันมีปัญหาในการพยายามโหลดบางอย่างภายใน Sublime Text แต่ในที่สุดฉันก็สามารถเขียนยูทิลิตี้นี้เพื่อโหลดโมดูลบน Sublime Text ตามรหัสที่sublime_plugin.pyใช้ในการโหลดโมดูล

ด้านล่างนี้ให้คุณโหลดโมดูลใหม่จากพา ธ ที่มีช่องว่างในชื่อของพวกเขาจากนั้นหลังจากโหลดใหม่คุณสามารถนำเข้าได้ตามปกติ

def reload_module(full_module_name):
    """
        Assuming the folder `full_module_name` is a folder inside some
        folder on the python sys.path, for example, sys.path as `C:/`, and
        you are inside the folder `C:/Path With Spaces` on the file 
        `C:/Path With Spaces/main.py` and want to re-import some files on
        the folder `C:/Path With Spaces/tests`

        @param full_module_name   the relative full path to the module file
                                  you want to reload from a folder on the
                                  python `sys.path`
    """
    import imp
    import sys
    import importlib

    if full_module_name in sys.modules:
        module_object = sys.modules[full_module_name]
        module_object = imp.reload( module_object )

    else:
        importlib.import_module( full_module_name )

def run_tests():
    print( "\n\n" )
    reload_module( "Path With Spaces.tests.semantic_linefeed_unit_tests" )
    reload_module( "Path With Spaces.tests.semantic_linefeed_manual_tests" )

    from .tests import semantic_linefeed_unit_tests
    from .tests import semantic_linefeed_manual_tests

    semantic_linefeed_unit_tests.run_unit_tests()
    semantic_linefeed_manual_tests.run_manual_tests()

if __name__ == "__main__":
    run_tests()

หากคุณเรียกใช้เป็นครั้งแรกสิ่งนี้ควรโหลดโมดูล แต่ถ้าภายหลังคุณสามารถทำได้อีกครั้งวิธี / ฟังก์ชั่นrun_tests()จะทำการโหลดไฟล์ทดสอบอีกครั้ง เมื่อใช้ Sublime Text ( Python 3.3.6) สิ่งนี้จะเกิดขึ้นมากเพราะล่ามไม่เคยปิด (เว้นแต่คุณจะรีสตาร์ท Sublime Text เช่นPython3.3ล่าม)


-1

อีกวิธีหนึ่งคือการนำเข้าโมดูลในฟังก์ชั่น วิธีนี้เมื่อฟังก์ชั่นเสร็จสมบูรณ์โมดูลจะได้รับการเก็บขยะ


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