4

In Python 3, prefixing a class variable makes it private my mangling the name within the class. How do I access a module variable within a class?

For example, the following two ways do not work:

__a = 3
class B:
    def __init__(self):
        self.a = __a
b = B()

results in:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __init__
NameError: name '_B__a' is not defined

Using global does not help either:

__a = 3
class B:
    def __init__(self):
        global __a
        self.a = __a
b = B()

results in:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in __init__
NameError: name '_B__a' is not defined

Running locals() shows that the variable __a exists unmangled:

>>> locals()
{'__package__': None, '__name__': '__main__',
 '__loader__': <class '_frozen_importlib.BuiltinImporter'>,
 '__doc__': None, '__a': 3, 'B': <class '__main__.B'>,
 '__builtins__': <module 'builtins' (built-in)>, '__spec__': None}

[Newlines added for legibility]

Running same code in a module (as opposed to interpreter) results in the exact same behavior. Using Anaconda's Python 3.5.1 :: Continuum Analytics, Inc..

Aran-Fey
  • 39,665
  • 11
  • 104
  • 149
Mad Physicist
  • 107,652
  • 25
  • 181
  • 264

4 Answers4

1

It's ugly but You could access globals:

__a = 3
class B:
    def __init__(self):
        self.a = globals()["__a"]
b = B()

You can also put it in a dict:

__a = 3

d = {"__a": __a}

class B:
    def __init__(self):
        self.a = d["__a"]
b = B()

Or a list, tuple etc.. and index:

__a = 3

l = [__a]

class B:
    def __init__(self):
        self.a = l[0]
b = B()
Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
Padraic Cunningham
  • 176,452
  • 29
  • 245
  • 321
  • I agree that this is ugly, but unless someone comes up with a better solution, it is the only one that seems to work so far. Thanks. I think that I'll ask this on the Python mailing list to see if it gets any different response. – Mad Physicist Jan 07 '16 at 15:45
  • 1
    @MadPhysicist, I added a couple of other ways to achieve the same, I cannot see any other way to avoid the name mangling – Padraic Cunningham Jan 07 '16 at 15:54
  • Looks like conceptually the same thing. You are storing a reference to `__a` in a non-mangled container outside the class first, whether `globals()` is doing it for you or you are doing it manually. – Mad Physicist Jan 07 '16 at 16:00
  • @MadPhysicist, it is, I just added it as accessing it gives you the option to name the list or dict with a name that may be more explicative of what is happening but you would probably want to use a weakref or dek d after so probably not worth the effort so I removed it. – Padraic Cunningham Jan 07 '16 at 16:14
  • I liked your other solutions (for completeness). Mind if I revert the deletion? – Mad Physicist Jan 07 '16 at 16:24
  • Signed up for python-list and posted there. Will post here if I get a better response there. – Mad Physicist Jan 07 '16 at 16:25
  • @MadPhysicist, work away, I meant to ask, I presume taking the arg is not possible? – Padraic Cunningham Jan 07 '16 at 16:26
  • I have already worked around the specific problem that this question represented (by trimming one of the underscores :)), but it bothers me that this specific thing can't be done directly in Python. – Mad Physicist Jan 07 '16 at 16:27
  • @MadPhysicist, it does seem to be bordering on a bug, I suppose if you create pass in into the class at initialisation `def __init__(self, somevar):` or to other methods it works fine but it is weird that you cannot even do something like `print(__a)` in the class – Padraic Cunningham Jan 07 '16 at 16:34
  • 1
    My thoughts exactly. Sure there are workarounds, but the way I see it, either mangling is getting too greedy or `global` is not doing its job right. – Mad Physicist Jan 07 '16 at 16:36
  • Even doing `m = sys.modules[__name__]`; `self.a = m.__a` give `'module' object has no attribute '_B__a'` so the mangling seems to happen first – Padraic Cunningham Jan 07 '16 at 16:53
  • Well, I'm going to go ahead and submit a bug report. – Mad Physicist Jan 07 '16 at 19:13
  • I got a response to my bug report, which was closed almost immediately. I posted it as an answer here just for reference. I will keep your answer selected because it is better than the "real" answer. – Mad Physicist Jan 08 '16 at 11:08
  • @MadPhysicist. I had a feeling the might be the answer you would get, thanks for the letting me know. – Padraic Cunningham Jan 08 '16 at 11:11
1

Apparently the "official" answer is not to use double underscores outside of a class. This is implied in the documentation here: https://docs.python.org/2/tutorial/classes.html#private-variables-and-class-local-references. Furthermore, the following (failed) bug report (and this response) make it explicit.

Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
-2

You are instantiating a class by passing a variable which is not defined. putting __a outside the class will not not work as the class will not see this variable. What you should do instead is:

__a = 3
class B:
def __init__(self, __a):
   self.a = __a
b = B(__a)

This way you would have passed an argument in the constructor for initializing.

Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
  • A) You can use normal global variables the way I am doing. This one is causing a problem because of Python 3's name mangling, therefore B) I am not looking for a workaround but an actual solution that would let me use my global variable if at all possible. – Mad Physicist Jan 06 '16 at 01:09
-2

If you are going to mangle the names as you are trying to do then I would refer you to this article: http://shahriar.svbtle.com/underscores-in-python

As such, my solution to what you are trying to do is as follows:`

class R:
    global _R__a
    _R__a = 3
    def __init__(self):
        pass

class B:    
    global _R__a    
    def __init__(self):     
        self.a = _R__a
b = B()
print b.a
#3`

This way, you are also more specific about the variable you are calling without much room for modifying it later. Hope this works.

  • This does not answer my question at all. I am trying to access a module-level attribute with two underscores in the name inside a class method. – Mad Physicist Jan 07 '16 at 15:44