การรวมไฟล์ข้อมูลด้วย PyInstaller (--onefile)


110

ฉันกำลังพยายามสร้าง EXE ไฟล์เดียวด้วย PyInstaller ซึ่งรวมรูปภาพและไอคอน --onefileฉันจะไม่ให้ชีวิตของฉันได้รับมันจะทำงานร่วมกับ

ถ้าฉันทำ--onedirมันได้ผลดีมาก เมื่อฉันใช้--onefileมันไม่พบไฟล์เพิ่มเติมที่อ้างอิง (เมื่อเรียกใช้ EXE ที่คอมไพล์แล้ว) พบว่า DLL และทุกอย่างดีไม่ใช่แค่สองภาพ

ฉันได้ดูใน temp-dir ที่สร้างขึ้นเมื่อเรียกใช้ EXE ( \Temp\_MEI95642\ตัวอย่าง) และไฟล์อยู่ในนั้น เมื่อฉันวาง EXE ในไดเร็กทอรี temp นั้นจะพบพวกเขา น่างงมาก

นี่คือสิ่งที่ฉันได้เพิ่มลงใน.specไฟล์

a.datas += [('images/icon.ico', 'D:\\[workspace]\\App\\src\\images\\icon.ico',  'DATA'),
('images/loaderani.gif','D:\\[workspace]\\App\\src\\images\\loaderani.gif','DATA')]     

ฉันควรเพิ่มว่าฉันได้พยายามที่จะไม่ใส่ไว้ในโฟลเดอร์ย่อยเช่นกันไม่ได้สร้างความแตกต่าง

แก้ไข: ทำเครื่องหมายคำตอบที่ใหม่กว่าว่าถูกต้องเนื่องจากการอัปเดต PyInstaller


11
ขอบคุณมาก! เส้นตรงนี้ ( a.datas += ...) ช่วยฉันได้จริงๆ เอกสาร pyinstaller พูดถึงการใช้งานCOLLECTแต่ล้มเหลวในการใส่ไฟล์ลงในไบนารีเมื่อใช้--onefile
Igor Serebryany

@IgorSerebryany: วินาที! ฉันมีปัญหาเดียวกันแน่นอน
Hubro

. exe ของฉันขัดข้องเมื่อฉันคลิกที่แถบเมนูถ้าฉันใช้
iacopo

1
พิจารณาว่าโฟลเดอร์ temp หายไปเมื่อการดำเนินการเสร็จสิ้นดังนั้นเพื่อตรวจสอบว่ามีอะไรอยู่ข้างในจึงใส่ listdir ของ sys._MEIPASS ไว้ในโปรแกรมหลัก
hithwen

นอกจากนี้ยังมีวิธีการใช้ไวยากรณ์ Tree () ที่ฉันเห็นในสถานที่นั้นหรือไม่?
fatuhoku

คำตอบ:


153

PyInstaller เวอร์ชันใหม่กว่าไม่ได้ตั้งค่าenvตัวแปรอีกต่อไปดังนั้นคำตอบที่ยอดเยี่ยมของ Shish จะไม่ทำงาน ตอนนี้เส้นทางได้รับการตั้งค่าเป็นsys._MEIPASS:

def resource_path(relative_path):
    """ Get absolute path to resource, works for dev and for PyInstaller """
    try:
        # PyInstaller creates a temp folder and stores path in _MEIPASS
        base_path = sys._MEIPASS
    except Exception:
        base_path = os.path.abspath(".")

    return os.path.join(base_path, relative_path)

10
ฉันใช้ pyinstaller 2 กับ python 2.7 และฉันไม่มี_MEIPASS2ใน envs แต่ใช้sys._MEIPASSงานได้ดีดังนั้น +1 ฉันขอแนะนำ:path = getattr(sys, '_MEIPASS', os.getcwd())
kbec

2
+1. ขอบคุณสำหรับสิ่งนั้น พยายามใช้ตัวแปรสภาพแวดล้อม _MEIPASS2 มาระยะหนึ่งแล้วเห็นได้ชัดว่าฉันเขียนโค้ดเมื่อยังเป็นตัวแปรสภาพแวดล้อมเพราะฉันจำได้ว่ามันใช้งานได้ ทันใดนั้นมันก็หยุดลงเมื่อฉันทำการคอมไพล์ใหม่เมื่อเร็ว ๆ นี้
Favil Orbedios

