python: immutable private class variables?
Asked Answered
T

8

21

Is there any way to translate this Java code into Python?

class Foo
{
    final static private List<Thingy> thingies = 
       ImmutableList.of(thing1, thing2, thing3);
}

e.g. thingies is an immutable private list of Thingy objects that belongs to the Foo class rather than its instance.

I know how to define static class variables from this question Static class variables in Python but I don't know how to make them immutable and private.

That answered 13/9, 2011 at 18:52 Comment(7)
There is no built-in concept of private variables like you have in Java (that isn't pythonic) there are only conventions people tend to obey (but it's still not the same): docs.python.org/tutorial/classes.html#private-variablesSturgis
Python quote. We are all gentlemen here right?Nymphomania
I think it's "we're all adults here." But the point is very, very important. The source is visible. Fussing around about "privacy" isn't very helpful.Enloe
Python is not Java: dirtsimple.org/2004/12/python-is-not-java.htmlGrandfather
@JakobBowyer no, some people here will be womenAsclepiadean
@Grandfather "Python is not Java" gives the impression that nothing in Java should be done in Python, which I don't think anyone would argue. That is, it's misleadingAsclepiadean
Absence of defined constants in Python is a feature. For example if you only want approximate answers in a numerical calculation, you are free to write math.pi = 3.Vigilant
C
20

You can't do either of those things in Python, not in the sense you do them in Java, anyway.

By convention, names prefixed with an underscore are considered private and should not be accessed outside the implementation, but nothing in Python enforces this convention. It's considered more of a warning that you're messing with an implementation detail that may change without warning in a future version of the code.

Croissant answered 13/9, 2011 at 18:54 Comment(4)
Immutability can be achieved by using property(). Private variables could, theoretically, be achieved the same way by having the getter perform some check to determine whether or not the it was an instance, and returning some acceptable default in place of the actual value based on the check. At least, I think that'd be possible. I haven't ever found a need to do so ...Hufnagel
It'd be a lot of work just to get something which is nonidiomatic and seen as unnecessary. Just prefix is with _.Eventuate
Properties can be made read-only only on instances, so you'd have to define them on a metaclass to get the desired behavior on the class. And you'd still be able to change them; you'd merely have to change them on the metaclass.Croissant
Properties still have to store the value somewhere. Typically, these are stored on the instance in an attribute beginning with an underscore... where they are just as unprotected as without the property.Croissant
R
38

In Python the convention is to use a _ prefix on attribute names to mean protected and a __ prefix to mean private. This isn't enforced by the language; programmers are expected to know not to write code that relies on data that isn't public.

If you really wanted to enforce immutability, you could use a metaclass[docs] (the class of a class). Just modify __setattr__ and __delattr__ to raise exceptions when someone attempts to modify it, and make it a tuple (an immutable list) [docs].

class FooMeta(type):
    """A type whose .thingies attribute can't be modified."""

    def __setattr__(cls, name, value):
        if name == "thingies":
            raise AttributeError("Cannot modify .thingies")
        else:
            return type.__setattr__(cls, name, value)

    def __delattr__(cls, name):
        if name == "thingies":
            raise AttributeError("Cannot delete .thingies")
        else:
            return type.__delattr__(cls, name)

thing1, thing2, thing3 = range(3)

class Foo(object):
    __metaclass__ = FooMeta
    thingies = (thing1, thing2, thing3)
    other = [1, 2, 3]

Examples

print Foo.thingies # prints "(0, 1, 2)"
Foo.thingies = (1, 2) # raises an AttributeError
del Foo.thingies # raise an AttributeError
Foo.other = Foo.other + [4] # no exception
print Foo.other # prints "[1, 2, 3, 4]"

It would still technically be possible to modify these by going through the class's internal .__dict__ of attributes, but this should be enough to deter most users, it's very difficult to entirely secure Python objects.

Rhombohedral answered 13/9, 2011 at 19:7 Comment(7)
+1. I'll add a warning: Just because Python allows you to do this doesn't mean you should. Good Java is not necessarily good Python (and vice versa).Booster
@StevenRumbalski: Definitely. I've added such disclaimer to my post.Rhombohedral
Serious overkill when you can use properties.Newmann
@EthanFurman: The answers using property are protected the instance attribute Foo().thingies, he asked to protect the class attribute Foo.thingies. I could have also used properties here, but lots of people don't understand metaclasses and I wanted to have a relatively straightforward example.Rhombohedral
I'm not sure this is a good example, though, as thingies can still be rebound on the instances. However, with properties (as you said), the class attribute of thingies can be rebound -- apparently the complete solution is a combination of metaclass and properties.Newmann
what is type over here?Trix
@Trix Type is type, the base meta-class of which most classes are instances. Metaclasses are a bit complicated to clearly explain in a comment, you can try the docs, but those still aren't perfect.Rhombohedral
C
20

You can't do either of those things in Python, not in the sense you do them in Java, anyway.

By convention, names prefixed with an underscore are considered private and should not be accessed outside the implementation, but nothing in Python enforces this convention. It's considered more of a warning that you're messing with an implementation detail that may change without warning in a future version of the code.

