在Fortran中有一个语句Implicit none
,当声明但未使用本地变量时会抛出编译错误。我知道Python是一种动态类型语言,变量的范围可以在运行时确定。
但我想避免在忘记初始化局部变量但在主代码中使用它时发生的某些意外错误。例如,以下代码中的变量x
是全局的,即使我不打算这样:
def test():
y=x+2 # intended this x to be a local variable but forgot
# x was not initialized
print y
x=3
test()
所以我的问题是:有没有办法确保test()
中使用的所有变量都是本地的,并且没有副作用。我使用的是Python 2.7.x.如果存在局部变量,则会打印错误。
答案 0 :(得分:8)
所以我的问题是:有没有办法确保使用所有变量 在test()中它是局部的,并且没有副作用。
有一种技术可以验证全局变量是否被访问。
这是一个装饰器,用于扫描函数的 LOAD_GLOBAL 的操作码。
import dis, sys, re, StringIO
def check_external(func):
'Validate that a function does not have global lookups'
saved_stdout = sys.stdout
sys.stdout = f = StringIO.StringIO()
try:
dis.dis(func)
result = f.getvalue()
finally:
sys.stdout = saved_stdout
externals = re.findall('^.*LOAD_GLOBAL.*$', result, re.MULTILINE)
if externals:
raise RuntimeError('Found globals: %r', externals)
return func
@check_external
def test():
y=x+2 # intended this x to be a local variable but forgot
# x was not initialized
print y
为了实现这一点,您需要一个可接受的全局引用(即模块)的停止列表。该技术可以扩展到涵盖其他操作码,例如 STORE_GLOBAL 和 DELETE_GLOBAL 。
所有这一切,我都没有看到直接的方法来检测副作用。
答案 1 :(得分:3)
你的意思是没有隐含的None
。赋值将创建一个新变量,因此拼写错误可能会在您的范围中引入新名称。
获得所需效果的一种方法是使用以下丑陋的黑客攻击:
def no_globals(func):
if func.func_code.co_names:
raise TypeError(
'Function "%s" uses the following globals: %s' %
(func.__name__, ', '.join(func.func_code.co_names)))
return func
因此,当您使用test
包装器声明函数no_globals
时,您将收到错误,如下所示:
>>> @no_globals
... def test():
... y = x + 2 # intended this x to be a local variable but forgot
... # x was not initialized
... print y
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in no_globals
TypeError: Function "test" uses the following globals: x
>>>
>>> x = 3
>>> test()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'test' is not defined
答案 2 :(得分:0)
完全避免使用全局范围的变量。如果必须的话,在名字前添加一些你永远不会在本地变量名中使用的东西。
答案 3 :(得分:0)
在Python中,这完全合法。事实上,它是语言的力量!这种(缺乏)错误是你可以做这样的事情的原因:
def function1():
# stuff here
function2()
def function2():
pass
在C中,您需要“转发声明”function2
。
Python有静态语法检查程序(如flake8),可以捕获错误和错误的样式,但这不是错误,并且不会被这样的检查程序捕获。否则,这样的事情将是一个错误:
FILENAME = '/path/to/file'
HOSTNAME = 'example.com'
def main():
with open(FILENAME) as f:
f.write(HOSTNAME)
或者,像这样更基本的东西将是一个错误:
import sys
def main():
sys.stdout.write('blah')
您可以做的最好的事情是对模块级变量声明使用不同的命名约定(如ALL_CAPS)。另外,养成将所有代码放在函数中的习惯(没有模块级逻辑),以防止变量泄漏到全局命名空间。
答案 4 :(得分:0)
如果您真的对此感到担心,可以尝试以下方法:
def test():
try:
x
except:
pass
else:
return
y = x+2
print y
但我建议您在编写一个在分配之前不要尝试引用的函数时要注意。如果可能,尝试单独测试每个功能,并使用各种精心定义的输入和预期输出。有很多testing suites and strategies,更不用说简单的assert
关键字了。
答案 5 :(得分:0)
有没有办法确保test()中使用的所有变量都是局部的,并且没有副作用。
没有。该语言不提供此类功能。
内置locals()
功能。所以你可以写:
y = locals()['x'] + 2
但我无法想象有人认为这是一种改进。
答案 6 :(得分:0)
要确保使用正确的变量,您需要限制查找的范围。在函数内部,Python将查找在行中定义的参数,然后查看args
和kwargs
。在那些之后,它将在功能之外看。如果函数依赖于在其他地方改变的全局变量,这可能会导致恼人的错误。
为避免意外使用全局变量,您可以使用关键字参数为要使用的变量定义函数:
def test(x=None):
y=x+2 # intended this x to be a local variable but forgot
# x was not initialized
print y
x=3
test()
我猜你不想为很多变数做这件事。但是,它将使函数停止使用全局变量。
实际上,即使你想在函数中使用全局变量,我认为最好将其明确化:
x = 2
def test(x=x):
y=x+2 # intended this x to be a local variable but forgot
# x was not initialized
print y
x=3
test()
此示例将使用x = 2作为函数,无论后续x的全局值发生什么。在函数内部,x
固定为编译时的值。
我在烧掉几次后开始将全局变量作为关键字参数传递。我认为这通常被视为良好做法?
答案 7 :(得分:0)
提供的解决方案很有意思,特别是使用dis.dis
的解决方案,但你真的在想错误的方向。你不想写这么麻烦的代码。
您是否害怕意外地到达全球?然后不要写全局变量。模块全局变量的目的主要是达到。 (在评论中我已经读过你在范围内有50个全局变量,在我看来你有一些设计错误。)
如果你仍然必须使用全局变量,那么要么使用命名约定(对于常量,建议使用UPPER_CASE,这可能会涵盖你的情况)。
如果命名约定也不是一个选项,只需将您不想要的函数放在单独的模块中的任何全局,并且不要在那里定义全局变量。例如,定义pure_funcs
并在该模块内部,在那里编写“纯”函数,然后导入该模块。由于python具有词法范围,因此函数可以仅到达在它们被编写的模块的外部作用域中定义的变量(当然还有本地或内置函数)。像这样:
# Define no globals here, just the functions (which are globals btw)
def pure1(arg1, arg2):
print x # This will raise an error, no way you can mix things up.