同情心如何运作?它如何与交互式Python shell交互,以及交互式Python shell如何工作?

时间:2010-07-07 03:42:35

标签: python scripting eval interactive sympy

当我按 Enter

时,内部会发生什么

除了好奇心之外,我的动机是要弄清楚当你

时会发生什么
from sympy import *

并输入一个表达式。它如何从 Enter 转到

__sympifyit_wrapper(a,b)
在sympy.core.decorators中? (当我尝试检查评估时,这是winpdb带给我的第一个地方。)我猜想有一些内置的eval函数可以正常调用,并在导入sympy时被覆盖?

4 个答案:

答案 0 :(得分:11)

好好再玩一遍后我觉得我已经得到了它...当我第一次提出我不知道operator overloading的问题时。

那么,这个python会话中发生了什么?

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

事实证明,解释器如何评估表达式并没有什么特别之处;重要的是python翻译

x + x

x.__add__(x)

和Symbol继承自Basic类,它定义__add__(self, other)以返回Add(self, other)。 (这些类可以在sympy.core.symbol,sympy.core.basic和sympy.core.add中找到,如果你想看一下。)

正如Jerub所说,Symbol.__add__()decorator _sympifyit,它在评估函数之前基本上将函数的第二个参数转换为一个sympy表达式,在返回过程中我之前看到的称为__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

通过一些重构,可以轻松扩展它以处理所有basic arithmetic

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__() instead of __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
    ...

瞧。无论如何,你可以想到要实现的各种其他事情,比如运算符优先级,替换评估,高级简化,区分等等,但我认为基础知识非常简单很酷。

答案 1 :(得分: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...

真正的魔法发生在词法分析器/解析器中。

词法分析,或lexing打破了对单个令牌的输入。标记是关键字或“不可分割的”元素。例如,=iftry:forpassimport都是Python令牌。要查看Python如何标记程序,您可以使用tokenize模块。

将一些代码放在名为“test.py”的文件中,并在该目录中运行以下命令:

来自tokenize import tokenize的

f = open('test.py') 记号化(f.readline)

对于print "Hello World!",您可以获得以下内容:

  

1,0-1,5:NAME'print'
  1,6-1,19:STRING'“你好世界”'   1,19-1,20:NEWLINE'\ n'
  2,0-2,0:ENDMARKER''

代码被标记化后,parsed成为abstract syntax tree。最终结果是程序的python字节码表示。对于print "Hello World!",您可以看到此过程的结果:

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

当然所有语言都是lex,解析,编译然后执行他们的程序。 Python lexes,parses和compiles to bytecode。然后字节码被“编译”(翻译可能更准确)到机器代码然后执行。这是解释语言和编译语言之间的主要区别 - 编译语言直接从原始源编译为机器代码,这意味着您只需在编译之前进行lex / parse,然后就可以直接执行该程序。这意味着更快的执行时间(没有lex / parse阶段),但这也意味着要达到初始执行时间,您必须花费更多时间,因为必须编译整个程序。

答案 2 :(得分:5)

我刚刚查看了同情的代码(http://github.com/sympy/sympy),看起来__sympifyit_wrapper是装饰者。它会调用的原因是因为某些代码看起来像这样:

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

__sympifyit_wrapper是由@_sympifyit返回的包装器。如果你继续调试,你可能已经找到了这个函数(在我的例子中名为func)。

我收集了sympy/__init__.py中导入的众多模块和软件包中的一个,其中一些内置代码被替换为sympy版本。这些同情版本可能会使用该装饰器。

exec使用的

>>>将不会被替换,操作的对象将会被替换。

答案 3 :(得分:1)

Python交互式解释器并没有做太多与Python代码运行时有任何不同之处。它确实有一些魔力来捕获异常并在执行它们之前检测不完整的多行语句,这样你就可以完成输入,但就是这样。

如果你真的很好奇,标准的code module是一个相当完整的Python交互式提示实现。我认为这并不是Python实际使用的东西(也就是说,我相信,用C实现),但你可以深入研究Python的系统库目录,并实际看看它是如何完成的。我在/usr/lib/python2.5/code.py