126

I don't know when an attribute should be private and whether I should use property.

I read recently that setters and getters are not pythonic but using the property decorator is OK.

But what if I have attribute, that mustn't be set from outside of class but can be read (read-only attribute). Should this attribute be private, and by private I mean with underscore, like that self._x? If yes, then how can I read it without using getter? Only method I know right now is to write

@property
def x(self):
    return self._x

That way I can read attribute by obj.x but I can't set it obj.x = 1 so it's fine.

But should I really care about setting object that mustn't be set? Maybe I should just leave it. But then again I can't use underscore because reading obj._x is odd for user, so I should use obj.x and then again user doesn't know that he mustn't set this attribute.

What's your opinion and practices?

martineau
  • 119,623
  • 25
  • 170
  • 301
Rafał Łużyński
  • 7,083
  • 5
  • 26
  • 38
  • 1
    The idea of a property is that it behaves like an attribute but can have extra code. If all you want is to get a value, I wouldn't even bother: just use `self.x` and trust that nobody will change `x`. If making sure that `x` can't be changed is important, then use a property. – li.davidm Jan 29 '13 at 23:42
  • Also, `_x` isn't odd at all: by *convention*, it means something "private". – li.davidm Jan 29 '13 at 23:42
  • 1
    I meant that reading from _x is odd. Not _x name itself. If user is reading directly from _x then he is unresponsible. – Rafał Łużyński Jan 30 '13 at 01:07
  • 3
    Important! Your class must be a [new-style class](https://wiki.python.org/moin/NewClassVsClassicClass) i.e. inherits from `object`, for this to actually stop you setting `obj.x`. On an old-style class you can actually still set `obj.x`, with pretty unexpected results. – Ian H Dec 08 '15 at 14:57
  • There are several valid reasons to have read-only properties. One is when you have a value that consists of merging two other (read/write) values. You can do this in a method, but you can do it in a read only property too. – philologon Jul 06 '16 at 17:44
  • If you are working by yourself on the project that would not survive 1 year it does not make sense to bother with access control. However, outside these borders it really makes sense to spend some time on tightening things up. – uuu777 Oct 21 '20 at 23:30

10 Answers10

98

Just my two cents, Silas Ray is on the right track, however I felt like adding an example. ;-)

Python is a type-unsafe language and thus you'll always have to trust the users of your code to use the code like a reasonable (sensible) person.

Per PEP 8:

Use one leading underscore only for non-public methods and instance variables.

To have a 'read-only' property in a class you can make use of the @property decoration, you'll need to inherit from object when you do so to make use of the new-style classes.

Example:

>>> class A(object):
...     def __init__(self, a):
...         self._a = a
...
...     @property
...     def a(self):
...         return self._a
... 
>>> a = A('test')
>>> a.a
'test'
>>> a.a = 'pleh'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
Jonny Henly
  • 4,023
  • 4
  • 26
  • 43
siebz0r
  • 18,867
  • 14
  • 64
  • 107
  • 20
    Python is not type unsafe, it's dynamically typed. And name mangling is not for making it more difficult to cheat, but to prevent name clashes in scenarios where inheritance could be problematic (if you're not programming in the big you shouldn't even care). – memeplex Mar 10 '16 at 01:59
  • 3
    But you should still remember, that mutable objects can be changed using this method anyway. For example if `self.__a = []`, you still can do this `a.a.append('anything')` and it will work. – Igor Sep 27 '16 at 09:11
  • 4
    It is not clear to me what bearing "a reasonable (sensible) person" has on this answer. Can you be more explicit about the kinds of things you think a reasonable person would and would not do? – winni2k Jun 22 '17 at 13:28
  • 3
    For me _make use of the @property decoration, you'll need to inherit from object when you do so_ was the whole point to this answer. Thanks. – akki Feb 13 '18 at 10:25
  • 1
    I could run the code without inheriting from `object`. is it necessary to do so? – s.ouchene Nov 07 '19 at 20:19
  • @winni2k: From context, "a reasonable (sensible) person" is someone who never allows a bug to sneak into the code they write. I know quite a few IRL. They do not write any code at all, tho. – kkm inactive - support strike Dec 28 '19 at 21:19
  • 2
    @kkm the only way to never allow a bug to sneak into code is to never write code. – Alechan Apr 11 '20 at 16:09
  • 1
    @Navaro In Python 3, no. I think it's only necessary in Python 2. See: https://stackoverflow.com/a/45062077/3806231. – Ruben9922 Apr 26 '20 at 14:06
