6

我正在开发一个 Web 应用程序,它将根据用户输入返回一组可变的模块。每个模块都是一个 Python 类,其构造函数接受单个参数并具有包含输出的“.html”属性。

从全局命名空间中动态拉取类是可行的:

result = globals()[classname](param).html

它肯定比以下内容更简洁:

if classname == 'Foo':
    result = Foo(param).html
elif classname == 'Bar':
    ...

从风格上讲,什么被认为是写这篇文章的最佳方式?是否有不使用全局命名空间的风险或理由?

4

3 回答 3

6

这种方法的一个缺陷是它可能使用户能够做的比你想要的更多。他们只需提供名称即可调用该命名空间中的任何单参数函数。您可以通过一些检查来帮助防止这种情况(例如 isinstance(SomeBaseClass, theClass),但最好避免这种方法。另一个缺点是它限制了您的班级布置。如果您最终有几十个这样的班级并决定要将它们分组到模块中,您的查找代码将停止工作。

您有几个备选方案:

  1. 创建显式映射:

     class_lookup = {'Class1' : Class1, ... }
     ...
     result = class_lookup[className](param).html
    

    尽管这样做的缺点是您必须重新列出所有类。

  2. 将类嵌套在封闭范围内。例如。在自己的模块或外部类中定义它们:

    class Namespace(object):
        class Class1(object):
            ...
        class Class2(object):
            ...
    ...
    result = getattr(Namespace, className)(param).html
    

    尽管(__bases__,__getattribute__ 等),您确实无意中在这里公开了几个额外的类变量 - 可能无法利用,但并不完美。

  3. 从子类树构造一个查找字典。让你的所有类都继承自一个基类。创建所有类后,检查所有基类并从中填充字典。这样做的好处是您可以在任何地方定义您的类(例如,在单独的模块中),并且只要您在创建所有注册表后创建注册表,您就会找到它们。

    def register_subclasses(base):
        d={}
        for cls in base.__subclasses__():
            d[cls.__name__] = cls
            d.update(register_subclasses(cls))
        return d
    
    class_lookup = register_subclasses(MyBaseClass)
    

    上面的一个更高级的变体是使用自注册类——创建一个元类,而不是在字典中自动注册任何创建的类。对于这种情况,这可能是矫枉过正——尽管它在某些“用户插件”场景中很有用。

于 2008-10-21T15:43:46.980 回答
4

首先,听起来您可能正在重新发明轮子……大多数 Python Web 框架(我所知道的是 CherryPy/TurboGears)已经包含一种根据 URL 的内容将请求分派到特定类的方法,或用户输入。

实际上,您执行此操作的方式没有任何问题,但根据我的经验,它往往表明您的程序中存在某种“缺少抽象”。您基本上是依靠 Python 解释器来存储您可能需要的对象列表,而不是自己存储它。

因此,作为第一步,您可能只想制作一个包含您可能要调用的所有类的字典:

dispatch = {'Foo': Foo, 'Bar': Bar, 'Bizbaz': Bizbaz}

最初,这不会有太大的不同。但是随着您的网络应用程序的增长,您可能会发现几个优点:(a)您不会遇到命名空间冲突,(b)使用globals()您可能会遇到安全问题,攻击者可以在本质上访问您程序中的任何全局符号,如果他们可以找到一种方法将任意classname注入到您的程序中,(c)如果您想要拥有classname除实际确切类名之外的其他内容,使用您自己的字典会更灵活,(d)您可以将dispatch字典替换为如果您发现需要,可以进行数据库访问或类似操作的更灵活的用户定义类。

安全问题对于 Web 应用程序来说尤为突出。globals()[variable]从 web 表单中输入 where只是variable找麻烦

于 2008-10-21T15:32:59.030 回答
0

在类名和类之间建立映射的另一种方法:

定义类时,将属性添加到要放入查找表中的任何类,例如:

class Foo:
    lookup = True
    def __init__(self, params):
        # and so on

完成后,构建查找图是:

class_lookup = zip([(c, globals()[c]) for c in dir() if hasattr(globals()[c], "lookup")])
于 2008-10-22T06:18:41.553 回答