17

を押すと内部で何が起こりEnterますか?

単純な好奇心に加えて、質問する私の動機は、あなたが

from sympy import *

式を入力します。Enter呼び出しから呼び出しにどのように移行しますか

__sympifyit_wrapper(a,b)

sympy.core.decoratorsで?(これが、評価を調べてみたときにwinpdbが私を連れて行った最初の場所です。)通常呼び出され、sympyをインポートするとオーバーライドされる組み込みのeval関数があると思いますか?

4

4 に答える 4

12

もう少し遊んだ後は、私はそれを持っていると思います。最初に質問したとき、演算子のオーバーロードについて知りませんでした。

では、このPythonセッションで何が起こっているのでしょうか。

>>> from sympy import *
>>> x = Symbol(x)
>>> x + x
2*x

インタプリタが式を評価する方法について特別なことは何もないことがわかりました。重要なことは、Pythonが翻訳することです

x + x

の中へ

x.__add__(x)

__add__(self, other)Symbolは、を返すように定義されているBasicクラスを継承しAdd(self, other)ます。(これらのクラスは、sympy.core.symbol、sympy.core.basic、およびsympy.core.addにあります(確認したい場合)。)

Jerubが言っていたように、関数を評価する前に、基本的に関数の2番目の引数をsympy式に変換するSymbol.__add__()というデコレータがあります。その過程で、以前に見たものであるという関数が返されます。_sympifyit__sympifyit_wrapper

オブジェクトを使用して操作を定義することは、かなり巧妙な概念です。独自の演算子と文字列表現を定義することにより、簡単な記号代数システムを非常に簡単に実装できます。

symbolic.py-

class Symbol(object):
    def __init__(self, name):
        self.name = name
    def __add__(self, other):
        return Add(self, other)
    def __repr__(self):
        return self.name

class Add(object):
    def __init__(self, left, right):
        self.left = left
        self.right = right
    def __repr__(self):
        return self.left + '+' + self.right

今、私たちはできる:

>>> from symbolic import *
>>> x = Symbol('x')
>>> x+x
x+x

少しリファクタリングすることで、すべての基本的な算術を処理するように簡単に拡張できます。

class Basic(object):
    def __add__(self, other):
        return Add(self, other)
    def __radd__(self, other): # if other hasn't implemented __add__() for Symbols
        return Add(other, self)
    def __mul__(self, other):
        return Mul(self, other)
    def __rmul__(self, other):
        return Mul(other, self)
    # ...

class Symbol(Basic):
    def __init__(self, name):
        self.name = name
    def __repr__(self):
        return self.name

class Operator(Basic):
    def __init__(self, symbol, left, right):
        self.symbol = symbol
        self.left = left
        self.right = right
    def __repr__(self):
        return '{0}{1}{2}'.format(self.left, self.symbol, self.right)

class Add(Operator):
    def __init__(self, left, right):
        self.left = left
        self.right = right
        Operator.__init__(self, '+', left, right)

class Mul(Operator):
    def __init__(self, left, right):
        self.left = left
        self.right = right
        Operator.__init__(self, '*', left, right)

# ...

もう少し調整するだけで、最初からsympyセッションと同じ動作を得ることができます。引数が等しい場合Addにインスタンスを返すように変更します。インスタンスを作成する前にMul取得しているので、これは少し注意が必要です。の代わりに使用する必要があります:__new__()__init__()

class Add(Operator):
    def __new__(cls, left, right):
        if left == right:
            return Mul(2, left)
        return Operator.__new__(cls)
    ...

シンボルの等式演算子を実装することを忘れないでください。

class Symbol(Basic):
    ...
    def __eq__(self, other):
        if type(self) == type(other):
            return repr(self) == repr(other)
        else:
            return False
    ...

そして出来上がり。とにかく、演算子の優先順位付け、置換による評価、高度な単純化、微分など、他のさまざまな実装方法を考えることができますが、基本が非常に単純であるのはかなりクールだと思います。

于 2010-08-07T06:25:12.373 に答える
6

これはsecondbananaの本当の質問とはあまり関係がありません-それはOmnifariousの賞金のショットです;)

通訳自体はとてもシンプルです。実際のところ、簡単なもの(完璧にはほど遠い、例外を処理しないなど)を自分で書くことができます。

