4

So I don't come from a computer science background and I am having trouble googling/SO searching on the right terms to answer this question. If I have a Python class with a class variable objects like so:

class MyClass(object):
    objects = None
    pass

MyClass.objects = 'test'
print MyClass.objects    # outputs 'test'

a = MyClass()
print a.objects          # also outputs 'test'

both the class and instances of the class will have access to the objects variable. I understand that I can change the instance value like so:

a.objects = 'bar'
print a.objects          # outputs 'bar'
print MyClass.objects    # outputs 'test'

but is it possible to have a class variable in Python that is accessible to users of the class (i.e. not just from within the class) but not accessible to the instances of that class? I think this is called a private member or static member in other languages?

Randy
  • 908
  • 12
  • 30
  • Perhaps I should clarify that I want users of my `MyClass` to use `objects`, but I don't want instances of `MyClass` to use objects. The link provided by jonrsharpe talks a lot about how to make something private, which isn't really what I want to get at with this question. I am more interested in if it possible for a class variable to be present only in the class, and not in instances. – Randy Jan 08 '14 at 17:18
  • why do you need to add such variable inside your class? It isn't simpler to add it to your module's root? e.g. `MY_VARIABLE='test'` outside your class definition – furins Jan 08 '14 at 17:31
  • Adding it to the module root may be simpler, but I am in the situation where doing so would require a lot of other changes to calling code, which is why I would like to keep it in the class. – Randy Jan 08 '14 at 19:23
  • This question is **not** a duplicate! The OP is using the term "private" in an incorrect way. The question is about class variables *not* accessible through instances, which has nothing to do with the other question! – Bakuriu Mar 06 '14 at 16:13
  • +1 Bakuriu, thanks for removing the duplicateness. – Randy Mar 06 '14 at 20:04

4 Answers4

3

Python is designed to allow instances of a class to access that class's attributes through the instance.

This only goes one level deep, so you can use a metaclass:

class T(type):
    x = 5

class A(object):
    __metaclass__ = T

Note that the metaclass syntax is different in Python 3. This works:

>>> A.x
5
>>> A().x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute 'x'

It doesn't prevent you setting the attribute on instances of the class, though; to prevent that you'd have to play with __setattr__ magic method:

class A(object):
    x = 1
    def __getattribute__(self, name):
        if name == 'x':
            raise AttributeError
        return super(A, self).__getattribute__(name)
    def __setattr__(self, name, value):
        if name == 'x':
            raise AttributeError
        return super(A, self).__setattr__(name, value)
    def __delattr__(self, name):
        if name == 'x':
            raise AttributeError
        return super(A, self).__delattr__(name)
ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • I am a recent fan of metaclasses, and my code is already using them in a similar way to do some bootstrapping of objects before they are handed off to the user. Doing this in a metaclass works very nicely. – Randy Jan 08 '14 at 22:29
2

The simplest way of achieving it is to use a descriptor. Descriptors are the thing meant for giving a higher level of control over attribute access. For example:

class ClassOnly(object):
    def __init__(self, name, value):
        self.name = name
        self.value = value
    def __get__(self, inst, cls):
        if inst is not None:
            msg = 'Cannot access class attribute {} from an instance'.format(self.name)
            raise AttributeError(msg)
        return self.value

class A(object):
    objects = ClassOnly('objects', [])

Used as:

In [11]: a = A()

In [12]: a.objects
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-12-24afc67fd0ba> in <module>()
----> 1 a.objects

<ipython-input-9-db6510cd313b> in __get__(self, inst, cls)
      5     def __get__(self, inst, cls):
      6         if inst is not None:
----> 7             raise AttributeError('Cannot access class attribute {} from an instance'.format(self.name))
      8         return self.value

AttributeError: Cannot access class attribute objects from an instance

In [13]: A.objects
Out[13]: []
Bakuriu
  • 98,325
  • 22
  • 197
  • 231
1

If you want there to be a "single source of truth" for objects, you could make it a mutable type:

class MyClass(object):
    objects = []

With immutable types, the fact that each instance starts out with the same reference from MyClass is irrelevant, as the first time that attribute is changed for the instance, it becomes "disconnected" from the class's value.

However, if the attribute is mutable, changing it in an instance changes it for the class and all other instances of the class:

>>> MyClass.objects.append(1)
>>> MyClass.objects
[1]
>>> a = MyClass()
>>> a.objects
[1]
>>> a.objects.append(2)
>>> a.objects
[1, 2]
>>> MyClass.objects
[1, 2] 

In Python, nothing is really "private", so you can't really prevent the instances from accessing or altering objects (in that case, is it an appropriate class attribute?), but it is conventional to prepend names with an underscore if you don't ordinarily want them to be accessed directly: _objects.

One way to actually protect objects from instance access would be to override __getattribute__:

def __getattribute__(self, name):
    if name == "objects":
        raise AttributeError("Do not access 'objects' though MyClass instances.")
    return super(MyClass, self).__getattribute__(name)

>>> MyClass.objects
[1]
>>> a.objects
...
AttributeError: Do not access 'objects' though MyClass instances.
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
  • I want others to access `MyClass.objects`, but not `a.objects`, so I suppose `__init__` could just set `self.objects = None`? – Randy Jan 08 '14 at 17:34
  • 1
    Please note that the instances of that class can still redefine the `__getattribute__` function, removing the protection. A part from that, this is an elegant approach. – furins Jan 08 '14 at 17:46
0

No, you can't (EDIT: you can't in a way that is completely unaccessible, like in Java or C++).

You can do this, if you like:

class MyClass(object):
    objects = None
    pass

MyClass_objects = 'test'
print MyClass_objects    # outputs 'test'

a = MyClass()
print a.objects          # outputs 'None'

or this:

in your_module.py:

objects = 'test'
class MyClass(object):
    objects = None
    pass

in yourapp.py:

import your_module
print your_module.objects    # outputs 'test'

a = your_module.MyClass()
print a.objects          # outputs 'None'

the reason is:

When you create an instance of some class there is nothing to prevent you from poking around inside and using various internal, private methods that are (a) necessary for the class to function, BUT (b) not intended for direct use/access.

Nothing is really private in python. No class or class instance can keep you away from all what's inside (this makes introspection possible and powerful). Python trusts you. It says "hey, if you want to go poking around in dark places, I'm gonna trust that you've got a good reason and you're not making trouble." Karl Fast

furins
  • 4,979
  • 1
  • 39
  • 57
  • Thanks @furins, this is what I was suspecting, but I just wanted to make sure of this type of behavior especially when I want certain accessibility to `MyClass.objects`. The name-mangling etc in other SO answers doesn't quite reach this nuanced approach I have. – Randy Jan 08 '14 at 17:41