使用Python全局变量的动态类的最佳方法()

时间:2008-10-21 14:41:55

标签: python coding-style namespaces

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

从全局命名空间动态提取类:

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

它肯定比以下更简洁:

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

什么被认为是风格上最好的写作方式?是否存在不使用全局命名空间的风险或原因?

3 个答案:

答案 0 :(得分: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. 从子类树构造一个查找字典。使所有类继承自单个基类。创建所有类后,检查所有基类并从中填充dict。这样做的好处是你可以在任何地方定义你的类(例如,在单独的模块中),只要你在创建完毕后创建注册表,你就会找到它们。

    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)
    

    上面的一个更高级的变体是使用自注册类 - 创建一个元类,而不是在dict中自动注册任何创建的类。对于这种情况,这可能有点过头了 - 但它在一些“用户插件”场景中很有用。

答案 1 :(得分:4)

首先,听起来你可能正在重新发明轮子......大多数Python web框架(CherryPy / TurboGears就是我所知道的)已经包含了一种基于内容的方式将请求分派给特定类的方法。 URL或用户输入。

没有什么错误与你这样做的方式,实际上,但根据我的经验,它往往表明你的程序中存在某种“缺失的抽象”。您基本上依赖Python解释器来存储您可能需要的对象列表,而不是自己存储它。

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

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

最初,这不会产生太大影响。但随着您的Web应用程序的增长,您可能会发现以下几个优点:(a)您不会遇到命名空间冲突,(b)使用globals()您可能遇到安全问题,攻击者实际上可以访问任何全局你的程序中的符号,如果他们可以找到一种方法将任意classname注入你的程序,(c)如果你想让classname成为除了实际的确切类名以外的东西,使用你自己的字典将更加灵活,(d)您可以使用更灵活的用户定义类来替换dispatch字典,该类可以进行数据库访问,或者在您找到需要时使用类似的类。

安全问题对于网络应用尤为突出。从网络表单输入globals()[variable]的{​​{1}}只是要求解决

答案 2 :(得分: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")])