【问题标题】:Metaclasses configure classes. But can we configure metaclasses?元类配置类。但是我们可以配置元类吗?
【发布时间】:2011-07-28 17:42:51
【问题描述】:

我发现元类的存在和使用可以通过为类创建过程提供一个优雅的句柄,从而使您免于编写大量代码。我在我的应用程序中使用它,其中实例化了几个交互服务器。详细说明:

每个设备都实例化一个特定于其操作的服务器类,它是(...的子类)的子类,最终是BaseServer 类。现在,有些设备服务器需要ThreadedTCPserver,有些需要SimpleTCPServer(模块:socketserver)。它们不能都派生自同一个类,因为使用ThreadingMixin 会覆盖SimpleTCPServer 的行为。

为了处理这个动态类配置,我创建了一个MetaServerType,它将BaseServer 的基类选择为(SimpleTCPServer,)(ThreadedTCPServer,) --> 生成我想要的动态配置服务器类的结果! (呜呼)

现在,我的问题来了: 我想使用一个存储参数的配置文件,这些参数默认由 MetaServerType 使用。例如:config.default_loglevel 或 config.default_handler 等。并且可以根据元类规范覆盖各个服务器(从命令行或其他方式)。

只有一个配置对象的实例通过程序流传递是一种好的设计实践吗?一种方法是在元类的类主体中初始化配置对象——但我的程序流程从其他地方开始,这意味着元类被多次调用,从而产生各种配置实例。似乎在导入时调用了元类(?)

所以非常欢迎详细回答:

  1. 如何为元类提供配置信息?
  2. 单个配置实例通过程序流进行编辑、更新甚至最终编写的好方法是什么?
  3. 元类的输入参数能否以某种方式扩展到Metaclass.__new__(meta, name, bases, attrs) 之外?
    1. 额外问题:这是否会让我们更接近(服务器的)有限状态机,从而可以“暂停”或“恢复”状态(而不是实例)?

【问题讨论】:

  • 简短回答:没有什么能阻止您将 metaclass 添加到一个本身用作另一个元类的类中。所以简短的回答是“是”。我现在将阅读您的完整问题。
  • 对我来说,一个简单问题的解决方案听起来很奇怪。如果他们不知道哪个服务器,为什么您的课程甚至从某些服务器继承?简单的聚合或混合似乎是一种更好的方法。

标签: python configuration metaclass finite-automata


【解决方案1】:

1 - 如何为元类提供配置信息?

有几种方法可以做到这一点 - 因为您的元类存在于它们自己的模块中 (是的,模块在import 时间执行一次,无论它在同一个应用程序中导入多少次),配置它们的好方法是拥有一个可调用对象(同一模块上的类或函数),这将设置用于配置的“全局变量”。

尽管由于“global”这个名字起源于 C 语言而声名狼藉,但 Python 中的全局变量实际上是“模块”变量:这意味着该模块中的所有函数(包括方法)都可以访问这些变量。其他模块中的函数或代码必须为此模块名称添加前缀。

这样的函数:

def configure_servers(p1, p2,...):
    global opt1, opt2, ...
    opt1 = p1
    opt2 = p2
    (...)

可以在创建服务器实例之前从您的应用程序入口点调用。 (当然,您可以传递要读取的配置文件路径,而不是 p1p2、...)

2 - 传递单个配置实例的好方法是什么 通过程序流,成为 编辑,更新,也许最终 写的?

元类模块上的全局(模块)变量名称可以被所有人读取,并且可以与复杂的配置对象相关联。也许像上面这样的“配置”函数的存在可以使这个问题过时。

但如果你真的需要一个“单例”对象,即只有一个实例的对象,你可以用简单的方法做到这一点:在元类字典上有一个类,然后传递那个类,而不是它的一个实例。如果你有字典而不是类,那就更好、更干净了。

如果您需要创建一个“真正的”单例对象,您应该创建一个类并覆盖其中的 __new__ 方法,以便它始终返回第一个创建的实例 - 示例:

class Singleton(object):
   _instance = None
   def __new__(cls, *args, **kw):
       if cls._instance is not None:
           return cls._instance
       self = object.__new__(cls, *args, **kw)
       cls._instance = self
       return self

3 - 元类的输入参数能否以某种方式扩展 Metaclass.new(meta, name, bases, attrs) ?

没有利用语言语法。 我的意思是,总是可以将元类调用为普通的 Python 调用,但这会阻止您使用语言语法来描述您的类:您需要将类主体定义为字典以作为 @987654331 传入@ 来电。

例如,要创建一个派生异常类,可以这样做:

MyException = type("MyException", (Exception, ), {})

代替:

class MyException(Exception):
    pass

向元类传递附加信息的常用方法是在类主体上使用具有固定名称的属性。然后元类检查attrs 中的这些属性并使用它们。它可以选择将 then 保留在结果类中,或者此时从 attrs dict 中删除它们。

如果您需要传递元类的信息仅在运行时已知,则这些属性可以指向其他(模块级)变量,或包含在类创建时评估的 Python 表达式。

mod_server_type = "TCP"

class YAServer(ParentServer):
   __metaclass__ = ServerMetaBase
   _sever_type = mod_server_type
   with open("config_file") as config:
      _server_params = pickle.load(config)
   del config

   def __init__(self,...):
      ...

在上面的示例中,您的元类可以使用 _server_type_server_params 属性来进一步控制类的创建。

【讨论】:

  • PS.关于你的奖金问题,我很抱歉。我想我需要更多关于“服务器有限状态机”的数据来回答这个问题。
  • 感谢您的详细回答。我从中学到了一些有趣的东西。我会接受这个答案,因为这个创建单例的漂亮技巧可能会成功。另外,我似乎将我的配置(开始时只读一次)与我的状态变量(指示当前的操作状态)混淆了。目前最好的想法似乎是在配置器模块中创建一个单例实例,所有其他模块在需要时导入。这将是同一个实例——状态变量必须移动到另一个地方。谢谢!
  • 在阅读更多内容时,(thisthisthat)我似乎感觉到单身人士是邪恶的。首选的是 Borg 模式实现——它提供了类似单例的行为和状态,但避免了身份问题。见 Martelli 的原文:code.activestate.com/recipes/66531
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-22
  • 1970-01-01
  • 1970-01-01
  • 2016-08-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多