print "Wayne's Python Prompt"

def getline(prompt):
    return raw_input(prompt).rstrip()

myinput = ''

while myinput.lower() not in ('exit()', 'q', 'quit'):
    myinput = getline('>>> ')
    if myinput:
        while myinput[-1] in (':', '\\', ','):
            myinput += '\n' + getline('... ')
        exec(myinput)

通常のプロンプトで、慣れていることのほとんどを実行できます。

Waynes Python Prompt
>>> print 'hi'
hi
>>> def foo():
...     print 3
>>> foo()
3
>>> from dis import dis
>>> dis(foo)
  2           0 LOAD_CONST               1 (3)
              3 PRINT_ITEM
              4 PRINT_NEWLINE
              5 LOAD_CONST               0 (None)
              8 RETURN_VALUE
>>> quit
Hit any key to close this window...

本当の魔法はレクサー/パーサーで起こります。

字句解析、つまり字句解析、入力を個々のトークンに分割します。トークンは、キーワードまたは「分割できない」要素です。たとえば、、、、、、、、およびは=すべてPythonトークンです。Pythonがプログラムをトークン化する方法を確認するには、モジュールを使用できます。iftry:forpassimporttokenize

'test.py'というファイルにコードを入れて、そのディレクトリで次のコマンドを実行します。

tokenizeからimporttokenizef = open('test.py')tokenize(f.readline)

あなたのためprint "Hello World!"に以下を得る:

1,0-1,5:NAME'print'
1,6-1,19:STRING'"hello world"'
1,19-1,20:NEWLINE'\
n'2,0-2,0:ENDMARKER ' '

コードがトークン化されると、抽象構文ツリーに解析されます。最終結果は、プログラムのPythonバイトコード表現です。あなたはこのプロセスの結果を見ることができます:print "Hello World!"

from dis import dis
def heyworld():
    print "Hello World!"
dis(heyworld)

もちろん、すべての言語は、プログラムをlex、解析、コンパイルしてから実行します。Pythonは、バイトコードを字句解析、解析、およびコンパイルします。次に、バイトコードがマシンコードに「コンパイル」され(より正確に変換される可能性があります)、マシンコードが実行されます。これが、インタプリタ言語とコンパイル言語の主な違いです。コンパイル言語は、元のソースからマシンコードに直接コンパイルされます。つまり、コンパイル前にlex /解析するだけで、プログラムを直接実行できます。これは、実行時間が短縮されることを意味します(lex / parseステージがない)が、プログラム全体をコンパイルする必要があるため、最初の実行時間に到達するには、より多くの時間を費やす必要があることも意味します。

于 2010-08-11T16:24:56.167 に答える
5

sympyのコード(http://github.com/sympy/sympy)を調べたところ__sympifyit_wrapper、デコレータのようです。呼び出される理由は、次のようなコードがどこかにあるためです。

class Foo(object):
    @_sympifyit
    def func(self):
        pass

そして__sympifyit_wrapper、によって返されるラッパーです@_sympifyit。デバッグを続けると、関数(私の例ではfunc)が見つかった可能性があります。

私は多くのモジュールの1つに集まり、sympy/__init__.pyいくつかの組み込みコードにインポートされたパッケージはsympyバージョンに置き換えられます。これらのsympyバージョンは、おそらくそのデコレータを使用しています。

execによって使用され>>>ていたものは置き換えられないため、操作されるオブジェクトは置き換えられます。

于 2010-07-07T05:54:50.680 に答える
1

Pythonインタラクティブインタープリターは、Pythonコードが実行されている他の時間と何ら変わりはありません。例外をキャッチし、実行する前に不完全な複数行のステートメントを検出して、入力を完了できるようにするための魔法がありますが、それだけです。

本当に興味があるなら、標準のコードモジュールはPythonインタラクティブプロンプトのかなり完全な実装です。Pythonが実際に使用しているもの(つまり、Cで実装されているもの)ではないと思いますが、Pythonのシステムライブラリディレクトリを掘り下げて、実際にどのように行われるかを確認できます。私のは/usr/lib/python2.5/code.py

于 2010-08-12T13:49:50.173 に答える