用Python编写解释器。是否认为有害?

时间:2009-09-04 22:45:14

标签: python scala language-design interpreter

我正在翻译我从Scala创建的域特定语言到Python的解释器。在这个过程中,我试图找到一种方式pythonic来模拟我广泛使用的Scala的case类特性。最后我使用了isinstance,但是我觉得我可能错过了一些东西。

诸如this one之类的文章攻击isinstance的使用让我想知道是否有更好的方法可以解决我的问题而不涉及一些基本的重写。

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

Scala允许像这样处理运营商评估:

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

到目前为止,对于Python的端口我已经广泛使用了elif块和isinstance调用来实现相同的效果,更加冗长和非pythonic。还有更好的方法吗?

7 个答案:

答案 0 :(得分:2)

而不是实例,只需使用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

答案 1 :(得分:2)

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

更好的方法涉及使用类和多态,访问者模式,字典查找等。在你的情况下,使用不同类型的重载的Operators类可以工作(如上所述),因此可以使用(类型,运算符)项目的dict

答案 2 :(得分:2)

摘要:这是编写编译器的常用方法,在这里就好了。

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

问题在于,这种编程语言实现的常用习惯是Python中的反模式。哦,哦。你可以说,这更像是一个政治问题,而不是一个语言问题。如果其他Pythonistas看到他们会尖叫的代码;如果其他语言实施者看到它,他们会立即理解它。

这是Python中的反模式的原因是因为Python鼓励使用duck-typed接口:你不应该有基于类型的行为,而应该通过运行时对象可用的方法来定义它们。如果你想让它成为惯用的Python,S. Lott's answer可以正常工作,但它几乎没有增加。

我怀疑你的设计并不是真正的鸭子型 - 毕竟它是一个编译器,而且使用名称定义的类具有静态结构,这很常见。如果您愿意,可以将对象视为具有“类型”字段,并使用isinstance基于该类型进行模式匹配。

<强> Addenum:

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

答案 3 :(得分:1)

该文章不会攻击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

如果你不想自己做这个注册表,你可以看看Zo​​pe组件架构,它通过接口和适配器处理这个,它真的很酷。但那可能有点过头了。

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

答案 4 :(得分:0)

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

但是,当我在输入中读取创建这些节点时,我确实使用了isinstance检查(对于抽象基类,如collections.Iterable等,Python 3提供了哪些,以及哪些是2.6以及我相信),以及对hasattr '__call__'的检查,因为我的输入中允许使用callables。这是我发现的最干净的方法(特别是涉及递归),而不仅仅是尝试对输入和捕获异常的操作,这是我想到的替代方案。当输入无效时,我自己提出自定义异常,以提供尽可能多的精确故障信息。

使用isinstance进行此类测试比使用type()更通用,因为isinstance将捕获子类 - 如果您可以针对抽象基类进行测试,那就更好了。有关抽象基类的信息,请参阅http://www.python.org/dev/peps/pep-3119/

答案 5 :(得分:0)

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

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

创建一个包含(binary_operator,type1,type2,result_type,evalfunc)的数据表。使用isinstance在该表中搜索匹配项,并且有一些标准可以优先选择某些匹配项。有可能使用比表更复杂的数据结构来使搜索更快,但是现在你基本上使用ifelse语句的长列表进行线性搜索,所以我打赌一个普通的旧表将比你现在做的要快一点。

我不认为isinstance在这里是错误的选择,主要是因为类型只是解释器正在做出决定的一段数据。双重调度和其他类似的技术只会掩盖你的计划正在做的真正的事情。

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

答案 6 :(得分:-1)

如果您需要参数的多态性(除了接收器),例如,如您的示例所示,使用二元运算符处理类型转换,您可以使用以下技巧:

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)
相关问题