python中的访客模式

bog*_*dan 12 python visitor

这是C++中访问者模式的简化实现.我有可能在Python中实现这样的东西吗?

我需要它,因为我会将对象从C++代码传递给Python中的函数.我的想法是在Python中实现一个访问者来找出Object的类型.

我的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 );
}
Run Code Online (Sandbox Code Playgroud)

Ton*_* 66 15

访问者模式可以用Python实现,我用它来实现我的数据和表示层之间的干净接口.数据层可以确定数据的排序.并且表示层只是打印/格式化它:

在我的数据模块中,我有:

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

 class typeA(visited):
    ....
Run Code Online (Sandbox Code Playgroud)

我的所有数据类都继承自此访问类,并且被访问类还为我的所有对象需要的基本数据公开了一些简单函数,例如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"""
          ...
Run Code Online (Sandbox Code Playgroud)

_find_data_instance是一些代码,建立或发现我的数据实例中的一个实例.在我的例子中,我的所有数据类都有一个构造函数,它接受objectId并返回,访问者对象知道要使用哪个数据类.


Jor*_*ren 11

您可以使用装饰器来获得您想要的东西.复制此博客中的示例:

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!"
Run Code Online (Sandbox Code Playgroud)

以及@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
Run Code Online (Sandbox Code Playgroud)

相关博客(第一个似乎已经失效):https://chris-lamb.co.uk/posts/visitor-pattern-in-python

编辑:

obj.__qualname__ 在Python 3.3之前不可用,所以我们必须使用hack来实现更低版本: -

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
Run Code Online (Sandbox Code Playgroud)

不幸的是,上面的解决方案不适用于3.3以下的python版本,因为方法在传递给装饰器时仍然是常规函数.您可以尝试同时使用类和方法装饰器,请参阅实例方法的Python装饰器是否可以访问该类?.


blu*_*ote 7

首先

  • 什么是面向对象编程?它是对单个参数的动态调度(thisJava 和 C++ 中的隐式)
  • 访问者模式有什么作用:它试图在这些语言上模拟双重调度,因此它使用两个类作为解决方法。

您可以在 python 中以相同的方式执行此操作,但您也可以使用装饰器实现双重调度。(Lisp 的 CLOS 使用了一种模糊相似的方法)

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")
Run Code Online (Sandbox Code Playgroud)


trv*_*vrm 5

可以在 Python 中实现它,但实际上没有必要。Python 是一种动态的解释型语言,这意味着类型信息在运行时很容易获得。

所以你上面的例子可能很简单

class C1(object):
    pass

class C2(object):
    pass

l = [C1(), C2()]
if __name__=="__main__":
    for element in l:
        print type(element)
Run Code Online (Sandbox Code Playgroud)

这将产生:

<class '__main__.C1'>
<class '__main__.C2'>
Run Code Online (Sandbox Code Playgroud)