如何在以下场景中使用 python 元类?
How to use python metaclass for the following scenario?
我想创建一个具有级联功能的配置 class。这是什么意思?假设我们有这样的配置 class
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
class 'Char' 中不存在。在这种情况下,应该调用 getattr()
应该 return 200
。我对 metaclasses 不够熟悉,无法做出很好的判断,但我想我需要定义 metaclass.
的 __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
您可以使用元class来设置属性:
class ConfigMeta(type):
def __new__(mt, clsn, bases, attrs):
try:
_ = attrs['size']
except KeyError:
attrs['size'] = 300
return super().__new__(mt, clsn, bases, attrs)
现在如果 class 没有 size
属性,它将被设置为 300(根据您的需要更改)。
这有点混乱。我不确定这是否是使用默认参数声明配置的最佳表示法——它看起来很冗长。但是,是的,考虑到 Python 中 metaclasses 和魔术方法的灵活性,可能 像这样的东西满足您需要的所有灵活性。
正因为如此,我想说的是,像您所做的那样,使用嵌套的 classes 作为命名空间可能是对它们唯一有用的东西。 (嵌套 classes)。经常看到很多人完全误解 Python OO 试图使用嵌套 classes。
所以 - 对于您的问题,您需要在最后的 class 中存在一个 __getattr__
方法,它可以获取属性的默认值。这些属性又被声明为嵌套 classes 的关键字——它们也可以具有相同的 metaclass。否则,嵌套 classes 的层次结构只为您使用 Python.
中的点符号来获取嵌套属性。
此外,对于嵌套集中的每个class,如果未定义下一级嵌套class,则可以传入要用作默认值的关键字参数。在给定的示例中,尝试使用不存在的 Char
访问 Config.Embedding.Char.size
应该 return 默认 "size"。并不是说 "Embedding" 中的 __getattr__
可以 return 你一个假的 "Char" 对象 - 但那个对象是必须产生 size
属性的对象。所以,我们的 __getattr__
还没有产生一个本身就有支撑物的对象 __getattr__
;
但是,我会建议更改您的要求 - 不要将默认值作为关键字参数传递,而是保留一个名称 - 如 _default
里面你可以把你的默认属性。这样,您可以提供深度嵌套的默认子树,而不仅仅是标量值,并且实现可能会更简单。
实际上 - 简单多了。按照您的建议,通过对 class 使用关键字,您实际上需要让元 class 在数据结构中设置这些默认参数(在 __new__
或 __init__
虽然)。但是,通过一直使用嵌套的 classes,使用保留名称,metac class 上的自定义 __getattr__
将起作用。这将在配置 classes 本身上检索不存在的 class 属性,如果请求的属性不存在,那么所有要做的就是尝试检索 _default
class 我提到了。
因此,您可以使用类似的东西:
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
顺便说一句——就在去年,我正在做一个项目,它有这样的配置,有默认值,但使用字典语法——这就是为什么我提到我不确定嵌套的 class 会是一个不错的设计。但是由于所有功能都可以由具有 3 个 LoC 的 metaclass 提供,我想这可以解决任何问题。
此外,这就是为什么我认为能够嵌套整个默认子树对您想要的东西很有用 - 我去过那里。
我想创建一个具有级联功能的配置 class。这是什么意思?假设我们有这样的配置 class
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
class 'Char' 中不存在。在这种情况下,应该调用 getattr()
应该 return 200
。我对 metaclasses 不够熟悉,无法做出很好的判断,但我想我需要定义 metaclass.
__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
您可以使用元class来设置属性:
class ConfigMeta(type):
def __new__(mt, clsn, bases, attrs):
try:
_ = attrs['size']
except KeyError:
attrs['size'] = 300
return super().__new__(mt, clsn, bases, attrs)
现在如果 class 没有 size
属性,它将被设置为 300(根据您的需要更改)。
这有点混乱。我不确定这是否是使用默认参数声明配置的最佳表示法——它看起来很冗长。但是,是的,考虑到 Python 中 metaclasses 和魔术方法的灵活性,可能 像这样的东西满足您需要的所有灵活性。
正因为如此,我想说的是,像您所做的那样,使用嵌套的 classes 作为命名空间可能是对它们唯一有用的东西。 (嵌套 classes)。经常看到很多人完全误解 Python OO 试图使用嵌套 classes。
所以 - 对于您的问题,您需要在最后的 class 中存在一个 __getattr__
方法,它可以获取属性的默认值。这些属性又被声明为嵌套 classes 的关键字——它们也可以具有相同的 metaclass。否则,嵌套 classes 的层次结构只为您使用 Python.
此外,对于嵌套集中的每个class,如果未定义下一级嵌套class,则可以传入要用作默认值的关键字参数。在给定的示例中,尝试使用不存在的 Char
访问 Config.Embedding.Char.size
应该 return 默认 "size"。并不是说 "Embedding" 中的 __getattr__
可以 return 你一个假的 "Char" 对象 - 但那个对象是必须产生 size
属性的对象。所以,我们的 __getattr__
还没有产生一个本身就有支撑物的对象 __getattr__
;
但是,我会建议更改您的要求 - 不要将默认值作为关键字参数传递,而是保留一个名称 - 如 _default
里面你可以把你的默认属性。这样,您可以提供深度嵌套的默认子树,而不仅仅是标量值,并且实现可能会更简单。
实际上 - 简单多了。按照您的建议,通过对 class 使用关键字,您实际上需要让元 class 在数据结构中设置这些默认参数(在 __new__
或 __init__
虽然)。但是,通过一直使用嵌套的 classes,使用保留名称,metac class 上的自定义 __getattr__
将起作用。这将在配置 classes 本身上检索不存在的 class 属性,如果请求的属性不存在,那么所有要做的就是尝试检索 _default
class 我提到了。
因此,您可以使用类似的东西:
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
顺便说一句——就在去年,我正在做一个项目,它有这样的配置,有默认值,但使用字典语法——这就是为什么我提到我不确定嵌套的 class 会是一个不错的设计。但是由于所有功能都可以由具有 3 个 LoC 的 metaclass 提供,我想这可以解决任何问题。
此外,这就是为什么我认为能够嵌套整个默认子树对您想要的东西很有用 - 我去过那里。