5
\$\begingroup\$

I wrote this in order to get a list of network interfaces on Linux. In the past, I have used publicly available methods from, say, ActiveState, which uses ioctl and was annoying to get working under Python 3. This version simply uses regular expressions on the output of ifconfig.

The documentation style used here follows the numpy documentation guide.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Get a list of network interfaces on Linux.

This code is compatible with Python versions 2 and 3.

"""
from collections import namedtuple
import re
import subprocess


def get_interfaces(external=False, ip=False):
    """
    Get a list of network interfaces on Linux.

    To access the MAC address and/or the IP address, set the relevant keyword
    arguments to True.

    Parameters
    ----------
    external : bool, optional
        Only show external interfaces, and ignore virtual (e.g. loopback)
        devices, and return their MAC addresses.
    ip : bool, optional
        Only show interfaces which are UP and have an IP address, and return
        their IPv4 addresses.

    Returns
    -------
    interfaces
        list of str containing the interface name by default, or list of
        namedtuple containing `name`, `mac`, and `ip` as requested.

    Examples
    --------
    >>> print(get_interfaces())
    ['eth0', 'lo', 'wlan0']
    >>> print(get_interfaces(external=True))
    [Interface(name='eth0', mac='a0:b1:c2:d3:e4:f5'), Interface(name='wlan0', ma
    c='f5:e4:d3:c2:b1:a0')]
    >>> print(get_interfaces(ip=True))
    [Interface(name='lo', ip='127.0.0.1'), Interface(name='wlan0', ip='192.168.1
    1.2')]
    >>> print(get_interfaces(external=True, ip=True))
    [Interface(name='wlan0', mac='f5:e4:d3:c2:b1:a0', ip='192.168.11.2')]

    """
    name_pattern = "^(\w+)\s"
    mac_pattern = ".*?HWaddr[ ]([0-9A-Fa-f:]{17})" if external else ""
    ip_pattern = ".*?\n\s+inet[ ]addr:((?:\d+\.){3}\d+)" if ip else ""
    pattern = re.compile("".join((name_pattern, mac_pattern, ip_pattern)),
                         flags=re.MULTILINE)

    ifconfig = subprocess.check_output("ifconfig").decode()
    interfaces = pattern.findall(ifconfig)
    if external or ip:
        Interface = namedtuple("Interface", "name {mac} {ip}".format(
            mac="mac" if external else "",
            ip="ip" if ip else ""))
        return [Interface(*interface) for interface in interfaces]
    else:
        return interfaces


if __name__ == "__main__":
    interfaces = get_interfaces(external=True, ip=True)
    for interface in interfaces:
        print("{name}: {ip}".format(name=interface.name, ip=interface.ip))
\$\endgroup\$
  • \$\begingroup\$ What do you mean by annoying to get working under Python 3? Because the IOCTL method is more efficient (after all it doesn't need to create another process), so if speed is very important... \$\endgroup\$ – user45891 Sep 12 '15 at 15:01
  • \$\begingroup\$ It required an understanding of what was going on under the hood to get it working. For example, the code linked above would not work under Python 3 as is because of the different treatment of bytes in python3. In this case, ifname[:15] on line 10 had to be changed to str.encode(ifname) to get it to work. You are correct about the speed; in this case, this function would run once at the start of two scripts when the robot was to be started, so it was not an issue. \$\endgroup\$ – Jean Nassar Sep 12 '15 at 15:33
  • \$\begingroup\$ ipconfig is deprecated on Linux. Better use the ip command instead. \$\endgroup\$ – aventurin Oct 9 '18 at 16:29
4
\$\begingroup\$

That is a huge docstring, it's longer than the code itself. You should read Python's PEP about how to write good docstrings.

For a start a docstring shouldn't contain examples, you can put that as a regular comment in the script if you want. And you don't need to be so detailed and explicit about the type and optional nature of your parameters. I would take some information out of your parameters and returns sections and just fold it back into your opening section. Docstrings should be relatively concise and mostly add context and explanation.

A user can easily see the default parameters get_interfaces(external=False, ip=False), which reveals that they're optional and booleans. All you need to explain is what setting them will affect.

| improve this answer | |
\$\endgroup\$
  • 2
    \$\begingroup\$ Thanks for your comment. The documentation style I use in real life is the numpy documentation standard (example), because it is useful for scientific documentation etc. (I used to use Google's Python style guide.) But with numpy, this is common. For instance, numpy's mean has 79 lines of docstring vs 9 of code. \$\endgroup\$ – Jean Nassar Sep 12 '15 at 13:58
  • \$\begingroup\$ @JeanNassar Ah, I'm not familiar with that style, I personally stick with the PEP I linked. If you run your script with help(get_interfaces) it prints a big long string, which seems unwieldy to me, but it is dependent on how it'll be used. \$\endgroup\$ – SuperBiasedMan Sep 12 '15 at 14:08
  • 1
    \$\begingroup\$ Ah, I see how this can be a problem. I REPL with ipython (both qtconsole and notebook). Typing get_interfaces( would show a hovering tooltip-style box with the signature, as well as the docstring. In this case, the docstring goes all the way to the first line of Returns interfaces (i.e., or list of). Which is good enough for most users. If I need more help, I type a question mark, and I get a less-like window with the documentation. If you use Spyder as your IDE, it's even better because everything is formatted beautifully, including headers and syntax highlighting etc. \$\endgroup\$ – Jean Nassar Sep 12 '15 at 14:54
1
\$\begingroup\$

Parsing the output of ifconfig is problematic: different versions have slightly different output formats, and it's hard to make code sufficiently flexible enough to handle them all. And it's impossible, in general, to also handle future output formats.

You're probably better off using ip addr, which has a more consistent and predictable output format.


The doctest cases in the docstring aren't very good unit tests - they might be correct for your machine at the time of writing, but they are too fragile, as they depend on environment outside your control.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.