9
ผมอยากแนะนำให้จับแทนทั่วไปมากขึ้นAttributeError Exceptionฉันวิ่งเข้าไปในปัญหาที่ผมหายไปนำเข้า SYS os.path.abspathและเส้นทางผิดนัดเงียบ
แอรอน

คุณอาจจะสามารถที่จะช่วยแก้ปัญหาของฉันปัญหา
Phillip

1
การใช้ไดเร็กทอรีการทำงานปัจจุบันในกรณีที่ไม่ได้ตั้งค่า _MEIPASS ผิด ดูคำตอบของฉัน
Jonathon Reinhart

52

pyinstaller คลายแพ็กข้อมูลของคุณลงในโฟลเดอร์ชั่วคราวและเก็บเส้นทางไดเร็กทอรีนี้ไว้ใน_MEIPASS2ตัวแปรสภาพแวดล้อม ในการรับ_MEIPASS2dir ในโหมดแพ็กเกจและใช้โลคัลไดเร็กทอรีในโหมด unpacked (การพัฒนา) ฉันใช้สิ่งนี้:

def resource_path(relative):
    return os.path.join(
        os.environ.get(
            "_MEIPASS2",
            os.path.abspath(".")
        ),
        relative
    )

เอาท์พุต:

# in development
>>> resource_path("app_icon.ico")
"/home/shish/src/my_app/app_icon.ico"

# in production
>>> resource_path("app_icon.ico")
"/tmp/_MEI34121/app_icon.ico"

10
จะใช้อย่างไรเมื่อไหร่และที่ไหน
gseattle

4
คุณควรใช้สคริปต์นี้ในไฟล์. py ที่คุณพยายามรวบรวมด้วย PyInstaller อย่าใส่ข้อมูลโค้ดนี้ในไฟล์. spec ซึ่งจะใช้ไม่ได้ resource_path("file_to_be_accessed.mp3")เข้าถึงไฟล์ของคุณโดยการแทนเส้นทางที่คุณต้องการปกติพิมพ์โดย โปรดระวังว่าคุณควรใช้ max 'answer สำหรับ PyInstaller เวอร์ชันปัจจุบัน
Exeleration-G

1
คำตอบนี้เฉพาะสำหรับการใช้--one-fileตัวเลือกหรือไม่?
fatuhoku

คุณอาจจะสามารถที่จะช่วยแก้ปัญหาของฉันปัญหา
Phillip

การใช้ไดเร็กทอรีการทำงานปัจจุบันในกรณีที่ไม่ได้ตั้งค่า _MEIPASS ผิด ดูคำตอบของฉัน
Jonathon Reinhart

30

คำตอบอื่น ๆ ทั้งหมดใช้ไดเร็กทอรีการทำงานปัจจุบันในกรณีที่แอปพลิเคชันไม่ได้ติดตั้ง PyInstalled (เช่นsys._MEIPASSไม่ได้ตั้งค่า) นั่นผิดเพราะมันป้องกันไม่ให้คุณเรียกใช้แอปพลิเคชันของคุณจากไดเร็กทอรีอื่นที่ไม่ใช่ที่สคริปต์ของคุณอยู่

ทางออกที่ดีกว่า:

import sys
import os

def resource_path(relative_path):
    """ Get absolute path to resource, works for dev and for PyInstaller """
    base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
    return os.path.join(base_path, relative_path)

ขอบคุณจอนวันนี้หน้าที่ของคุณช่วยฉันแก้ปัญหาได้ ฉันแค่มีคำถาม ทำไมบางคนเขียน _MEIPASS2 แทน _MEIPASS เมื่อฉันลองเพิ่ม 2 ก่อนหน้านี้มันไม่ได้ผล
Ahmed AbdelKhalek

1
ฉันคิดว่าos.env['_MEIPASS2'](การใช้สภาพแวดล้อมระบบปฏิบัติการ) เป็นวิธีเก่าในการรับไดเร็กทอรี AFAIK sys._MEIPASSเป็นวิธีเดียวที่ถูกต้องในตอนนี้
Jonathon Reinhart

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

