views:

699

answers:

4

Ruby uses require, Python uses import. They're substantially different models, and while I'm more used to the require model, I can see a few places where I think I like import more. I'm curious what things people find particularly easy — or more interestingly, harder than they should be — with each of these models.

In particular, if you were writing a new programming language, how would you design a code-loading mechanism? Which "pros" and "cons" would weigh most heavily on your design choice?

+13  A: 

The Python import has a major feature in that it ties two things together -- how to find the import and under what namespace to include it.

This creates very explicit code:

import xml.sax

This specifies where to find the code we want to use, by the rules of the Python search path.

At the same time, all objects that we want to access live under this exact namespace, for example xml.sax.ContentHandler.

I regard this as an advantage to Ruby's require. require 'xml' might in fact make objects inside the namespace XML or any other namespace available in the module, without this being directly evident from the require line.

If xml.sax.ContentHandler is too long, you may specify a different name when importing:

import xml.sax as X

And it is now avalable under X.ContentHandler.

This way Python requires you to explicitly build the namespace of each module. Python namespaces are thus very "physical", and I'll explain what I mean:

  • By default, only names directly defined in the module are available in its namespace: functions, classes and so.
  • To add to a module's namespace, you explicitly import the names you wish to add, placing them (by reference) "physically" in the current module.

For example, if we have the small Python package "process" with internal submodules machine and interface, and we wish to present this as one convenient namespace directly under the package name, this is and example of what we could write in the "package definition" file process/__init__.py:

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

Thus we lift up what would normally be accessible as process.machine.Machine up to process.Machine. And we add all names from process.interface to process namespace, in a very explicit fashion.

The advantages of Python's import that I wrote about were simply two:

  • Clear what you include when using import
  • Explicit how you modify your own module's namespace (for the program or for others to import)
kaizer.se
I agree that this is a good and useful feature, but I found that there was a steeper learning curve with things like `__init__.py` and if you didn't have a flexible directory structure for some reason, this made namespacing things exactly the way you want somewhat difficult.
Bob Aman
I wonder for what reason you would not have a "flexible directory structure" ?
Luper Rouch
@Bob: Of course, that's the downside. The way I see it, you're trading a steeper learning curve for the advantage @kaizer.se mentioned. If you ask me, it's worth it.
musicfreak
@Luper: see comment below regarding plugins; I had to be way more deliberate about naming things than I was expecting with Python. With Ruby, I could have picked whatever filenames I wanted with virtually zero effect on the public API.
Bob Aman
@Bob: And choosing arbitrary and confusing paths and filenames with zero connection to names seen from language level is a great feature because...??? I just don't get it. After years of working with C++ and its #include/namespace/compilation-unit model I find Python way... I don't know... nice... friendly... liberating...
Tomek Szpakowicz
@tomekszpakowicz: Yeah, the paths aren't arbitrary or confusing, it's just that the project is technically still confidential, so I munged the paths slightly.
Bob Aman
+1  A: 

Disclaimer, I am by no means a Python expert.

The biggest advantage I see to require over import is simply that you don't have to worry about understanding the mapping between namespaces and file paths. It's obvious: it's just a standard file path.

I really like the emphasis on namespacing that import has, but can't help but wonder if this particular approach isn't too inflexible. As far as I can tell, the only means of controlling a module's naming in Python is by altering the filename of the module being imported or using an as rename. Additionally, with explicit namespacing, you have a means by which you can refer to something by its fully-qualified identifier, but with implicit namespacing, you have no means to do this inside the module itself, and that can lead to potential ambiguities that are difficult to resolve without renaming.

i.e., in foo.py:

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

This fails with:

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

Both implementations use a list of locations to search from, which strikes me as a critically important component, regardless of the model you choose.

What if a code-loading mechanism like require was used, but the language simply didn't have a global namespace? i.e., everything, everywhere must be namespaced, but the developer has full control over which namespace the class is defined in, and that namespace declaration occurs explicitly in the code rather than via the filename. Alternatively, defining something in the global namespace generates a warning. Is that a best-of-both-worlds approach, or is there an obvious downside to it that I'm missing?

