Операторы импорта должны всегда быть наверху модуля?

Что такое NullPointerException?

Хорошим местом для начала является JavaDocs . Они охватывают это:

Брошено, когда приложение пытается использовать null в случае, когда требуется объект. К ним относятся:

  • Вызов метода экземпляра нулевого объекта.
  • Доступ или изменение поля нулевого объекта.
  • Выполнение длины null, как если бы это был массив.
  • Доступ или изменение слотов с нулевым значением, как если бы это был массив.
  • Бросать нуль, как если бы это было значение Throwable.

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

blockquote>

Также, если вы попытаетесь использовать нулевую ссылку с synchronized, который также выдаст это исключение, за JLS :

SynchronizedStatement:
    synchronized ( Expression ) Block
  • В противном случае, если значение выражения равно null, NullPointerException.
blockquote>

Как это исправить?

Итак, у вас есть NullPointerException. Как вы это исправите? Возьмем простой пример, который выдает NullPointerException:

public class Printer {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public void print() {
        printString(name);
    }

    private void printString(String s) {
        System.out.println(s + " (" + s.length() + ")");
    }

    public static void main(String[] args) {
        Printer printer = new Printer();
        printer.print();
    }
}

Идентифицирует нулевые значения

. Первый шаг - точно определить , значения которого вызывают исключение . Для этого нам нужно выполнить некоторую отладку. Важно научиться читать stacktrace . Это покажет вам, где было выбрано исключение:

Exception in thread "main" java.lang.NullPointerException
    at Printer.printString(Printer.java:13)
    at Printer.print(Printer.java:9)
    at Printer.main(Printer.java:19)

Здесь мы видим, что исключение выбрано в строке 13 (в методе printString). Посмотрите на строку и проверьте, какие значения равны нулю, добавив протоколирующие операторы или используя отладчик . Мы обнаруживаем, что s имеет значение null, а вызов метода length на него вызывает исключение. Мы видим, что программа перестает бросать исключение, когда s.length() удаляется из метода.

Трассировка, где эти значения взяты из

Затем проверьте, откуда это значение. Следуя вызовам метода, мы видим, что s передается с printString(name) в методе print(), а this.name - null.

Трассировка, где эти значения должны быть установлены

Где установлен this.name? В методе setName(String). С некоторой дополнительной отладкой мы видим, что этот метод вообще не вызывается. Если этот метод был вызван, обязательно проверьте порядок , что эти методы вызывают, а метод set не будет называться после методом печати. ​​

Этого достаточно, чтобы дать нам решение: добавить вызов printer.setName() перед вызовом printer.print().

Другие исправления

Переменная может иметь значение по умолчанию setName может помешать ему установить значение null):

private String name = "";

Либо метод print, либо printString может проверить значение null например:

printString((name == null) ? "" : name);

Или вы можете создать класс, чтобы name всегда имел ненулевое значение :

public class Printer {
    private final String name;

    public Printer(String name) {
        this.name = Objects.requireNonNull(name);
    }

    public void print() {
        printString(name);
    }

    private void printString(String s) {
        System.out.println(s + " (" + s.length() + ")");
    }

    public static void main(String[] args) {
        Printer printer = new Printer("123");
        printer.print();
    }
}

См. также:

Я все еще не могу найти проблему

Если вы попытались отладить проблему и до сих пор не имеете решения, вы можете отправить вопрос для получения дополнительной справки, но не забудьте включить то, что вы пробовали до сих пор. Как минимум, включите stacktrace в вопрос и отметьте важные номера строк в коде. Также попробуйте сначала упростить код (см. SSCCE ).

372
задан martineau 6 April 2018 в 14:15
поделиться

12 ответов

Импорт модуля довольно быстр, но не мгновенен. Это означает что:

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

Поэтому, если Вы заботитесь об эффективности, поместите импорт наверху. Только переместите их в функцию, если бы Ваши профильные шоу, которые помогли бы (Вы сделали профиль для наблюдения, где лучше всего улучшить производительность, правильно??)

<час>

лучшие причины я видел для выполнения, ленивый импорт:

  • поддержка библиотеки Optional. Если Ваш код имеет разнообразные пути, которые пользуются различными библиотеками, не повреждайтесь, если дополнительная библиотека не установлена.
  • В __init__.py из плагина, который мог бы импортироваться, но не на самом деле использоваться. Примерами являются плагины Базара, которые используют bzrlib лениво загружающаяся платформа.
263
ответ дан John Millikin 23 November 2019 в 00:04
поделиться

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

from foo import bar
from baz import qux
# Note: datetime is imported in SomeClass below
6
ответ дан Drew Stephens 23 November 2019 в 00:04
поделиться

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

Пара, что информация с аргументом в пользу удобочитаемости и я сказал бы это, лучше иметь оператора импорта в объеме модуля.

4
ответ дан Jeremy Brown 23 November 2019 в 00:04
поделиться