1
เสียง @Guimoute เช่นรหัสในคำตอบนี้จะทำงานได้ตามที่ออกแบบ: มัน localizes __file__ทรัพยากรที่จะโมดูลที่ให้มันเพราะมันใช้ หากคุณต้องการให้โมดูลสามารถเข้าถึงทรัพยากรนอกขอบเขตของตัวเองได้คุณจะต้องใช้โค้ดที่นำเข้านั้นจัดเตรียมพา ธ ให้โมดูลของคุณใช้เช่นโดยโมดูลที่ให้การเรียกใช้ "set_resource_location ()" ซึ่ง การโทรของผู้นำเข้า - อย่าคิดว่าสิ่งนี้จะแตกต่างจากวิธีการทำงานของคุณเมื่อไม่ใช้ pyinstaller
barny

@barny ขอบคุณสำหรับคำใบ้! ฉันจะลองทำอะไรบางอย่างตามแนวเหล่านั้น
Guimoute

15

บางทีฉันพลาดขั้นตอนหรือทำอะไรผิดพลาด แต่วิธีการข้างต้นไม่ได้รวมไฟล์ข้อมูลกับ PyInstaller ไว้ในไฟล์ exe เดียว ให้ฉันแบ่งปันขั้นตอนสิ่งที่ฉันได้ทำ

  1. ขั้นตอน: เขียนหนึ่งในวิธีการข้างต้นลงในไฟล์ py ของคุณด้วยการนำเข้า sys และโมดูลระบบปฏิบัติการ ฉันลองทั้งสองอย่างแล้ว สุดท้ายคือ:

    def resource_path(relative_path):
    """ Get absolute path to resource, works for dev and for PyInstaller """
        base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
        return os.path.join(base_path, relative_path)
  2. ขั้นตอน: เขียนpyi-makepec file.pyไปที่คอนโซลเพื่อสร้างไฟล์ file.spec

  3. ขั้นตอน: เปิด file.spec ด้วย Notepad ++ เพื่อเพิ่มไฟล์ข้อมูลดังต่อไปนี้:

    a = Analysis(['C:\\Users\\TCK\\Desktop\\Projeler\\Converter-GUI.py'],
                 pathex=['C:\\Users\\TCK\\Desktop\\Projeler'],
                 binaries=[],
                 datas=[],
                 hiddenimports=[],
                 hookspath=[],
                 runtime_hooks=[],
                 excludes=[],
                 win_no_prefer_redirects=False,
                 win_private_assemblies=False,
                 cipher=block_cipher)
    #Add the file like the below example
    a.datas += [('Converter-GUI.ico', 'C:\\Users\\TCK\\Desktop\\Projeler\\Converter-GUI.ico', 'DATA')]
    pyz = PYZ(a.pure, a.zipped_data,
         cipher=block_cipher)
    exe = EXE(pyz,
              a.scripts,
              exclude_binaries=True,
              name='Converter-GUI',
              debug=False,
              strip=False,
              upx=True,
              #Turn the console option False if you don't want to see the console while executing the program.
              console=False,
              #Add an icon to the program.
              icon='C:\\Users\\TCK\\Desktop\\Projeler\\Converter-GUI.ico')
    
    coll = COLLECT(exe,
                   a.binaries,
                   a.zipfiles,
                   a.datas,
                   strip=False,
                   upx=True,
                   name='Converter-GUI')
  4. ขั้นตอน: ฉันทำตามขั้นตอนข้างต้นจากนั้นบันทึกไฟล์ข้อมูลจำเพาะ ในที่สุดก็เปิดคอนโซลและเขียนpyinstaller file.spec (ในกรณีของฉัน file = Converter-GUI)

สรุป: ยังมีไฟล์มากกว่าหนึ่งไฟล์ในโฟลเดอร์ dist

หมายเหตุ: ฉันใช้ Python 3.5

