2016-08-05 3 views
11

Сравните следующий код в C++:Передача "указатель на виртуальную функцию" в качестве аргумента в Python

#include <iostream> 
#include <vector> 

struct A 
{ 
    virtual void bar(void) { std::cout << "one" << std::endl; } 
}; 

struct B : public A 
{ 
    virtual void bar(void) { std::cout << "two" << std::endl; } 
}; 

void test(std::vector<A*> objs, void (A::*fun)()) 
{ 
    for (auto o = objs.begin(); o != objs.end(); ++o) 
    { 
    A* obj = (*o); 
    (obj->*fun)(); 
    } 
} 

int main() 
{ 
    std::vector<A*> objs = {new A(), new B()}; 

    test(objs, &A::bar); 
} 

и Python:

class A: 

    def bar(self): 
     print("one") 


class B(A): 

    def bar(self): 
     print("two") 


def test(objs, fun): 
    for o in objs: 
     fun(o) 

objs = [A(), B()] 
test(objs, A.bar) 

C++ код будет печататься:

one 
two 

в то время как Python код напечатает

one 
one 

Как я могу передать «указатель на метод» и разрешить его переопределенном один, достигая того же поведения в Python, как и в C++?

Чтобы добавить контекст и объяснить, почему я изначально думал об этом шаблоне. У меня есть дерево, состоящее из узлов, которые могут быть подклассифицированы. Я хотел бы создать общую функцию обхода графа, которая берет узел графика, а также функцию, которая может быть переопределена в подклассах узлов графа. Функция вычисляет некоторое значение для узла, заданные значения, рассчитанные для соседних узлов. Цель состоит в том, чтобы вернуть значение, рассчитанное для данного узла (что требует прохождения всего графика).

+0

Рассматривали ли вы структурировать код Python, так что вы * не * пытаться повторить семантику другой язык? Я чувствую, что чем больше Pythonic способ сделать это не имеет ничего общего с наследованием или динамической диспетчеризацией. –

+0

Я согласен, проблема в том, что у меня нет лучшего шаблона для моей проблемы. Смотрите описание проблемы. Я бы предпочел решение Питонов. –

+0

Похоже, вы хотите шаблон посетителя. –

ответ

9

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

В вашем примере, единственная часть, которая потребуется, чтобы изменить это вызов test функции:

test(objs, (lambda x: x.bar())) 
+0

Это кажется чистым, путинским способом решения проблемы. Тем не менее, это не позволяет динамическую отправку, что можно подражать с другим ответом (если это необходимо). –

+0

Я не понимаю, что вы имеете в виду. Вызов метода «bar» будет динамически отправлен. – hugomg

+0

Моя терминология может быть выключена. Я имел в виду, что при таком подходе невозможно обработать случай, когда 'bar' принимает разные аргументы в' A' и в 'B', например, в' B' он принимает дополнительный аргумент. В строчном подходе 'test' может выбрать применение различных аргументов к' bar' в зависимости от типа 'obj'. –

4

Следующая производит вывод, который вы хотите:

class A: 
    def bar(self): 
     print("one") 

class B(A): 
    def bar(self): 
     print("two") 

def test(objs, funcname): 
    noop = lambda: None 
    for o in objs: 
     getattr(o, funcname, noop)() 

objs = [A(), B()] 
test(objs, "bar") 
+0

Это отвечает на вопрос буквально, но я не думаю, что это вообще означает то, что пытается сделать OP. Разумеется, эта программа не будет масштабироваться на более сложном примере. –

+0

@uh oh: IMO отвечает на вопрос и представляет то, что пытается выполнить OP, учитывая, что у Python нет указателей. Кроме того, он должен «масштабироваться», а также версия на C++. – martineau

+1

Это была моя точка. Python не имеет указателей или множественной отправки, поэтому я бы ожидал более идиоматического способа достижения той же * цели *. Все это усиливает неправильный способ делать что-то. –

Смежные вопросы