Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Masquerading real module of a class

Tags:

python

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.

like image 350
Stefano Borini Avatar asked Feb 28 '23 00:02

Stefano Borini


2 Answers

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'.)

like image 78
bobince Avatar answered Mar 10 '23 11:03

bobince


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'
like image 44
miku Avatar answered Mar 10 '23 11:03

miku