【问题标题】:Python property callablePython 属性可调用
【发布时间】:2012-10-13 07:33:23
【问题描述】:

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

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

【问题讨论】:

  • 如果您来自 PHP,语言直接支持这一点,请参阅here for some similar differences,其中我还提到了 PHP 所做的属性与方法的区别,但 Python 没有。附言欢迎使用 StackOverflow!

标签: python properties callable


【解决方案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

这并没有完全满足您的要求,但对于某些用例子集(即您将多次调用 @ 987654326@ 设置了标志,只是偶尔更改标志——例如当include_toc 由最终用户设置时,或者当一本书几乎总是或几乎不应该在其页数中包含_toc 时。)

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

虽然这可能确实是过度设计,但它非常简单,我还是会演示一下。只需将其添加到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

这会为您重置标志:

>>> from foo import Book
>>> b = Book(5, 55)
>>> with b.set_toc_reset(True):
...     print b.pages
... 
60
>>> b.include_toc
False

【讨论】:

  • 注意反之亦然,切记不要忘记在访问pages之前设置include_toc,否则会得到意想不到的结果。例如,您可能在上面添加了一些需要不同设置的代码,但忘记恢复其状态。
  • 您可以将其设为上下文管理器,例如 with b.include_toc(): pages = b.pages。方法include_toc 可以设置self._include_toc = True__exit__ 可以重置它。但与简单地拥有一个单独的属性相比,这是过度设计这个问题。
  • @eryksun,你说得对,在很多情况下它可能是过度设计的。但我认为仍然值得考虑,因为创建上下文管理器非常容易。
【解决方案2】:

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属性,然后调用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 成为常​​规函数,或者为 pages_without_toc 添加单独的属性)

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 我建议不要使用share the same namespace 的措辞,因为方法实际上是属性,恰好是可调用的。
    【解决方案4】:

    不,你不能。

    () 总是从其左侧的表达式中调用一个对象

    这意味着,b.pages() 可以读作如下:

    _tmp = b.pages
    _tmp()
    

    如您所见,方法属性。

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

    【讨论】:

    • 是的,我首先想到的是继承int
    • @eryksun 在您的情况下,我可能只是使用一个函数并接受您始终必须调用它,或者称为第二个属性 .pages_including_toc
    • 哦,废话,我是这么认为的。在这种特殊情况下,我将采用@phant0m 方式。
    • 对不起,我的意思是子类int 并添加一个__call__ 方法(根据需要加上实例数据,例如toc_num_pages)。我同意这太过分了。
    • @eryksun 不用担心,我听不懂你的意思;)
    猜你喜欢
    • 1970-01-01
    • 2022-01-18
    • 1970-01-01
    • 1970-01-01
    • 2018-09-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-16
    相关资源
    最近更新 更多