88

Generally, Python programs should be written with the assumption that all users are consenting adults, and thus are responsible for using things correctly themselves. However, in the rare instance where it just does not make sense for an attribute to be settable (such as a derived value, or a value read from some static datasource), the getter-only property is generally the preferred pattern.

Silas Ray
  • 25,682
  • 5
  • 48
  • 63
  • 38
    It looks like your answer is contradict to itself. You say that users should be responsible and use things correctly, then you say that sometimes it does not make sense for an attribute to be settable and getter property is a preffered way. In my opinion you can or can't set attribute. Only question is if I should protect this attr or leave it. There should be no answers in between. – Rafał Łużyński Jan 29 '13 at 23:59
  • 23
    No, I said if you literally cannot set a value, then it doesn't make sense to have a setter. For example, if you have a circle object with a radius member and a circumference attribute that is derived from the radius, or you have an object that wraps some read-only real-time api with a number of getter-only properties. Nothing contradicting anything. – Silas Ray Jan 30 '13 at 00:07
  • 10
    But responsible user won't try to set attr that literally cannot be set. And again not responsible user would set attr that literally can be set and will raise error somewhere else in code due to his set. So in the end both attr cannot be set. Should I use property on both or don't use it on any? – Rafał Łużyński Jan 30 '13 at 00:12
  • 8
    But responsible user _shouldn't_ try to set attr that literally cannot be set. In programming, if something is strictly a non-settable value, the responsible or sensible thing is to ensure that it cannot be. These small things all contribute to reliable programs. – Robin Smith Nov 21 '15 at 14:18
  • 6
    That is a position lots of people and languages take. If it's a position you find non negotiable, you probably shouldn't be using Python. – Silas Ray Nov 21 '15 at 15:01
  • Can you provide an example of a getter-only property? – Stevoisiak Jan 31 '18 at 17:32
  • @winni2k This verbiage is actually put out officially by the Python community. https://github.com/kennethreitz/python-guide/blob/master/docs/writing/style.rst#we-are-all-responsible-users – Silas Ray Feb 02 '18 at 21:32
  • That is clearer verbiage than I remember reading from the python community. I think that narrows it down sufficiently. Cheers. – winni2k Feb 02 '18 at 22:06
  • @StevenM.Vascellaro – a getter-only property is just one where you don't specify a getter method; just the `@property` as shown in the original question's example code... – AJ Poulter Feb 21 '19 at 14:17
  • Some languages use things like consts to optimize cache utilization - if you know it won't change, you won't have to worry about cache invalidation and thus can speed things up. – Slothario Oct 19 '20 at 22:12
  • Some say `all users are consenting adults`, some say the perfect language for `beginners`! – Vassilis Apr 26 '21 at 15:32
  • If you use a getter-only property instead of a simple public attribute for a value that should be get-only, a consenting adult will thank you. Yes, *users are responsible for using things correctly* but good code can help them minimize the amount of bugs they make. – Jeyekomon Feb 06 '23 at 14:56
70

Here is a way to avoid the assumption that

all users are consenting adults, and thus are responsible for using things correctly themselves.

please see my update below

Using @property, is very verbose e.g.:

   class AClassWithManyAttributes:
        '''refactored to properties'''
        def __init__(a, b, c, d, e ...)
             self._a = a
             self._b = b
             self._c = c
             self.d = d
             self.e = e

        @property
        def a(self):
            return self._a
        @property
        def b(self):
            return self._b
        @property
        def c(self):
            return self._c
        # you get this ... it's long

Using

No underscore: it's a public variable.
One underscore: it's a protected variable.
Two underscores: it's a private variable.

Except the last one, it's a convention. You can still, if you really try hard, access variables with double underscore.

So what do we do? Do we give up on having read only properties in Python?

Behold! read_only_properties decorator to the rescue!

