如何在以下场景中使用python元类?

时间:2018-01-16 19:03:54

标签: python metaclass

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

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

2 个答案:

答案 0 :(得分:1)

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

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(更改为满足您的需要)。

答案 1 :(得分:1)

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

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

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

此外,对于嵌套集中的每个类,如果未定义下一级嵌套类,则可以传入将用作默认值的关键字参数。在给定的示例中,尝试使用非现有Config.Embedding.Char.size访问Char应返回默认的“大小”。并非“嵌入”中的__getattr__可以返回一个假的“Char”对象 - 但该对象必须产生size属性。所以,我们的__getattr__尚未产生一个自身为__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的元类提供,我想这可以胜过任何事情。

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