I think I need to explain the title, not really sure how accurately title this post. Here it is a quick code:

from my_module import foobar

Where foobar is not defined in my_module module, but the code will not raise ImportError. So, it’s a module whose attributes can be imported and which does not exist within it.

I did not get this idea on my own and I don’t know who firstly got this interesting idea. I saw this in a project called pbs1, a Python subprocess wrapper. (How abbreviated as pbs?) It is written by Andrew Moffat and licensed under the MIT License.

To be honest, I am not interested in what this project can do, I don’t feel writing Python script for easy command-line access would be more productive or easier than scripting in shell script. But that’s not the point of this post and that code has many to learn from even I don’t use.

I found this project via GitHub Explore, the part of being able to import any commands caught my eyes and I was thinking: this only main code pbs.py must be hell long like traffic in <INSERT YOUR CITY>. When I clicked in, it had only around 450 lines and most of them were comments. So, I took a close look at it.

To strip it down, it could be just like (for Python 3.x and 2.x)

#############
# main script
#############

from my_module import attr1
print('Value of attr1: %s' % attr1)
print('')

from my_module import attr2
print('Value of attr2: %s' % attr2)

##############
# my_module.py
##############

import sys

attr1 = 123

class SelfWrapper(object):

  def __init__(self, self_module):

    self.self_module = self_module

  def __getattr__(self, name):

    print('%s is being imported:' % name)
    if hasattr(self.self_module, name):
      print('  found in self_module')
      return getattr(self.self_module, name)
    elif name.startswith('__'):
      print('  starts with __ and not found, raising AttributeError')
      raise AttributeError
    print('  returns a generated string')
    return 'automatically generated ' + name

sys.modules[__name__] = SelfWrapper(sys.modules[__name__])

Where attr1 is defined, but attr2 is not. The output is

__path__ is being imported:
  starts with __ and not found, raising AttributeError
attr1 is being imported:
  found in self_module
Value of attr1: 123

__path__ is being imported:
  starts with __ and not found, raising AttributeError
attr2 is being imported:
  returns a generated string
Value of attr2: automatically generated attr2

If you don’t check for __xyz__, __path__ in this case, __getattr__ will be invoked twice for attr1 and attr2. I can not answer the behind scene of module import for this __path__ part, feel free to educate me in comments.

The project is great for this idea, however I would say it’s fancy but not really necessary. I see no much trouble of using like Cmd.curl, Cmd.wget, etc. But it’s awesome to use import like that. Cool, right?


[1]It’s later renamed to sh.