-->

Prevent altering instance variable

2019-09-14 18:11发布

问题:

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'

回答1:

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.



回答2:

Dilanm is right. If you need to make member variables read-only or add validation or other tasks on access use properties. Note that (object) in class declaration is optional, and as there is no way to enforce private members in python classes I would just emphasize my intention with '_', unless you really have a reason to '__'.

#!/usr/bin/env python3

import os.path

class File:
    def __init__(self, path):
        self._path = path
        self.compute_others()

    def compute_others(self):
        self._parent = os.path.dirname(self._path)
        pass # add other attributes to be computed

    # getter, also makes .path read-only
    @property
    def path(self):
        return self._path

    # setter, allows setting but adds validation or other tasks
    @path.setter
    def path(self, path):
        self._path = path
        self.compute_others()

    # other attributes only have getters (read-only)
    @property
    def parent(self):
        return self._parent

    def __str__(self):
        return 'path:{}\nparent:{}\n\n'.format(self.path, self.parent)

f = File('/usr')
print(f)

f.path = '/usr/local'
#f.parent = '/tmp' # error, read-only property
print(f)

To override a member just define it again in child class. Properties are no different.