【问题标题】:NameSpacing: How to set class variable of inner class based on class variable of outer class?NameSpacing:如何根据外部类的类变量设置内部类的类变量?
【发布时间】:2018-08-25 12:31:54
【问题描述】:

我正在尝试在 python 中“模拟”命名空间。我正在使用内部和外部类层次结构来创建我的命名空间。例如,您想将文件的路径(如资源)保存在一个位置。我尝试过这样的事情:

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"

访问元素如下所示:

logoPath = Resources.Images.image1

很遗憾,由于以下错误,这不起作用:

root = Resources.root + "fonts\\"
NameError: name 'Resources' is not defined

我的问题

是否可以根据外部类的类变量设置内部类的类变量?如果没有,是否有另一种方法可以在不使用多个文件的情况下访问上述元素?

【问题讨论】:

  • 为什么我们不使用字典?在Resources 类定义之外,定义完成后Resources.Fonts.root = Resources.root + "fonts\\" 将起作用,但您为什么要这样做?
  • python中的命名空间是通过使用子模块来模拟的,而不是使用内部类。
  • @StephenRauch 你能举一个例子来说明你的字典解决方案吗?是的,在类外设置变量会起作用,但是我必须声明Resources.Fonts.font1 = Resources.Fonts.root + "font1.tff",这看起来也很奇怪。我想要干净的结构化代码。在我看来,访问上述资源可以提高可读性和可变性。
  • @Adirio 这意味着我必须创建多个文件,对吗?我想故意避免这种情况。
  • 你应该看看标准库中的pathlib。这是使用路径操作的pythonic方式。尽量避免使用str(pathlib.Path())。而是按照PEP 519 中的建议使用os.fspath(pathlib.Path())

标签: python namespaces


【解决方案1】:

我认为您对 OOP 中的类和实例的概念没有明确的概念。如果你想存储这种信息Resources不应该是一个类,它应该是一个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")
...

我已使用os.path.join 函数委托给 Python,为每个 SO 选择正确的分隔符,而不是像您那样硬编码 Windows 分隔符。 __getitem__方法允许直接获取项目,就好像变量是字典一样。

编辑:

如果您不喜欢 pathlib 的 div operator usage,可以利用 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'

【讨论】:

  • 我认为 OP 实际上很好地理解什么是类和实例,并且它的目的是 - 如前所述 - 使用类作为纯粹的对象来“模拟命名空间”(请记住,在 Python 类中 也是对象)。
  • @Truntle 编辑了我的答案,包括一个如何将这种符号添加到 python 标准库 pathlib 的示例
【解决方案2】:

是否可以根据外部类的类变量设置内部类的类变量?

如果不求助于自定义元类来处理内部类,这肯定不会提高可读性和可维护性(并且将 - 正确地 - 被任何有经验的 Python 程序员视为完全 WTF)。

编辑: 实际上,对于您的示例 sn-p,元类解决方案并不复杂,参见这个答案的结尾

原因是在 Python 中几乎所有事情都发生在运行时。 class 是一个可执行语句,类对象仅在整个类语句主体的结尾 之后创建并绑定到它的名称。

如果没有,是否有另一种方法可以在不使用多个文件的情况下访问上述元素?

很简单(简单的例子):

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)

编辑:经过更多思考,为您的用例提供基于元类的解决方案不会那么复杂(但这不会是通用的):

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"

【讨论】:

  • 遗憾的是,这并不像我想的那么简单,但也不是很复杂(至少是第一个解决方案)。
猜你喜欢
  • 2023-03-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-08
  • 1970-01-01
  • 2013-07-08
  • 1970-01-01
  • 2019-02-04
相关资源
最近更新 更多