แก้ไข: ในที่สุดก็ใช้ได้กับวิธีการของ Jonathan Reinhart

  1. ขั้นตอน: เพิ่มรหัสด้านล่างลงในไฟล์ python ของคุณด้วยการนำเข้า sys และ os

    def resource_path(relative_path):
    """ Get absolute path to resource, works for dev and for PyInstaller """
        base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
        return os.path.join(base_path, relative_path)
  2. ขั้นตอน: เรียกใช้ฟังก์ชันข้างต้นโดยเพิ่มเส้นทางของไฟล์ของคุณ:

    image_path = resource_path("Converter-GUI.ico")
  3. ขั้นตอน: เขียนตัวแปรด้านบนที่เรียกใช้ฟังก์ชันไปยังตำแหน่งที่รหัสของคุณต้องการเส้นทาง ในกรณีของฉันมันคือ:

        self.window.iconbitmap(image_path)
  4. ขั้นตอน: เปิดคอนโซลในไดเร็กทอรีเดียวกันของไฟล์ python ของคุณเขียนโค้ดด้านล่าง:

        pyinstaller --onefile your_file.py
  5. ขั้นตอน: เปิดไฟล์. spec ของไฟล์ python และต่อท้ายอาร์เรย์ a.datas และเพิ่มไอคอนในคลาส exe ซึ่งได้รับไว้ข้างต้นก่อนการแก้ไขในขั้นตอนที่ 3
  6. ขั้นตอน: บันทึกและออกจากไฟล์พา ธ ไปที่โฟลเดอร์ของคุณซึ่งมีไฟล์ spec และ py เปิดหน้าต่างคอนโซลอีกครั้งและพิมพ์คำสั่งด้านล่าง:

        pyinstaller your_file.spec

หลังจากขั้นตอนที่ 6. ไฟล์เดียวของคุณก็พร้อมใช้งาน


ขอบคุณ @dilde! ไม่ใช่ว่าa.datasอาร์เรย์จากขั้นตอนที่ 5 จะใช้ tuples ของสตริงโปรดดูpythonhosted.org/PyInstaller/spec-files.html#adding-data-filesสำหรับการอ้างอิง
Ian Campbell

ขออภัยฉันไม่เข้าใจส่วนหนึ่งของข้อความของคุณเช่นกันฉันต้องการเพราะภาษาอังกฤษของฉันอ่อนแอ ส่วนนั้นคือ"Not that the a.datas .... " , Did you want to write 'Note that the a.datas ... "มีบางอย่างผิดปกติกับสิ่งที่ฉันเขียนเกี่ยวกับการเพิ่มสตริงในอาร์เรย์ a.datas หรือไม่
dildeolupbiten

อ๊ะ! ควรจะทราบว่า ... ใช่ขอโทษเกี่ยวกับการพิมพ์ผิด ขั้นตอนของคุณได้ผลดีสำหรับฉัน :) ฉันไม่พบข้อมูลนี้ในเอกสารประกอบหรือที่อื่น
Ian Campbell

8

แทนที่จะเขียนรหัสเส้นทางทั้งหมดของฉันใหม่ตามที่แนะนำฉันเปลี่ยนไดเร็กทอรีการทำงาน:

if getattr(sys, 'frozen', False):
    os.chdir(sys._MEIPASS)

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


2
ไม่แอปพลิเคชันที่ดีไม่ควรเปลี่ยนไดเร็กทอรีการทำงาน มีวิธีที่ดีกว่านี้
Jonathon Reinhart

3

ปรับเปลี่ยนคำตอบที่ยอมรับได้เล็กน้อย

def resource_path(relative_path):
    """ Get absolute path to resource, works for dev and for PyInstaller """
    if hasattr(sys, '_MEIPASS'):
        return os.path.join(sys._MEIPASS, relative_path)

    return os.path.join(os.path.abspath("."), relative_path)

การใช้ไดเร็กทอรีการทำงานปัจจุบันในกรณีที่ไม่ได้ตั้งค่า _MEIPASS ผิด ดูคำตอบของฉัน
Jonathon Reinhart

2

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

หากคุณต้องการดูตัวอย่างจริงที่เก็บของฉันอยู่ที่ GitHub

หมายเหตุ: ใช้สำหรับการคอมไพล์โดยใช้คำสั่ง--onefileor -Fกับ pyinstaller

สภาพแวดล้อมของฉันมีดังนี้


การแก้ปัญหาใน 2 ขั้นตอน

ในการแก้ปัญหานี้เราจำเป็นต้องบอก Pyinstaller โดยเฉพาะว่าเรามีไฟล์พิเศษที่ต้อง "รวม" กับแอปพลิเคชัน

เราจำเป็นต้องใช้เส้นทาง 'สัมพัทธ์'ด้วยดังนั้นแอปพลิเคชันจึงสามารถทำงานได้อย่างถูกต้องเมื่อทำงานเป็น Python Script หรือ Frozen EXE

