【问题标题】:How to use python metaclass for the following scenario?如何在以下场景中使用 python 元类?
【发布时间】:2018-06-25 13:40:39
【问题描述】:

我想创建一个具有级联功能的配置类。我这是什么意思?假设我们有一个这样的配置类

class BaseConfig(metaclass=ConfigMeta, ...):
   def getattr():
       return 'default values provided by the metaclass'

class Config(BaseConfig):
   class Embedding(BaseConfig, size=200):
       class WordEmbedding(Embedding):
           size = 300

当我在代码中使用它时,我将按如下方式访问配置,

def function(Config, blah, blah):
    word_embedding_size = Config.Embedding.Word.size
    char_embedding_size = Config.Embedding.Char.size

最后一行访问Embedding 类'Char' 中不存在的属性。这应该调用getattr(),在这种情况下应该返回200。我对元类的熟悉程度不足以做出良好的判断,但我想我需要定义元类的__new__()

这种方法有意义还是有更好的方法?

编辑:

class Config(BaseConfig):
   class Embedding(BaseConfig, size=200):
       class WordEmbedding(Embedding):
           size = 300
   class Log(BaseConfig, level=logging.DEBUG):
       class PREPROCESS(Log):
           level = logging.INFO

#When I use 
log = logging.getLogger(level=Config.Log.Model.level) #level should be INFO

【问题讨论】:

  • 我觉得元类没问题,你应该在元类中覆盖 getattr 然后以某种方式获取值。 (覆盖 new 并在你的情况下保存 kwargs)

标签: python metaclass


【解决方案1】:

这有点混乱。我不确定这是否是使用默认参数声明配置的最佳表示法——它看起来很冗长。但是,是的,鉴于 Python 中元类和魔术方法的灵活性,可能这样的东西可以满足您需要的所有灵活性。

仅仅为了它,我想说的是,像您正在做的那样,使用嵌套类作为命名空间可能对它们来说是唯一有用的东西。 (嵌套类)。经常看到很多人完全误解 Python OO 试图利用嵌套类。

所以 - 对于您的问题,您需要在最后一个类中存在一个 __getattr__ 方法,该方法可以获取属性的默认值。这些属性又被声明为嵌套类的关键字——它们也可以具有相同的元类。否则,嵌套类的层次结构仅适用于获取嵌套属性,使用 Python 中的点表示法。

此外,对于嵌套集中的每个类,如果未定义下一级嵌套类,则可以传入要用作默认值的关键字参数。在给定的示例中,尝试使用不存在的Char 访问Config.Embedding.Char.size 应该返回默认的“大小”。并不是说“嵌入”中的__getattr__ 可以返回一个假的“Char”对象——但该对象是必须产生size 属性的对象。所以,我们的__getattr__ 还没有产生一个对象,它本身有一个propper __getattr__

但是,我建议您更改您的要求 - 不要将默认值作为关键字参数传递,而是使用保留名称 - 例如 _default,您可以在其中放置默认属性.这样,您可以提供深度嵌套的默认子树,而不仅仅是标量值,并且实现可能更简单。

实际上 - 简单得多。通过按照您的建议对类使用关键字,您实际上需要有一个元类在数据结构中设置这些默认参数(尽管在__new____init__ 中都是可能的)。但是通过一直使用嵌套类和保留名称,metac 类上的自定义 __getattr__ 将起作用。这将在配置类本身上检索不存在的类属性,如果请求的属性不存在,则只需尝试检索我提到的_default 类。

因此,您可以使用以下内容:

class ConfigMeta(type):
    def __getattr__(cls, attr):
        return cls._default

class Base(metaclass=ConfigMeta):
    pass

class Config(Base):
    class Embed(Base):
        class _default(Base):
            size = 200
        class Word(Base):
            size = 300

assert Config.Embed.Char.size == 200
assert Config.Embed.Word.size == 300

顺便说一句 - 就在去年,我正在做一个项目来进行这样的配置,使用默认值,但使用字典语法 - 这就是为什么我提到我不确定嵌套类是否是一个不错的设计。但是由于所有功能都可以由具有 3 个 LoC 的元类提供,我想这比任何事情都要好。

另外,这就是为什么我认为能够嵌套整个默认子树对你想要的东西很有用 - 我去过那里。

【讨论】:

    【解决方案2】:

    您可以使用元类来设置属性:

    class ConfigMeta(type):
        def __new__(mt, clsn, bases, attrs):
            try:
                _ = attrs['size']
            except KeyError:
                attrs['size'] = 300
            return super().__new__(mt, clsn, bases, attrs)
    

    现在,如果该类没有 size 属性,则将其设置为 300(更改此值以满足您的需要)。

    【讨论】:

    • “大小”一词不固定。对于不同的配置,它可能会有所不同。大小用于嵌入,我可能对日志配置有“级别”。例如,我需要通过class Log(BaseConfig, level=logging.DEBUG) 将属性的名称传递给元类
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-19
    相关资源
    最近更新 更多