Python - То, что я не должен делать? - PullRequest
4 голосов
/ 23 декабря 2009

У меня есть несколько вопросов о лучших практиках в Python. Не так давно я сделал бы что-то подобное с моим кодом:

...
junk_block = "".join(open("foo.txt","rb").read().split())
...

Я больше не делаю этого, потому что вижу, что код становится труднее читать, но будет ли он работать медленнее, если я разделю операторы следующим образом:

f_obj = open("foo.txt", "rb")
f_data = f_obj.read()
f_data_list = f_data.split()
junk_block = "".join(f_data_list)

Я также заметил, что ничто не мешает вам выполнить «импорт» внутри функционального блока, есть ли причина, почему я должен это делать?

Ответы [ 6 ]

21 голосов
/ 23 декабря 2009

Пока вы находитесь внутри функции (, а не на верхнем уровне модуля), присваивание промежуточных результатов локальным голым именам обходится по существу незначительно (на верхнем уровне модуля, присваивая «локальному»). Голые имена подразумевают отток на dict - модуль __dict__ - и измеримо дороже, чем это было бы внутри функции: средство никогда не должно иметь "существенный" код на верхнем уровне модуля ... всегда прятать существенный код в пределах функция -!)

.

Общая философия Python включает в себя «квартира лучше вложенной» - и это включает в себя «вложенные» выражения. Глядя на ваш оригинальный пример ...:

junk_block = "".join(open("foo.txt","rb").read().split())

представляет еще одну важную проблему: когда этот файл закрывается? В CPython сегодня вам не нужно беспокоиться - на практике подсчет ссылок гарантирует своевременное закрытие. Но большинство других реализаций Python (Jython на JVM, IronPython на .NET, PyPy на всех видах бэкэндов, pynie на Parrot, Unladen Swallow на LLVM, если и когда оно созревает согласно опубликованному плану, ...), не не гарантирует использование подсчета ссылок - может быть задействовано много стратегий сбора мусора со всеми другими преимуществами.

Без какой-либо гарантии подсчета ссылок (и даже в CPython это всегда считалось артефактом реализации, не частью семантики языка!), Вы могли бы исчерпать ресурсы, выполняя такие "открытые, но нет" код close в узком цикле - сборка мусора запускается из-за нехватки памяти и не учитывает другие ограниченные ресурсы, такие как файловые дескрипторы. Начиная с версии 2.6 (и 2.5, с «импортом из будущего»), Python предлагает отличное решение с помощью подхода RAII («получение ресурсов - инициализация»), поддерживаемого оператором with:

with open("foo.txt","rb") as f:
  junk_block = "".join(f.read().split())

- это наименее «неопубликованный» способ, который обеспечит своевременное закрытие файла во всех совместимых версиях Python. Более сильная семантика делает это предпочтительным.

Помимо обеспечения правильной и разумной семантики ;-) не так уж много выбора между вложенными и плоскими версиями выражения, подобного этому. Учитывая задачу «удалить все пробелы из содержимого файла», я хотел бы попробовать альтернативные подходы, основанные на re и методе строк .translate (последний, особенно в Python 2. *, является часто это самый быстрый способ удалить все символы из определенного набора!), прежде чем остановиться на подходе «разделяй и возвращайся», если он окажется быстрее - но это действительно совсем другая проблема; -).

4 голосов
/ 23 декабря 2009

Во-первых, на самом деле нет причины, по которой вы не должны использовать первый пример - он был бы вполне читабелен, поскольку он лаконичен в отношении того, что он делает. Нет причин разбивать его, так как это просто линейная комбинация вызовов.

Во-вторых, import в функциональном блоке полезен, если есть определенная библиотечная функция, которая вам нужна только в этой функции - поскольку область действия импортированного символа - это только блок, в котором он импортируется, если вы когда-либо используете Что-то один раз, вы можете просто импортировать его туда, где вам это нужно, и вам не придется беспокоиться о конфликтах имен в других функциях. Это особенно удобно для операторов from X import Y, поскольку Y не будет квалифицироваться по имени содержащего его модуля и, следовательно, может конфликтовать с функцией с аналогичным именем в другом модуле, используемом в другом месте.

3 голосов
/ 23 декабря 2009

из PEP 8 (что стоит прочитать в любом случае)

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

2 голосов
/ 23 декабря 2009

Эта строка имеет такой же результат, как этот:

junk_block = open ("foo.txt", "rb"). Read (). Replace ('', '')

В вашем примере вы разбиваете слова текста на список слов, а затем соединяете их обратно без пробелов. В приведенном выше примере вместо этого используется метод str.replace ().

Различия:

Ваш встраивает файловый объект в память, строит строку в памяти, читая ее, строит список в памяти, разделяя строку, строит новую строку, присоединяясь к списку.

Мой встраивает файловый объект в память, строит строку в памяти, читая ее, встраивает в память новую строку, заменяя пробелы.

Вы можете видеть, что в новом варианте используется немного меньше оперативной памяти, но используется больше процессора. В некоторых случаях ОЗУ является более ценным, и поэтому, когда этого можно избежать, неохотно расходуют память.

Большая часть памяти будет сразу же собрана, но несколько пользователей одновременно будут загружать ОЗУ.

0 голосов
/ 23 декабря 2009

Я думаю, что эти два кода читабельны. Я (и это просто вопрос личного стиля), вероятно, воспользуюсь первым, добавив строку комментария, что-то вроде: «Откройте файл и преобразуйте данные внутри в список»

Кроме того, бывают случаи, когда я использую второе, может быть, не так отдельно, но что-то вроде

f_data = open("foo.txt", "rb").read()
f_data_list = f_data.split()
junk_block = "".join(f_data_list)

Но тогда я даю больше сущностей каждой операции, что может быть важно в потоке кода. Я думаю, что важно, чтобы вы чувствовали себя комфортно и не думали, что код будет трудно понять в будущем.

Определенно, код не будет (по крайней мере, намного) медленнее, поскольку единственная «перегрузка», которую вы делаете, заключается в преобразовании результатов в значения.

0 голосов
/ 23 декабря 2009

Если вы хотите узнать, медленнее ли ваш второй фрагмент кода, быстрый способ выяснить это - просто использовать timeit . Я не ожидал, что будет такая большая разница, поскольку они кажутся довольно эквивалентными.

Вы также должны спросить, имеет ли значение разница в производительности в рассматриваемом коде. Часто читаемость имеет большую ценность, чем производительность.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...