@read_only_properties('readonly', 'forbidden')
class MyClass(object):
    def __init__(self, a, b, c):
        self.readonly = a
        self.forbidden = b
        self.ok = c

m = MyClass(1, 2, 3)
m.ok = 4
# we can re-assign a value to m.ok
# read only access to m.readonly is OK 
print(m.ok, m.readonly) 
print("This worked...")
# this will explode, and raise AttributeError
m.forbidden = 4

You ask:

Where is read_only_properties coming from?

Glad you asked, here is the source for read_only_properties:

def read_only_properties(*attrs):

    def class_rebuilder(cls):
        "The class decorator"

        class NewClass(cls):
            "This is the overwritten class"
            def __setattr__(self, name, value):
                if name not in attrs:
                    pass
                elif name not in self.__dict__:
                    pass
                else:
                    raise AttributeError("Can't modify {}".format(name))

                super().__setattr__(name, value)
        return NewClass
    return class_rebuilder

update

I never expected this answer will get so much attention. Surprisingly it does. This encouraged me to create a package you can use.

$ pip install read-only-properties

in your python shell:

In [1]: from rop import read_only_properties

In [2]: @read_only_properties('a')
   ...: class Foo:
   ...:     def __init__(self, a, b):
   ...:         self.a = a
   ...:         self.b = b
   ...:         

In [3]: f=Foo('explodes', 'ok-to-overwrite')

In [4]: f.b = 5

In [5]: f.a = 'boom'
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-5-a5226072b3b4> in <module>()
----> 1 f.a = 'boom'

/home/oznt/.virtualenvs/tracker/lib/python3.5/site-packages/rop.py in __setattr__(self, name, value)
    116                     pass
    117                 else:
--> 118                     raise AttributeError("Can't touch {}".format(name))
    119 
    120                 super().__setattr__(name, value)

AttributeError: Can't touch a
oz123
  • 27,559
  • 27
  • 125
  • 187
  • 1
    This is really helpful, and does exactly what I wanted to do. Thank you. However, it is for those who have Python 3 installed. I am using Python 2.7.8, so I have to apply two minor tweaks to your solution: "class NewClass(cls, object<\b>):"... "super(NewClass, self)<\b>.__setattr__(name, value)". – Ying Zhang Aug 11 '17 at 14:02
  • 1
    In addition, one should be careful about class member variables being lists and dictionaries. You can't really 'lock them down' from being updated in this way. – Ying Zhang Aug 11 '17 at 17:41
  • 2
    One improvement and three problems here. Improvement: the `if..elif..else` block could just be `if name in attrs and name in self.__dict__: raise Attr...` with no `pass` required. Problem 1: classes thus decorated all end up with an identical `__name__`, and the string representation of their type is homogenized as well. Problem 2: this decoration overwrites any custom `__setattr__`. Problem 3: users can defeat this with `del MyClass.__setattr__`. – TigerhawkT3 Mar 05 '18 at 12:10
  • Just a language thing. "Alas ..." means "Sad to say,..." which is not what you want, I think. – Thomas Andrews Jul 31 '18 at 16:12
  • Nothing will prevent me from doing `object.__setattr__(f, 'forbidden', 42)`. I don't see what `read_only_properties` adds that isn't handled by the double underscore name mangling. – L3viathan Jan 23 '20 at 13:22
  • Yes, you are correct. But it takes much more effort than just assigning. – oz123 Jan 23 '20 at 13:53
  • 1
    I like the approach of the decorator, but it loses the original class name and class doc, which might be bad for "official, documented" class. Wouldn't it be better to have a decorator that modifies the original class instead of creating a new class? Simply define an inner function `fixSetAttr` that is essentially the same as the overridden `__setattr__`; it needs to call `super(cls, self)` instead of `super()` inside, and then modify the original class with `setattr(cls, '__setattr__', fixSetAttr)` instead of creating a new class. – Georg Sander Apr 01 '21 at 09:27
6

Here is a slightly different approach to read-only properties, which perhaps should be called write-once properties since they do have to get initialized, don't they? For the paranoid among us who worry about being able to modify properties by accessing the object's dictionary directly, I've introduced "extreme" name mangling:

