Python函数属性-使用和滥用[关闭]

Posted

技术标签:

【中文标题】Python函数属性-使用和滥用[关闭]【英文标题】:Python function attributes - uses and abuses [closed] 【发布时间】:2010-09-25 04:25:23 【问题描述】:

没有多少人知道这个特性,但是 Python 的函数(和方法)可以有attributes。看:

>>> 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 使用文档字符串将语法规则与方法相关联。但是自定义属性呢?有充分的理由使用它们吗?

【问题讨论】:

查看PEP 232。 这很令人惊讶吗?通常,Python 对象支持 ad-hoc 属性。当然,有些不是,尤其是那些具有内置类型的。对我来说,那些不支持这一点的人似乎是例外,而不是规则。 Django 中的一个应用程序:Customize the admin change list @GrijeshChauhan 我在看到这些文档后提出了这个问题! 遗憾的是它已关闭,我想补充一点,您可以附加该函数可能引发的任何自定义异常,以便在调用代码中捕获它时提供方便的访问。我会提供一个说明性示例,但最好在答案中完成。 【参考方案1】:

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

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

那我可以定义

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

然后,当webservice调用到来时,我查找方法,检查底层函数是否有is_webmethod属性(实际值无关),如果方法不存在或不打算调用,则拒绝服务网络。

【讨论】:

你认为这有缺点吗?例如如果两个库尝试编写相同的 ad-hoc 属性怎么办? 我正在考虑这样做。然后我停下了自己。 “这是一个坏主意吗?”我想知道。然后,我徘徊到SO。经过一番摸索,我找到了这个问题/答案。仍然不确定这是否是个好主意。 这绝对是所有答案中最合法的函数属性使用(截至 2012 年 11 月)。大多数(如果不是全部)其他答案使用函数属性作为全局变量的替代;但是,它们并没有摆脱全局状态,这正是全局变量的问题。这是不同的,因为一旦设置了值,它就不会改变;它是恒定的。这样做的一个很好的结果是您不会遇到全局变量固有的同步问题。是的,您可以提供自己的同步,但这就是重点:自动同步并不安全。 确实,我说,只要属性不改变相关函数的行为,就很好。比较.__doc__ 这种方法也可以用来将输出描述附加到修饰函数上,这在python 2.*中是没有的。【参考方案2】:

我将它们用作函数的静态变量。例如,给定以下 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

这肯定属于“滥用”范畴。

【讨论】:

有趣。有没有其他方法可以在python中实现静态变量? -1,这将使用 python 中的生成器来实现。 这是一个非常糟糕的理由来否决这个答案,它展示了 C 和 Python 之间的类比,而不是提倡编写这个特定函数的最佳方法。 @RobertRossney 但是如果生成器是要走的路,那么这是对函数属性的不良使用。如果是这样,那么这是一种滥用。不确定是否要支持滥用行为,因为问题也要求那些:P @hobs 我不明白 PEP 232 是如何滥用的。PEP 232 为该机制提供了一些用例,但似乎不建议将使用仅限于这些用例。 【参考方案3】:

你可以用 javascript 的方式来做对象......这没有意义,但它有效;)

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

【讨论】:

+1 滥用此功能的一个很好的例子,这是问题要求的事情之一。 这与 mipadi 的回答有何不同?似乎是一样的东西,除了不是int,属性值是一个函数。 def test() 真的有必要吗?【参考方案4】:

我很少使用它们,但它们非常方便:

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

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

【讨论】:

重新闻:但这并没有摆脱全局日志文件。它只是将它隐藏在另一个全局日志函数中。 @allyourcode:但如果您必须在同一个模块中为不同的功能拥有一堆全局日志文件,它可以帮助避免名称冲突。【参考方案5】:

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

#!/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

【讨论】:

当我们有方便的类时,为什么要推送函数?我们不要忘记类可以模拟一个函数。 还有time.sleep(1)os.system('sleep 1') @bgbg 是的,虽然这个例子不是关于睡觉的。 这绝对是滥用;在这里使用函数是完全免费的。 muhuk 完全正确:类是更好的解决方案。 我也会问,“这比上课有什么优势?”以抵消这对许多 Python 程序员来说不那么明显的缺点。【参考方案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 2个主要区别:样式,多个属性更容易设置。您绝对可以将其设置为属性,但在我看来会因多个属性而变得冗长,并且您还有机会扩展装饰器以进行进一步处理(例如在一个地方定义默认值,而不是在所有使用该函数的地方定义或必须设置属性后调用额外的函数)。还有其他方法可以实现所有这些结果,我只是觉得这样更干净,但很高兴改变主意;) 快速更新:对于 Python 3,您需要使用 items() 而不是 iteritems() 你不需要额外的包装函数。你可以直接用 setattr 修改fn 并返回它。【参考方案7】:

有时我使用函数的属性来缓存已经计算的值。您还可以拥有一个泛化这种方法的通用装饰器。请注意此类函数的并发问题和副作用!

【讨论】:

我喜欢这个主意!缓存计算值的一个更常见的技巧是使用 dict 作为调用者从不打算提供的属性的默认值——因为 Python 在定义函数时只评估一次,你可以将数据存储在其中并粘贴大约。虽然使用函数属性可能不那么明显,但对我来说感觉不那么老套了。【参考方案8】:

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

【讨论】:

我同意您关于这很可能令人困惑的主要观点,但请重新说明文档字符串:是的,但是为什么函数具有 AD-HOC 属性?可能有一组固定的属性,一个用于保存文档字符串。 @allyourcode 将通用案例而不是特定的临时案例设计到语言中使事情变得更简单,并增加了与旧版本 Python 的兼容性。 (例如,设置/操作文档字符串的代码仍然适用于不执行文档字符串的 Python 版本,只要它处理属性不存在的情况。)

以上是关于Python函数属性-使用和滥用[关闭]的主要内容,如果未能解决你的问题,请参考以下文章

python—类的属性和方法总结

Python中matlab imfilter的等效函数

在 python 中,将函数用作类的属性的正确语法是啥? [关闭]

Haversine 函数 - Python [关闭]

使用python关闭计算机(linux)

pytho lambda表达式