ysfaran
ysfaran

Reputation: 6962

NameSpacing: How to set class variable of inner class based on class variable of outer class?

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

Answers (2)

Adirio
Adirio

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

Upvotes: 0

bruno desthuilliers
bruno desthuilliers

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

Related Questions