Откройте документ с помощью приложения ОС по умолчанию на Python, как в Windows, так и в Mac OS

126

Мне нужно иметь возможность открывать документ с помощью приложения по умолчанию в Windows и Mac OS. По сути, я хочу сделать то же самое, что происходит, когда вы дважды щелкаете значок документа в проводнике или Finder. Как лучше всего это сделать в Python?

Абдулла Джибали
источник
9
С 2008 года возникла проблема с включением этого в стандартную библиотеку в трекере Python: bugs.python.org/issue3177
Рам Рахум,

Ответы:

77

openи startявляются для этого средствами интерпретатора команд для Mac OS / X и Windows соответственно.

Чтобы вызвать их из Python, вы можете использовать subprocessмодуль или os.system().

Вот соображения относительно того, какой пакет использовать:

  1. Вы можете позвонить им через os.system, что работает, но ...

    Экранирование: os.system работает только с именами файлов, в которых нет пробелов или других метасимволов оболочки в имени пути (например A:\abc\def\a.txt), иначе их нужно экранировать. Есть shlex.quoteдля Unix-подобных систем, но ничего стандартного для Windows. Возможно, посмотрите также python, windows: разбор командных строк с помощью shlex

    • Mac OS X: os.system("open " + shlex.quote(filename))
    • Окна: os.system("start " + filename)куда, собственно говоря filename, тоже нужно убежать.
  2. Вы также можете вызвать их через subprocessмодуль, но ...

    Для Python 2.7 и новее просто используйте

    subprocess.check_call(['open', filename])

    В Python 3.5+ вы можете эквивалентно использовать немного более сложный, но также несколько более универсальный

    subprocess.run(['open', filename], check=True)

    Если вам необходимо обеспечить полную совместимость с Python 2.4, вы можете использовать subprocess.call()и реализовать свою собственную проверку ошибок:

    try:
        retcode = subprocess.call("open " + filename, shell=True)
        if retcode < 0:
            print >>sys.stderr, "Child was terminated by signal", -retcode
        else:
            print >>sys.stderr, "Child returned", retcode
    except OSError, e:
        print >>sys.stderr, "Execution failed:", e

    Итак, каковы преимущества использования subprocess?

    • Безопасность: Теоретически это более безопасно, но на самом деле нам нужно так или иначе выполнить командную строку; в любой среде нам нужна среда и службы для интерпретации, получения путей и т. д. Ни в том, ни в другом случае мы не выполняем произвольный текст, поэтому он не имеет присущей 'filename ; rm -rf /'проблеме «но вы можете печатать », и если имя файла может быть повреждено, использование subprocess.callдает нам небольшую дополнительную защиту.
    • Обработка ошибок: на самом деле это не дает нам больше возможностей для обнаружения ошибок, retcodeв любом случае мы все еще зависим от них; но поведение явного вызова исключения в случае ошибки, безусловно, поможет вам заметить сбой (хотя в некоторых сценариях обратная трассировка может оказаться не более полезной, чем простое игнорирование ошибки).
    • Создает (неблокирующий) подпроцесс : нам не нужно ждать дочерний процесс, так как мы по формулировке задачи запускаем отдельный процесс.

    На возражение «Но subprocessпредпочтительнее». Однако os.system()он не является устаревшим, и в некотором смысле это самый простой инструмент для этой конкретной работы. Вывод: использование os.system()- тоже правильный ответ.

    Заметное недостатком является то , что Windows , startкоманда требует , чтобы передать в shell=Trueкоторый сводит на нет большую часть преимуществ использования subprocess.

Чарли Мартин
источник
2
В зависимости от того, где filenameпоявляется форма, это прекрасный пример того, почему os.system () небезопасен и плох. подпроцесс лучше.
Devin Jeanpierre
6
Ответ Ника мне понравился. Ничего не мешало. Объяснение вещей на неправильных примерах нелегко.
Девин Жанпьер
2
Это менее безопасно и менее гибко, чем использование подпроцесса. Мне это кажется неправильным.
Девин Жанпьер,
8
Конечно, это важно. В этом разница между хорошим ответом и плохим ответом (или ужасным ответом). Сами документы для os.system () говорят: «Используйте модуль подпроцесса». Что еще нужно? Для меня этого достаточно.
Devin Jeanpierre
20
Мне немного не хочется возобновлять это обсуждение, но я думаю, что раздел «Позднее обновление» совершенно неверен. Проблема os.system()заключается в том, что он использует оболочку (и вы не выполняете здесь экранирование оболочки, поэтому плохие вещи будут происходить с совершенно допустимыми именами файлов, которые содержат метасимволы оболочки). Причина, по которой subprocess.call()это предпочтительнее, заключается в том, что у вас есть возможность обойти оболочку с помощью subprocess.call(["open", filename]). Это работает для всех допустимых имен файлов и не представляет уязвимости внедрения оболочки даже для ненадежных имен файлов.
Sven Marnach
151

