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'
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.
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.