Шаблон посетителя в питоне

вот упрощенная реализация шаблона посетителя на C++. Можно ли реализовать что-то подобное на Python?

Мне это нужно, потому что я буду передавать объекты из кода C++ в функцию на Python. Моя идея заключалась в том, чтобы реализовать посетителя на Python, чтобы узнать тип объекта.

Мой код С++:

#include <iostream>
#include <string>


class t_element_base
{
public:
    virtual void accept( class t_visitor &v ) = 0;
};


class t_element_deriv_one: public t_element_base
{
public:
    void accept( t_visitor &v );

    std::string t_element_deriv_one_text()
    {
        return "t_element_deriv_one";
    }
};


class t_element_deriv_two: public t_element_base
{
public:
    void accept( t_visitor &v );

    std::string t_element_deriv_two_text()
    {
        return "t_element_deriv_one";
    }
};


class t_visitor
{
public:
    void visit( t_element_deriv_one& e ){ std::cout << e.t_element_deriv_one_text() << std::endl; }
    void visit( t_element_deriv_two& e ){ std::cout << e.t_element_deriv_two_text() << std::endl; }
};


void t_element_deriv_one::accept( t_visitor &v )
{
    v.visit( *this );
}

void t_element_deriv_two::accept( t_visitor &v )
{
    v.visit( *this );
}


int
main
(
void
)
{
    t_element_base* list[] =
    {
        new t_element_deriv_one(), new t_element_deriv_two()
    };
    t_visitor visitor;

    for( int i = 0; i < 2; i++ )
        list[ i ]->accept( visitor );
}

person bogdan    schedule 17.09.2014    source источник


Ответы (5)


Шаблон посетителя может быть реализован на Python, я использую его для реализации чистого интерфейса между моими данными и уровнем представления. Уровень данных может определять порядок данных. и уровень представления просто печатает/форматирует его:

В моем модуле данных у меня есть:

 class visited(object):
     ....
     def accept(self, visitor):
         visitor.visit(self)
         for child in self.children():
             child.accept(visitor)

 class typeA(visited):
    ....

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

Мой класс посетителей выглядит следующим образом:

class visitor(object):
      def __init__(self, obj_id):
          data_obj = _find_data_instance( obj_id )
          data_obj.accept(self)

      def visit( self, data_obj):
          if isinstance(data_obj, typeA):
               self.visit_typeA( dataobj)

      def visit_typeA(self, dataobj):
          """Formats the data for typeA"""
          ...

_find_data_instance — это некоторый код, который создает или находит экземпляр одного из моих экземпляров данных. В моем случае все мои классы данных имеют конструктор, который принимает objectId и возвращает значение, а объект посетителя знает, какой класс данных использовать.

person Tony Suffolk 66    schedule 17.09.2014
comment
Спасибо за помощь. - person bogdan; 18.09.2014
comment
Не знаю, какую версию Python вы используете, но в версии 2.7.6 вызов instance не существует. Вы должны использовать isinstance - person Matthias; 28.11.2015
comment
@Matthias - хороший улов - был бы рад, если бы вы добавили это в качестве предложения по редактированию. - person Tony Suffolk 66; 30.11.2015

Вы можете использовать декораторы, чтобы получить то, что вы хотите. Копирую пример из этого блога:

class Lion: pass
class Tiger: pass
class Bear: pass

class ZooVisitor:
    @visitor(Lion)
    def visit(self, animal):
        return "Lions"

    @visitor(Tiger)
    def visit(self, animal):
        return "tigers"

    @visitor(Bear)
    def visit(self, animal):
        return "and bears, oh my!"

animals = [Lion(), Tiger(), Bear()]
visitor = ZooVisitor()
print(', '.join(visitor.visit(animal) for animal in animals))
# Prints "Lions, tigers, and bears, oh my!"

и код декоратора @visitor (на случай, если ссылка не работает):

# A couple helper functions first

def _qualname(obj):
    """Get the fully-qualified name of an object (including module)."""
    return obj.__module__ + '.' + obj.__qualname__