Croissant answered 13/9, 2011 at 18:54 Comment(4)
Immutability can be achieved by using property(). Private variables could, theoretically, be achieved the same way by having the getter perform some check to determine whether or not the it was an instance, and returning some acceptable default in place of the actual value based on the check. At least, I think that'd be possible. I haven't ever found a need to do so ...Hufnagel
It'd be a lot of work just to get something which is nonidiomatic and seen as unnecessary. Just prefix is with _.Eventuate
Properties can be made read-only only on instances, so you'd have to define them on a metaclass to get the desired behavior on the class. And you'd still be able to change them; you'd merely have to change them on the metaclass.Croissant
Properties still have to store the value somewhere. Typically, these are stored on the instance in an attribute beginning with an underscore... where they are just as unprotected as without the property.Croissant
N
9

You can make it un-writeable (subtly different from immutable) by using properties, but there is no way to make it private -- that goes against Python's philosophy.

class Foo(object):    # don't need 'object' in Python 3
    @property
    def thingies(self):
        return 'thing1', 'thing2', 'thing3'

f = Foo()
print f.thingies
#('thing1', 'thing2', 'thing3')
f.thingies = 9
#Traceback (most recent call last):
#  File "test.py", line 8, in <module>
#    f.thingies = 9
#AttributeError: can't set attribute

Whether it's immutable or not depends on what you return; if you return a mutable object you may be able to mutate that and have those changes show up in the instance/class.

class FooMutable(object):
    _thingies = [1, 2, 3]
    @property
    def thingies(self):
        return self._thingies

foo = FooMutable()
foo.thingies.append(4)
print foo.thingies
# [1, 2, 3, 4]

This will let you mutate thingies, and because the object returned is the same object kept in the instance/class the changes will be reflected on subsequent access.

Compare that with:

class FooMutable(object):
    @property
    def thingies(self):
        return [1, 2, 3]

foo = FooMutable()
foo.thingies.append(4)
print foo.thingies
# [1, 2, 3]

Because a brand new list is returned each time, changes to it are not reflected in subsequent accesses.

Newmann answered 13/9, 2011 at 19:2 Comment(3)
Or just make _thingies a tuple.Booster
@Steven I could still do myinstance._thingies = None if you made it a tuple.Brescia
@Steven, you mean like the first example? ;)Newmann
H
2

You want to look into the property() function. It allows you to define your own custom Getter and Setter for a member attribute of a class. It might look something like this:

class myClass(object):
  _x = "Hard Coded Value"

  def set_x(self, val): return

  def get_x(self): return self._x

  def del_x(self): return

  x = property(get_x, set_x, del_x, "I'm an immutable property named 'x'")

I haven't used it enough to be certain whether it can be used to create something "private" so you'd have to delve into that yourself, but isinstance may help.

Hufnagel answered 13/9, 2011 at 19:1 Comment(0)
A
0

You can achieve the final part using type hints*. As others have said, __ achieves the private aspect well enough, so

from typing import List
from typing_extensions import Final

class Foo:
    __thingies: Final[List[Thingy]] = ImmutableList.of(thing1, thing2, thing3)

I'll leave the definition of ImmutableList to you. A tuple will probably do.

*with the usual caveat that users can ignore them

Asclepiadean answered 21/6, 2020 at 15:54 Comment(0)
R
0

A different flavor of @Jeremy's answer which is in my view cleaner and also provides a type hint in IDE (assuming mypy usage).

from typing import Final    # (optional Final): Final is not enforced, but this provide a nice hint in IDE (assuming mypy is used)


class Immutable(type):
    def __setattr__(cls, attr, value):
        raise AttributeError("Cannot reassign members.")

    def __delattr__(cls, name):
        raise AttributeError("Cannot delete members.")


class ImmutableConstant(metaclass=Immutable):
    FOO: Final = 1  # (optionnal Final)

Here is a runtime example:

print(ImmutableConstant.FOO)    # Prints '1'
try:
    ImmutableConstant.FOO = 3   #  Nice IDE warning (assuming mypy is used: Cannot assign to final attribute "FOO" mypy)
except AttributeError as e:
    print(e)                    # Cannot reassign members.
    try:
        del ImmutableConstant.FOO
    except AttributeError as e:
        print(e)                # Cannot delete members.
Rajab answered 21/12, 2023 at 16:52 Comment(0)
V
0

Solution with inheritance:

class PrivateImmutable(object):
    def __init__(self):
        self.__immutable__ = True

    def __setattr__(self, name, value):
        if hasattr(self, '__immutable__'):
            raise AttributeError(f'object is immutable')
        object.__setattr__(self, name, value)

class Baz(PrivateImmutable):
    def __init__(self):
        self.blue = 98     # Set all attribute in constructor.
        super().__init__() # Then call parent constructor, attributes are no longer mutable.

b = Baz()
print(b.blue) # 98

b.lol = 443 # AttributeError
Vistula answered 29/5 at 19:34 Comment(0)
T
0
class NameControl:
"""
Descriptor which don't allow to change field value after initialization.
"""    
def __set_name__(self, owner, name):
    self.public_name = name
    self.private_name = '_' + name

def __get__(self, obj, objtype=None):
    return getattr(obj, self.private_name)

def __set__(self, obj, value):
    #the attribute does not exist until initialization
    if hasattr(obj, self.private_name):
        raise ValueError(f'{self.public_name.title()} can not be changed.')
    else:
        setattr(obj, self.private_name, value)
Tingly answered 11/6 at 11:16 Comment(1)
Thank you for posting this answer. While this code may answer the question, might you please edit your post to add an explanation as to why/how it works? This can help future readers learn and apply your answer. You are also more likely to get positive feedback (upvotes) when you include an explanation.Suanne

© 2022 - 2024 — McMap. All rights reserved.