Search code examples
pythonpython-2.7command-linepydoc

When I call /usr/bin/pydoc, /usr/bin/pydoc2.7 called indeed


when I run the command /usr/bin/pydoc, it prints

durd@FelixDu bin$ /usr/bin/pydoc
pydoc - the Python documentation tool

pydoc <name> ...
    Show text documentation on something.  <name> may be the name of a
    Python keyword, topic, function, module, or package, or a dotted
    reference to a class or function within a module or module in a
    package.  If <name> contains a '/', it is used as the path to a
    Python source file to document. If name is 'keywords', 'topics',
    or 'modules', a listing of these things is displayed.

pydoc -k <keyword>
    Search for a keyword in the synopsis lines of all available modules.

pydoc -p <port>
    Start an HTTP server on the given port on the local machine.  Port
    number 0 can be used to get an arbitrary unused port.

pydoc -g
    Pop up a graphical interface for finding and serving documentation.

pydoc -w <name> ...
    Write out the HTML documentation for a module to a file in the current
    directory.  If <name> contains a '/', it is treated as a filename; if
    it names a directory, documentation is written for all the contents.

but I checked the code of /usr/bin/pydoc, it shows

durd@FelixDu bin$ cat /usr/bin/pydoc
#!/usr/bin/python

import sys, os
import glob, re

partA = """\
python version %d.%d.%d can't run %s.  Try the alternative(s):

"""
partB = """
Run "man python" for more information about multiple version support in
Mac OS X.
"""

sys.stderr.write(partA % (sys.version_info[:3] + (sys.argv[0],)))

dir, base = os.path.split(sys.argv[0])
specialcase = (base == 'python-config')
if specialcase:
    pat = "python*-config"
else:
    pat = base + '*'
g = glob.glob(os.path.join(dir, pat))
# match a single digit, dot and possibly multiple digits, because we might
# have 2to32.6, where the program is 2to3 and the version is 2.6.
vpat = re.compile("\d\.\d+")
n = 0
for i in g:
    vers = vpat.search(i)
    if vers is None:
    continue
    sys.stderr.write("%s (uses python %s)\n" % (i, i[vers.start():vers.end()]))
    n = 1
if n == 0:
    sys.stderr.write("(Error: no alternatives found)\n")

sys.stderr.write(partB)
sys.exit(1)

it should prints following text if it run /usr/bin/pydoc, but actually it is not.

python version 2.7.10 can't run ./pydoc.  Try the alternative(s):

(Error: no alternatives found)

Run "man python" for more information about multiple version support in
Mac OS X.

then I checked the file in /usr/bin

durd@FelixDu Documents$ ls -al /usr/bin/pydoc*
-rwxr-xr-x  4 root  wheel  925 Jul 16  2017 /usr/bin/pydoc
lrwxr-xr-x  1 root  wheel   74 Sep 27  2017 /usr/bin/pydoc2.7 -> ../../System/Library/Frameworks/Python.framework/Versions/2.7/bin/pydoc2.7

It seems when I run /usr/bin/pydoc, it run /usr/bin/pydoc2.7 indeed, but I can't find there is some symbol link from /usr/bin/pydoc to /usr/bin/pydoc2.7, so I very confuse how does it works. Could anyone help to explain this? thanks in advance.


Solution

  • The files aren't symlinks, because their contents aren't the same.

    pydoc2.7 is a trivial entry-point script that uses /System/Library/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python as its interpreter and just calls into that Python installation's pydoc.cli.

    pydoc is a special Apple wrapper script that uses /usr/bin/python. It isn't calling pydoc2.7; it's indirectly calling a script buried inside the same framework that does the same thing.

    /usr/bin/python isn't actually a Python interpreter, it's a special tool that looks at your python-select selection to decide which actual Python interpreter to run, and also handles special magic python-select-aware scripts like this one.

    These scripts are handy for things like 2to3, which doesn't exist in Python 2.5. If you have 2.5 selected as your default Python, instead of a mysterious error, the script will tell you that you don't have 2to3 for Python 2.5, and suggest selecting 2.6 instead.

    This also works for packages installed via easy_install that use distribute magic to create their entry-point scripts. For example, if you install such a package with 2.5, and then select 2.6 and try to run it, it'll tell you the problem and suggest either selecting 2.5 or installing the package for 2.6.

    This is all very clever, and works pretty well. It would have worked a lot better if Apple had worked with third parties to let them plug other Python installations into the python-select mechanism. Or if they'd kept up to date and make it work with Python 3, and with setuptools/pip instead of distribute/easy_install. And so on.

    But since Apple no longer distributes anything but 2.7,1 and doesn't include the python-select tool, and there are no cooperating distribute libraries to install anymore, it's all just a bit of historic legacy lying around in a few scripts in /usr/bin on every Mac.


    1. Actually, they still have part of a Python 2.5 and 2.6 installation, and even a fragment of 2.3, which you can find by digging around inside the framework. But there's no python2.5 or python2.6 (or python2.3) executable in there, just a copy of the python shim that looks at your selection and ends up running 2.7.