Python函数属性-使用和滥用


195

没有多少人知道此功能,但是Python的函数(和方法)可以具有attribute。看哪:

>>> def foo(x):
...     pass
...     
>>> foo.score = 10
>>> dir(foo)
['__call__', '__class__', '__delattr__', '__dict__', '__doc__', '__get__', '__getattribute__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name', 'score']
>>> foo.score
10
>>> foo.score += 1
>>> foo.score
11

Python中此功能的可能用法和滥用是什么?我知道的一个很好的用法是PLY使用docstring将语法规则与方法相关联。但是自定义属性呢?是否有充分的理由使用它们?


3
检出PEP 232
user140352

2
这非常令人惊讶吗?通常,Python对象支持即席属性。当然,有些不是,尤其是内置类型的。在我看来,那些不支持这一点的人似乎是例外,而不是规则。
allyourcode

3
Django中的一个应用程序:自定义管理员更改列表
Grijesh Chauhan 2013年

2
@GrijeshChauhan我在看了这些文档后来到了这个问题!
Alexander Suraphel 2015年

5
遗憾的是,这是封闭的,我想补充一点,您可以附加该函数可能引发的任何自定义异常,以在将其捕获到调用代码中时提供方便的访问。我将提供一个说明性的示例,但这最好是在答案中完成。
哈迪将于

Answers:


153

我通常使用函数属性作为注释的存储。假设我想以C#的方式编写(表示某种方法应该成为Web服务接口的一部分)

class Foo(WebService):
    @webmethod
    def bar(self, arg1, arg2):
         ...

然后我可以定义

def webmethod(func):
    func.is_webmethod = True
    return func

然后,当Web服务调用到达时,我查找该方法,检查基础函数是否具有is_webmethod属性(实际值无关紧要),如果该方法不存在或不打算通过Web调用,则拒绝该服务。


2
您认为这有不利之处吗?例如,如果两个库尝试编写相同的临时属性怎么办?
allyourcode 2012年

18
我当时正考虑这样做。然后我停下了脚步。“这是一个坏主意吗?” 我想知道 然后,我漫步到SO。经过一番摸索,我找到了这个问题/答案。仍然不确定这是否是一个好主意。
allyourcode

7
绝对是所有答案中函数属性的最合法使用(截至2012年11月)。大多数(如果不是全部)其他答案都使用函数属性来代替全局变量。但是,它们并没有摆脱全局状态,这正是全局变量存在的问题。这是不同的,因为一旦设置了值,它就不会改变;它是恒定的。这样做的一个很好的结果是您不会遇到全局变量固有的同步问题。是的,您可以提供自己的同步,但这很重要:自动运行并不安全。
allyourcode

的确,我说,只要属性不改变所讨论功能的行为,就很好。比较.__doc__
Dima Tisnek

这种方法也可以用于将输出描述附加到装饰后的函数中,这在python 2. *中是缺失的。
2013年

125

我已经将它们用作函数的静态变量。例如,给出以下C代码:

int fn(int i)
{
    static f = 1;
    f += i;
    return f;
}

我可以在Python中类似地实现该功能:

def fn(i):
    fn.f += i
    return fn.f
fn.f = 1

这肯定属于频谱的“滥用”端。


2
有趣。还有其他方法可以在python中实现静态变量吗?
伊莱·班德斯基

4
-1,可以使用python中的生成器实现。

124
这是拒绝这个答案的一个很糟糕的理由,这表明了C和Python之间的类比,而不是倡导编写此特定函数的最佳方法。
罗伯特·罗斯尼

3
@RobertRossney但是,如果要使用生成器的话,那就是对函数属性的不良使用。如果是这样,那么这是一种滥用。但不确定是否支持滥用行为,因为问题也是如此:P
allyourcode 2012年

1
根据PEP 232,绝对似乎是一种滥用,谢谢@ user140352,以及hop的注释和此SO答案
滚刀

52

您可以用JavaScript的方式来做对象...虽然没有任何意义,但是它是可行的;)

>>> def FakeObject():
...   def test():
...     print "foo"
...   FakeObject.test = test
...   return FakeObject
>>> x = FakeObject()
>>> x.test()
foo

36
+1滥用此功能的一个很好的例子,这是问题要解决的问题之一。
迈克尔·邓恩

1
这与mipadi的答案有何不同?似乎是一样,除了属性值不是int之外,它是一个函数。
allyourcode

def test()真的有必要吗?
Keerthana Prabhakaran

15

我很少使用它们,但是它们可以很方便:

def log(msg):
   log.logfile.write(msg)

现在,我可以log在整个模块中使用,只需设置即可重定向输出log.logfile。有很多其他方法可以实现这一点,但是这种方法轻巧而简单。虽然我第一次这样做时闻起来很可笑,但我开始相信它闻起来比具有全局logfile变量更好。


