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

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

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

мой C++ код:

#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 );
}

4 ответов


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

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

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

 class typeA(visited):
    ....

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


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

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!"

и код @гость оформителя (в случае, если ссылка мертва):

# 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

EDIT:

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, поскольку методы по-прежнему являются регулярными функциями при передаче декоратору. Вы можете попробовать использовать как класс, так и метод decorator, см. может ли декоратор Python метода экземпляра получить доступ к классу?.


вы может реализовать это в 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'>

в случае, если кто-то находит это полезным, я получил следующую версию Joren по @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