ด้วยเหตุนี้เราจึงต้องการฟังก์ชันที่ช่วยให้เรามีพา ธ สัมพัทธ์ การใช้ฟังก์ชั่นที่Max โพสต์เราสามารถแก้ปัญหาเส้นทางสัมพัทธ์ได้อย่างง่ายดาย

def img_resource_path(relative_path):
    """ Get absolute path to resource, works for dev and for PyInstaller """
    try:
        # PyInstaller creates a temp folder and stores path in _MEIPASS
        base_path = sys._MEIPASS
    except Exception:
        base_path = os.path.abspath(".")

    return os.path.join(base_path, relative_path)

เราจะใช้ฟังก์ชันข้างต้นเช่นนี้เพื่อให้ไอคอนแอปพลิเคชันปรากฏขึ้นเมื่อแอปทำงานเป็นสคริปต์หรือ Frozen EXE

icon_path = img_resource_path("app/img/app_icon.ico")
root.wm_iconbitmap(icon_path)

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

เราสามารถแก้ปัญหานี้ได้สองวิธีดังที่แสดงในเอกสารแต่โดยส่วนตัวแล้วฉันชอบจัดการไฟล์. spec ของตัวเองดังนั้นเราจะทำอย่างไร

ขั้นแรกคุณต้องมีไฟล์. spec อยู่แล้ว ในกรณีของฉันฉันก็สามารถที่จะสร้างสิ่งที่ฉันต้องการโดยการทำงานpyinstallerกับ args พิเศษคุณสามารถค้นหา args พิเศษที่นี่ ด้วยเหตุนี้ไฟล์ข้อมูลจำเพาะของฉันอาจดูแตกต่างจากของคุณเล็กน้อย แต่ฉันโพสต์ทั้งหมดเพื่ออ้างอิงหลังจากที่ฉันอธิบายบิตที่สำคัญ

added_filesเป็นรายการที่มี Tuple เป็นหลักในกรณีของฉันฉันแค่ต้องการเพิ่มภาพ SINGLE แต่คุณสามารถเพิ่ม ico, png หรือ jpgได้หลายรายการโดยใช้('app/img/*.ico', 'app/img')คุณสามารถสร้าง tuple อื่นได้เช่นกันเพื่อadded_files = [ (), (), ()]ให้มีการนำเข้าหลายรายการ

ส่วนแรกของทูเพิลจะกำหนดไฟล์หรือประเภทของไฟล์ที่คุณต้องการเพิ่มรวมถึงตำแหน่งที่จะค้นหา คิดว่านี่คือ CTRL + C

ส่วนที่สองของ tuple บอกให้ Pyinstaller สร้างพา ธ 'app / img /' และวางไฟล์ในไดเร็กทอรีนั้นสัมพันธ์กับไดเร็กทอรี temp ใด ๆ ที่สร้างขึ้นเมื่อคุณเรียกใช้. exe คิดว่านี่คือ CTRL + V

