4

ScalaからPythonに作成したドメイン固有言語のインタープリターを移植しています。その過程で、私が広く使用したScalaのケースクラス機能をエミュレートする方法をpythonicで見つけようとしました。結局、isinstanceを使うことにしましたが、何かが足りないのではないかと感じました。

isinstanceの使用を攻撃するこのような記事は、根本的な書き直しを伴わない私の問題を解決するためのより良い方法があるかどうか疑問に思いました。

For、While、Break、Return、Statementなど、それぞれが異なるタイプの抽象構文ツリーノードを表すPythonクラスをいくつか構築しました。

Scalaでは、次のような演算子評価の処理が可能です。

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

これまでのところ、Pythonへの移植では、elifブロックとisinstance呼び出しを多用して、同じ効果を実現しました。これは、はるかに冗長で非Pythonです。もっと良い方法はありますか?

4

7 に答える 7

2

はい。

インスタンスの代わりに、ポリモーフィズムを使用してください。簡単です。

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
于 2009-09-04T23:22:44.237 に答える
2

Pythonには経験則があります。同様の条件(たとえば、一連のisinstance(...))を使用してif / elifステートメントの大きなブロックを記述している場合は、問題を間違った方法で解決している可能性があります。

より良い方法は、クラスとポリモーフィズム、ビジターパターン、dictルックアップなどを使用することです。あなたの場合、さまざまなタイプのオーバーロードを使用してOperatorsクラスを作成すると(上記のように)、(type、operator)アイテムを使用してdictを作成できます。

于 2009-09-06T01:15:23.400 に答える
2

要約:これはコンパイラーを作成するための一般的な方法であり、ここでは問題ありません。

他の言語でこれを処理する非常に一般的な方法は、「パターンマッチング」によるものです。これは、まさにあなたが説明したことです。それがScalaでのそのcaseステートメントの名前だと思います。プログラミング言語の実装とツールを作成するための非常に一般的なイディオムです。コンパイラー、インタープリターなどです。なぜこれほど優れているのでしょうか。実装はデータから完全に分離されているためです(これは多くの場合悪いですが、コンパイラーでは一般的に望ましいです)。

問題は、プログラミング言語の実装に関するこの一般的なイディオムがPythonのアンチパターンであるということです。ええとああ。おそらくお分かりのように、これは言語の問題というよりも政治的な問題です。他のPythonistaがコードを見た場合、彼らは悲鳴を上げるでしょう。他の言語実装者がそれを見た場合、彼らはすぐにそれを理解するでしょう。

これがPythonのアンチパターンである理由は、Pythonがダックタイピングインターフェイスを推奨しているためです。タイプに基づく動作ではなく、オブジェクトが実行時に使用できるメソッドによって定義する必要があります。S. Lottの答えは、慣用的なPythonにしたい場合は問題なく機能しますが、ほとんど追加されません。

あなたのデザインは実際にはダックタイピングされていないのではないかと思います。結局のところ、そのコンパイラであり、静的構造を持つ名前を使用して定義されたクラスはかなり一般的です。必要に応じて、オブジェクトを「タイプ」フィールドを持つものと考えることができ、isinstanceそのタイプに基づいてパターンマッチングを行うために使用されます。

補遺:

パターンマッチングは、おそらく人々が関数型言語でコンパイラなどを書くのが好きな最大の理由です。

于 2009-09-07T16:04:00.120 に答える
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

このレジストリを自分で作成したくない場合は、インターフェイスとアダプタを介してこれを処理するZopeコンポーネントアーキテクチャを確認できます。これは非常に便利です。しかし、それはおそらくやり過ぎです。

なんらかのタイプのタイプチェックを回避できる場合はさらに良いですが、それは難しいかもしれません。

于 2009-09-04T23:01:54.467 に答える
0

Python 3を使用して作成したDSLでは、S。Lottが推奨しているように、ノードがすべて多形であるように、コンポジットデザインパターンを使用しました。

しかし、最初にこれらのノードを作成するために入力を読み込んでいたとき、私はisinstanceチェックをたくさん使用しました(Python 3が提供するcollections.Iterableなどの抽象基本クラスに対して、2.6にもあります私は信じています)、そして'__call__'呼び出し可能オブジェクトが私の入力で許可されていたので、hasattrをチェックします。これは、入力に対して操作を試みて例外をキャッチするだけでなく、(特に再帰を伴う)それを行うために私が見つけた最もクリーンな方法でした。これは頭に浮かぶ代替手段です。できるだけ正確な障害情報を提供するために、入力が無効なときにカスタム例外を自分で発生させていました。

isinstanceはサブクラスをキャッチするため、このようなテストにisinstanceを使用する方が、type()を使用するよりも一般的です。抽象基本クラスに対してテストできる場合は、それがさらに優れています。抽象基本クラスの詳細については、http: //www.python.org/dev/peps/pep-3119/を参照してください。

于 2009-09-05T00:04:16.883 に答える
0

この特定のケースでは、実装しているように見えるのは、呼び出す予定のオペレーターの選択メカニズムとしてオブジェクトのタイプを使用するオペレーターオーバーロードシステムです。あなたのノードタイプはたまたまあなたの言語のタイプにかなり直接対応していますが、実際にはあなたはインタプリタを書いています。ノードのタイプは単なるデータです。

人々があなたのドメイン固有言語に独自のタイプを追加できるかどうかはわかりません。ただし、テーブル駆動型の設計をお勧めします。

(binary_operator、type1、type2、result_type、evalfunc)を含むデータのテーブルを作成します。isinstanceを使用してそのテーブルを検索し、いくつかの一致を他の一致よりも優先するためのいくつかの基準を用意します。テーブルよりもいくらか洗練されたデータ構造を使用して検索を高速化することも可能かもしれませんが、現在は基本的にifelseステートメントの長いリストを使用して線形検索を行っているので、単純な古いテーブルを使用することになります。あなたが今していることより少し速くなりなさい。

タイプはインタプリタが決定を下すために使用している単なるデータであるため、ここではisinstanceが間違った選択であるとは考えていません。その同類のダブルディスパッチと他のテクニックは、あなたのプログラムがしていることの本当の肉を覆い隠そうとしているだけです。

Pythonの優れた点の1つは、演算子の関数と型はすべてファーストクラスのオブジェクトであるため、テーブル(または選択したデータ構造)に直接詰め込むことができることです。

于 2009-09-05T15:13:07.767 に答える
-1

たとえば、例で提案されているように2項演算子を使用して型変換を処理するために、(レシーバーに加えて)引数にポリモーフィズムが必要な場合は、次のトリックを使用できます。

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)
于 2009-09-05T00:27:57.370 に答える