Используйте subprocessмодуль, доступный в Python 2.4+, os.system()поэтому вам не нужно иметь дело с экранированием оболочки.

import subprocess, os, platform
if platform.system() == 'Darwin':       # macOS
    subprocess.call(('open', filepath))
elif platform.system() == 'Windows':    # Windows
    os.startfile(filepath)
else:                                   # linux variants
    subprocess.call(('xdg-open', filepath))

Двойные скобки нужны потому, что subprocess.call()в качестве первого аргумента требуется последовательность, поэтому мы используем здесь кортеж. В системах Linux с Gnome также есть gnome-openкоманда, которая делает то же самое, но xdg-openявляется стандартом Free Desktop Foundation и работает в средах рабочего стола Linux.

Ник
источник
5
Использование start в subprocess.call () не работает в Windows - start на самом деле не является исполняемым файлом.
Томаш Седович
4
nitpick: на всех linuxen (и, я думаю, на большинстве BSD) вы должны использовать xdg-open- linux.die.net/man/1/xdg-open
gnud
6
start в Windows - это команда оболочки, а не исполняемый файл. Вы можете использовать subprocess.call (('start', filepath), shell = True), хотя, если вы выполняете в оболочке, вы также можете использовать os.system.
Питер Грэм
Я побежал, xdg-open test.pyи он открыл для меня диалог загрузки firefox. В чем дело? Я использую manjaro linux.
Джейсон
1
@Jason Похоже, ваша xdg-openконфигурация запуталась, но это не совсем то, что мы можем устранить в комментариях. Может быть, см. Unix.stackexchange.com/questions/36380/…
tripleee
44

Я предпочитаю:

os.startfile(path, 'open')

Обратите внимание, что этот модуль поддерживает имена файлов, в папках и файлах которых есть пробелы, например

A:\abc\folder with spaces\file with-spaces.txt

( python docs ) 'open' добавлять не нужно (это значение по умолчанию). В документах специально упоминается, что это похоже на двойной щелчок по значку файла в проводнике Windows.

Это решение только для окон.

DrBloodmoney
источник
Спасибо. Я не заметил доступности, так как в документации он добавлен в последний абзац. В большинстве других разделов примечание о доступности занимает отдельную строку.
DrBloodmoney
В Linux по какой-то причине вместо того, чтобы вызывать ошибку, startfileфункция даже не существует, а это означает, что пользователи получат сбивающее с толку сообщение об ошибке об отсутствующей функции. Вы можете проверить платформу, чтобы избежать этого.
cz
39

Просто для полноты (это не было вопросом), xdg-open сделает то же самое в Linux.

дР.
источник
6
+1 Обычно респонденты не должны отвечать на вопросы, которые не задавали, но в данном случае я думаю, что это очень актуально и полезно для сообщества SO в целом.
demongolem
искал это
nurettin
25
import os
import subprocess

def click_on_file(filename):
    '''Open document with default application in Python.'''
    try:
        os.startfile(filename)
    except AttributeError:
        subprocess.call(['open', filename])
nosklo
источник
2
Ха, я не знал о startfile. Было бы неплохо, если бы версии Python для Mac и Linux имели схожую семантику.
Ник
3
Соответствующая ошибка python: bugs.python.org/issue3177 - предоставьте хороший патч, и он может быть принят =)
gnud
Команда
xdg
21

Если вам нужно использовать эвристический метод, вы можете подумать webbrowser.
Это стандартная библиотека, и, несмотря на название, она также пытается открывать файлы:

Обратите внимание, что на некоторых платформах попытка открыть имя файла с помощью этой функции может сработать и запустить программу, связанную с операционной системой. Однако это не поддерживается и не переносится. ( Ссылка )