ภายใต้a = Analysis([main...ฉันตั้งไว้datas=added_filesแต่เดิมเคยเป็นdatas=[]แต่เราต้องการให้รายการการนำเข้าเป็นอย่างดีนำเข้าดังนั้นเราจึงส่งผ่านการนำเข้าที่กำหนดเองของเรา

คุณไม่จำเป็นต้องทำเช่นนี้ถ้าคุณต้องการไอคอนที่เฉพาะเจาะจงสำหรับ EXE ที่ด้านล่างของแฟ้มข้อมูลจำเพาะฉันบอก Pyinstaller การตั้งค่าไอคอนโปรแกรมของฉันสำหรับ exe icon='app\\img\\app_icon.ico'ที่มีตัวเลือก

added_files = [
    ('app/img/app_icon.ico','app/img/')
]
a = Analysis(['main.py'],
             pathex=['D:\\Github Repos\\Processes-Killer\\Process Killer'],
             binaries=[],
             datas=added_files,
             hiddenimports=[],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          a.binaries,
          a.zipfiles,
          a.datas,
          [],
          name='Process Killer',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          upx_exclude=[],
          runtime_tmpdir=None,
          console=True , uac_admin=True, icon='app\\img\\app_icon.ico')

กำลังคอมไพล์ไปที่ EXE

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

เนื่องจากไฟล์. spec มีการตั้งค่าและการคอมไพล์ทั้งหมดของเรา (aka option) เราจึงต้องให้ไฟล์. spec นั้นแก่ Pyinstaller

pyinstaller.exe "Process Killer.spec"

1

คำร้องเรียน / คำถามที่พบบ่อยที่สุดที่ฉันเคยเห็น WRt PyInstaller คือ "รหัสของฉันไม่พบไฟล์ข้อมูลที่ฉันรวมอยู่ในชุดข้อมูลนั้นอยู่ที่ไหน" และการดูรหัสของคุณนั้นไม่ใช่เรื่องง่าย กำลังค้นหาเนื่องจากโค้ดที่แยกออกมาอยู่ในตำแหน่งชั่วคราวและจะถูกลบออกเมื่อออก เพิ่มโค้ดนี้เพื่อดูว่ามีอะไรอยู่ในไฟล์เดียวของคุณและอยู่ที่ไหนโดยใช้ @Jonathon Reinhart'sresource_path()

for root, dirs, files in os.walk(resource_path("")):
    print(root)
    for file in files:
        print( "  ",file)

1

ฉันจัดการกับปัญหานี้มานานแล้ว (ดีนานมาก ) ฉันค้นหาแหล่งที่มาเกือบทุกแหล่ง แต่สิ่งต่างๆไม่ได้อยู่ในรูปแบบในหัวของฉัน

ในที่สุดฉันคิดว่าฉันได้ทราบขั้นตอนที่แน่นอนในการปฏิบัติตามแล้วฉันต้องการแบ่งปัน

โปรดทราบว่าคำตอบของฉันใช้ข้อมูลเกี่ยวกับคำตอบของผู้อื่นในคำถามนี้

วิธีสร้างโปรแกรม python แบบสแตนด์อโลน

สมมติว่าเรามี project_folder และแผนผังไฟล์เป็นดังนี้:

project_folder/
    main.py
    xxx.py # modules
    xxx.py # modules
    sound/ # directory containing the sound files
    img/ # directory containing the image files
    venv/ # if using a venv

ก่อนอื่นสมมติว่าคุณได้กำหนดพา ธsound/และimg/โฟลเดอร์ของคุณเป็นตัวแปรsound_dirแล้วimg_dirดังนี้:

img_dir = os.path.join(os.path.dirname(__file__), "img")
sound_dir = os.path.join(os.path.dirname(__file__), "sound")

คุณต้องเปลี่ยนแปลงดังต่อไปนี้:

img_dir = resource_path("img")
sound_dir = resource_path("sound")

โดยที่resource_path()กำหนดไว้ที่ด้านบนสุดของสคริปต์ของคุณเป็น:

def resource_path(relative_path):
    """ Get absolute path to resource, works for dev and for PyInstaller """
    base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
    return os.path.join(base_path, relative_path)

เปิดใช้งาน env เสมือนหากใช้ venv

ติดตั้ง pyinstaller หากคุณยังไม่ได้ทำโดย: pip3 install pyinstaller.

วิ่ง: pyi-makespec --onefile main.pyเพื่อสร้างไฟล์ข้อมูลจำเพาะสำหรับกระบวนการคอมไพล์และบิลด์

สิ่งนี้จะเปลี่ยนลำดับชั้นของไฟล์เป็น:

project_folder/
    main.py
    xxx.py # modules
    xxx.py # modules
    sound/ # directory containing the sound files
    img/ # directory containing the image files
    venv/ # if using a venv
    main.spec

เปิด (ด้วย edior) main.spec:

ที่ด้านบนให้แทรก:

added_files = [

("sound", "sound"),
("img", "img")

]

จากนั้นเปลี่ยนบรรทัดdatas=[],เป็นdatas=added_files,

สำหรับรายละเอียดของการดำเนินงานทำในmain.specเห็นที่นี่

วิ่ง pyinstaller --onefile main.spec

และนั่นคือทั้งหมดที่คุณสามารถเรียกใช้mainในproject_folder/distจากที่ใดก็ได้โดยไม่ต้องมีสิ่งอื่นใดในโฟลเดอร์ของ คุณสามารถแจกจ่ายเฉพาะmainไฟล์นั้น ก็คือตอนนี้ที่เป็นจริงแบบสแตนด์อโลน


@JonathonReinhart ถ้าคุณยังอยู่ฉันอยากจะแจ้งให้คุณทราบว่าฉันใช้ฟังก์ชันของคุณในคำตอบของฉัน
muyustan

1

อีกวิธีหนึ่งคือการสร้าง runtime hook ซึ่งจะคัดลอก (หรือย้าย) ข้อมูลของคุณ (ไฟล์ / โฟลเดอร์) ไปยังไดเร็กทอรีที่จัดเก็บไฟล์ปฏิบัติการ hook เป็นไฟล์ python ธรรมดาที่แทบจะทำอะไรก็ได้ก่อนที่จะเรียกใช้แอพของคุณ ในการตั้งค่าคุณควรใช้--runtime-hook=my_hook.pyตัวเลือกของ pyinstaller ดังนั้นในกรณีที่ข้อมูลของคุณเป็นโฟลเดอร์รูปภาพคุณควรเรียกใช้คำสั่ง:

pyinstaller.py --onefile -F --add-data=images;images --runtime-hook=cp_images_hook.py main.py

cp_images_hook.py อาจเป็นดังนี้:

import sys
import os
import shutil

path = getattr(sys, '_MEIPASS', os.getcwd())

full_path = path+"\\images"
try:
    shutil.move(full_path, ".\\images")
except:
    print("Cannot create 'images' folder. Already exists.")

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

แนวทางที่สอง

คุณสามารถใช้ประโยชน์จากกลไก runtime-hook และเปลี่ยนไดเร็กทอรีปัจจุบันซึ่งไม่ใช่แนวทางปฏิบัติที่ดีสำหรับนักพัฒนาบางคน แต่ก็ใช้ได้ดี

รหัสเบ็ดอยู่ด้านล่าง:

import sys
import os

path = getattr(sys, '_MEIPASS', os.getcwd())   
os.chdir(path)

0

ฉันพบว่าคำตอบที่มีอยู่สับสนและใช้เวลานานในการหาว่าปัญหาอยู่ที่ใด นี่คือการรวบรวมทุกสิ่งที่ฉันพบ

เมื่อฉันเรียกใช้แอปฉันได้รับข้อผิดพลาดFailed to execute script foo(หากfoo.pyเป็นไฟล์หลัก) ในการแก้ไขปัญหานี้อย่าเรียกใช้ PyInstaller ด้วย--noconsole(หรือแก้ไขmain.specเพื่อเปลี่ยนconsole=False=>console=True ) ด้วยสิ่งนี้ให้เรียกใช้ไฟล์ปฏิบัติการจากบรรทัดคำสั่งและคุณจะเห็นความล้มเหลว

สิ่งแรกที่ต้องตรวจสอบคือบรรจุไฟล์พิเศษของคุณอย่างถูกต้อง คุณควรเพิ่ม tuples เช่น('x', 'x')ถ้าคุณต้องการที่โฟลเดอร์xที่จะรวม

หลังจากเกิดปัญหาแล้วอย่าคลิกตกลง หากคุณอยู่ใน Windows คุณสามารถใช้ค้นหาทุกอย่าง มองหาไฟล์ของคุณ (เช่นsword.png) คุณควรหาเส้นทางชั่วคราวที่มันคลายไฟล์ (เช่นC:\Users\ashes999\AppData\Local\Temp\_MEI157682\images\sword.png) คุณสามารถเรียกดูไดเร็กทอรีนี้และตรวจสอบให้แน่ใจว่ารวมทุกอย่างแล้ว ถ้าหาแบบนี้ไม่เจอให้มองหาmain.exe.manifest(Windows) หรือpython35.dll(ถ้าใช้ Python 3.5)

หากโปรแกรมติดตั้งมีทุกอย่างปัญหาที่น่าจะเกิดขึ้นต่อไปคือไฟล์ I / O: โค้ด Python ของคุณกำลังค้นหาในไดเร็กทอรีของไฟล์ปฏิบัติการแทนไดเร็กทอรี temp สำหรับไฟล์

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

if hasattr(sys, '_MEIPASS'): os.chdir(sys._MEIPASS)


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

0

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

https://stackoverflow.com/a/59415662/999943

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

หวังว่าจะช่วยได้

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