Is `import module` better coding style than `from module import function`?
Asked Answered
E

5

74

Let from module import function be called the FMIF coding style.

Let import module be called the IM coding style.

Let from package import module be called the FPIM coding style.

Why is IM+FPIM considered a better coding style than FMIF? (See this post for the inspiration for this question.)

Here are some criteria which lead me to prefer FMIF over IM:

  1. Shortness of code: It allows me to use shorter function names and thus help stick to the 80 columns-per-line convention.
  2. Readability: chisquare(...) appears more readable than scipy.stats.stats.chisquare(...). Although this is a subjective criterion, I think most people would agree.
  3. Ease of redirection: If I use FMIF and for some reason at some later time want to redirect python to define function from alt_module instead of module I need to change just one line: from alt_module import function. If I were to use IM, I'd need to change many lines of code.
I realize FPIM goes some way to nullifying the first two issues, but what about the third?

I am interested in all reasons why IM+FPIM may be better than FMIF, but in particular, I'd be interested in elaboration on the following points mentioned here:

Pros for IM:

  1. ease of mocking/injecting in tests. (I am not very familiar with mocking, though I recently learned what the term means. Can you show code which demonstrates how IM is better than FMIF here?)
  2. ability for a module to change flexibly by redefining some entries. (I must be misunderstanding something, because this seems to be an advantage of FMIF over IM. See my third reason in favor of FMIF above.)
  3. predictable and controllable behavior on serialization and recovery of your data. (I really don't understand how the choice of IM or FMIF affects this issue. Please elaborate.)
  4. I understand that FMIF "pollutes my namespace", but beyond being a negative-sounding phrase, I don't appreciate how this hurts the code in any concrete way.
PS. While writing this question I received a warning that the question appears subjective and is likely to be closed. Please don't close it. I'm not looking for subjective opinion, but rather concrete coding situations where IM+FPIM is demonstrably better than FMIF.

Many thanks.

Epigraphic answered 16/11, 2009 at 19:18 Comment(1)
As an aside, though it really shouldn't impact your coding practices much, every '.' dot call in python is literally an operation every time you do it. So if you're ever doing something in a loop, you want to make sure you local code does something like rand = random.uniform if you're doing IM or FPIM and efficiency starts to be an issue.Orbiculate
B
48

The negatives you list for IM/FPIM can often be ameliorated by appropriate use of an as clause. from some.package import mymodulewithalongname as mymod can usefully shorten your code and enhance its readability, and if you rename mymodulewithalongname to somethingcompletelydifferent tomorrow, the as clause can be used as a single statement to edit.

Consider your pro-FMIF point 3 (call it R for redirection) vs your pro-FPIM point 2 (call it F for flexibility): R amounts to facilitating the loss of integrity of module boundaries, while F strenghtens it. Multiple functions, classes and variables in a module are often intended to work together: they should not be independently switched to different meanings. For example, consider module random and its functions seed and uniform: if you were to switch the import of just one of them to a different module, then you'd break the normal connection between calls to seed and results of calls to uniform. When a module is well designed, with cohesion and integrity, R's facilitation of breaking down the module's boundaries is actually a negative -- it makes it easier to do something you're better off not doing.

Vice versa, F is what enables coordinated switching of coupled functions, classes, and variables (so, generally, of entities that belong together, by modularity). For example, to make testing repeatable (FPIM pro-point 1), you mock both seed and random in the random module, and if your code follows FPIM, you're all set, coordination guaranteed; but if you have code that has imported the functions directly, you have to hunt down each such module and repeat the mocking over and over and over again. Making tests perfectly repeatable typically also requires "coordinated mocking" of date and time functions -- if you use from datetime import datetime in some modules, you need to find and mock them all (as well as all those doing from time import time, and so forth) to ensure that all the times received when the various parts of the system ask "so what time is it now?" are perfectly consistent (if you use FPIM, you just mock the two relevant modules).

I like FPIM, because there's really not much added value by using a multiply qualified name rather than a singly qualified one (while the difference between barenames and qualified names is huge -- you get so much more control with a qualified name, be it singly or multiply, than you possibly ever can with a barename!).

Ah well, can't devote all of the working day to responding to each and every one of your points -- your question should probably be half a dozen questions;-). I hope this at least addresses "why is F better than R" and some of the mocking/testing issues -- it boils down to preserving and enhancing well-designed modularity (via F) rather than undermining it (via R).

Beavers answered 16/11, 2009 at 19:49 Comment(5)
random.seed affects random.uniform, but FMIF allows one to change seed without changing uniform. FMIF allows the code to break in a subtle way if you aren't careful! FPIM requires related function to stay related. This prevents the above bug from ever happening. Ah, I finally understand. Thank you. Man, I have a lot of code to rewrite... :)Epigraphic
On second thought, I can write a python script to do the job for me!Epigraphic
@unutbu, yes, especially if your favorite editor is powerful and Python-scriptable (e.g., vim), some "mass edits" can usefully be automated (be sure to rerun all unit tests afterwards though;-).Beavers
I notice that your answer uses FMIF: from question.FMIF import point_2 as F, point_3 as R.Flori
Good answer. A bit dense perhaps? See this answer on "import best practice" for a simpler formulation of most of these points.Scheffler
L
18

The classic text on this, as so often, is from Fredrik Lundh, the effbot. His advice: always use import - except when you shouldn't.

In other words, be sensible. Personally I find that anything that's several modules deep tends to get imported via from x.y.z import a - the main example being Django models. But as much as anything else it's a matter of style, and you should have a consistent one - especially with modules like datetime, where both the module and the class it contains are called the same thing. Do you need to write datetime.datetime.now() or just datetime.now()? (In my code, always the former.)

Items 1 and 2 in your list of questions seem to be the same issue. Python's dynamic nature means it is fairly simple to replace an item in a module's namespace no matter which of the methods you use. The difficulty comes if one function in a module refers to another, which is the one you want to mock. In this case, importing the module rather than the functions means you can do module.function_to_replace = myreplacementfunc and everything works transparently - but that is as easy to do via FPIM as it is via IM.

I also don't understand how item 3 has anything to do with anything. I think your item 4, however, is based on a bit of a misunderstanding. None of the methods you give will 'pollute your namespace'. What does do that is from module import *, where you have no idea at all what you're importing and so functions can appear in your code with no clue given to the reader where they came from. That's horrible, and should be avoided at all costs.

Larson answered 16/11, 2009 at 19:32 Comment(4)
In almost all my scripts I have import datetime, now = datetime.datetime.now so I can conveniently do print(now(), 'reading file X')...Commando
@gerrit: usually not a good idea if you use more than 1 or 2 functions from a module: it will pollute namespace and force you to go back and forth from code to module declaration everytime you ask "where this somefunc() at line 2345 came from?"Scibert
@Scibert I do it for exactly one function (classmethod) and that is datetime.datetime.now. Otherwise I use from X import Y only for packages, so that Y still is a module.Commando
@gerrit: I've never used datetime.dametime.now() often enough to find it annoying or warrant it an exception. But, thinking about it, it does suck that the "canonical" way to get current time is so verbose in PythonScibert
S
6

Great answers here (I upvoted them all), and here are my thoughts on this matter:

First, addressing each of your bullets:

(Allegedly) Pros of FMIF:

  • Shortness of code: shorter function names help stick to the 80 columns-per-line.

Perhaps, but module names are usually short enough so this is not relevant. Sure, there's datetime, but also os, re, sys, etc. And Python has free line breaks inside { [ (. And for nested modules there's always as in both IM and FPIM

  • Readability: chisquare(...) appears more readable than scipy.stats.stats.chisquare(...).

Strongly disagree. When reading foreign code (or my own code after a few months) it's hard to know where each function comes from. Qualified names saves me from going back and forth from line 2345 to module declarations header. And it also gives you context: "chisquare? What's that? Oh, it's from scypy? Ok, some math-related stuff then". And, once again, you can always abbreviate scipy.stats.stats as scypyst. scypyst.chisquare(...) is short enough with all benefits of a qualified name.

import os.path as osp is another good example, considering it's very common to chain 3 or more of its functions together in a single call: join(expanduser(),basename(splitext())) etc.

  • Ease of redirection: one-line redefinition of a function from altmodule instead of module.

How often you want to redefine a single function but not whole module? Module boundaries and function coordination should be preserved, and Alex already explained this in great depth. For most (all?) real-world scenarios, if alt_module.x is a viable replacement for module.x, then probably alt_module itself is a drop in alternative for module, so both IM and FPIM are one-liners just like FMIF, provided you use as.

  • I realize FPIM goes some way to nullifying the first two issues...

Actually, as is the one that mitigates the first 2 issues (and the 3rd), not FPIM. You can use IM for that too: import some.long.package.path.x as x for the same result as FPIM.


So none of the above are really pros of FMIF. And the reasons I prefer IM/FPIM are:

For the sake of simplicity and consistency, when I import something, either IM or FPIM, I'm always importing a module, not an object from a module. Remember FMIF can be (ab-)used to import functions, classes, variables, or even other modules! Think about the mess of from somemodule import sys, somevar, os, SomeClass, datetime, someFunc.

Also, if you want more than a single object from a module, FMIF will pollute your namespace more than IM or FPIM, which will use a single name no matter how many objects you want to use. And such objects will have a qualified name, which is a pro, not a con: as I've said in issue 2, IMHO a it improves readability.

it all comes down to consistency, simplicity, organization. "Import modules, not objects" is a good, easy mind model to stick with.

Scibert answered 7/11, 2013 at 3:26 Comment(0)
D
5

Like Alex Martelli, I am fond of using as when importing a function.

One thing I have done is to use some prefix on all the functions that were imported from the same module:

from random import seed as r_seed
from random import random as r_random

r_seed is shorter to type than random.seed but somewhat preserves the module boundaries. Someone casually looking at your code can see r_seed() and r_random() and have a chance to grok that they are related.

Of course, you can always simply do:

import random as r

and then use r.random() and r.seed(), which may be the ideal compromise for this case. I only use the prefix trick when I'm importing one or two functions from a module. When I want to use many functions from the same module, I'll import the module, perhaps with an as to shorten the name.

Di answered 16/11, 2009 at 20:16 Comment(0)
R
2

I agree with MestreLion the most here (and so an upvote).

My perspective: I review code frequently that I am unfamiliar with, and not knowing what module a function is coming from just looking at the function is quite frustrating.

Code is written once and read many times, and so readability and maintainability trumps ease of typing.

In a similar vein, typically code is not being written for the benefit of the coder, but for the benefit of another entity.

Your code should be readable to someone who knows python better than you, but is unfamiliar with the code.

Full path imports can also better help IDE's point you at the correct source of the function or object you're looking at.

For all of these reasons and the reasons MestreLion noted, I conclude that it is best practice to import and use the full path.

Radom answered 10/11, 2013 at 1:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.