0

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?

10
  • 2
    And why are we not using dictionaries? Outside of the Resources class definition, after it is done being defined Resources.Fonts.root = Resources.root + "fonts\\" will work, but why do you want to do this? Commented Mar 16, 2018 at 12:31
  • Namespacing in python is simulated by the use of submodules, not using inner classes. Commented Mar 16, 2018 at 12:41
  • @StephenRauch could you give a example for your solution with dictionaries ? Yeah setting variables outside the class would work, but then i would have to declare Resources.Fonts.font1 = Resources.Fonts.root + "font1.tff" also which looks odd. I want to have clean structured code. Accessing resources like mentioned above improves readabilty and changeability in my opinion. Commented Mar 16, 2018 at 12:53
  • @Adirio this would mean i have to create multiple files right? I wanted to avoid that on purpose. Commented Mar 16, 2018 at 12:58
  • 1
    You should take a look at pathlib from the standard library. It is the pythonic way to operate with paths. Just try to avoid the use of str(pathlib.Path()). Instead use os.fspath(pathlib.Path()) as suggested in PEP 519. Commented Mar 19, 2018 at 10:40

2 Answers 2

1

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"
Sign up to request clarification or add additional context in comments.

1 Comment

Sadly that's not as simple as i would like it to be, but it's not very complicated as well (at least the first solution).
0

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

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.

EDIT:

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'

2 Comments

I think the OP does actually well understand what classes and instances are and that it's purpose here is - as stated - to "simulate namespacing" using classes as mere objects (remember that in Python classes are objects too).
@Truntle edited my answer to include an example of how to add this kind of notation to the python standard library pathlib

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.