python-3.x 我在做什么,是防止pyinstaller exe找到我的数据文件夹

iih3973s  于 2023-10-21  发布在  Python
关注(0)|答案(1)|浏览(154)

我总是遇到一个pyinstaller发行版文件的问题。我找不到发行版应用程序找不到我的数据文件夹的原因。当我运行下面的python脚本时,它按预期工作,但当我用pyinstaller构建它并尝试运行时,我得到以下错误。

错误:

Exception in Tkinter callback
Traceback (most recent call last):
File "tkinter/__init__.py", line 1921, in __call__
File "Bible_Search.py", line 264, in \<lambda\>
File "Bible_Search.py", line 109, in search_bible
FileNotFoundError: \[Errno 2\] No such file or directory: './data/KJV.txt'

这是我的.spec文件,但我没有编辑它。我只是用下面的命令行生成它:

pyinstaller -F --windowed --add-data "data/KJV.txt:./data" --add-data "data/ASV.txt:./data" --add-data "data/YLT.txt:./data" Bible_Search.py

.spec文件:

# -*- mode: python ; coding: utf-8 -*-

a = Analysis(
    ['Bible_Search.py'],
    pathex=[],
    binaries=[],
    datas=[('data/KJV.txt', './data'), ('data/ASV.txt', './data'), ('data/YLT.txt', './data')],
    hiddenimports=[],
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    noarchive=False,
)
pyz = PYZ(a.pure)

exe = EXE(
    pyz,
    a.scripts,
    a.binaries,
    a.datas,
    [],
    name='Bible_Search',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    upx_exclude=[],
    runtime_tmpdir=None,
    console=False,
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
)
app = BUNDLE(
    exe,
    name='Bible_Search.app',
    icon=None,
    bundle_identifier=None,
)

Python脚本

import tkinter as tk
from tkinter import ttk
import tkinter.filedialog as filedialog
import re
import subprocess
import os
import sys

# Create global variables for search_button, search_entry, and validate_input
search_button = None
search_entry = None
validate_input = None
search_entry_additional = None
selected_version = None
selected_bible_var = None  
ignore_case_var = None
expand_var = None
result_text = None
notebook = None
results_tab = None

# Get the path to the directory containing the executable
def get_executable_dir():
    if getattr(sys, 'frozen', False):
        # Running as a compiled executable
        return os.path.dirname(sys.executable)
    else:
        # Running as a script
        return os.path.dirname(os.path.abspath(__file__))

# Function to validate the search entry
def validate_search_input(P):
    global search_button
    if not P:
        search_button.config(state=tk.DISABLED)
        return True
    search_button.config(state=tk.NORMAL)
    return True
    
# Create an event handler for when the selection changes
def on_version_selected(event):
    global selected_bible_var  # Add this line to access the global variable
    selected_bible_file = selected_version.get() + ".txt"
    set_selected_bible(selected_bible_var, selected_bible_file)

# In your set_selected_bible function, you can use the selected Bible file relative path
def set_selected_bible(selected_bible_var, bible_file):
    selected_bible_var.set(bible_file)

def remove_ansi_escape_codes(text):
    # Regular expression to match ANSI escape codes
    ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')

    # Remove ANSI escape codes from the text
    return ansi_escape.sub('', text)

def extract_bible_book(text):
    # Split the text by whitespace
    parts = text.split()

    # Initialize the book name
    book = ""

    # Iterate through the parts to find the book name
    for part in parts:
        if re.match(r'\d+:\d+', part):  # Check if part contains chapter and verse information
            break
        book += part + " "  # Append the part to the book name

    # Remove trailing whitespace and return the book name
    return book.strip()

def search_additional_text(verse, text_to_find):
    # Check if both "go to" and the additional text are in the line
    if text_to_find in verse:
        return True
    return False
    

# Function to open a file dialog and select the Bible text file
def select_bible_file():
    # Get the executable's directory
    executable_dir = get_executable_dir()

    # Construct the path to the Bible text file based on its location relative to the executable
    bible_file = os.path.join(executable_dir, "data", selected_version.get() + ".txt")

    # Set the selected Bible file
    set_selected_bible(selected_bible_var, bible_file)

def search_bible(selected_bible_var):
    global search_entry_additional, ignore_case_var, expand_var 

    search_string = search_entry.get()
    search_string_additional = search_entry_additional.get()
    ignore_case = ignore_case_var.get()
    expand_value = expand_var.get()

    bible_file = "./data/" + selected_bible_var.get()
    result_text.config(state=tk.NORMAL)
    result_text.delete(1.0, tk.END)

    bible_book = ""
    multi_search = True
    if search_string_additional == "":
        multi_search = False
    # Use the selected Bible file's relative path to load it
    with open(bible_file, 'r') as file:
        bible_text = file.read()

    options = ""
    if ignore_case:
        options += " -i"  # Use -i for case-insensitive searching
    if expand_value > 0:
        options += f" -x {expand_value}"

    # Construct the search pattern without line numbers
    search_pattern = f"grep {options} --color=always '{search_string}' '{bible_file}'"

    # Capture the result with ANSI escape codes
    import subprocess
    result = subprocess.getoutput(search_pattern)

    # Remove ANSI escape codes and insert the result into the Text widget
    result_without_ansi = remove_ansi_escape_codes(result)
        
    # Split the result into lines and insert them into the Text widget
    result_lines = result_without_ansi.splitlines()

    # Configure the Bible book and additional text for a red foreground
    result_text.tag_configure("red_bold_underline_text", foreground="red", font=("bold", 18), underline=True)
    result_text.tag_configure("red_match_text", foreground="red")

    #iterate through text finding lines with the search string(s) in them    
    for line in result_lines:
    # Extract Bible book
        new_bible_book = extract_bible_book(line)
    
        if multi_search == True and search_additional_text(line, search_string_additional):
            if bible_book != new_bible_book:
                bible_book = new_bible_book
                result_text.insert(tk.END, f'{bible_book}\n', "red_bold_underline_text")
            # Split the line into parts, highlighting the matched text
            parts = line.split(search_string_additional)
            for i, part in enumerate(parts):
                result_text.insert(tk.END, part)
                if i < len(parts) - 1:
                    # Insert the matched text with the "red_match_text" tag
                    result_text.insert(tk.END, search_string_additional, "red_match_text")
            result_text.insert(tk.END, '\n\n')
        elif multi_search == False:
            if bible_book != new_bible_book:
                bible_book = new_bible_book
                result_text.insert(tk.END, f'{bible_book}\n', "red_bold_underline_text")
            result_text.insert(tk.END, line + '\n\n')
    
    # Highlight the search text using Tkinter tags
    tag = "highlight"
    start = 1.0
    while True:
        start = result_text.search(search_string, start, stopindex=tk.END, nocase=ignore_case)
        if not start:
            break
        end = f"{start}+{len(search_string)}c"
        result_text.tag_add(tag, start, end)
        start = end

    # Configure the tag to apply the red foreground
    result_text.tag_configure(tag, foreground="red")
    result_text.config(state=tk.DISABLED)

    # Switch to the "Results" tab
    notebook.select(results_tab)

