5

As currently being discussed in this question, I am writing inclusive versions of the built-in range and xrange functions. If these are placed within a module named inclusive, I see two possible ways to name the functions themselves:

  1. Name the functions inclusive_range and inclusive_xrange so that client code can use them as follows:

    from inclusive import *
    ...
    inclusive_range(...)
    
  2. Name the functions range and xrange:

    import inclusive
    ...
    inclusive.range(...)
    

To me, the second example of client code looks better, but should I avoid re-using built-in names in this way?

Community
  • 1
  • 1
user200783
  • 13,722
  • 12
  • 69
  • 135
  • Good question. The first example isn't good. If I were to avoid using the same names, I'd call them `inclusive.irange()` and `inclusive.ixrange()`. But maybe using `range()` and `xrange()` is OK. – wrgrs Apr 18 '15 at 09:16

4 Answers4

5

This turned into a bit of a list of different options, rather than a straight answer. However, two concepts are paramount:

  • Users should be able to replace range and xrange if they explicitly choose to; but
  • Users should not be able to implicitly/accidentally replace range and xrange;

it should always be clear to readers of their code where the built-ins are used and where the replacements are used.

For that reason, all options I've outlined below prevent a wildcard import (from inclusive import *) from shadowing the built-ins. Which is the best option for you depends on whether you see replacing the built-ins as a primary or secondary use of your module. Will users generally want to replace the built-ins, or use them alongside each other?


In your position, I think I would do the following:

inclusive.py:

"""Inclusive versions of range and xrange."""

__all__ = []  # block 'from inclusive import *'

old_range, old_xrange = range, xrange  # alias for access

def range(...):  # shadow the built-in
    ...

def xrange(...):  # ditto
    ...

This allows users to either:

  1. import inclusive and access inclusive.range and inclusive.xrange;
  2. from inclusive import range, xrange, clearly replacing the built-ins without any unpleasant side effects; or
  3. from inclusive import range as irange, xrange as ixrange to use the built-in and replacement versions alongside one another.

Defining __all__ as an empty list means that from inclusive import * won't quietly shadow the built-ins.


If you really wanted to, you could add:

irange, ixrange = range, xrange

to the end of inclusive.py and modify the __all__ definition to:

__all__ = ['irange', 'ixrange']

Now the users have two additional choices:

  • from inclusive import irange, ixrange (slightly simpler than manually aliasing the functions as in option 3 above); and
  • from inclusive import * (same result as above, still no implicit shadowing of built-ins).

Of course, you could go completely the other way - name your own versions irange and ixrange, then if the user really wants to replace the built-ins they would have to:

from inclusive import irange as range, ixrange as xrange

This doesn't require you to define __all__ to avoid a wildcard import shadowing the built-ins.

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
  • 1
    Might also be worth pointing out that you can always recover the original `range` and `xrange` functions from `__builtins__` by using `__builtins__.range` and `__builtins__.xrange`. – Vaibhav Sagar Apr 18 '15 at 11:00
  • Thank you for the detailed answer, in particular for the two concepts mentioned at the top - they are certainly appropriate. I also appreciate the list of possible ways to achieve these concepts, although I am yet to decide exactly which option to use. – user200783 Apr 18 '15 at 14:16
  • Regarding "Will users generally want to replace the built-ins, or use them alongside each other?". I expect replacing the built-ins could easily be a source of confusion, so do not expect that to be a common use case. – user200783 Apr 18 '15 at 14:18
0

You should avoid reusing builtin function names in this way, because this can break any libraries that you are depending on and is very confusing for other people (or you in the future) to read and maintain. However, if you must, the option to do so is available to you.

Vaibhav Sagar
  • 2,208
  • 15
  • 21
  • 1
    *"this can break any libraries that you are depending on"* - Python is careful to **not** let this happen, the replacement of the built-ins would **only** be in the scope of `inclusive` unless the user decides otherwise. – jonrsharpe Apr 18 '15 at 09:34
  • Yes, but if you do `from inclusive import *` then this can definitely happen. – Vaibhav Sagar Apr 18 '15 at 10:50
  • Only in the module in which that import is made, and that's why wildcard imports are discouraged - see my answer for options to avoid that happening (while still providing the option for users to **explicitly** replace `range` and `xrange`). – jonrsharpe Apr 18 '15 at 10:57
  • Yup, your answer is much more comprehensive than mine. – Vaibhav Sagar Apr 18 '15 at 11:01
0

You can, but it is probably not a good idea.

The issue is that if someone else uses the library they may introduce side effects

 from your_lib import inclusive range

 ..... lots of code .....

 range()    # ambiguous 
acutesoftware
  • 1,091
  • 3
  • 14
  • 33
  • 1
    That's not ambiguous at all - `range` is still the built-in, the inclusive version is `inclusive.range`. It only becomes a problem if the user does `from your_lib.inclusive import *` or `from your_lib.inclusive import range, xrange` (in which case they probably **want** to replace the built-ins). – jonrsharpe Apr 18 '15 at 09:20
  • sorry your right - I changed my answer to show the import which may be an issue if the import is way up the top and range is used deep in the code. Not a major issue, but still a potential trap when it doesnt really need to be – acutesoftware Apr 18 '15 at 09:46
0

The problem here is that inside your module you wouldn't be able to call the builtin range() or xrange(). The way you describe inclusive.range() looks good, but any other users might not import in the same way, and they'd get conflicts too.

What about inclusive.irange() and inclusive.ixrange()?

Edit: it's true you would be able to use range() in your own module if you took care to import and rename them, but this is probably another sign that this is a potentially tricky way to go.

wrgrs
  • 2,467
  • 1
  • 19
  • 24
  • You *would* still be able to use them, but you'd have to alias them before the function definitions (e.g. `old_range = range; def range(...):`). – jonrsharpe Apr 18 '15 at 09:22