Я попробовал этот код, и он отлично работал в Windows 7 и Ubuntu Natty:

import webbrowser
webbrowser.open("path_to_file")

Этот код также отлично работает в Windows XP Professional с использованием Internet Explorer 8.

etuardu
источник
3
Насколько я могу судить, это, безусловно, лучший ответ. Кажется кроссплатформенным, и нет необходимости проверять, какая платформа используется, или импортировать ОС, платформу.
polandeer
2
@jonathanrocher: Я вижу поддержку Mac в исходном коде . Он использует open locationтам, что должно работать, если вы укажете путь как действительный URL-адрес.
jfs
1
macOS:import webbrowser webbrowser.open("file:///Users/nameGoesHere/Desktop/folder/file.py")
Дэниел Спрингер,
3
docs.python.org/3/library/webbrowser.html#webbrowser.open «Обратите внимание, что на некоторых платформах попытка открыть имя файла с помощью [webbrowser.open (url)] может сработать и запустить программу, связанную с операционной системой. Однако , это не поддерживается и не переносится ".
nyanpasu64
6

Если вы хотите пойти своим subprocess.call()путем, в Windows это должно выглядеть так:

import subprocess
subprocess.call(('cmd', '/C', 'start', '', FILE_NAME))

Вы не можете просто использовать:

subprocess.call(('start', FILE_NAME))

потому что start это не исполняемый файл, а команда cmd.exeпрограммы. Это работает:

subprocess.call(('cmd', '/C', 'start', FILE_NAME))

но только если в FILE_NAME нет пробелов.

Хотя subprocess.callметод en правильно цитирует параметры, startкоманда имеет довольно странный синтаксис, где:

start notes.txt

делает что-то еще, кроме:

start "notes.txt"

Первая строка в кавычках должна устанавливать заголовок окна. Чтобы он работал с пробелами, нам нужно сделать:

start "" "my notes.txt"

что и делает код сверху.

Томаш Седович
источник
5

Start не поддерживает длинные имена путей и пробелы. Вы должны преобразовать его в 8,3-совместимые пути.

import subprocess
import win32api

filename = "C:\\Documents and Settings\\user\\Desktop\file.avi"
filename_short = win32api.GetShortPathName(filename)

subprocess.Popen('start ' + filename_short, shell=True )

Файл должен существовать для работы с вызовом API.

bFloch
источник
1
Другой обходной путь, чтобы дать ему название в кавычки, напримерstart "Title" "C:\long path to\file.avi"
user3364825
3

Я довольно опаздываю, но вот решение, использующее windows api. Это всегда открывает связанное приложение.

import ctypes

shell32 = ctypes.windll.shell32
file = 'somedocument.doc'

shell32.ShellExecuteA(0,"open",file,0,0,5)

Множество магических констант. Первый ноль - это hwnd текущей программы. Может быть нулевым. Два других нуля - это необязательные параметры (параметры и каталог). 5 == SW_SHOW, он указывает, как запускать приложение. Прочтите документацию по API ShellExecute для получения дополнительной информации.

Джордж
источник
1
как это сравнивается с os.startfile(file)?
jfs
2

в mac os вы можете назвать "открыть"

import os
os.popen("open myfile.txt")

это откроет файл с помощью TextEdit или любого другого приложения, установленного по умолчанию для этого типа файла

lcvinny
источник
2

Если вы хотите указать приложение для открытия файла в Mac OS X, используйте это: os.system("open -a [app name] [file name]")


источник
2

В Windows 8.1 ниже работали, в то время как другие способы с subprocess.callошибками с path имеют пробелы.

subprocess.call('cmd /c start "" "any file path with spaces"')

Используя этот и другие ответы ранее, вот встроенный код, который работает на нескольких платформах.

import sys, os, subprocess
subprocess.call(('cmd /c start "" "'+ filepath +'"') if os.name is 'nt' else ('open' if sys.platform.startswith('darwin') else 'xdg-open', filepath))
Ch.Idea
источник
2

os.startfile(path, 'open')под Windows это хорошо, потому что, когда в каталоге существуют пробелы, os.system('start', path_name)приложение не может правильно открыть, а когда в каталоге существует i18n, os.systemнеобходимо изменить Unicode на кодек консоли в Windows.

BearPy
источник