有没有办法拥有同名的属性和方法?我的意思是一个可以按通常方式使用并且可以同时调用的属性?像这样:
>>> b = Book()
>>> b.pages
123
>>> b.pages()
123
>>> b.pages(including_toc=False)
123
>>> b.pages(including_toc=True)
127
有没有办法拥有同名的属性和方法?我的意思是一个可以按通常方式使用并且可以同时调用的属性?像这样:
>>> b = Book()
>>> b.pages
123
>>> b.pages()
123
>>> b.pages(including_toc=False)
123
>>> b.pages(including_toc=True)
127
不,你不能。
()
总是从其左侧的表达式中调用一个对象。
这意味着,b.pages()
可以如下解读:
_tmp = b.pages
_tmp()
如您所见,方法是属性。
您可以(但不应该)做的是将整数包装在一些自定义类中并提供一种__call__
方法……但我建议不要使用这种黑魔法。
您的类的实例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
.
简短回答:不,因为属性和方法是类的属性,并且属于同一个命名空间。因此,稍后声明的会覆盖前一个。
我假设您的主要目标是在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
这并不完全符合您的要求,但对于某些用例子集(即您将pages
使用标志集进行多次调用,只是偶尔更改标志的那些用例),它同样好或更好 - - 例如何时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