在Python中编写解释器,isinstance算不算有害?
我正在把我自己创建的一个特定领域语言的解释器从Scala移植到Python。在这个过程中,我想找到一种更符合Python风格的方法,来模拟Scala中我经常使用的case class特性。最后,我使用了isinstance这个函数,但我感觉这样做可能还有更好的办法。
像这篇文章批评使用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调用来实现同样的效果,这样写显得冗长且不够Python风格。有没有更好的方法呢?
7 个回答
是的。
与其使用实例,不如直接用多态。这样更简单。
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
在Python编程中,有个简单的经验法则:如果你发现自己写了很多类似的if/elif语句,条件也差不多(比如一堆isinstance(...)),那么你可能是在用错方法解决问题。
更好的解决办法是使用类和多态,或者使用访问者模式、字典查找等等。在你的情况下,可以考虑创建一个Operators类,里面为不同类型重载操作符(就像上面提到的那样),或者用一个字典来存放(类型,操作符)这样的组合。
总结: 这是写编译器的一种常见方法,在这里也没问题。
在其他编程语言中,处理这个问题的一个非常常见的方法是“模式匹配”,这正是你所描述的。我想这就是Scala中case
语句的意思。这是一种编写编程语言实现和工具(比如编译器、解释器等)时非常常见的写法。为什么它这么好呢?因为实现和数据是完全分开的(虽然有时候这样不好,但在编译器中通常是希望的)。
但问题是,这种编程语言实现的常见写法在Python中却是反模式。哎呀。你可能已经察觉到,这更多是个政治问题,而不是语言问题。如果其他Python开发者看到这段代码,他们会大喊;而如果其他语言的实现者看到,他们会立刻理解。
在Python中,这种写法被认为是反模式的原因是,Python鼓励使用鸭子类型的接口:你不应该根据类型来决定行为,而是应该根据对象在运行时可用的方法来定义。S. Lott的回答如果你想让它符合Python的习惯用法,那是可以的,但其实帮助不大。
我怀疑你的设计其实并不是真正的鸭子类型——毕竟它是个编译器,使用名称定义的类和静态结构是相当常见的。如果你愿意,可以把你的对象看作有一个“类型”字段,而isinstance
就是用来根据这个类型进行模式匹配的。
附录:
模式匹配可能是人们喜欢在函数式语言中编写编译器等的第一大原因。