Bob Aman
Actually you can return 'foo.Bar', if you do 'import foo' just above it in the 'myself' method. But why would you want to do that when you can just return 'Bar' ?
Luper Rouch
In this example, obviously, yes, returning Bar is exactly what you'd want to do. However, I have a situation in some code I'm currently writing where are are plugins written by other authors. I need to be particularly careful about ambiguous namespaces. Fortunately, I just discovered `from __future__ import absolute_import`, and that almost completely mitigates this issue.
Bob Aman
Why not put your plugins in a directory along your app, like 'plugins', each with its own subdirectory ? This way there can be no conflict between plugins, you can __import__ without doing black magic and find implementations with __subclasses__. If the plugin authors do conflicting imports like 'import strings' it's not your problem, or if it is they are not really plugins anymore ;)
Luper Rouch
That's exactly what I'm doing actually. The problem is that I have a module named `myproject.components` and the plugins are named `components.mycomponent`. Any attempt by other modules within the `myproject` module to load `components.mycomponent` fail, because they're searching the wrong module for `mycomponent`. `from __future__ import absolute_import` fixes this.
Bob Aman
Oh I see, old style relative imports are evil (I stopped using them long time ago), I'm glad they remove it in python 2.7.
Luper Rouch
Exactly! What I've been trying to explain... albeit poorly.
Bob Aman
+3  A: 

A nice property of require is that it is actually a method defined in Kernel. Thus you can override it and implement your own packaging system for Ruby, which is what e.g. Rubygems does!

PS: I am not selling monkey patching here, but the fact that Ruby's package system can be rewritten by the user (even to work like python's system). When you write a new programming language, you cannot get everything right. Thus if your import mechanism is fully extensible (into totally all directions) from within the language, you do your future users the best service. A language that is not fully extensible from within itself is an evolutionary dead-end. I'd say this is one of the things Matz got right with Ruby.

Adrian
I'm not going to -1 this, but monkey-patching as it exists in Ruby is problematic, and not something I want to encourage. A good packaging system would simply modify the search path and leave the code loading stuff alone.
Bob Aman
I edited my post to address that.
Adrian
Well... I'm fairly sure you can't emulate Python's import system in Ruby. You might get something vaguely similar, but it would be a total hack, it'd probably break all kinds of code, and you'd piss a lot of people off if you used it in a production environment.
Bob Aman
-1 for claiming not to sell monkey patching while ...selling monkey patching. Also for total cluelessness about Python's import mechanism (and willingness to harsh on it anyway). First answer I've ever seen on SO that I would downvote more than once if I could.
Jason Orendorff
Ugh, this shouldn't have been the accepted answer, and because it's a bounty, I can't change it. SO went off and picked the top answer written after the bounty was started instead of the most up-voted answer it seems.
Bob Aman
Already thought so :) you best ask Jeff or one of the site moderators to fix it.
Adrian
A: 

Python's import provides a very explicit kind of namespace: the namespace is the path, you don't have to look into files to know what namespace they do their definitions in, and your file is not cluttered with namespace definitions. This makes the namespace scheme of an application simple and fast to understand (just look at the source tree), and avoids simple mistakes like mistyping a namespace declaration.

A nice side effect is every file has its own private namespace, so you don't have to worry about conflicts when naming things.

Sometimes namespaces can get annoying too, having things like some.module.far.far.away.TheClass() everywhere can quickly make your code very long and boring to type. In these cases you can import ... from ... and inject bits of another namespace in the current one. If the injection causes a conflict with the module you are importing in, you can simply rename the thing you imported: from some.other.module import Bar as BarFromOtherModule.

Python is still vulnerable to problems like circular imports, but it's the application design more than the language that has to be blamed in these cases.

So python took C++ namespace and #include and largely extended on it. On the other hand I don't see in which way ruby's module and require add anything new to these, and you have the exact same horrible problems like global namespace cluttering.

Luper Rouch
Ruby's require is fully extensible from within the language. If you don't like its import semantics, you can go change them! This cannot be done in C++ or python, so at least to that respect Ruby adds some big news to the way importing works.
Adrian
You can customize python import mechanism exactly as in ruby. But in both cases it sounds like a terribly bad idea. What happens when two 3rd party modules customize the import mechanism ? This kind of things has to be standard, and the standard mechanism of ruby is not enough for me.
Luper Rouch