Python property callable

我的未来我决定 提交于 2020-02-03 04:24:31

问题


Is there any way to have a property and a method with the same name? I mean a property that can be used the usual way and to be callable at the same time? Like this:

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

回答1:


No, you can't.

() always calls an object from the expression on its left-hand side.

What this means is, that b.pages() can be read as follows:

_tmp = b.pages
_tmp()

As you can see, methods are attributes.

What you could (but shouldn't) do is wrap integers in some custom class and provide a __call__ method… but I would advise against such black magic.




回答2:


A instance of your Book class can only have one attribute called pages. That attribute can be anything (an integer, a callable function, anything really), but it can only be one thing.

Another way of viewing things is at the byte-code level - given two lines of code:

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

Here is what it 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...]

The first line (book.pages) loads the book object, and from that loads the pages attribute (LOAD_ATTR)

The second line (book.pages()) does the exact same thing, loads the book object, loads the pages attribute, then calls the CALL_FUNCTION attribute.

There is no sane way you can make the LOAD_ATTR return something different based on how it will eventually be used. The closest you can get is to return a weird object which acts like both

>>> 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

..but, don't do that. No one using you code will expect the pages attribute to work like this, and bits of code the pages will be passed to might require an actual integer.

Instead design your Books class differently (maybe make pages an regular function, or add a separate property for the pages_without_toc)




回答3:


Short Answer: No, as properties and methods are attributes of a class and are part of the same namespace. So, which one gets declared later overrides the previous one.




回答4:


I am assuming that your main goal is to have pages return a different value if a particular flag is set. One approach that might work, depending on your goal, would be to make pages a property (in the precise Python sense) rather than an attribute. Then have it return a value with toc or without, depending on whether a flag is set. So for example:

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

Here's how it would work:

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

This doesn't do exactly what you've asked for, but it is as good or better for a certain subset of use cases (i.e. those in which you will be making multiple calls to pages with the flag set, only changing the flag occasionally -- such as when include_toc is set by an end user, or when a particular book should almost always or almost never include the _toc in its page count.)

However, as phant0m points out, the flag is persistent, so this could generate unexpected results in some cases, if you set it and then fail to reset it when you're done. And as eryksun points out, a context manager is a classic solution to that problem.

While this may indeed be overengineering, it's so simple that I'll demonstrate it nonetheless. Simply add this to the definition of 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

This takes care of resetting the flag for you:

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


来源:https://stackoverflow.com/questions/13029254/python-property-callable

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!