我可以在Python中的任何位置定义范围吗?

时间:2011-11-08 05:01:43

标签: python scope

有时我发现我必须在几行代码中使用长名称的函数,例如os.path.abspathos.path.dirname lot 。我不认为使用这些函数乱丢全局命名空间是值得的,但是能够在我需要这些函数的行周围定义范围将是非常有帮助的。举个例子,这将是完美的:

import os, sys

closure:
    abspath = os.path.abspath
    dirname = os.path.dirname

    # 15 lines of heavy usage of those functions

# Can't access abspath or dirname here

我很想知道这是否可行

7 个答案:

答案 0 :(得分:19)

Python在Lisp或Scheme中没有像 let 这样的临时名称空间工具。

Python中常用的技术是将名称放在当前命名空间中,然后在完成它们时将其取出。这种技术在标准库中大量使用:

abspath = os.path.abspath
dirname = os.path.dirname
# 15 lines of heavy usage of those functions
a = abspath(somepath)
d = dirname(somepath)
...
del abspath, dirname

减少打字工作量的另一种方法是缩短经常性前缀:

>>> import math as m
>>> m.sin(x / 2.0) + m.sin(x * m.pi)

>>> p = os.path
...
>>> a = p.abspath(somepath)
>>> d = p.dirname(somepath)

标准库中常用的另一种技术是不必担心污染模块命名空间,只需依靠 __ all __ 列出您打算公开的名称。 __ all __ 的效果在docs for the import statement中讨论。

当然,您也可以通过将名称存储在字典中来创建自己的命名空间(尽管此解决方案并不常见):

d = dict(abspath = os.path.abspath,
         dirname = os.path.dirname)
...
a = d['abspath'](somepath)
d = d['dirname'](somepath)

最后,您可以将所有代码放在一个函数中(它有自己的本地命名空间),但这有许多缺点:

  • 设置很尴尬(非典型和神秘的功能使用)
  • 您需要声明全局您想要做的任何非临时的分配。
  • 在您调用函数
  • 之前,代码不会运行
 def temp():                        # disadvantage 1: awkward setup
    global a, d                     # disadvantage 2: global declarations
    abspath = os.path.abspath
    dirname = os.path.dirname
    # 15 lines of heavy usage of those functions
    a = abspath(somepath)
    d = dirname(somepath)
 temp()                             # disadvantage 3: invoking the code

答案 1 :(得分:5)

这种做你想要的,但你必须重复名称

try:
    abspath = os.path.abspath
    dirname = os.path.dirname
    # fifteen lines of code
finally:
    del abspath
    del dirname

如果在下面的情况中存在异常,这可以避免污染命名空间

try:
    ...
    try:
        abspath = os.path.abspath
        dirname = os.path.dirname
        # fifteen lines of code
    finally:
        del abspath
        del dirname

    ... # don't want abspath or dirname in scope here even if there was
    ... # an exception in the above block

except:
    ...

答案 2 :(得分:2)

简短的回答是“不。”。

Python有三个范围。它具有功能范围,全局(aka模块)范围和内置范围。您不能声明其他范围。

class声明看起来有点像作用域,但事实并非如此。它基本上是在一个对象上分配一堆字段的简写。该类中的函数无法访问这些字段而无需浏览它们所定义的对象。

这听起来比它更具限制性。在Python中,您还可以嵌套函数定义。嵌套函数定义获得对外部作用域的只读访问权限。这是“动态的”。在定义函数之前不必提及名称。这是一个例子:

def joe(x):
    def bar():
        return y
    def baz(z):
        y = x + 20
        return x
    y = x+5
    return bar, baz

>>> a, b = joe(5)
>>> b(20)
5
>>> a()
10

因此,您可以通过定义一个嵌套函数来创建所需的值,使用它们并返回结果,从而在不牺牲太多局部性的情况下获得此效果。

我记得,在学习Python时,习惯于相当奇怪的范围规则是一个比较困难的部分。当引入嵌套函数时,在我看来,由于外部作用域的只读语义和闭包的动态作用域,它们使得作用域规则更加奇怪。

