可以从范围外部为Python函数提供新属性吗?

时间:2009-11-18 02:27:05

标签: python attributes namespaces scope

我不知道你能做到这一点:

def tom():
    print "tom's locals: ", locals()

def dick(z):
    print "z.__name__ = ", z.__name__
    z.guest = "Harry"
    print "z.guest = ", z.guest
    print "dick's locals: ", locals()

tom()              #>>> tom's locals:  {}
#print tom.guest    #AttributeError: 'function' object has no attribute 'guest'
print "tom's dir:", dir(tom)  # no 'guest' entry

dick( tom)         #>>> z.__name__ =  tom
                   #>>> z.guest =  Harry
                   #>>> dick's locals:  {'z': <function tom at 0x02819F30>}
tom()              #>>> tom's locals:  {}
#print dick.guest  #AttributeError: 'function' object has no attribute 'guest'

print tom.guest    #>>> Harry
print "tom's dir:", dir(tom)  # 'guest' entry appears

函数tom()没有本地。函数dick()知道tom()在哪里生活,并将Harry作为'guest'放在tom()的位置。哈里并不像汤姆()那样出现在当地,但如果你要汤姆的客人,哈利会回答。哈里是tom()的一个新属性。

更新:从tom()外面,您可以说“print dir(tom)”并查看tom-object的字典。 (你也可以从里面 tom()这样做。所以汤姆可以发现他有一个新的房客,哈利,以'客人'的名义。)

那么,属性可以从函数外部添加到函数的命名空间中吗?经常这样做吗?这是否可以接受?在某些情况下是否推荐?它有时真的至关重要吗? (它是Pythonic吗?)

更新:标题现在说'属性';它曾经说'变量'。这是一个PEP about Function Attributes

7 个答案:

答案 0 :(得分:4)

我认为您可能会混淆局部变量功能属性的概念。有关Python函数属性的更多信息,请参阅SO问题Python function attributes - uses and abuses

答案 1 :(得分:4)

@behindthefall,给函数的动机对象一般可分配的属性(他们没有使用让他们)是,不存在这样的可能性,真正的和流行的框架是在滥用数分配属性的存在 (通常__doc__)记录有关每个给定函数对象的信息。因此,这个功能显然存在“被压抑的需求”,所以Guido决定直接解决它(为每个函数对象添加一个可选的 dict 来记录它的属性并不是什么大问题 - 大多数函数对象不需要它, 是可选的,因此空指针的成本只有4个字节; - )。

在任意位置分配这些属性将是非常糟糕的做法,使得代码更难理解而没有实际好处,但是当以受控方式使用时它们非常有用 - 例如,装饰器可以有用地记录所有类型关于正在装饰的函数以及装饰发生的上下文,作为包装函数的属性,允许在以后随时根据需要轻松地自省这些元数据。

作为其他的答案已经指出的那样,局部变量(它是按实例,而不是每个功能对象!)是从一个函数对象的属性完全不相交的命名空间中保持它的__dict__

答案 2 :(得分:3)

在python中,命名空间只是一个字典对象,将变量名称作为字符串(在本例中为“guest”)映射到一个值(在本例中为“Harry”)。因此,只要您可以访问某个对象,并且它是可变的,您就可以更改其命名空间的任何内容。

在小型项目中,这不是一个大问题,并且可以让你更快地将事情放在一起,但是在大型项目中令人难以置信的混乱,你可以从任何地方修改数据。

有一些方法可以使类的属性“更私密”,例如Name Mangling

答案 3 :(得分:1)

tom.guest只是tom函数对象的一个​​属性,它与该函数中的scope或locals()无关,与tom是函数的事实无关,它可以在任何函数上运行对象

答案 4 :(得分:1)

我过去曾用过这个来制作一个包含“enums”的自包含功能。

假设我正在实施seek()功能。内置的Python(文件对象)采用整数来告诉它如何操作;哎呀,请给我一个枚举。

def seek(f, offset, whence=0):
    return f.seek(offset, whence)

seek.START = 0
seek.RELATIVE = 1
seek.END = 2

f = open(filename)

seek(f, 0, seek.START)  # seek to start of file
seek(f, 0, seek.END)  # seek to end of file

你觉得怎么样,太棘手和怪异?我喜欢它如何将“枚举”值与功能捆绑在一起;如果从模块导入函数,则会自动获得其“枚举”值。

答案 5 :(得分:0)

Python函数是词法范围的,因此无法在其定义范围之外的函数中添加变量。

但是,如果你真的想设计这样的系统(通常被认为是不好的做法),该函数仍然可以访问所有父作用域:

>>> def foo():
>>>     def bar():
>>>         print x
>>>     x = 1
>>>     bar()
1

变量函数变量通常是一个坏主意,因为假定函数是不可变的。实现此行为的最pythonic方法是使用类和方法。

答案 6 :(得分:0)

Python API文档生成工具(例如pydocepydoc)使用内省来确定函数的名称和文档字符串(可用作__name____doc__属性)。表现良好的函数装饰器应该保留这些属性,因此这些工具继续按预期工作(即装饰函数应该保留装饰函数的文档)。您可以通过将这些属性从修饰函数复制到装饰器来完成此操作。请查看update_wrapper模块中的functools

WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__')
WRAPPER_UPDATES = ('__dict__',)

def update_wrapper(wrapper,
                   wrapped,
                   assigned = WRAPPER_ASSIGNMENTS,
                   updated = WRAPPER_UPDATES):
    """Update a wrapper function to look like the wrapped function

       wrapper is the function to be updated
       wrapped is the original function
       ...
    """
    for attr in assigned:
        setattr(wrapper, attr, getattr(wrapped, attr))
    for attr in updated:
        getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
    ...

所以,这至少是一个修改函数属性是有用和可接受的例子。

在某些情况下,通过设置属性来“注释”函数会很有用; Django在以下几个地方使用此功能:

  • 您可以将alters_data设置为True 关于改变的模型方法 数据库,阻止他们 在模板中调用。

  • 你可以设置 关于模型方法的allow_tags 将显示在管理员中 表示该方法返回HTML 内容,不应该 自动逃脱。

一如既往,用你的判断。如果修改属性是可接受的练习(例如,在编写装饰器时),那么一定要继续。如果它将成为well documented API的一部分,那么它也可能很好。