代码之家  ›  专栏  ›  技术社区  ›  Chris

用Python编写解释器。这种情况被认为是有害的吗?

  •  4
  • Chris  · 技术社区  · 17 年前

    我正在将我从Scala创建的特定领域语言的解释器移植到Python。在这个过程中,我试图找到一种Python化的方式来模拟我广泛使用的Scala的case类特性。最后,我求助于使用isinstance,但感觉我可能错过了什么。

    文章如 this one

    我构建了许多Python类,每个类都表示不同类型的抽象语法树节点,如For、While、Break、Return、Statement等

    case EOp("==",EInt(l),EInt(r)) => EBool(l==r)
    case EOp("==",EBool(l),EBool(r)) => EBool(l==r)
    

    7 回复  |  直到 17 年前
        1
  •  2
  •   Community Mohan Dere    9 年前

    总结 :这是编写编译器的常见方法,在这里很好。

    在其他语言中处理此问题的一种非常常见的方法是“模式匹配”,这正是您所描述的。我想这就是它的名字 case Scala中的语句。这是编写编程语言实现和工具的一个非常常见的习惯用法:编译器、解释器等。为什么这么好?因为实现与数据完全分离(这通常是不好的,但在编译器中通常是可取的)。

    那么问题是,这种编程语言实现的常见习惯用法是Python中的反模式。噢。你可能会发现,这更多的是一个政治问题,而不是语言问题。如果其他Python主义者看到代码,他们会尖叫;如果其他语言实现者看到它,他们会立即理解。

    这在Python中是一种反模式的原因是Python鼓励鸭子类型的接口:你不应该有基于类型的行为,而是应该由对象在运行时可用的方法来定义。 S. Lott's answer 如果你想让它成为地道的Python,它可以很好地工作,但它几乎没有增加什么。

    我怀疑你的设计并不是真正的鸭子型——毕竟它是一个编译器,使用名称和静态结构定义的类非常常见。如果你愿意,你可以把你的对象想象成有一个“类型”字段 isinstance 用于基于该类型进行模式匹配。

    附录:

    模式匹配可能是人们喜欢用函数式语言编写编译器等的首要原因。

        2
  •  2
  •   Eloff    17 年前

    python中有一条经验法则,如果你发现自己编写了一大块if/eif语句,并且条件相似(例如一堆isinstance(…)),那么你可能是用错误的方式解决了问题。

        3
  •  2
  •   S.Lott    17 年前

    而不是实例,只需使用 Polymorphism

    class Node( object ):
        def eval( self, context ):
            raise NotImplementedError
    
    class Add( object ):
        def eval( self, context ):
            return self.arg1.eval( context ) + self.arg2.eval( context )
    

    isinstance .


    在需要强制的情况下,这样的事情怎么样?

    Add( Double(this), Integer(that) )
    

    这仍然是一个多态性问题。

    class MyType( object ):
        rank= None
        def coerce( self, another ):
            return NotImplemented
    
    class Double( object ):
        rank = 2
        def coerce( self, another ):
            return another.toDouble()
        def toDouble( self ):
            return self
        def toInteger( self ):
            return int(self)
    
    class Integer( object ):
        rank = 1
        def coerce( self, another ):
            return another.toInteger() 
        def toDouble( self ):
            return float(self)
        def toInteger( self ): 
            return self
    
     class Operation( Node ):
        def conform( self, another ):
            if self.rank > another.rank:
                this, that = self, self.coerce( another )
            else:
                this, that = another.coerce( self ), another
            return this, that
        def add( self, another ):
            this, that = self.coerce( another )
            return this + that
    
        4
  •  1
  •   Lennart Regebro    17 年前

    文章没有攻击 isinstance 。它攻击了为特定类进行代码测试的想法。

    是的,有更好的方法。或者几个。例如,您可以将类型的处理转换为函数,然后通过按类型查找来找到正确的函数。这样地:

    def int_function(value):
       # Do what you mean to do here
    
    def str_function(value):
       # Do what you mean to do here
    
    type_function = {int: int_function, str: str_function, etc, etc}
    
    def handle_value(value):
       function = type_function[type(value)]
       result = function(value)
       print "Oh, lovely", result
    

    更好的是,如果你能以某种方式避免进行任何类型的类型检查,但这可能很棘手。

        5
  •  0
  •   Anon    17 年前

    在我使用Python 3编写的DSL中,我使用了Composite设计模式,因此节点在使用中都是多态的,正如S.Lott所建议的那样。

    但是,当我一开始阅读输入以创建这些节点时,我确实使用了很多isinstance检查(针对Python 3提供的抽象基类,如collections.Iterable等,我相信它们也在2.6中),以及对hasattr的检查 '__call__'

    使用isinstance进行此类测试比使用type()更通用,因为isinstance会捕获子类——如果你能对抽象基类进行测试,那就更好了。看 http://www.python.org/dev/peps/pep-3119/ 有关抽象基类的信息。

        6
  •  0
  •   Omnifarious    17 年前

    在这种特殊情况下,您似乎正在实现一个运算符重载系统,该系统使用对象的类型作为您要调用的运算符的选择机制。你的节点类型恰好相当直接地对应于你的语言类型,但实际上你正在编写一个解释器。节点的类型只是一段数据。

    我不知道人们是否可以将自己的类型添加到您的领域特定语言中。但无论如何,我都会推荐一种表驱动的设计。

    制作一个包含(binary_operator、type1、type2、result_type、evalfunc)的数据表。使用isinstance在该表中搜索匹配项,并有一些条件来选择某些匹配项。也许可以使用比表更复杂的数据结构来加快搜索速度,但现在你基本上是在使用长列表的ifelse语句来进行线性搜索,所以我打赌一个普通的旧表会比你现在做的稍微快一些。

    我不认为isinstance在这里是错误的选择,主要是因为该类型只是您的解释器正在处理以做出决定的一段数据。双重分派和其他类似的技术只会掩盖你的程序正在做什么的真正内容。

    Python中的一个巧妙之处是,由于运算符函数和类型都是第一类对象,因此您可以直接将它们填充到表中(或您选择的任何数据结构中)。

        7
  •  -1
  •   Aoife    17 年前

    class EValue(object):
    
        def __init__(self, v):
            self.value = v
    
        def __str__(self):
            return str(self.value)
    
        def opequal(self, r):
            r.opequal_value(self)
    
        def opequal_int(self, l):
            print "(int)", l, "==", "(value)", self
    
        def opequal_bool(self, l):
            print "(bool)", l, "==", "(value)", self
    
        def opequal_value(self, l):
            print "(value)", l, "==", "(value)", self
    
    
    class EInt(EValue):
    
        def opequal(self, r):
            r.opequal_int(self)
    
        def opequal_int(self, l):
            print "(int)", l, "==", "(int)", self
    
        def opequal_bool(self, l):
            print "(bool)", l, "==", "(int)", self
    
        def opequal_value(self, l):
            print "(value)", l, "==", "(int)", self
    
    class EBool(EValue):
    
        def opequal(self, r):
            r.opequal_bool(self)
    
        def opequal_int(self, l):
            print "(int)", l, "==", "(bool)", self
    
        def opequal_bool(self, l):
            print "(bool)", l, "==", "(bool)", self
    
        def opequal_value(self, l):
            print "(value)", l, "==", "(bool)", self
    
    
    if __name__ == "__main__":
    
        v1 = EBool("true")
        v2 = EInt(5)
        v1.opequal(v2)