显然,在Python3中有一种使用nonlocal关键字(类似于global关键字)从封闭范围“导入”变量的方法,因此您可以在读/写中使用它上下文:

def joe(x):
    def bar():
        return y
    def baz(z):
        nonlocal y
        y = x + 20
        return x
    y = x+5
    return bar, baz

>>> a, b = joe(5)
>>> b(20)
5
>>> a()
25

否则,每当Python在=符号的左侧看到变量时,它就会假设您正在创建一个新的局部变量。 globalnonlocal关键字是一种表明您打算修改不在函数范围内的变量的方法。

答案 3 :(得分:1)

刚做一个功能?

def do_things_with_those_functions():
    abspath = os.path.abspath
    dirname = os.path.dirname
    # etc.

您可以在任何范围内执行此操作:

def outer_function():
    a = 5
    def inner_function():
        print(a)
    inner_function()

答案 4 :(得分:0)

您可以在任何地方定义函数,调用它们,然后删除它们。但是没有办法使用可以使用语句的匿名函数。

答案 5 :(得分:0)

目前尚不清楚你是否更容易被写入的识别表达式的长度所困扰,或者在使用它们后在名称空间中留下剩余标识符的痕迹。

  • 首先,正如Raymond Hettinger所描述的那样,为长前缀定义别名的技术是第一个应用的技术。

  • 对于第二种情况,我很惊讶没有人使用输入一个模块,在这个模块中,您认为重的和乱扔垃圾的指令和线路都是托运的。

顺便说一句,如果您通过os.path.abspathos.path.dirnames访问函数,那么说这些函数(我想你的意思是他们的名字)就会丢失命名空间。由于它们属于模块 os os.path (它取决于哪一个已导入),因此只有模块名称​​'os'命名空间中的'os.path',以及内存中的模块对象,但不直接在命名空间中的函数名称。

因此,可以创建一个名为“heavy_code.py”的脚本:

def doing(x):
    from os.path import abspath as a,dirname as d
    ## Execute all the desired processes and creations
    def fufu(s,t):
        return s+t
    dedex = d[x]
    #.........
    #...........
    #........
    #............
    #..........

    ## Return to the calling scope all that is needed there
    return (dedex,fufu)

在主模块中,取一下gnibbler的答案:

one_path = 'I:/all/magala/zeru/kiol.py'
try:
    from pp.bududu import doing
    w,ff = doing(one_path)
finally:
    del doing

要了解它的工作原理:

one_path = 'I:/all/magala/zeru/kiol.py'
try:
    from pp.bududu.heavy_code import doing
    print "in try : "
    print dir()
    print "executing doing()"
    w,ff = doing(one_path)
    print dir()
finally:
    del doing

print "\nafter finally : "
print dir()
print '\nw ==',w
print 'ff(10,12) ==',ff(10,12)

产生结果:

in try :
['__builtins__', '__doc__', '__name__', '__package__', 'doing', 'one_path']
executing doing()
['__builtins__', '__doc__', '__name__', '__package__', 'doing', 'ff', 'one_path', 'w']

after finally :
['__builtins__', '__doc__', '__name__', '__package__', 'ff', 'one_path', 'w']

w == I:/all/magala/zeru
ff(10,12) == 22

执行代码段后,主模块中不再存在 doing()功能,而是执行执行()时创建的对象在主模块的命名空间中没有杂乱的名字。此外,函数 doing()中所需的所有标识符都是本地的。

创建所有需要和需要的对象可以委派给模块 heavy_code ,无论它们有多少,同时导入和执行函数 doing()需要主模块中只有两行, heavy_code 中的函数 doing()加上其调用行可以轻松修改。

这不是为什么设计的模块?

答案 6 :(得分:-3)

一般来说,打字并不是编写软件的难点,但如果你坚持:

class LocalNamespace(object):
    def __enter__(self, *args, **kwargs):
        pass
    def __exit__(self, *args, **kwargs):
        pass
 with LocalNamespace() as foo:
     abspath = os.path.abspath
     dirname = os.path.dirname
     # etc.

希望这有帮助。