Динамический вызов класса

 
0
 
Python
ava
WolfAlone | 23.12.2010, 17:07
Доброго времени суток!

Есть некий класс, например class1, у него есть метод: method1, который принимает 1 или более параметров (param1, ... paramX). Метод возвращает число или строку (не принципиально).

Есть список, list1, со следующим содержимым (по индексу):
0 - имя класса;
1 - имя метода;
2 - * параметр1, 2, 3 и т.д.

Необходимо, обработать список и на основе его содержимого обратиться к указанному в нём классу, затем методу и передать ему параметры. Результат работы - вернуть в вызывающую функцию.

Объясню суть вопроса.
В PHP для подобных целей есть, например функция: __autoload(), которая позволяет подгружать файл, в котором содержится объявление необходимого класса. Имя класса и его метод могут содержаться в переменной в виде строки. Параметры в функцию или метод класса можно передавать в виде массива, при этом, каждый элемент массива будет равен 1-му параметру функции/метода.

Хотелось бы узнать, как сделать подобное в Python?
Kommentare (9)
ava
skyboy | 30.12.2010, 00:26 #
Цитата (WolfAlone @ 23.12.2010, 16:07 findReferencedText)
В PHP для подобных целей есть

call_user_func_array есть в PHP
а для Python, как я понял. можно использовать getattr

später ergänzt:
а вот как передать массив параметров, как совокупность - не знаю.
ava
av0000 | 30.12.2010, 12:49 #
Ну, например: как-то так (без проверок на ошибки)


import my_module

for x in list1:
cls = getattr(my_module, x[0]) # класс
c = cls() # экземпляр класса
m = getattr(c, x[1]) # метод

ret = m(*x[2:]) # остатки массива как развёрнутый список параметров, для именованных (dict) - **{}


UPD: Для динамической загрузке модулей смотри в сторону imp
Вот кусок из моего проектика, где загружаются плагины, так, для иллюстрации:

def load_plugins(self):
"""Enumerate and load plugin modules without creation instances."""

dirs = [os.path.join(config.APP_PATH, config.DIR_PLUGINS)]
s = config.ini.value("pluginspath").toString()
if s:
#dirs.extend([os.path.join(unicode(s),x) for x in os.listdir(s)])
dirs.append(unicode(s))
plgs = []
for d in set(dirs): # set prevents double-includes
for x in os.listdir(d):
dd = os.path.join(d, x)
if os.path.isdir(dd):
try:
f = imp.find_module(x, [d])
except:
self.log.debug("Non-plugin folder in plugins tree: '%s'" % (dd))
f = None
if f:
self.log.debug("Loading plugin '%s' from '%s'..." % (x, dd))
try:
m = imp.load_module(x, *f)
self.log.debug("The plugin module '%s' loaded." % (x,))
self.plug_modules.append(m) # array of modules
except Exception as e:
self.log.exception("Error loading plugin '%s':\n%s" % (x, e))


def create_plugins(self):
"""Enumerate plugin modules and create plugin classes.

Should be done AFTER loading translations."""
for m in self.plug_modules:
for mm in dir(m):
cl = getattr(m, mm)
if inspect.isclass(cl) and issubclass(cl, base.BasePlugin):
self.log.debug("Create plugin instance %s:%s" % (m.__name__, mm))
self.addPlugin(cl) # exception-safe
ava
bilbobagginz | 07.01.2011, 18:48 #
Цитата (WolfAlone @ 23.12.2010, 16:07 findReferencedText)
Хотелось бы узнать, как сделать подобное в Python?

обычно так не работают в питоне, но если уж не в терпеж:

class A:
def func(self, a):
print 'called to A::func(%s)' %a

class B:
def funcb(self, b):
print 'called to B::funcb(%s)' %b


l1 = [ 'A', 'func', 'par1', 'par2', 'parN']
l2 = [ 'B', 'funcb', 'par3', 'par5', 'parZ']

def test_dyncall(l):
cn = eval(l[0]) # находим имя класса А
args = l[2:] # список параметров
b = cn() #создаем объект класса А
b.fn = eval("b.%s" %l[1]) # определим метод по имени fn
for i in args:
b.fn(i) # de facto вызовы A.func(i) по указателю fn.

for l in (l1, l2):
test_dyncall(l)


ессно надо протестить на всякие бяки....


ava
WolfAlone | 18.01.2011, 12:16 #
Цитата (bilbobagginz @ 7.1.2011, 18:48 findReferencedText)
обычно так не работают в питоне, но если уж не в терпеж:

Подскажите пожалуйста, как правильно решать подобную задачу?

В своё время работал с такой штукой на PHP как CodeIgniter, там очень удобно прасится URL. Выглядит это примерно так:
example.com/class/func/param1/param2/param3/etc

