Каковы преимущества и недостатки методов загрузки кода по сравнению с требованиями импорта и импорта? - PullRequest
9 голосов
/ 04 декабря 2009

Ruby использует require, Python использует import. Это существенно разные модели, и хотя я больше привык к модели require, я вижу несколько мест, где, мне кажется, мне нравится import больше. Мне любопытно, какие вещи люди находят особенно легкими - или, что еще интереснее, сложнее, чем они должны быть - с каждой из этих моделей.

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

Ответы [ 4 ]

14 голосов
/ 04 декабря 2009

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

Это создает очень явный код:

import xml.sax

Указывает, где найти код, который мы хотим использовать, по правилам пути поиска Python.

В то же время все объекты, к которым мы хотим получить доступ, живут в этом точном пространстве имен, например xml.sax.ContentHandler.

Я рассматриваю это как преимущество по требованию Руби. require 'xml' может фактически сделать объекты внутри пространства имен XML или любого другого пространства имен, доступного в модуле, без непосредственного указания из строки с требованием.

Если xml.sax.ContentHandler слишком длинный, вы можете указать другое имя при импорте:

import xml.sax as X

И теперь он доступен под X.ContentHandler.

Таким образом, Python требует от вас явного построения пространства имен каждого модуля. Таким образом, пространства имен Python очень «физические», и я объясню, что я имею в виду:

  • По умолчанию только имена, определенные в модуле, доступны в его пространстве имен: функции, классы и т. Д.
  • Чтобы добавить в пространство имен модуля, вы явно импортируете имена, которые хотите добавить, поместив их (по ссылке) «физически» в текущий модуль.

Например, если у нас есть маленький пакет Python «process» с внутренними подмодулями machine и interface, и мы хотим представить это как одно удобное пространство имен непосредственно под именем пакета, это и пример того, что мы можно записать в файл "определения пакета" process/__init__.py:

from process.interface import *
from process.machine import Machine, HelperMachine

Таким образом, мы поднимаем то, что обычно доступно как process.machine.Machine до process.Machine. И мы добавляем все имена из process.interface в process пространство имен, в очень явной форме.

Преимущества импорта Python, о котором я писал, были просто два:

  • Очистить что вы включаете при использовании import
  • Явный способ изменения пространства имен вашего собственного модуля (для импорта программы или других)
3 голосов
/ 09 декабря 2009

Хорошим свойством require является то, что на самом деле это метод, определенный в Kernel. Таким образом, вы можете переопределить его и реализовать собственную упаковочную систему для Ruby, как, например, Rubygems делает!

PS: Я не продаю здесь патчи для обезьян, но тот факт, что система пакетов Ruby может быть переписана пользователем (даже для работы как система python). Когда вы пишете новый язык программирования, вы не можете получить все правильно. Таким образом, если ваш механизм импорта полностью расширяется (во все направления) изнутри языка, вы предоставляете своим будущим пользователям лучший сервис. Язык, который не является полностью расширяемым изнутри, является эволюционным тупиком. Я бы сказал, что это одна из тех вещей, которые Мэтц правильно понял с Руби.

1 голос
/ 09 декабря 2009

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

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

Иногда пространства имен тоже могут раздражать, поскольку такие вещи, как some.module.far.far.away.TheClass() везде, могут быстро сделать ваш код очень длинным и скучным для ввода. В этих случаях вы можете import ... from ... и вставлять биты из другого пространства имен в текущее. Если внедрение вызывает конфликт с импортируемым модулем, вы можете просто переименовать импортируемую вещь: from some.other.module import Bar as BarFromOtherModule.

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

Итак, python взял C ++ namespace и #include и в значительной степени расширил его. С другой стороны, я не вижу, каким образом рубины module и require добавляют что-то новое к ним, и у вас возникают такие же ужасные проблемы, как загромождение глобального пространства имен.

1 голос
/ 09 декабря 2009

Отказ от ответственности, Я ни в коем случае не эксперт по Python.

Самое большое преимущество, которое я вижу перед require перед import, заключается в том, что вам не нужно беспокоиться о понимании соответствия между пространствами имен и путями к файлам. Это очевидно: это просто стандартный путь к файлу.

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

т.е. в foo.py:

class Bar:
  def myself(self):
    return foo.Bar

Это не с:

Traceback (most recent call last):
  File "", line 1, in ?
  File "foo.py", line 3, in myself
    return foo.Bar
NameError: global name 'foo' is not defined

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

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

...