def main():
    global search_button, search_entry, validate_input, search_entry_additional, selected_version, selected_bible_var, ignore_case_var, expand_var, result_text, notebook, results_tab
    
    # Create the main Tkinter application
    root = tk.Tk()

    # Get the screen width
    screen_width = root.winfo_screenwidth()
    screen_height = root.winfo_screenheight()

    # Set the window's width to fit the screen and a fixed height
    window_width = screen_width - 100
    window_height = 600  # Adjust the height as needed

    # Calculate the x and y coordinates for the window to center it
    window_x = (screen_width - window_width) // 2
    window_y = (screen_height - window_height) // 2

    # Set the window's geometry
    root.geometry(f"{window_width}x{window_height}+{window_x}+{window_y}")
    
    # Create the main window
    # window = tk.Tk()
    root.title("Bible Search")

    # Create and set variables
    selected_bible_var = tk.StringVar()
    #selected_bible_var.set("KJV.txt")
    ignore_case_var = tk.BooleanVar()
    ignore_case_var.set(False)  # Set an initial value (True or False) as needed
    expand_var = tk.IntVar()
    expand_var.set(0)  # Set an initial value as needed

    # Create and configure the notebook
    notebook = ttk.Notebook(root)
    notebook.pack(fill=tk.BOTH, expand=True)

    # Create the search tab
    search_tab = ttk.Frame(notebook)
    notebook.add(search_tab, text="Search")
    # Create a frame for Bible version selection
    version_frame = ttk.Frame(search_tab)
    version_frame.pack()

    # Create a label for the dropdown
    version_label = ttk.Label(version_frame, text="Select Bible Version:")
    version_label.pack()
    # Create a Combobox (dropdown) for selecting the Bible version
    version_options = ["ASV", "KJV", "YLT"]  # Add more options if needed
    selected_version = tk.StringVar()
    selected_version.set("KJV")
    version_dropdown = ttk.Combobox(version_frame, textvariable=selected_version, values=version_options)
    version_dropdown.pack()
    # Set the initial Bible file path based on the selected version
    select_bible_file()

    # Bind the event to the on_version_selected function
    version_dropdown.bind("<<ComboboxSelected>>", lambda event, var=selected_version: on_version_selected(var))
    on_version_selected(selected_version)  # Call it to set the initial selected Bible file

    # Create and configure widgets on the search tab
    ignore_case_check = tk.Checkbutton(search_tab, text="Ignore Case", variable=ignore_case_var)
    ignore_case_check.pack()

    '''TEMPORARILY REMOVED UNTIL I CAN GET TO WORK
    # Create a Label and Spinbox for selecting the number of verses to expand
    expand_label = tk.Label(search_tab, text="Expand Verses Before and After:")
    expand_label.pack()
    expand_spinbox = tk.Spinbox(search_tab, from_=0, to=10, textvariable=expand_var)
    expand_spinbox.pack()
    '''

    # Create an entry widget for search input
    search_label = tk.Label(search_tab, text="Search String:")
    search_label.pack(pady=5)
    validate_input = root.register(validate_search_input)
    search_entry = tk.Entry(search_tab, validate="key", validatecommand=(validate_input, "%P"))
    search_entry.pack(pady=5)

    # Create an entry widget for alternate search input
    search_label_additional = tk.Label(search_tab, text="Additional Search String:")
    search_label_additional.pack(pady=5)
    #validate_input_additional = root.register(validate_search_input)
    search_entry_additional = tk.Entry(search_tab)
    search_entry_additional.pack(pady=5)

    # Create the "Search" button initially disabled
    search_button = tk.Button(search_tab, text="Search", command=lambda: search_bible(selected_bible_var), state=tk.DISABLED)
    search_button.pack(pady=10)

    # Bind the "Enter" key to trigger the search function
    search_entry.bind('<Return>', lambda event=None: search_button.invoke())

    # Bind the "Enter" key to trigger the search function for the additional search entry field
    search_entry_additional.bind('<Return>', lambda event=None: search_button.invoke())

    # Create the results tab
    results_tab = ttk.Frame(notebook)
    notebook.add(results_tab, text="Results")
    
    # Create and configure the results text widget
    result_text = tk.Text(results_tab, state=tk.DISABLED, wrap=tk.WORD)
    result_text.pack(fill=tk.BOTH, expand=True)

    #if not os.path.exists("./data/KJV.txt"):
        #raise FileNotFoundError("The file ./data/KJV.txt does not exist.")

    # Start the GUI main loop
    root.mainloop()

if __name__ == "__main__":
    main()

文本文件,例如KJV.txt,只是专门制作的文本文件,以处理此代码。以下是KJV.txt的前100行左右的示例

