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