from uuid import uuid4

class ReadOnlyProperty:
    def __init__(self, name):
        self.name = name
        self.dict_name = uuid4().hex
        self.initialized = False

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            return instance.__dict__[self.dict_name]

    def __set__(self, instance, value):
        if self.initialized:
            raise AttributeError("Attempt to modify read-only property '%s'." % self.name)
        instance.__dict__[self.dict_name] = value
        self.initialized = True

class Point:
    x = ReadOnlyProperty('x')
    y = ReadOnlyProperty('y')
    def __init__(self, x, y):
        self.x = x
        self.y = y

if __name__ == '__main__':
    try:
        p = Point(2, 3)
        print(p.x, p.y)
        p.x = 9
    except Exception as e:
        print(e)
Neuron
  • 5,141
  • 5
  • 38
  • 59
Booboo
  • 38,656
  • 3
  • 37
  • 60
  • Nice. If you mangle `dict_name` instead, e.g. `dict_name = "_spam_" + name` it removes the dependency on `uuid4` and makes debugging a lot easier. – c z Oct 09 '18 at 10:23
  • But then I can say `p.__dict__['_spam_x'] = 5` to change the value of `p.x`, so this does not provide suffice name mangling. – Booboo Oct 09 '18 at 15:42
4

I am dissatisfied with the previous two answers to create read only properties because the first solution allows the readonly attribute to be deleted and then set and doesn't block the __dict__. The second solution could be worked around with testing - finding the value that equals what you set it two and changing it eventually.

Now, for the code.

def final(cls):
    clss = cls
    @classmethod
    def __init_subclass__(cls, **kwargs):
        raise TypeError("type '{}' is not an acceptable base type".format(clss.__name__))
    cls.__init_subclass__ = __init_subclass__
    return cls


def methoddefiner(cls, method_name):
    for clss in cls.mro():
        try:
            getattr(clss, method_name)
            return clss
        except(AttributeError):
            pass
    return None
            
            
def readonlyattributes(*attrs):
    """Method to create readonly attributes in a class
    
    Use as a decorator for a class. This function takes in unlimited 
    string arguments for names of readonly attributes and returns a
    function to make the readonly attributes readonly. 
    
    The original class's __getattribute__, __setattr__, and __delattr__ methods
    are redefined so avoid defining those methods in the decorated class
    
    You may create setters and deleters for readonly attributes, however
    if they are overwritten by the subclass, they lose access to the readonly
    attributes. 
    
    Any method which sets or deletes a readonly attribute within
    the class loses access if overwritten by the subclass besides the __new__
    or __init__ constructors.
    
    This decorator doesn't support subclassing of these classes
    """
    def classrebuilder(cls):
        def __getattribute__(self, name):
            if name == '__dict__':
                    from types import MappingProxyType
                    return MappingProxyType(super(cls, self).__getattribute__('__dict__'))
            return super(cls, self).__getattribute__(name)
        def __setattr__(self, name, value): 
                if name == '__dict__' or name in attrs:
                    import inspect
                    stack = inspect.stack()
                    try:
                        the_class = stack[1][0].f_locals['self'].__class__
                    except(KeyError):
                        the_class = None
                    the_method = stack[1][0].f_code.co_name
                    if the_class != cls: 
                         if methoddefiner(type(self), the_method) != cls:
                            raise AttributeError("Cannot set readonly attribute '{}'".format(name))                        
                return super(cls, self).__setattr__(name, value)
        def __delattr__(self, name):                
                if name == '__dict__' or name in attrs:
                    import inspect
                    stack = inspect.stack()
                    try:
                        the_class = stack[1][0].f_locals['self'].__class__
                    except(KeyError):
                        the_class = None
                    the_method = stack[1][0].f_code.co_name
                    if the_class != cls:
                        if methoddefiner(type(self), the_method) != cls:
                            raise AttributeError("Cannot delete readonly attribute '{}'".format(name))                        
                return super(cls, self).__delattr__(name)
        clss = cls
        cls.__getattribute__ = __getattribute__
        cls.__setattr__ = __setattr__
        cls.__delattr__ = __delattr__
        #This line will be moved when this algorithm will be compatible with inheritance
        cls = final(cls)
        return cls
    return classrebuilder