KJV
King James Bible: Pure Cambridge Edition - Text courtesy of www.BibleProtector.com
Genesis 1:1 In the beginning God created the heaven and the earth.
Genesis 1:2 And the earth was without form, and void; and darkness was upon the face of the deep. And the Spirit of God moved upon the face of the waters.
Genesis 1:3 And God said, Let there be light: and there was light.
Genesis 1:4 And God saw the light, that it was good: and God divided the light from the darkness.
Genesis 1:5 And God called the light Day, and the darkness he called Night. And the evening and the morning were the first day.
Genesis 1:6 And God said, Let there be a firmament in the midst of the waters, and let it divide the waters from the waters.
Genesis 1:7 And God made the firmament, and divided the waters which were under the firmament from the waters which were above the firmament: and it was so.
Genesis 1:8 And God called the firmament Heaven. And the evening and the morning were the second day.
Genesis 1:9 And God said, Let the waters under the heaven be gathered together unto one place, and let the dry land appear: and it was so.
Genesis 1:10 And God called the dry land Earth; and the gathering together of the waters called he Seas: and God saw that it was good.
Genesis 1:11 And God said, Let the earth bring forth grass, the herb yielding seed, and the fruit tree yielding fruit after his kind, whose seed is in itself, upon the earth: and it was so.
Genesis 1:12 And the earth brought forth grass, and herb yielding seed after his kind, and the tree yielding fruit, whose seed was in itself, after his kind: and God saw that it was good.
Genesis 1:13 And the evening and the morning were the third day.
Genesis 1:14 And God said, Let there be lights in the firmament of the heaven to divide the day from the night; and let them be for signs, and for seasons, and for days, and years:
Genesis 1:15 And let them be for lights in the firmament of the heaven to give light upon the earth: and it was so.
Genesis 1:16 And God made two great lights; the greater light to rule the day, and the lesser light to rule the night: he made the stars also.
Genesis 1:17 And God set them in the firmament of the heaven to give light upon the earth,
Genesis 1:18 And to rule over the day and over the night, and to divide the light from the darkness: and God saw that it was good.
Genesis 1:19 And the evening and the morning were the fourth day.
Genesis 1:20 And God said, Let the waters bring forth abundantly the moving creature that hath life, and fowl that may fly above the earth in the open firmament of heaven.
Genesis 1:21 And God created great whales, and every living creature that moveth, which the waters brought forth abundantly, after their kind, and every winged fowl after his kind: and God saw that it was good.
Genesis 1:22 And God blessed them, saying, Be fruitful, and multiply, and fill the waters in the seas, and let fowl multiply in the earth.
Genesis 1:23 And the evening and the morning were the fifth day.
Genesis 1:24 And God said, Let the earth bring forth the living creature after his kind, cattle, and creeping thing, and beast of the earth after his kind: and it was so.
Genesis 1:25 And God made the beast of the earth after his kind, and cattle after their kind, and every thing that creepeth upon the earth after his kind: and God saw that it was good.
Genesis 1:26 And God said, Let us make man in our image, after our likeness: and let them have dominion over the fish of the sea, and over the fowl of the air, and over the cattle, and over all the earth, and over every creeping thing that creepeth upon the earth.
Genesis 1:27 So God created man in his own image, in the image of God created he him; male and female created he them.
Genesis 1:28 And God blessed them, and God said unto them, Be fruitful, and multiply, and replenish the earth, and subdue it: and have dominion over the fish of the sea, and over the fowl of the air, and over every living thing that moveth upon the earth.
Genesis 1:29 And God said, Behold, I have given you every herb bearing seed, which is upon the face of all the earth, and every tree, in the which is the fruit of a tree yielding seed; to you it shall be for meat.
Genesis 1:30 And to every beast of the earth, and to every fowl of the air, and to every thing that creepeth upon the earth, wherein there is life, I have given every green herb for meat: and it was so.
Genesis 1:31 And God saw every thing that he had made, and, behold, it was very good. And the evening and the morning were the sixth day.
Genesis 2:1 Thus the heavens and the earth were finished, and all the host of them.
Genesis 2:2 And on the seventh day God ended his work which he had made; and he rested on the seventh day from all his work which he had made.
Genesis 2:3 And God blessed the seventh day, and sanctified it: because that in it he had rested from all his work which God created and made.
Genesis 2:4 These are the generations of the heavens and of the earth when they were created, in the day that the LORD God made the earth and the heavens,
Genesis 2:5 And every plant of the field before it was in the earth, and every herb of the field before it grew: for the LORD God had not caused it to rain upon the earth, and there was not a man to till the ground.
Genesis 2:6 But there went up a mist from the earth, and watered the whole face of the ground.
Genesis 2:7 And the LORD God formed man of the dust of the ground, and breathed into his nostrils the breath of life; and man became a living soul.
Genesis 2:8 And the LORD God planted a garden eastward in Eden; and there he put the man whom he had formed.
Genesis 2:9 And out of the ground made the LORD God to grow every tree that is pleasant to the sight, and good for food; the tree of life also in the midst of the garden, and the tree of knowledge of good and evil.
Genesis 2:10 And a river went out of Eden to water the garden; and from thence it was parted, and became into four heads.
Genesis 2:11 The name of the first is Pison: that is it which compasseth the whole land of Havilah, where there is gold;
Genesis 2:12 And the gold of that land is good: there is bdellium and the onyx stone.
Genesis 2:13 And the name of the second river is Gihon: the same is it that compasseth the whole land of Ethiopia.
Genesis 2:14 And the name of the third river is Hiddekel: that is it which goeth toward the east of Assyria. And the fourth river is Euphrates.
Genesis 2:15 And the LORD God took the man, and put him into the garden of Eden to dress it and to keep it.
Genesis 2:16 And the LORD God commanded the man, saying, Of every tree of the garden thou mayest freely eat:
Genesis 2:17 But of the tree of the knowledge of good and evil, thou shalt not eat of it: for in the day that thou eatest thereof thou shalt surely die.
Genesis 2:18 And the LORD God said, It is not good that the man should be alone; I will make him an help meet for him.
Genesis 2:19 And out of the ground the LORD God formed every beast of the field, and every fowl of the air; and brought them unto Adam to see what he would call them: and whatsoever Adam called every living creature, that was the name thereof.
Genesis 2:20 And Adam gave names to all cattle, and to the fowl of the air, and to every beast of the field; but for Adam there was not found an help meet for him.
Genesis 2:21 And the LORD God caused a deep sleep to fall upon Adam, and he slept: and he took one of his ribs, and closed up the flesh instead thereof;
Genesis 2:22 And the rib, which the LORD God had taken from man, made he a woman, and brought her unto the man.
Genesis 2:23 And Adam said, This is now bone of my bones, and flesh of my flesh: she shall be called Woman, because she was taken out of Man.
Genesis 2:24 Therefore shall a man leave his father and his mother, and shall cleave unto his wife: and they shall be one flesh.
Genesis 2:25 And they were both naked, the man and his wife, and were not ashamed.
Genesis 3:1 Now the serpent was more subtil than any beast of the field which the LORD God had made. And he said unto the woman, Yea, hath God said, Ye shall not eat of every tree of the garden?
Genesis 3:2 And the woman said unto the serpent, We may eat of the fruit of the trees of the garden:
Genesis 3:3 But of the fruit of the tree which is in the midst of the garden, God hath said, Ye shall not eat of it, neither shall ye touch it, lest ye die.
Genesis 3:4 And the serpent said unto the woman, Ye shall not surely die:
Genesis 3:5 For God doth know that in the day ye eat thereof, then your eyes shall be opened, and ye shall be as gods, knowing good and evil.
Genesis 3:6 And when the woman saw that the tree was good for food, and that it was pleasant to the eyes, and a tree to be desired to make one wise, she took of the fruit thereof, and did eat, and gave also unto her husband with her; and he did eat.
Genesis 3:7 And the eyes of them both were opened, and they knew that they were naked; and they sewed fig leaves together, and made themselves aprons.
Genesis 3:8 And they heard the voice of the LORD God walking in the garden in the cool of the day: and Adam and his wife hid themselves from the presence of the LORD God amongst the trees of the garden.
Genesis 3:9 And the LORD God called unto Adam, and said unto him, Where art thou?
Genesis 3:10 And he said, I heard thy voice in the garden, and I was afraid, because I was naked; and I hid myself.
Genesis 3:11 And he said, Who told thee that thou wast naked? Hast thou eaten of the tree, whereof I commanded thee that thou shouldest not eat?
Genesis 3:12 And the man said, The woman whom thou gavest to be with me, she gave me of the tree, and I did eat.
Genesis 3:13 And the LORD God said unto the woman, What is this that thou hast done? And the woman said, The serpent beguiled me, and I did eat.
Genesis 3:14 And the LORD God said unto the serpent, Because thou hast done this, thou art cursed above all cattle, and above every beast of the field; upon thy belly shalt thou go, and dust shalt thou eat all the days of thy life:
Genesis 3:15 And I will put enmity between thee and the woman, and between thy seed and her seed; it shall bruise thy head, and thou shalt bruise his heel.
Genesis 3:16 Unto the woman he said, I will greatly multiply thy sorrow and thy conception; in sorrow thou shalt bring forth children; and thy desire shall be to thy husband, and he shall rule over thee.
Genesis 3:17 And unto Adam he said, Because thou hast hearkened unto the voice of thy wife, and hast eaten of the tree, of which I commanded thee, saying, Thou shalt not eat of it: cursed is the ground for thy sake; in sorrow shalt thou eat of it all the days of thy life;
Genesis 3:18 Thorns also and thistles shall it bring forth to thee; and thou shalt eat the herb of the field;
Genesis 3:19 In the sweat of thy face shalt thou eat bread, till thou return unto the ground; for out of it wast thou taken: for dust thou art, and unto dust shalt thou return.
Genesis 3:20 And Adam called his wife’s name Eve; because she was the mother of all living.
Genesis 3:21 Unto Adam also and to his wife did the LORD God make coats of skins, and clothed them.
Genesis 3:22 And the LORD God said, Behold, the man is become as one of us, to know good and evil: and now, lest he put forth his hand, and take also of the tree of life, and eat, and live for ever:
Genesis 3:23 Therefore the LORD God sent him forth from the garden of Eden, to till the ground from whence he was taken.
Genesis 3:24 So he drove out the man; and he placed at the east of the garden of Eden Cherubims, and a flaming sword which turned every way, to keep the way of the tree of life.

