Лучший подход к динамическим классам с использованием Python globals () - PullRequest
6 голосов
/ 21 октября 2008

Я работаю над веб-приложением, которое будет возвращать переменный набор модулей в зависимости от ввода пользователя. Каждый модуль представляет собой класс Python с конструктором, который принимает один параметр и имеет свойство .html, которое содержит выходные данные.

Динамическое извлечение класса из глобального пространства имен работает:

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

И это, конечно, более кратко, чем:

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

Что считается лучшим способом написать это стилистически? Есть ли риски или причины не использовать глобальное пространство имен?

Ответы [ 3 ]

6 голосов
/ 21 октября 2008

Недостаток этого подхода заключается в том, что он может дать пользователю больше возможностей, чем вы хотите. Они могут вызвать любую однопараметрическую функцию в этом пространстве имен, просто указав имя. Вы можете избежать этого с помощью нескольких проверок (например, 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)
    

    Более сложный вариант вышеперечисленного заключается в использовании саморегистрационных классов - создайте метакласс, чем автоматически регистрируйте любые созданные классы в dict. Это, вероятно, излишне для этого случая - хотя и полезно в некоторых сценариях «пользовательских плагинов».

4 голосов
/ 21 октября 2008

Прежде всего, звучит так, как будто вы немного изобретаете колесо ... большинство веб-фреймворков Python (CherryPy / TurboGears - это то, что я знаю) уже включают способ отправки запросов в определенные классы на основе содержимого URL или пользовательский ввод.

Нет ничего неправильного в том, как вы это делаете, на самом деле, но по моему опыту это имеет тенденцию указывать на своего рода "недостающую абстракцию" в вашей программе. В основном вы полагаетесь на интерпретатор Python, который хранит список объектов, которые могут вам понадобиться, а не храните его самостоятельно.

Итак, в качестве первого шага вы можете просто составить словарь для всех классов, которые вы хотите вызвать:

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

Первоначально, это не будет иметь большого значения. Но по мере роста вашего веб-приложения вы можете найти несколько преимуществ: (а) вы не столкнетесь с конфликтами в пространстве имен, (б) при использовании globals() у вас могут возникнуть проблемы с безопасностью, когда злоумышленник, по сути, может получить доступ к любому глобальному символу в вашей программе, если они могут найти способ вставить произвольный classname в вашу программу, (c) если вы когда-нибудь захотите, чтобы classname отличался от реального точного имени класса, использование вашего собственного словаря будет более гибким, ( d) вы можете заменить словарь dispatch более гибким пользовательским классом, который осуществляет доступ к базе данных, или что-то в этом роде, если вы обнаружите необходимость.

Проблемы безопасности особенно заметны для веб-приложения. Выполнение globals()[variable], где variable является вводом из веб-формы, просто , требующий неприятностей .

0 голосов
/ 22 октября 2008

Другой способ построить карту между именами классов и классами:

При определении классов добавьте атрибут к любому классу, который вы хотите поместить в таблицу поиска, например ::100100

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")])
...