def setreadonlyattributes(cls, *readonlyattrs):
    return readonlyattributes(*readonlyattrs)(cls)


if __name__ == '__main__':
    #test readonlyattributes only as an indpendent module
    @readonlyattributes('readonlyfield')
    class ReadonlyFieldClass(object):
        def __init__(self, a, b):
            #Prevent initalization of the internal, unmodified PrivateFieldClass
            #External PrivateFieldClass can be initalized
            self.readonlyfield = a
            self.publicfield = b
            

    attr = None
    def main():
        global attr
        pfi = ReadonlyFieldClass('forbidden', 'changable')
        ###---test publicfield, ensure its mutable---###
        try:
            #get publicfield
            print(pfi.publicfield)
            print('__getattribute__ works')
            #set publicfield
            pfi.publicfield = 'mutable'
            print('__setattr__ seems to work')
            #get previously set publicfield
            print(pfi.publicfield)
            print('__setattr__ definitely works')
            #delete publicfield
            del pfi.publicfield 
            print('__delattr__ seems to work')
            #get publicfield which was supposed to be deleted therefore should raise AttributeError
            print(pfi.publlicfield)
            #publicfield wasn't deleted, raise RuntimeError
            raise RuntimeError('__delattr__ doesn\'t work')
        except(AttributeError):
            print('__delattr__ works')
        
        
        try:
            ###---test readonly, make sure its readonly---###
            #get readonlyfield
            print(pfi.readonlyfield)
            print('__getattribute__ works')
            #set readonlyfield, should raise AttributeError
            pfi.readonlyfield = 'readonly'
            #apparently readonlyfield was set, notify user
            raise RuntimeError('__setattr__ doesn\'t work')
        except(AttributeError):
            print('__setattr__ seems to work')
            try:
                #ensure readonlyfield wasn't set
                print(pfi.readonlyfield)
                print('__setattr__ works')
                #delete readonlyfield
                del pfi.readonlyfield
                #readonlyfield was deleted, raise RuntimeError
                raise RuntimeError('__delattr__ doesn\'t work')
            except(AttributeError):
                print('__delattr__ works')
        try:
            print("Dict testing")
            print(pfi.__dict__, type(pfi.__dict__))
            attr = pfi.readonlyfield
            print(attr)
            print("__getattribute__ works")
            if pfi.readonlyfield != 'forbidden':
                print(pfi.readonlyfield)
                raise RuntimeError("__getattr__ doesn't work")
            try:
                pfi.__dict__ = {}
                raise RuntimeError("__setattr__ doesn't work")
            except(AttributeError):
                print("__setattr__ works")
            del pfi.__dict__
            raise RuntimeError("__delattr__ doesn't work")
        except(AttributeError):
            print(pfi.__dict__)
            print("__delattr__ works")
            print("Basic things work")


main()
        

There is no point to making read only attributes except when your writing library code, code which is being distributed to others as code to use in order to enhance their programs, not code for any other purpose, like app development. The __dict__ problem is solved, because the __dict__ is now of the immutable types.MappingProxyType, so attributes cannot be changed through the __dict__. Setting or deleting __dict__ is also blocked. The only way to change read only properties is through changing the methods of the class itself.

Though I believe my solution is better than of the previous two, it could be improved. These are this code's weaknesses:

  1. Doesn't allow adding to a method in a subclass which sets or deletes a readonly attribute. A method defined in a subclass is automatically barred from accessing a readonly attribute, even by calling the superclass' version of the method.

  2. The class' readonly methods can be changed to defeat the read only restrictions.

However, there is not way without editing the class to set or delete a read only attribute. This isn't dependent on naming conventions, which is good because Python isn't so consistent with naming conventions. This provides a way to make read only attributes that cannot be changed with hidden loopholes without editing the class itself. Simply list the attributes to be read only when calling the decorator as arguments and they will become read only.

Credit to Brice's answer for getting the caller classes and methods.

Neuron
  • 5,141
  • 5
  • 38
  • 59
Elijah
  • 206
  • 1
  • 8
  • `object.__setattr__(pfi, 'readonly', 'foobar')` breaks this solution, without editing the class itself. – L3viathan Jan 23 '20 at 13:25
