【问题标题】:Can you use a string to instantiate a class?你可以使用字符串来实例化一个类吗?
【发布时间】:2010-10-07 21:58:26
【问题描述】:

我正在使用构建器模式来分离一堆不同的配置可能性。基本上,我有一堆名为 ID 的类(类似于 ID12345)。这些都继承自基础构建器类。在我的脚本中,每次运行此应用程序时,我都需要为每个类(大约 50 个)实例化一个实例。所以,我想看看是否不做这样的事情:

ProcessDirector = ProcessDirector()
ID12345 = ID12345()
ID01234 = ID01234()

ProcessDirector.construct(ID12345)
ProcessDirector.construct(ID01234)

ID12345.run()
ID01234.run()

我可以做这样的事情吗(我知道这行不通):

IDS = ["ID12345", "ID01234"]

ProcessDirector = ProcessDirector()
for id in IDS:
  builder = id() #some how instantiate class from string
  ProcessDirector.construct(builder)
  builder.run()

这样,当我将来需要添加一个新的时,我所要做的就是将 id 添加到 IDS 列表中,而不是在整个代码中添加新的 ID。

编辑

根据数据的来源,似乎有一些不同的意见。这些 ID 被输入到其他人无权访问的文件中。我没有从命令行读取字符串,我希望将来在添加新 ID 时能够做的改动很少。

【问题讨论】:

  • 这些 ID 类是否与循环在同一个文件中,还是从其他地方导入?
  • 这不是那个的重复,这是一个python问题,而不是一个java问题,你引用的问题是询问python中是否存在一个函数,它存在于java中,后面几乎没有解释,记住问题是人们如何找到它,而不是答案,所以仅仅因为另一个问题中存在一个可以回答这个问题的答案并不意味着人们会找到它,除非他们用 Java 来思考这个问题,比如该问题的OP。

标签: python design-patterns reflection


【解决方案1】:

如果你想避免 eval(),你可以这样做:

id = "1234asdf"
constructor = globals()[id]
instance = constructor()

前提是该类在您当前的作用域中定义(或导入)。

【讨论】:

  • +1 - 这很好用,只是结合abc 来动态构建基类的所有子类的一个实例。
  • globals()[classname_string]() 仍然是实例化对象的好方法吗?只给出类的名称作为字符串?仍然是因为我是在原始回复后将近 8 年才提出这个问题的。
【解决方案2】:

不确定这是您想要的,但它似乎是一种更 Pythonic 的方式来实例化字符串中列出的一堆类:

class idClasses:
    class ID12345:pass
    class ID01234:pass
# could also be: import idClasses

class ProcessDirector:
    def __init__(self):
        self.allClasses = []

    def construct(self, builderName):
        targetClass = getattr(idClasses, builderName)
        instance = targetClass()
        self.allClasses.append(instance)

IDS = ["ID12345", "ID01234"]

director = ProcessDirector()
for id in IDS:
    director.construct(id)

print director.allClasses
# [<__main__.ID12345 instance at 0x7d850>, <__main__.ID01234 instance at 0x7d918>]

【讨论】:

  • 但是,如果您在运行时才知道类名是什么,该怎么办?如果从文件或命令行中读取它们会怎样?
  • @RyanN 不确定您的意思 - IDS 列表只是一个示例,它可能来自任何地方(文件等)
  • 我可能忽略了一些东西,但在 idClasses 中有一个名为 ID12345 的类。我现在看到 OP 已经有一堆他想要实例化的类,而且我正在考虑多次实例化一个泛型类,例如一个类class car():\r\n def __init__(self): pass 实例化为 ['ford', 'toyota', 'vw'] 在construct() 你可以做setattr(self,builderName,idClasses())。这样你最终会得到director.ford、director.toyota 和director.vw 对象。
【解决方案3】:

永远不要使用eval(),如果可以的话。 Python 有如此许多更好的选项(调度字典、getattr() 等),您永远不必使用称为 eval() 的安全漏洞。

【讨论】:

  • -1:“从不”太强了。这不是安全漏洞,除非您也有可以访问配置文件的恶意用户。防止恶意用户接触配置文件很容易。
  • 我读到“从不”的意思是“不惜一切代价避免,除非你完全确定你需要什么并且你知道你在做什么”。不过,使用“从不”这个词是一个很好的简短替代方案,因为它会吓到那些不知道自己在做什么的人。那些这样做的人,无论如何都会知道何时忽略这一点。
  • 如果没有实际显示任何替代方案,这个答案并没有真正的帮助
