【问题标题】:python dynamic class namespython动态类名
【发布时间】:2010-12-22 20:00:43
【问题描述】:

尝试基于字符串值实例化一个类并且...失败了。下面的parser 对象是dict,在示例中假设我们有一个名为foo 的对象,这里parser['name'] 是'foo':

obj = parser['name']()

失败,产生TypeError: 'str' object is not callable。但是,既然我有:

class foo:
    def __init__(self():
        print 'Hello'

如果我这样做 obj = foo() 它工作正常并创建正确的对象。另外,调用obj = type(parser['name'])() 也不起作用。

如何解决这个问题? 更新:我真的不想使用映射系统:这些类的名称是定义的 INI 文件,并以这种方式解析,因此它们将是字符串..

【问题讨论】:

    标签: python


    【解决方案1】:
    classmap = {
      'foo': foo
    }
    
    obj = classmap[parser['name']]()
    

    【讨论】:

    • 所以如果你使用工厂模式,你必须在代码的某个地方映射所有可能返回的类?
    • 只需使用装饰器来枚举它们。
    • 为什么这比对字符串的每个可能值执行 if 语句更好
    • @Prinsig 内存消耗 ;)
    【解决方案2】:

    回答于:
    Python dynamic class names

    如果您知道类是在哪个模块中定义的,那么有一种更简单的方法可以做到这一点,例如:

    getattr(my_module, my_class_name)()
    

    【讨论】:

      【解决方案3】:

      不要使用字符串:

      parser = {}
      
      class foo:
          pass
      
      
      parser['foo'] = foo
      
      obj = parser['foo']()
      

      【讨论】:

        【解决方案4】:

        您可以使用存储已知类字典的元类:

        # a metaclass
        class Registry(type):
            # store all the types we know
            registered = {}
            def __new__(cls, name, bases, attrs):
                # create the new type
                newtype = super(Registry, cls).__new__(cls, name, bases, attrs)
                # store it
                cls.registered[name] = newtype
                return newtype
        
            @classmethod
            def class_by_name(cls, name):
                # get a class from the registerd classes
                return cls.registered[name]
        
        
        # arbitrary base class for every class that should be in the Register
        class Registered(object):
            __metaclass__ = Registry
        
        # some classes
        class Foo(Registered):
            pass
        
        class Bar(Foo):
            pass
        
         # get the class object: 
        print Registry.class_by_name('Foo') # <class '__main__.Foo'>
        # it can be instanciated too ofc:
        print Registry.class_by_name('Bar')() # <__main__.Bar object at 0x01F0F9F0>
        

        但并不是每个人都了解元类,因此您可能希望避免使用元类以防止混淆。它们对于这样的事情很有用,但正如您从其他答案中看到的那样,还有很多其他方法可以做到这一点。

        【讨论】:

          【解决方案5】:

          type(name, bases, dict) 内置函数是动态构造类的正确方法——尤其是在给定类名字符串时。请参阅此处的文档:http://docs.python.org/library/functions.html#type

          在您的特定示例中,它可能如下所示:

          >>> def init(self):
          ...     print 'Hello'
          ...
          >>> Foo = type('Foo', (object,), {'__init__': init})
          >>> foo = Foo()
          Hello
          

          【讨论】:

          • 问题不是动态构造类,而是动态实例化它们。
          • 那么obj = type('Foo', (object,), {'__init__': init})() 应该可以解决问题。打印 'Hello' 并使 obj 成为该类的实例。
          • 这样做会为每个实例创建一个新类。它还采用了一个很好的语义类定义,并将其分解为分布在文件中的函数。此外,您如何将不同的功能映射到它们各自的类。如果有一个类 'Bar' 的 init 打印 'Goodbye' 怎么办?
          • +1。虽然这不是问题的正确答案,但这是我想要做的。 (也就是说,答案本身,而不是愚蠢的后续评论。)
          【解决方案6】:

          您可以使用检查来创建类映射:

          def get_classes():
              classes = {}
              for name, obj in inspect.getmembers(sys.modules[__name__]):
                  if inspect.isclass(obj):
                      classes[name] = obj
              return classes
          

          然后实例化一个类

          >>> classes = get_classes()
          >>> c = classes['ClassName']
          >>> c
          <class ClassName...>
          

          大部分来自How can I get a list of all classes within current module in Python?

          【讨论】:

            【解决方案7】:

            响应您的更新:唯一的方法是使用地图系统。如果您不想使用显式映射系统,那么您可以使用 Python 的内置映射系统之一,尽管在我看来它不太好和显式。

            obj = globals()[parser['name']]() 
            

            将访问名称为parser['name'] == 'foo' 的全局对象。如果这恰好是一个类(或者您实际上希望根据用户输入实例化的类),那么您应该很高兴。否则,您将不得不围绕它构建逻辑以将您真正想要的类列入白名单。

            如果类来自一个模块,那么您可以使用该模块的__dict__ 属性来达到同样的效果。

            obj = somemodule.__dict__[parser['name']]()
            

            与前一种情况相同的警告适用于这种情况。最好只使用显式映射

            【讨论】:

              【解决方案8】:

              我会像其他人所说的那样使用类名映射到类对象。您可以使用parser[foo.__name__] = foo 之类的语句对其进行初始化。如果您不想使用映射对象,则必须回退到使用 eval 函数,如下所示:

              >>> class foo:
              ...   pass
              ...
              >>> klass_name = 'foo'
              >>> klass_inst = eval(klass_name)
              >>> klass_inst
              <class __main__.foo at 0x1004b2b90>
              >>> inst = klass_inst()
              >>> inst
              <__main__.foo instance at 0x1004d2680>
              >>>
              

              当然,如果您想使用嵌入在包中的类,则必须先导入包,然后再执行eval。您确实应该构建一个映射对象,以便您可以限制可以使用此代码访问的类。

              【讨论】:

              • 当有一种更清洁、更安全的解决方案可用时,甚至无需提及eval。 (虽然我没有投反对票)
              • 从技术上讲,当使用eval 时,您使用的是映射系统:globals()
              • @delnan:这就是为什么我说如果您不想使用显式映射对象,eval 是后备方案。我想提一下,因为有些人实际上更喜欢使用它。尤其是来自 Perl 和 Shell 的人。感谢您限制反对票;)
              • @aaronasterling:我应该说显式映射,因为大多数人不认为解释器中的变量查找是映射。
              猜你喜欢
              • 2011-11-09
              • 1970-01-01
              • 1970-01-01
              • 2017-03-13
              • 2017-12-22
              • 2016-02-09
              • 2017-07-02
              • 1970-01-01
              相关资源
              最近更新 更多