3

That's my workaround.

@property
def language(self):
    return self._language
@language.setter
def language(self, value):
    # WORKAROUND to get a "getter-only" behavior
    # set the value only if the attribute does not exist
    try:
        if self.language == value:
            pass
        print("WARNING: Cannot set attribute \'language\'.")
    except AttributeError:
        self._language = value
André
  • 4,417
  • 4
  • 29
  • 56
rusiano
  • 364
  • 2
  • 11
1

Notice that instance methods are also attributes (of the class) and that you could set them at the class or instance level if you really wanted to be a badass. Or that you may set a class variable (which is also an attribute of the class), where handy readonly properties won't work neatly out of the box. What I'm trying to say is that the "readonly attribute" problem is in fact more general than it's usually perceived to be. Fortunately there are conventional expectations at work that are so strong as to blind us wrt these other cases (after all, almost everything is an attribute of some sort in python).

Building upon these expectations I think the most general and lightweight approach is to adopt the convention that "public" (no leading underscore) attributes are readonly except when explicitly documented as writeable. This subsumes the usual expectation that methods won't be patched and class variables indicating instance defaults are better let alone. If you feel really paranoid about some special attribute, use a readonly descriptor as a last resource measure.

memeplex
  • 2,297
  • 27
  • 26
1

While I like the class decorator from Oz123, you could also do the following, which uses an explicit class wrapper and __new__ with a class Factory method returning the class within a closure:

class B(object):
    def __new__(cls, val):
        return cls.factory(val)

@classmethod
def factory(cls, val):
    private = {'var': 'test'}

    class InnerB(object):
        def __init__(self):
            self.variable = val
            pass

        @property
        def var(self):
            return private['var']

    return InnerB()
oz123
  • 27,559
  • 27
  • 125
  • 187
Apollo Marquis
  • 181
  • 2
  • 8
1

someone mentioned using a proxy object, I didn't see an example of that so I ended up trying it out, [poorly].

/!\ Please prefer class definitions and class constructors if possible

this code is effectively re-writing class.__new__ (class constructor) except worse in every way. Save yourself the pain and do not use this pattern if you can.

def attr_proxy(obj):
    """ Use dynamic class definition to bind obj and proxy_attrs.
        If you can extend the target class constructor that is 
        cleaner, but its not always trivial to do so.
    """
    proxy_attrs = dict()

    class MyObjAttrProxy():
        def __getattr__(self, name):
            if name in proxy_attrs:
                return proxy_attrs[name]  # overloaded

            return getattr(obj, name)  # proxy

        def __setattr__(self, name, value):
            """ note, self is not bound when overloading methods
            """
            proxy_attrs[name] = value

    return MyObjAttrProxy()


myobj = attr_proxy(Object())
setattr(myobj, 'foo_str', 'foo')

def func_bind_obj_as_self(func, self):
    def _method(*args, **kwargs):
        return func(self, *args, **kwargs)
    return _method

def mymethod(self, foo_ct):
    """ self is not bound because we aren't using object __new__
        you can write the __setattr__ method to bind a self 
        argument, or declare your functions dynamically to bind in 
        a static object reference.
    """
    return self.foo_str + foo_ct

setattr(myobj, 'foo', func_bind_obj_as_self(mymethod, myobj))
Community
  • 1
  • 1
ThorSummoner
  • 16,657
  • 15
  • 135
  • 147
-2

I know i'm bringing back from the dead this thread, but I was looking at how to make a property read only and after finding this topic, I wasn't satisfied with the solutions already shared.

So, going back to the initial question, if you start with this code:

@property
def x(self):
    return self._x

And you want to make X readonly, you can just add:

@x.setter
def x(self, value):
    raise Exception("Member readonly")

Then, if you run the following:

print (x) # Will print whatever X value is
x = 3 # Will raise exception "Member readonly"
vincedjango
  • 1,022
  • 1
  • 13
  • 24
  • 5
    But if you just don't make a setter, trying to assign will raise an error as well (An `AttributeError('can't set attribute')`) – Artyer Jun 28 '17 at 16:32