def _declaring_class(obj):
    """Get the name of the class that declared an object."""
    name = _qualname(obj)
    return name[:name.rfind('.')]

# Stores the actual visitor methods
_methods = {}

# Delegating visitor implementation
def _visitor_impl(self, arg):
    """Actual visitor method implementation."""
    method = _methods[(_qualname(type(self)), type(arg))]
    return method(self, arg)

# The actual @visitor decorator
def visitor(arg_type):
    """Decorator that creates a visitor method."""

    def decorator(fn):
        declaring_class = _declaring_class(fn)
        _methods[(declaring_class, arg_type)] = fn

        # Replace all decorated methods with _visitor_impl
        return _visitor_impl

    return decorator

Связанный блог (первый, похоже, уже не работает): https://chris-lamb.co.uk/posts/visitor-pattern-in-python

ИЗМЕНИТЬ:

obj.__qualname__ недоступен до Python 3.3, поэтому мы должны использовать хак для более ранних версий:-

def _qualname(obj):
    """Get the fully-qualified name of an object (including module)."""

    if hasattr(obj, '__qualname__'):
        qualname = obj.__qualname__
    else:
        qualname = str(obj).split(' ')[1]

    return obj.__module__ + '.' + qualname

К сожалению, приведенное выше решение не работает для версий Python ниже 3.3, поскольку методы по-прежнему являются обычными функциями при передаче декоратору. Вы можете попробовать использовать декоратор класса и метода, см. Может ли декоратор Python метода экземпляра получить доступ к классу?.

person Joren Van Severen    schedule 08.02.2015

Прежде всего

  • что такое объектно-ориентированное программирование? это динамическая отправка одного аргумента (неявный this в java и C++)
  • что делает шаблон посетителя: он пытается эмулировать двойную отправку на этих языках, поэтому он использует два класса в качестве обходного пути.

Вы можете сделать то же самое в Python, но вы также можете реализовать двойную отправку с помощью декоратора. (CLOS в Lisp использует отчасти похожий подход)

class A: pass
class B: pass

class visitor:
    def __init__(self, f):
        self.f = f
        self.cases = {}

    def case(self, type1, type2):
        def call(fun):
            self.cases[(type1, type2)] = fun
        return call


    def __call__(self, arg1, arg2):
        fun = self.cases[type(arg1), type(arg2)]
        return fun(arg1, arg2)

@visitor
def f(x, y): pass


@f.case(A, int)
def fun1(a, b):
    print("called with A and int")


@f.case(B, str)
def fun2(a, b):
    print("called with B and string")



f(A(), 5)
f(B(), "hello")
person blue_note    schedule 12.05.2019

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

Таким образом, ваш приведенный выше пример может быть таким же простым, как

class C1(object):
    pass

class C2(object):
    pass

l = [C1(), C2()]
if __name__=="__main__":
    for element in l:
        print type(element)

что даст:

<class '__main__.C1'>
<class '__main__.C2'>
person trvrm    schedule 17.09.2014
comment
Спасибо за помощь! Я бы принял ваш ответ, но Тони был немного более подробным. - person bogdan; 18.09.2014

На случай, если кто-то сочтет это полезным, я получил следующую версию ответа Джорена @visitor, работающую с использованием самоанализа в Python 2:

_visitors = {}

def visitor(arg_type):
    "A @visitor decorator"
    def decorated(fn):
        import inspect
        stack = inspect.currentframe()
        class_name = stack.f_back.f_code.co_name
        full_name = fn.__module__ + '.' + class_name + '.' + fn.__name__
        _visitors[(full_name, arg_type)] = fn

        def _visitor_impl(self, arg, *rest, **kwargs):
            full_name = fn.__module__ + '.' + self.__class__.__name__ + '.' + fn.__name__
            assert (full_name, arg.__class__) in _visitors, "Can't find visitor in {} for {}".format(full_name, arg.__class__.__name__)
            method = _visitors[(full_name, arg.__class__)]
            return method(self, arg, *rest, **kwargs)

        return _visitor_impl

    return decorated
person Jerzy    schedule 19.04.2018