我试着调试Python,但无法生成日志文件。出于无奈,我把数据文件目录放在两个不同的位置,看看可执行文件是否找不到数据文件夹(见下面的文件夹结构)。我也试着把它放在dist目录中,但这也没有解决问题。我怀疑我的.app在执行过程中创建了一个临时文件夹,所以我包含了'get_executable_dir()'函数,但这也不起作用。无论如何,我现在已经超出了我的知识范围来解决它了。
我甚至在py2app中尝试了这个,以防它是Mac的东西,但我遇到了类似的问题。我在M2 Pro上运行macOS Ventura 13.2.1。

/tool_directory
├── dist
│   ├── Bible_Search      \<Unix Executable File - created by PyInstaller\>
│   ├── Bible_Search.app
│   │   └── Contents
│   │       └── MacOS
│   │           └── \<Other Folders created by PyInstaller\>
│   │           └── Bible_Search
│   │           └── data
│   │               └── KJV.txt
│   │               └── ASV.txt
│   │               └── YLT.txt
├── data
│   ├── KJV.txt
│   ├── ASV.txt
│   ├── YLT.txt
├── build
│   └── \<Other Folders created by PyInstaller\>
├── Bible_Search.py
└── Bible_Search.spec
50few1ms

50few1ms1#

好吧,我从这个What is sys._MEIPASS in Python问题中弄明白了。我错误地搜索了Python正在创建的临时文件夹。
原代码:

def get_executable_dir():
if getattr(sys, 'frozen', False):
    return os.path.dirname(sys.executable)
else:
    return os.path.dirname(os.path.abspath(__file__))

更正代码:

def get_executable_dir():
if getattr(sys, 'frozen', False):
    return sys._MEIPASS
else:
    return os.path.dirname(os.path.abspath(__file__))

相关问题