pythonvariablesinstance-variables

Prevent altering instance variable


I would like to allow the user to change self.path after instantiation but not any other instance variable. However, if self.path is changed then the other instance variables should be reevaluated. Is this possible?

class File(object):

    def __init__(self, path):
        self.path = os.path.abspath(path)
        self.name = os.path.basename(self.path)
        self.parent = os.path.dirname(self.path)

        self.extension = self._get_extension()
        self.category = self.get_category(self.extension)
        self.exists = os.path.isfile(self.path)

    def _get_extension(self):
        extension = None
        result = os.path.splitext(self.name)[1][1:]
        if result:
            extension = result
        return extension

    def get_category(self, extension):
        if extension:
            file_extension = extension.upper()
            for key in fileGroups.keys():
                common = set(fileGroups[key]) & set([file_extension])
                if common:
                    return key
        return 'UNDEFINED'

Solution

  • From https://stackoverflow.com/a/598092/3110529 what you're looking for is the property getter/setter pattern. Python implements this via @property and @member.setter, you can see this in the example of the answer above.

    In terms of your issue you can solve it by doing the following:

    class File(object):
    
        def __init__(self, path):
            self.__path = os.path.abspath(path)
            self.__name = os.path.basename(self.path)
            self.__parent = os.path.dirname(self.path)
    
            self.__extension = self._get_extension()
            self.__category = self.get_category(self.extension)
            self.__exists = os.path.isfile(self.path)
    
        @property
        def path(self):
            return self.__path
    
        @path.setter
        def path(self, value):
            self.__path = value
            # Update other variables here too
    
        @property
        def name(self):
            return self.__name
    
    etc for the rest of your properties
    

    This means you can do stuff like:

    file = File("a path")
    print(file.path)
    file.path = "some other path"
    
    # This will throw an AttributeError
    file.name = "another name"
    

    Note that everything works the same but properties without setters will throw errors if tried to be modified.

    This does make your File class significantly larger, but it prevents the user from changing members other than path as there is no setter implemented. Technically the user can still do file.__path = "something else" but there's generally an understanding that members prefixed with double underscores are private and shouldn't be tampered with.