【解决方案4】:

最简单的方法就是创建一个字典。

class A(object): 
    pass
class B(object): 
    pass

namedclass = {'ID12345': A, 'ID2': A, 'B': B, 'AnotherB': B,  'ID01234': B}

然后使用它(您的代码示例):

IDS = ["ID12345", "ID01234"]

ProcessDirector = ProcessDirector()
for id in IDS:
    builder = namedclass[id]() 
    ProcessDirector.construct(builder)
    builder.run()

【讨论】:

  • 这是最pythonic的方式!事实上,这也是我们在 python 中模仿缺失的“switch 语句”的方式!!
  • 这当然是一种方法,但它并不完全是问题所在:它不是使用字符串来派生要实例化的类,而是使用它的实际类。这样做的一个缺点是namedclass 必须在它列出的所有类之后。 Django 还使用字符串来引用尚未定义的模型。
  • @minusf 问题 is 实际上是关于从字符串中派生出哪个类。通过使用 dict 你有最有效和最安全的方法,所以我不明白为什么不使用它。
【解决方案5】:

我决定将accepted answercomment on the accepted answer. 放在一起,我还添加了重载的__getitem__,所以这看起来更像是工厂模式。

import sys
import traceback
import ipdb


class CarTypes:
    class Toyota:
        def __repr__(self):
            return "Toyota()"
        def __str__(self):
            return "Instance of Toyota() class"
    class Nissan:
        def __repr__(self):
            return "Nissan()"
        def __str__(self):
            return "Instance of Nissan() class"


class Car:
    def __init__(self):
        self._all_classes = {}

    def construct(self, builder_name):
        setattr(self, builder_name, CarTypes())
        try:
            target_class = getattr(CarTypes, builder_name)
            instance = target_class()
            self._all_classes[builder_name] = instance
        except AttributeError:
            print("Builder {} not defined.".format(builder_name))
            traceback.print_stack()

    def __getitem__(self, type_name):
        return self._all_classes[type_name]

    def car_type(self, type_name):
        return self._all_classes[type_name]


IDS = ["Toyota", "Nissan", "Unknown"]

director = Car()
for id in IDS:
    director.construct(id)

print(director["Toyota"])
print(director["Nissan"])
print(director.car_type("Toyota"))
print(director.car_type("Nissan"))

已编辑:我添加了一些错误处理。

已编辑:置于permissive Creative Commons license 下。享受吧。

【讨论】:

    【解决方案6】:

    你的问题中缺少一些东西,所以我不得不猜测省略的东西。随时编辑您的问题以更正遗漏。

    class ProcessDirector( object ):
        # does something
    
    class ID12345( SomeKindOfProcess ):
        pass
    
    class ID001234( SomeKindOfProcess ):
        pass
    
    idList= [ID12345, ID01234]
    
    theProcessDirector = ProcessDirector()
    for id in idList:
      builder = id() #Instantiate an object from the class object
      theProcessDirector.construct(builder)
      builder.run()
    

    这很好用。它不会从字符串实例化——实际上你并不经常想要这个。有时,但很少。更常见的是,您需要从中获取实例对象的类对象列表。

    如果您实际上是从命令行获取类名,那么您需要进行以下小改动。

    validClasses = [ ID12345, ID01234 ]
    validMap = dict( ( (c.__name__, c) for c in validClasses ) )
    nameStrings = [ "ID12345", "ID01234" ] # from your command-line 
    idList= [ validMap[s] for s in nameStrings ]
    

    其他一切都保持不变。

    [此外,如果可能,请尝试以小写字母开头的实例变量名称。以大写字母开头的名称通常是类名。]

    编辑

    删除了eval。尽管eval() 绝对是不是安全漏洞。仅当有人专门授予恶意用户访问权限时,评估(以及 execexecfile)才会成为问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-12-23
      • 1970-01-01
      • 2023-04-04
      • 1970-01-01
      • 1970-01-01
      • 2012-05-01
      相关资源
      最近更新 更多