Использование pywin32 при установке программы в Program Files
Я пытаюсь создать установщик для программы, которая использует привязки pywin32 для редактирования некоторых таблиц Excel. Я создал исполняемый файл, используя py2exe, и все работает при запуске исполняемого файла из папки на рабочем столе. Тем не менее, я хочу иметь возможность распространять один установочный файл, который установит программу в C:\Program Files\ или эквивалентную папку в любой системе. Я также преуспел в этом, однако, когда используются привязки pywin32, они создают временные файлы, где бы ни находился рабочий каталог.
Это очень проблематично, так как в более новых версиях Windows это сделано, поэтому только администраторы могут писать в эти каталоги. Из-за этого, когда приложение запускается из этих каталогов, оно завершается с ошибкой:
WindowsError: [Error 5] Access is denied: 'C:\\Program Files (x86)\\DataPlotter\\.\\win32com\\gen_py\
\00020813-0000-0000-C000-000000000046x0x1x6'
Изменение приложения для запуска с правами администратора является плохим решением, поскольку оно может создавать уязвимости.
Кто-нибудь знает о решении этой проблемы или о том, как изменить расположение, которое привязки pywin32 используют в качестве временного расположения файла.
2 ответа
Это глупое хакерское решение, но этой проблемы можно избежать, выполнив обход на pywin32.
Переключив текущий рабочий каталог на каталог, который гарантированно безопасен для записи, такой как временный каталог, можно избежать проблемы.
#Save the current working directory and then switch back once
#excel has been started. This is so pywin32 does not ruin everything
#by trying to write in the current directory without the proper
#permission. This mainly happens (for me) when the program is installed in
#program files which requires administrator permissions to write to.
import os
import tempfile
import win32com.client
cwd = os.getcwd()
tdir = tempfile.gettempdir()
os.chdir(tdir)
self.xl = win32com.client.gencache.EnsureDispatch("Excel.Application")
os.chdir(cwd)
NB. Переключатель в конце обратно в исходный рабочий каталог не является необходимым, но он будет работать, если вам это нужно (как я сделал).
Мартино предложил более надежный способ сделать это ниже, используя контекстные менеджеры:
from contextlib import contextmanager
@contextmanager
def tempManager(self):
cwd = os.getcwd()
tdir = tempfile.gettempdir()
os.chdir(tdir)
yield
os.chdir(cwd)
with self.tempManager():
self.xl = win32com.client.gencache.EnsureDispatch("Excel.Application")
Как я описал в своем комментарии к вашему ответу о том, как превратить ваш код в менеджер контекста, используя contextlib.contextmanager
Декоратор был немного упрощен, если вы хотите восстановить предыдущий текущий каталог, даже когда возникает необработанное исключение. Чтобы это произошло, необходимо также добавить try
...finally
пункт вокруг yield
(увидеть ниже).
Кроме того, я думаю, что было бы даже лучше сделать ее автономной функцией, а не методом некоторого класса, а также передать ей каталог, в который можно переключаться в качестве аргумента - оба из которых делают ее более универсальной и легко используемой повторно. Я назвал это pushd()
из-за сходства с командой оболочки Windows и Unix с тем же именем.
from contextlib import contextmanager
import os
import tempfile
@contextmanager
def pushd(dir=None):
""" Context manager which saves the current working directory before
changing either to the one passed or the default folder for temporary
files and then restores the former after the controlled suite of
statements have executed. This will happened even if an unhandled
exception occurs within the block. """
cwd = os.getcwd()
os.chdir(dir if dir is not None else tempfile.gettempdir())
try:
yield
finally:
os.chdir(cwd)
# sample usage
with pushd():
self.xl = win32com.client.gencache.EnsureDispatch("Excel.Application")
Также может быть полезно сделать yield cwd
вместо ничего, что позволило бы любой необязательный as
переменная для получения значения, указывающего предыдущий текущий рабочий каталог для возможной ссылки внутри блока.