"Private" attribute properties in Python

I'm relatively new to Python so I hope I haven't missed something, but here goes...

I'm trying to write a Python module, and I'd like to create a class with a "private" attribute that can (or maybe 'should') only be modified through one or more functions within the module. This is in an effort to make the module more robust, since setting of this attribute outside of these functions could lead to unwanted behaviour. For example, I might have:

  • A class that stores x and y values for a scatter plot, Data
  • A function to read x and y values from a file and store them in the class, read()
  • A function to plot them, plot()
  • In this case, I would prefer if the user wasn't able to do something like this:

    data = Data()
    read("file.csv", data)
    data.x = [0, 3, 2, 6, 1]
    plot(data)
    

    I realise that adding a single leading underscore to the name indicates to the user that the attribute should not be changed, ie rename to _x and add a property decorator so that the user can access the value without feeling guilty. However, what if I wanted to add a setter property as well:

    class Data(object):
        _x = []
        _y = []
        @property
        def x(self):
            return self._x
        @x.setter
        def x(self, value):
            # Do something with value
            self._x = value
    

    I'm now in the same position as I was before - the user can no longer directly access the attribute _x , but they can still set it using:

    data.x = [0, 3, 2, 6, 1]
    

    Ideally I'd rename the property function definitions to _x() , but this leads to confusion about what self._x actually means (depending on the order in which they are declared, this seems to result in either the setter being called recursively or the setter being ignored in favour of the attribute).

    A couple of solutions I can think of:

  • Add a double leading underscore to the attribute, __x , so that the name becomes mangled and does not get confused with the setter function. As I understand it, this should be reserved for attributes that a class does not wish to share with possible subclasses, so I'm not sure if this is a legitimate use.
  • Rename the attribute, eg _x_stored . While this solves the problem completely, it makes the code harder to read and introduces naming convention issues - which attributes do I rename? just the ones that are relevant? just the ones that have properties? just the ones within this class?
  • Are either of the above solutions applicable? And if not, is there a better way to solve this problem?

    Edit

    Thanks for the responses so far. A few points thrown up by the comments:

  • I want to retain the extra logic that the setter property gives me - the # Do something with value section in the above example - so internally setting the attribute through direct access of self._x doesn't solve the problem.
  • Removing the setter property and creating a separate function _set_x() does solve the problem, but is not a very neat solution since it allows setting of _x in two different ways - either by calling that function or through direct access of self._x . I'd then have to keep track of which attributes should be set by their own (non-property) setter function and which should be modified through direct access. I'd probably rather use one of the solutions I suggested above, because even though they make a mess of the naming conventions within the class they are at least consistent in their use outside of the class, ie they all use the syntactical sugar of properties. If there's no way of doing this in a neater way then I guess I just have to choose the one that causes the least disruption.

  • If you want to discourage users from changing a property, but want it to be clear that they can read it, I'd use @property without providing a setter, similar to what you described earlier:

    class Data(object):
        def __init__(self):
           self._x = []
           self._y = []
    
        @property 
        def x(self):
            return self._x
    
        @property 
        def y(self):
            return self._x
    

    I know you mention "What if I wanted to add a setter to the property?", but I guess I would counter that with: Why add the setter if you don't want your clients to be able to set the property? Internally, you can access self._x directly.

    As for a client directly accessing _x or _y , any variable with an '_' prefix is understood to be "private" in Python, so you should trust your clients to obey that. If they don't obey that, and end up screwing things up, that's their own fault. This kind of mindset is counter to a many other languages (C++, Java, etc.) where keeping data private is considered very important, but Python's culture is just different in this regard.

    Edit

    One other note, since your private properties in this particular case are lists, which are mutable (unlike strings or ints, which are immutable), a client could end up changing them somewhat accidentally:

    >>> d = Data()
    >>> print d.x
    ['1', '2']
    >>> l = d.x
    >>> print l
    ['1', '2']
    >>> l.append("3")
    >>> print d.x
    ['1', '2', '3']  # Oops!
    

    If you want to avoid this, you'd need your property to return a copy of the list:

    @property
    def x(self):
        return list(self._x)
    
    链接地址: http://www.djcxy.com/p/20200.html

    上一篇: 集成测试多个Celery Workers和一个DB支持的Django API

    下一篇: Python中的“私有”属性属性