Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Restrict modification of class variables except with a new instance

This is a follow up question to the following post (checking the link is not required to understand the question)

Counter variable for class

We set idCounter as a class variable for class Student and it counts the number of instances created .

This is the class:

class Student:
# A student ID counter
    idCounter = 0
def __init__(self):
    self.gpa = 0
    self.record = {}
    # Each time I create a new student, the idCounter increment
    Student.idCounter += 1
    self.name = 'Student {0}'.format(Student.idCounter)

Now, we instantiate a few instances then check the value of idCounter:

student1 = Student()
student2 = Student()
student3 = Student()
student4 = Student()

Student.idCounter
4

However, maintaining a counter is rendered moot if you can do this:

Student.idCounter = 2000

Now create new instance:

student5 = Student()

and check idCounter:

Student.idCounter

2001

idCounter can simply screw up the counter without ever running __init__.

How can you create a counter (or any class variable) that will only increment when __init__ runs ? and cannot be modified independently by calling the class variable from the class as shown above.

Is there a general way to restrict a class variable from being modified using the syntax?

ClassName.ClassVariable = new_value

Thank you.

like image 510
hussam Avatar asked Dec 24 '22 10:12

hussam


1 Answers

EDIT

Improved version with a property but same principle:

class Meta(type):

    def __init__(cls, *args, **kwargs):
        cls.__value = 0
        super().__init__(*args, **kwargs)

    @property
    def idCounter(cls):
        return cls.__value

class Student(metaclass=Meta):

    def __init__(self):
        self.__class__._Meta__value += 1

Now:

>>> s1 = Student()
>>> Student.idCounter
1
>>> s2 = Student()
>>> Student.idCounter
2
>>> Student.idCounter = 100    
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-64-a525899df18d> in <module>()
----> 1 Student.idCounter = 100

AttributeError: can't set attribute  

Old version

Using a descriptor and a metaclass:

class Counter:
    def __init__(self):
        self.value = 0
    def __get__(self, instance, cls):
        return getattr(instance, '_{}__hidden_counter'.format(instance.__name__ ))
    def __set__(self, instance, value):
        raise NotImplementedError

class Meta(type):
    idCounter = Counter()

class Student(metaclass=Meta):
    __hidden_counter = 0

    def __init__(self):
        Student.__hidden_counter += 1

seems to achieve this:

>>> s1 = Student()
>>> Student.idCounter
1
>>> s2 = Student()
>>> Student.idCounter
2
>>> Student.idCounter = 200
---------------------------------------------------------------------------
NotImplementedError                       Traceback (most recent call last)
<ipython-input-51-dc2483b583f6> in <module>()
----> 1 Student.idCounter = 200

<ipython-input-46-b21e03bf3cb3> in __set__(self, instance, value)
      5         return getattr(instance, '_{}__hidden_counter'.format(instance.__name__ ))
      6     def __set__(self, instance, value):
----> 7         raise NotImplementedError
      8 
      9 class Meta(type):

NotImplementedError:
>>> Student.idCounter
2

This can still intentionally be broken:

>>> Student._Student__hidden_counter = 100
>>> Student.idCounter
100

but not by accident.

like image 128
Mike Müller Avatar answered Dec 26 '22 00:12

Mike Müller