где:
example.com - адрес сайта
class - пользовательский контроллер (класс)
func - функция в этом классе (метод класса)
param 1-3 (всё остальное) - входные параметры для этой функции

Задумка мне очень понравилась, решил сделать нечто аналогичное на Python...
ava
WolfAlone | 19.01.2011, 22:50 #
Писал я тут писал... Много написал smile Как вдруг до меня стало доходить!
av0000, спасибо огромное, Вы натолкнули меня на верное направление!
bilbobagginz, премного благодарен! Из двух примеров выше сейчас буду пробовать собрать 1 smile
ava
WolfAlone | 19.01.2011, 23:16 #
Остался пожалуй только 1 вопрос:
Как защититься от "кул хацкеров", т.е. проще говоря как "найти" класс, зная его имя записанное в строковую переменную без eval() или как проверить, то ли (что нужно/можно) выполняется в eval(), что может выполняться? Если создать список с допустимыми значениями - это будет надёжной защитой?

Я немного поясню:

s = 'class1'
c = eval(s)
z = c() #Этот код прекрасно работает, именно так - как я хотел.
#Проблема заключается только в том, что s - в скрипт передаёт пользователь, и соответственно, передать он суда может всё, что угодно!
ava
av0000 | 20.01.2011, 11:33 #
Ну, во-первых я сильно не люблю пользоваться eval по двум причинам - выжирание памяти "как под целый питон" на время запуска (можно сказать, что на вызов eval "запускается" новый интерпретатор) и как раз эта дыра с запуском произвольного кода.

решение "раз" - сделать словарь с именами классов и классами и искать допустимые только в нем:

AAA = {
'class1': Class1, #
'class2': MySuperPuperClass,
}
# юзер вводит класс
s = rawinput()
cls = AAA.get(s, None)
if cls:
instance = cls()


решение "два" если охота таки использовать eval:

try:
inst = eval(s + '()', {}, {})
except:
pass # как-то обрабатываем

"фишка" второго решения - во-первых принудительно создаём экземпляр внутри eval - какую-то часть "мусора" удастся этим отсечь, во-вторых - ограничиваем список того, чем может пользоваться eval (см. доку на её параметры) НО! нормального сандбокса таким ограничением не сделать :( и при желании юзер может ввести что-то типа "import os; os.system('rm -rf /')" и кому-то будет очень весело ;)

PS: прошу не воспринимать строку с import буквально smile прям так, конечно никто сделать не даст, но обойти "защиту" в виде {} можно, такая инфа мне попадалась в и-нете, так что стОит быть аккуратным
PPS: а заполнять словарь из первого примера можно по аналогии с тему куском кода, что я привёл раньше

import mmm
import inspect
AAA = {}
for x in dir(mmm):
a = getattr(mmm, x)
if inspect.isclass(a):
AAA[a.__name__] = a

ava
stalk13 | 14.05.2011, 16:04 #
Я бы в этом случае использовал декораторы:


"""
Examples:

>>> @controller
... class default:
...
... @action
... def index(self, arg1=100):
... return arg1
...
... @action
... def other_action(self, arg1, arg2='test'):
... return 'Other action: %s, %s' % (arg1, arg2)
...
... def no_action_method(self, *args):
... pass

>>> route('default', 'index')
100

>>> params = ['default', 'other_action', 'foo', 'bar']
>>> route(*params)
'Other action: foo, bar'

>>> params = 'default/no_action_method/param1'.split('/')
>>> route(*params)
Traceback (most recent call last):
...
RuntimeError: Action "no_action_method" of controller "default" is not found
"""

def route(controller, action, *args):
if not controller in route.controllers:
raise RuntimeError('Controller "%s" is not found' % controller)

cls = route.controllers[controller]()

if not action in cls._actions:
raise RuntimeError('Action "%s" of controller "%s" is not found' % (action, controller))

return cls._actions[action](cls, *args)

def controller(cls):
""" Class decorator """

cls._actions = controller.actions
controller.actions = {}
route.controllers[cls.__name__] = cls
return cls

def action(func):
""" Method decorator """

controller.actions[func.__name__] = func
return func

route.controllers = {}
controller.actions = {}

if __name__ == '__main__':
import doctest
doctest.testmod()
ava
bilbobagginz | 15.05.2011, 07:49 #
согласен, что eval ресурсы хавает.
но:
Цитата (av0000 @ 20.1.2011, 10:33 findReferencedText)
и при желании юзер может ввести что-то типа "import os; os.system('rm -rf /')"

не напугал. при этом желании юзер должен стать root-ом. ну или вылезти из jail. что не так уж и просто...
Registrieren Sie sich oder melden Sie sich an, um schreiben zu können.
Unternehmen des Tages
Вы также можете добавить свою фирму в каталог IT-фирм, и публиковать статьи, новости, вакансии и другую информацию от имени фирмы.
Подробнее
Mitwirkende
advanced
Absenden