对 plpython 存储过程中奇怪的导入行为感到困惑

问题描述

我正在运行 Postgresql 9.4,我对它如何处理 plpython 导入感到困惑,特别是关于 Decimal。

一个例子:

CREATE OR REPLACE FUNCTION devel.foo()
  RETURNS void AS
$BODY$
def myfoo():
    eval("Decimal('40.0')")

myfoo()
plpy.notice("done")
$BODY$
  LANGUAGE plpythonu VOLATILE
  COST 100;
ALTER FUNCTION devel.foo()
  OWNER TO postgres;

SELECT devel.foo();

结果:

错误:NameError:未定义名称“十进制”

有点令人惊讶,因为 Postgresql 9.4 中的一个变化是它应该隐式地使用十进制而不是在 plpython 中使用浮点数。但是好的,让我们添加导入:

CREATE OR REPLACE FUNCTION devel.foo()
  RETURNS void AS
$BODY$
from decimal import Decimal

def myfoo():
    eval("Decimal('40.0')");

myfoo()
plpy.notice("done")
$BODY$
  LANGUAGE plpythonu VOLATILE
  COST 100;
ALTER FUNCTION devel.foo()
  OWNER TO postgres;

select devel.foo()

再次:

错误:NameError:未定义名称“十进制”

现在让我们尝试将导入放在函数中:

CREATE OR REPLACE FUNCTION devel.foo()
  RETURNS void AS
$BODY$
def myfoo():
    from decimal import Decimal
    eval("Decimal('40.0')")

myfoo()
plpy.notice("done")
$BODY$
  LANGUAGE plpythonu VOLATILE
  COST 100;
ALTER FUNCTION devel.foo()
  OWNER TO postgres;


select devel.foo();

结果:devel.foo() 现在可以正常执行,打印“完成”。另一个奇怪的事情:以下也有效:

CREATE OR REPLACE FUNCTION devel.foo()
  RETURNS void AS
$BODY$
from decimal import Decimal
def myfoo():
    var = Decimal('40.0')
    eval("Decimal('40.0')")

myfoo()
plpy.notice("done")
$BODY$
  LANGUAGE plpythonu VOLATILE
  COST 100;
ALTER FUNCTION devel.foo()
  OWNER TO postgres;

问题:为什么在 myfoo() 中看不到顶级导入?为什么我在函数中使用 Decimal 会突然生效?

解决方法

这是记录在案的 Python 行为 here。 plpythonu 执行环境不是全局的。

注意,eval() 无权访问嵌套的作用域(非本地) 封闭环境。

在 python 中试试这个代码(在 PostgreSQL 之外):

def bar():
    from decimal import Decimal
    def foo():
        return eval("Decimal('40.0')")

    return foo()

print(bar())

它以 NameError: name 'Decimal' is not defined 失败。

使用 Decimalglobal 放入您的全局变量中:

def bar():
    from decimal import Decimal
    global Decimal
    def foo():
        return eval("Decimal('40.0')")

    return foo()

print(bar())