7
重新闻一下:虽然这不会摆脱全局日志文件。它只是把它分散在另一个全局函数log函数中。
allyourcode

2
@allyourcode:但是,如果您必须在同一模块中使用一堆用于不同功能的全局日志文件,则可以避免名称冲突。
firegurafiku

11

函数属性可用于编写将代码和关联数据包装在一起的轻量级闭包:

#!/usr/bin/env python

SW_DELTA = 0
SW_MARK  = 1
SW_BASE  = 2

def stopwatch():
   import time

   def _sw( action = SW_DELTA ):

      if action == SW_DELTA:
         return time.time() - _sw._time

      elif action == SW_MARK:
         _sw._time = time.time()
         return _sw._time

      elif action == SW_BASE:
         return _sw._time

      else:
         raise NotImplementedError

   _sw._time = time.time() # time of creation

   return _sw

# test code
sw=stopwatch()
sw2=stopwatch()
import os
os.system("sleep 1")
print sw() # defaults to "SW_DELTA"
sw( SW_MARK )
os.system("sleep 2")
print sw()
print sw2()

1.00934004784

2.00644397736

3.01593494415


3
当我们方便上课时,为什么要推送功能?而且不要忘了类可以模拟一个函数。
muhuk

1
time.sleep(1)os.system('sleep 1')
Boris Gorelik

3
@bgbg正确,尽管此示例与睡眠无关。
allyourcode

这绝对是一种滥用;在这里使用功能是完全免费的。muhuk完全正确:类是更好的解决方案。
allyourcode

1
我还会问:“相对于课堂,这样做有什么好处?” 以消除这种缺点,这对许多Python程序员而言并不那么明显。
cjs

6

我创建了这个辅助装饰器来轻松设置函数属性:

def with_attrs(**func_attrs):
    """Set attributes in the decorated function, at definition time.
    Only accepts keyword arguments.
    E.g.:
        @with_attrs(counter=0, something='boing')
        def count_it():
            count_it.counter += 1
        print count_it.counter
        print count_it.something
        # Out:
        # >>> 0
        # >>> 'boing'
    """
    def attr_decorator(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            return fn(*args, **kwargs)

        for attr, value in func_attrs.iteritems():
            setattr(wrapper, attr, value)

        return wrapper

    return attr_decorator

用例是创建工厂的集合,并查询它们可以在功能元级别创建的数据类型。
例如(非常愚蠢):

@with_attrs(datatype=list)
def factory1():
    return [1, 2, 3]

@with_attrs(datatype=SomeClass)
def factory2():
    return SomeClass()

factories = [factory1, factory2]

def create(datatype):
    for f in factories:
        if f.datatype == datatype:
            return f()
    return None

装饰者如何提供帮助?为什么factory1.datatype=list不像老式的装饰器那样仅在声明的下方设置?
Ceasar Bautista 2015年

2
2个主要区别:样式,多个属性更易于设置。您绝对可以将其设置为一个属性,但在我看来却具有多个属性,并且还可以扩展装饰器以进行进一步处理(例如将默认值定义在一个位置而不是使用该功能的所有位置,或者必须设置属性后,调用一个额外的函数)。还有其他方法可以实现所有这些结果,我只是觉得这样更干净,但是很高兴改变主意;)
DiogoNeves

快速更新:对于Python 3,您需要使用items()而不是iteritems()
斯科特表示

4

有时,我使用函数的属性来缓存已计算的值。您也可以使用通用装饰器来概括此方法。注意此类功能的并发问题和副作用!


我喜欢这个主意!缓存计算值的一种更常见的技巧是使用dict作为调用者永远不会提供的属性的默认值-因为Python在定义函数时只评估一次,因此您可以在其中存储数据并将其粘贴周围。虽然使用函数属性可能不太明显,但对我而言,它的hacky却明显更少。
索伦·比约斯塔德

1

我一直以为,这是唯一可能的原因,因此在逻辑上可以放置文档字符串或其他类似的东西。我知道如果我将它用于任何生产代码,都会使大多数阅读它的人感到困惑。


1
我同意您关于这很可能造成混淆的主要观点,但是关于文档字符串:是的,但是为什么函数具有AD-HOC属性?可能有一组固定的属性,一个用于保存文档字符串。
allyourcode 2012年

@allyourcode在语言中设计了一般情况而不是特定的特殊情况,使事情变得更简单,并增强了与旧版本Python的兼容性。(例如,设置/处理文档字符串的代码仍可与不执行文档字符串的Python版本一起使用,只要它能够处理属性不存在的情况。)
cjs
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.