Stefano Borini
Stefano Borini

Reputation: 143865

Masquerading real module of a class

Suppose you have the following layout for a python package

./a
./a/__init__.py
./a/_b.py

inside __init__.py you have

from _b import *

and inside _b.py you have

class B(object): pass

If you import from interactive prompt

>>> import a
>>> a.B
<class 'a._b.B'>
>>> 

How can I completely hide the existence of _b ?

The problem I am trying to solve is the following: I want a facade package importing "hidden" modules and classes. The classes available from the facade (in my case a) are kept stable and guaranteed for the future. I want, however, freedom to relocate classes "under the hood", hence the hidden modules. This is all nice, but if some client code pickles an object provided by the facade, this pickled data will refer to the hidden module nesting, not to the facade nesting. In other words, if I reposition the B class in a module _c.py, client codes will not be able to unpickle because the pickled classes are referring to a._b.B, which has been moved. If they referred to a.B, I could relocate the B class as much as I want under the hood, without ruining pickled data.

Upvotes: 4

Views: 225

Answers (2)

bobince
bobince

Reputation: 536567

try:

B.__module__= 'a'

Incidentally you probably want an absolute import:

from a._b import *

as relative imports without the new explicit dot syntax are going away (see PEP 328).

ETA re comment:

I would have to set the module explicitly for every class

Yes, I don't think there's a way around that but you could at least automate it, at the end of __init__:

for value in globals().values():
    if inspect.isclass(value) and value.__module__.startswith('a.'):
        value.__module__= 'a'

(For new-style classes only you could get away with isinstance(value, type) instead of inspect. If the module doesn't have to run as __main__ you could use __name__ instead of hard-coding 'a'.)

Upvotes: 6

miku
miku

Reputation: 188114

You could set the __module__ variable for Class B

class B(object): pass
B.__module__ = 'a'

For classes, functions, and methods, this attribute contains the name of the module in which the object was defined.

Or define it once in your __init__.py:

from a._b import B # change this line, when required, e.g. from a._c import B
B.__module__ = 'a'

Upvotes: 2

Related Questions