Я не волновался бы об эффективности загрузки модуля впереди слишком много. Память, поднятая модулем, не будет очень большой (предположение, что это является достаточно модульным), и стоимость запуска будет незначительна.

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

Одно серьезное основание импортировать модуль в другое место в коде состоит в том, если это используется в отладочном операторе.

, Например:

do_something_with_x(x)

я мог отладить это с:

from pprint import pprint
pprint(x)
do_something_with_x(x)

, Конечно, другая причина импортировать модули в другое место в коде состоит в том, если необходимо динамично импортировать их. Это вызвано тем, что у Вас в значительной степени нет выбора.

я не волновался бы об эффективности загрузки модуля впереди слишком много. Память, поднятая модулем, не будет очень большой (предположение, что это является достаточно модульным), и стоимость запуска будет незначительна.

8
ответ дан Chris Henry 23 November 2019 в 00:04
поделиться

Это - компромисс, это, только программист может решить сделать.

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

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

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

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

6
ответ дан pjz 23 November 2019 в 00:04
поделиться

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

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

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

9
ответ дан Dan Lenski 23 November 2019 в 00:04
поделиться

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

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

Добавленное Примечание: В IronPython, импорт может быть вполне немного более дорогим, чем в CPython, потому что код в основном компилируется, когда это импортируется.

14
ответ дан Curt Hagenlocher 23 November 2019 в 00:04
поделиться

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

Во-первых, у Вас мог быть модуль с модульным тестом формы:

if __name__ == '__main__':
    import foo
    aa = foo.xyz()         # initiate something for the test

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

if [condition]:
    import foo as plugin_api
else:
    import bar as plugin_api
xx = plugin_api.Plugin()
[...]

существуют, вероятно, другие ситуации, куда Вы могли бы поместить импорт в другие части в коде.

38
ответ дан ConcernedOfTunbridgeWells 23 November 2019 в 00:04
поделиться

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

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

существует штраф скорости, как упомянуто в другом месте. Я измерил это в своем приложении и нашел, что он незначителен в моих целях.

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

я обычно помещал импорт sys внутренняя часть эти if __name__=='__main__' проверка и затем передаю аргументы (как sys.argv[1:]) к main() функция. Это позволяет мне использовать main в контексте, куда sys не был импортирован.

58
ответ дан 23 November 2019 в 00:04
поделиться

Вставление оператора импорта функции может предотвратить круговые зависимости. Например, если у Вас будет 2 модуля, X.py и Y.py и они оба потребность импортировать друг друга, это вызовет круговую зависимость при импорте одного из модулей, вызывающих бесконечный цикл. При перемещении оператора импорта в один из модулей тогда, это не попытается импортировать другой модуль, пока функция не будет вызвана, и тот модуль будет уже импортирован, таким образом, никакой бесконечный цикл. Читайте здесь для более - effbot.org/zone/import-confusion.htm

76
ответ дан Sam Hartman 23 November 2019 в 00:04
поделиться

Вот пример, где весь импорт на самой вершине (это - единственное время, я должен был сделать это). Я хочу быть в состоянии завершить подпроцесс и на Un*x и на Windows.

import os
# ...
try:
    kill = os.kill  # will raise AttributeError on Windows
    from signal import SIGTERM
    def terminate(process):
        kill(process.pid, SIGTERM)
except (AttributeError, ImportError):
    try:
        from win32api import TerminateProcess  # use win32api if available
        def terminate(process):
            TerminateProcess(int(process._handle), -1)
    except ImportError:
        def terminate(process):
            raise NotImplementedError  # define a dummy function

(На обзоре: что сказал John Millikin .)

6
ответ дан Community 23 November 2019 в 00:04
поделиться

Это - захватывающее обсуждение. Как многие другие я даже не рассмотрел эту тему. Я был загнан в угол в необходимость иметь импорт в функциях из-за желания использовать Django ORM в одной из моих библиотек. Я должен был звонить django.setup() прежде, чем импортировать мои образцовые классы и потому что это было наверху файла, он перетаскивался в полностью код библиотеки non-Django из-за конструкции инжектора МОК.

я отчасти бездельничал немного и закончил тем, что поместил django.setup() в одноэлементного конструктора и соответствующий импорт во главе каждого метода класса. Теперь это хорошо работало, но сделало меня обеспокоенным, потому что импорт не был наверху, и также я начал волноваться о хите дополнительного времени импорта. Затем я приехал сюда, и считайте с большим интересом общее взятие на этом.

я имею длинный фон C++ и теперь использую Python/Cython. Мое взятие на этом - то, что, почему бы не поместить импорт в функцию, если она не вызывает Вас представленное узкое место. Это только похоже на объявление пространства для переменных перед необходимостью в них. Проблема, у меня есть тысячи строк кода со всем импортом наверху! Таким образом, я думаю, что сделаю это с этого времени и изменю нечетный файл тут и там, когда я пройду и имею время.

0
ответ дан 23 November 2019 в 00:04
поделиться
Другие вопросы по тегам:

Похожие вопросы: