bendtherules
bendtherules

Reputation: 382

How to make class variables that are not shared among sub-classes?

I need to use variables, which are shared among instances of a class. So, class variables seemed to fit the bill. But also those class variables are shared among sub-classes, which needs to be avoided.

Here's the problem:

class Parent(object):
    a=[]

class Child(Parent):
    pass

print(Child.a is Parent.a) # => True # Need to avoid this

Here's how I tried to solve:

  1. Its possible to just shadow parent's class variable by re-declaring it the Child, but its still possible to delete child's "a" variable, when Child.a again points back to Parent.a .

    class Parent(object):
        a=[]
    
    class Child(Parent):
        a=[] # Works, but delete-able.
    
    print(Child.a is Parent.a) # => False # Works
    del Child.a
    print(Child.a is Parent.a) # => True # Breaks again
    
  2. Same as prev, but "a" added through metaclass, which is nicer.

    class meta(type):
        def __new__(cls, name, base, clsdict):
                temp_class = type.__new__(cls, name, base, clsdict)
                temp_class.a=[]
                return temp_class
    class Parent(object):
        __metaclass__=meta
    
    class Child(Parent):
        pass
    
    print(Child.a is Parent.a) # => False # Works
    del Child.a
    print(Child.a is Parent.a) # => True # Breaks again
    

But none of them solves the "possible to delete Child's class variable" problem.

Is it possible to have some sort of descriptor for class variables, which can make deletion impossible? If not, what would be a good way to solve this?

Upvotes: 2

Views: 978

Answers (1)

johntellsall
johntellsall

Reputation: 15170

To make a class attribute private to that class, and not subclasses, prefix it with "__" (two underscores). This is called a "class-private member" or "class-private reference."

In the following __update is in the Mapping class, but not in the subclass.

class Mapping:
    def __init__(self, iterable):
        self.items_list = []
        self.__update(iterable)

    def update(self, iterable):
        for item in iterable:
            self.items_list.append(item)

    __update = update   # private copy of original update() method

class MappingSubclass(Mapping):

    def update(self, keys, values):
        # provides new signature for update()
        # but does not break __init__()
        for item in zip(keys, values):
            self.items_list.append(item)

From: Python Classes documentation

Here's the original code. Note that the Child class doesn't inherit the __a attribute from the parent class.

Also note that a Child instance object doesn't inherit the __a attribute, either. The __a attr is private to the Parent and its instances, it's not inherited.

source

class Parent(object):
    __a = []

class Child(Parent):
    def check(self):
        print self.__a  # raises exception, __a is in Parent only, not in self

try:
    print(Child.__a is Parent.__a)
except AttributeError as exc:
    print exc
print
try:
    Child().check()
except AttributeError as exc:
    print exc

output

type object 'Child' has no attribute '__a'

'Child' object has no attribute '_Child__a'

Upvotes: 2

Related Questions