Python属性可调用

时间:2012-10-23 11:16:18

标签: python properties callable

有没有办法让一个属性和方法具有相同的名称? 我的意思是可以通常使用同时可以调用的属性? 像这样:

>>> b = Book()
>>> b.pages
123
>>> b.pages()
123
>>> b.pages(including_toc=False)
123
>>> b.pages(including_toc=True)
127

4 个答案:

答案 0 :(得分:10)

不,你不能。

()始终从左侧的表达式调用对象

这意味着,b.pages()可以理解如下:

_tmp = b.pages
_tmp()

如您所见,方法属性。

可能(但不应该)做的是在一些自定义类中包装整数并提供__call__方法......但我建议不要使用这种黑魔法。

答案 1 :(得分:5)

Book类的实例只能有一个名为pages的属性。该属性可以是任何东西(整数,可调用函数,任何东西),但它只能是一个东西。

另一种查看事物的方法是在字节码级别 - 给出两行代码:

>>> def a():
...     book.pages
...     book.pages()
... 

以下是disassembles

>>> dis.dis(a)
  2           0 LOAD_GLOBAL              0 (book)
              3 LOAD_ATTR                1 (pages)
              6 POP_TOP

  3           7 LOAD_GLOBAL              2 (book)
             10 LOAD_ATTR                1 (pages)
             13 CALL_FUNCTION            0
  [...a few more irrelevant lines...]

第一行(book.pages)加载book对象,并从中加载pages属性(LOAD_ATTR

第二行(book.pages())执行完全相同的操作,加载book对象,加载pages属性,然后调用{{1属性。

没有理智的方法可以让CALL_FUNCTION根据最终的使用方式返回不同的内容。你可以得到的最接近的是返回一个像

一样的奇怪物体
LOAD_ATTR

..但是,不要这样做。没有人使用你的代码会期望>>> class WeirdInteger(int): ... def __call__(self, including_toc): ... print "WeirdInteger(%s) called" % (self) ... >>> a = WeirdInteger(10) >>> a 10 >>> a*2 20 >>> a() WeirdInteger(10) called 属性像这样工作,pages传递给的代码位可能需要一个实际的整数。

而是以不同方式设计您的pages类(可能使Books成为常规函数,或为pages添加单独的属性)

答案 2 :(得分:3)

  

简答:不,因为属性和方法是类的属性,并且是同一名称空间的一部分。   因此,稍后声明哪一个会覆盖前一个。

答案 3 :(得分:1)

我假设你的主要目标是让pages在设置特定标志时返回不同的值。根据您的目标,可能有效的一种方法是使pages属性(在精确的Python意义上)而不是属性。然后让它返回一个toc或不带的值,具体取决于是否设置了标志。例如:

class Book(object):
    def __init__(self, toc, pages):
        self._toc = toc
        self._pages = pages
        self.include_toc = False

    @property
    def pages(self):
        if self.include_toc:
            return self._pages + self._toc
        else:
            return self._pages

以下是它的工作原理:

>>> b = Book(5, 55)
>>> b.pages
55
>>> b.include_toc = True
>>> b.pages
60

这并不是完全你要求的内容,但它对某个用例子集(即你将多次调用{的那些用例)一样好或更好{1}}设置了标记,偶尔只更改标记 - 例如最终用户设置pages时,或者特定图书几乎总是或几乎从不包含include_toc时它的页数。)

但是,正如phant0m指出的那样,该标志是持久的,因此在某些情况下这可能会产生意外结果,如果您设置它然后在完成后无法重置它。正如eryksun所指出的,上下文管理器是解决该问题的经典解决方案。

虽然这可能确实是过度工程,但它很简单,我仍然会展示它。只需将其添加到_toc

的定义中即可
Book

这会为您重置标志:

    @contextlib.contextmanager
    def set_toc_reset(self, state):
        try:
            old_flag = self.include_toc
            self.include_toc = state
            yield self
        finally:
            self.include_toc = old_flag