当我按下 时,内部会发生什么Enter?
我问的动机,除了单纯的好奇,是想弄清楚当你
from sympy import *
并输入一个表达式。它是如何从Enter调用
__sympifyit_wrapper(a,b)
在 sympy.core.decorators 中?(当我尝试检查评估时,这是winpdb带我去的第一个地方。)我猜想有一些内置的 eval 函数可以正常调用,并且在导入 sympy 时会被覆盖?
好吧,在玩了一些之后,我想我已经明白了..当我第一次问这个问题时,我不知道运算符重载。
那么,这个 python 会话中发生了什么?
>>> from sympy import *
>>> x = Symbol(x)
>>> x + x
2*x
事实证明,解释器如何评估表达式并没有什么特别之处。重要的是python翻译
x + x
进入
x.__add__(x)
和 Symbol 继承自 Basic 类,该类定义__add__(self, other)
为 return Add(self, other)
。(如果您想查看这些类,可以在 sympy.core.symbol、sympy.core.basic 和 sympy.core.add 中找到。)
正如 Jerub 所说,Symbol.__add__()
有一个装饰器调用_sympifyit
,它基本上在评估函数之前将函数的第二个参数转换为 sympy 表达式,在此过程中返回一个调用的函数__sympifyit_wrapper
,这就是我之前看到的。
使用对象来定义操作是一个非常巧妙的概念。通过定义自己的运算符和字符串表示,您可以很容易地实现一个简单的符号代数系统:
符号.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
...
瞧。无论如何,你可以想出各种其他的东西来实现,比如运算符优先级、替换评估、高级简化、微分等,但我认为基础如此简单非常酷。
这与 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...
真正的魔法发生在词法分析器/解析器中。
词法分析或词法分析将输入分解为单个标记。标记是关键字或“不可分割的”元素。例如,=
, if
, try
, :
, for
, pass
, 和import
都是 Python 标记。要查看 Python 如何标记程序,您可以使用该tokenize
模块。
将一些代码放入名为“test.py”的文件中,并在该目录中运行以下命令:
从标记化导入标记化 f = 打开('test.py')标记化(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/parse 即可直接执行程序。这意味着更快的执行时间(没有 lex/parse 阶段),但这也意味着要达到初始执行时间,您必须花费更多时间,因为必须编译整个程序。
我刚刚检查了 sympy 的代码(位于http://github.com/sympy/sympy),它看起来像是__sympifyit_wrapper
一个装饰器。它会调用的原因是因为某处有一些代码看起来像这样:
class Foo(object):
@_sympifyit
def func(self):
pass
并且__sympifyit_wrapper
是由@_sympifyit
. 如果您继续调试,您可能已经找到了该函数(在我的示例中名为func
)。
我收集了在sympy/__init__.py
一些内置代码中导入的许多模块和包之一,这些模块和包被替换为 sympy 版本。这些 sympy 版本可能使用该装饰器。
exec
as used by>>>
不会被替换,被操作的对象将会被替换。
Python 交互式解释器并没有做很多与 Python 代码运行时有任何不同的事情。它确实有一些魔力来捕获异常并在执行它们之前检测不完整的多行语句,以便您可以完成输入它们,但仅此而已。
如果你真的很好奇,标准代码模块是 Python 交互式提示的相当完整的实现。我认为这并不是 Python 实际使用的(也就是说,我相信是用 C 实现的),但是您可以深入研究 Python 的系统库目录并实际查看它是如何完成的。我的在/usr/lib/python2.5/code.py