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?

ysfaran
  • 5,189
  • 3
  • 21
  • 51
  • 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? – Stephen Rauch Mar 16 '18 at 12:31
  • Namespacing in python is simulated by the use of submodules, not using inner classes. – Adirio Mar 16 '18 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. – ysfaran Mar 16 '18 at 12:53
  • @Adirio this would mean i have to create multiple files right? I wanted to avoid that on purpose. – ysfaran Mar 16 '18 at 12:58
  • Yes it does mean that. But you do not need namespaces for your example as I've proven in my answer. I think you don't have object-oriented programming concepts clear. Classes are not variables, classes are models, instances are then used as the realization of that model. A class is an abtract thing as Car, and an instance would be any car you can see by looking at the window. – Adirio Mar 16 '18 at 13:01
  • @Adirio know the concepts of OOP. I just wanted my code to look "obvious" so its easy to change and read. But i am not very familiar with the concepts of Python. In other OO languages (like C#, Java, C++..) it would never come into my mind to use classes for this purpose. Your solution might be functional but it's not very readable at the beginning. This is something i would like to avoid, because my issue sounds quite simple and i would like to keep it simple in code. – ysfaran Mar 16 '18 at 13:08
  • @Truntle cf my edited answer for a metaclass-based solution example (very specific to your example snippet - a fully generic solution is not possible). – bruno desthuilliers Mar 16 '18 at 13:56
  • Truntle you are trying to do with Python classes something that they are not designed for. This is like trying to translate a sentence from English to Spanish swapping every word with a dictionary, you will get a non-readable sentence. When you learn a new language, forget what you know about others, learn it and then stablish relations to remember parts if you want. Its like trying to use `interface{}` in Go to replace generic types when you come from C++. Do not try to adapt a programming language to your programming habits, make your programming habits adapt to your currently used language. – Adirio Mar 16 '18 at 14:22
  • @Adirio Just so you can follow my line of thougts: I was reading this [post](https://stackoverflow.com/questions/3576596) before and i think there are different opinions on that. I really understand what you mean and tbh i didn't feel very comfortable using my solution either. Never the less Python is totally different to other OOP languages like C++, so i really tried to do things that i usually dont do. I would really like to access my resources as shown above, because it looks really structured/clean. I just couldn't find a better way to achieve this goal (e.g. using multiple modules). – ysfaran Mar 17 '18 at 16:54
  • 1
    You should take a look at [pathlib](https://docs.python.org/3/library/pathlib.html) 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](https://www.python.org/dev/peps/pep-0519/#rationale). – Adirio Mar 19 '18 at 10:40

2 Answers2

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"
bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
  • 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). – ysfaran Mar 17 '18 at 20:21
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'
Adirio
  • 5,040
  • 1
  • 14
  • 26
  • 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). – bruno desthuilliers Mar 16 '18 at 13:22
  • @Truntle edited my answer to include an example of how to add this kind of notation to the python standard library pathlib – Adirio Mar 19 '18 at 14:09