Reputation: 6962
I'm trying to "simulate" namespacing in python. I'm using inner and outer class hirarchies to create my namespaces. For example you want to save paths of files (like resources) in one location. I tried something like this:
src = #path to source folder
class Resources:
root = src + "Resources\\"
class Fonts:
root = Resources.root + "fonts\\"
font1 = root + "font1.ttf"
font2 = root + "font2.ttf"
class Images:
root = Resources.root + "images\\"
logo = root + "logo"
image1= root + "image1"
class StyleSheets:
root = Resources.root + "stylesheets\\"
default = root + "default.qss"
class JsonData:
root = src + "Data\\"
class TableEntries:
root = JsonData.root
entries1 = root + "Entries1.json"
entries2 = root + "Entries2.json"
Accessing elements would look like this:
logoPath = Resources.Images.image1
Unfortunatly this isn't working due to the following error:
root = Resources.root + "fonts\\"
NameError: name 'Resources' is not defined
My Question
Is it possible to set class variables of inner class based on class variables of outer class? If not, is there another way to access the elements as shown above without using multiple files?
Upvotes: 0
Views: 77
Reputation: 5286
I think that you do not have clear the concept of class and instaces in OOP. If you want to store this kind of information Resources
shoult not be a class, it should be an instance of a Dir
class.
class Dir:
def __init__(self, path="/", parent=None):
self.parent = parent
self.path = path
self.contents = {}
def __getitem__(self, key):
return self.contents[key]
def create_subdir(name):
self.contents[name] = Dir(os.path.join(self.path + name), self)
def add_file(file):
self.contents[file] = file # You should probably also have a File type
# ...
resources = Dir(os.path.join(src, "Resources"))
resources.create_subdir("fonts")
fonts = resources["fonts"]
fonts.add_file("font1.ttf")
...
I've used os.path.join
function to delegate to Python choosing the correct delimiter for each SO instead of hardcoding Windows delimiters as you have. The __getitem__
method allows to get items as if the variable was a dictionary directly.
You could take advantage of pathlib standard module and add the attribute access notation (using '.' to acces the subdirectories) if you don't like the div operator usage of pathlib.
from pathlib import Path as Path_, WindowsPath as WPath_, PosixPath as PPath_
import os
class Path(Path_):
def __new__(cls, *args, **kwargs):
return super().__new__(WindowsPath if os.name == 'nt' else PosixPath,
*args, **kwargs)
def __getattr__(self, item):
if item == '_str':
raise AttributeError
for i in self.iterdir():
if i.name == item:
return i
raise AttributeError
class WindowsPath(WPath_, Path):
pass
class PosixPath(PPath_, Path):
pass
current = Path()
subdir = current.subdir_name # current / 'subdir_name'
Upvotes: 0
Reputation: 77912
Is it possible to set class variables of inner class based on class variables of outer class?
Not without ressorting to a custom metaclass to process the inner classes, which will certainly not help readability nor maintainability (and will be - rightly - seen by any experienced python programmer as a total WTF).
EDIT : well actually for your example snippet the metaclass solution is not that complicated, cf the end of this answer
The reason is that in Python almost everything happens at runtime. class
is an executable statement, and the class object is only created and bound to it's name after the end of the whole class statement's body.
If not, is there another way to access the elements as shown above without using multiple files?
Quite simply (dumbed down example):
import os
# use a single leading underscore to mark those classes
# as "private" (=> not part of the module's API)
class _Fonts(object):
def __init__(self, resource):
self.font1 = os.path.join(resource.root, "font1.ttf")
self.font2 = os.path.join(resource.root, "font2.ttf")
class _Resources(object):
def __init__(self, src):
self.root = os.path.join(rsc, "Ressources")
self.Fonts = _Fonts(self)
# then instanciate it like any other class
src = "/path/to/source/folder"
Resources = _Resources(src)
print(Resources.Fonts.font1)
EDIT : after a bit more thinking a metaclass-based solution for your use case would not be that complicated (but this will NOT be anything generic):
import os
class ResourcesMeta(type):
def __init__(cls, name, bases, attrs):
for name in attrs:
obj = getattr(cls, name)
if isinstance(obj, type) and issubclass(obj, SubResource):
instance = obj(cls)
setattr(cls, name, instance)
class SubResourceMeta(type):
def __new__(meta, name, bases, attrs):
if not bases:
# handle the case of the SubResource base class
return type.__new__(meta, name, bases, attrs)
root = attrs.pop("root")
cls = type.__new__(meta, name, bases, {})
cls._root = root
cls._attrs = attrs
return cls
class SubResource(metaclass=SubResourceMeta):
def __init__(self, parent):
self.root = os.path.join(parent.root, self._root)
for name, value in self._attrs.items():
setattr(self, name, os.path.join(self.root, value))
class Resources(metaclass=ResourcesMeta):
root = "/path/to/somewhere"
class Fonts(SubResource):
root = "fonts"
font1 = "font1.ttf"
font2 = "font2.ttf"
class Images(SubResource):
root = "images"
logo = "logo"
image1= "image1"
Upvotes: 1