Baffled about instance attributes, please explain

I am on Python 3.12.

I’d expected that the following code does not work, issues an Exception, that the attribute ‘__private’’ does not exist:

#!/usr/bin/env python3
#   -*- coding: utf8 -*- #
#
#=======================================================================================

class Scratch:

    def __init__( self):
        self.__private = 0
        self._protected = 0
        self.public = 0
        return

    def dumps( self, printer=None):
        ss = (\
            f"self.public = {self.public}",
            f"self._protected = {self._protected}",
            f"self.__private = {self.__private}"
        )
        dumps = "\n".join( ss)
        if printer:
            printer( dumps)

        return dumps


s = Scratch()
s.public = 1 
    # Works as expected
s._protected = 2 
    # Works as expected
s.__private = 3 
    # An attribute __private is created. Its value is 3. I'd expected an exception as Python knows this 
    # attribute as _Scratch__private (name mangling).  
s.dumps( print)
print( s.__private)
    # Prints 3. I'd expected an exception.
print( "Finished.")

The output of this code is:

self.public = 1
self._protected = 2
self.__private = 0
3
Finished.

Any idea where the exceptions have gone?

Cheers
Paul

Python doesn’t have private attributes. A double _ does name mangling:
self.__private = 0 is equivalent to self._Scratch__private = 0 here.

You can see this by using s._Scratch__private = 3.

Their purpose it to avoid name clashes in subclasses.

I don’t agree.

s = Scratch()
s.public = 1
s._protected = 2
s.__private = 3
s.dumps( print)
print( s.__private)
print( s._Scratch__private)

prints

self.public = 1
self._protected = 2
self.__private = 0
3
0

self.__private should issue an error, no matter if it’s read or written, and s._Scratch__private should not (and doesn’t). The two attrs coexist. This can be seen, if the debugger is stopped before the program ends and dir( s) is issued.

Unless you use __slots__, you can set pretty much whatever attribute you prefer:

class C:
    pass

C.foo = 'bar'    # fine
C().baz = 'qux'  # fine
2 Likes

See the language documentation on identifiers:

Private name mangling: When an identifier that textually occurs in a class definition begins with two or more underscore characters and does not end in two or more underscores, it is considered a private name of that class. Private names are transformed to a longer form before code is generated for them. The transformation inserts the class name, with leading underscores removed and a single underscore inserted, in front of the name. For example, the identifier __spam occurring in a class named Ham will be transformed to _Ham__spam . This transformation is independent of the syntactical context in which the identifier is used. If the transformed name is extremely long (longer than 255 characters), implementation defined truncation may happen. If the class name consists only of underscores, no transformation is done.

Note this transformation only applies inside a class definition, not when you write

s.__private = 3 

outside the definition of Scratch.

2 Likes

Note that they’re just private by convention, nothing prevents you from accessing them.

1 Like

Hi,

generally, to either get or set private attributes, you use methods called getters and setters.

Here is a small example:

class Hello:

    def __init__(self, name):
        self.a = name
        self._b = name
        self.__c = name

    def getter(self):

        return 'Hello ' + str(self.__c)

    def setter(self, name):

        self.__c = name

hello = Hello('John')

print(hello.a)
print(hello._b)
# print(hello.__c)  # If you uncomment this out, an exception will be generated
# hello.__c = 'Rob'  # Change will not be recognized
# hello.setter('Rob')  # Change will be recognized

print(hello.getter())

hello.setter('Rick')
print(hello.getter())

You should use properties for that:

class Hello:
    def __init__(self, name):
        self.a = name
        self._b = name
        self.__c = name

    @property
    def c(self):
        self.__c

    @c.setter
    def c(self, name):
        self.__c = name
1 Like

Thanks to you all.

So, in summary: Exceptions are issued only if a subclass tries to access a private attribute of one of its base classes. From outside an instance anything goes. Right?

Correction: you need to return self.__c for the getter.


No, you’ll get a attribute error if you didn’t define it yourself:

class Base:
    def __init__(self, value):
        self.__value = value

class Sub(Base):
    def print_value(self):
        print(self.__value)

    def set_value(self, value):
        self.__value = value

sub = Sub('foo')
sub.set_value('bar')
sub.print_value()  # bar
Sub('baz').print_value()
# AttributeError: 'Sub' object has no attribute '_Sub__value'

Name mangling is not a for private data in the sense of protection against external access. Its purpose is to prevent accidental collisions between attribute/method names in child classes, so that both classes can use __value without having to worry what the parent class is doing.

Otherwise python doesn’t care what you do with attribute accesses, and will allow you to do whatever.

3 Likes

See also: 9. Classes — Python 3.12.3 documentation. Should we link to that in the identifiers?