我正在阅读Python Essential Reference 4th ed。我无法弄清楚如何解决以下代码中的问题
class Account(object):
num_accounts = 0
def __init__(self, name, balance):
self.name = name
self.balance = balance
Account.num_accounts += 1
def __del__(self):
Account.num_accounts -= 1
def deposit(self, amt):
self.balance += amt
def withdraw(self, amt):
self.balance -= amt
def inquire(self):
return self.balance
class EvilAccount(Account):
def inquire(self):
if random.randint(0,4) == 1:
return self.balance * 1.1
else:
return self.balance
ea = EvilAccount('Joe',400)
如果我理解正确,当程序结束并且应该调用继承的__del__
函数时,ea对象超出范围,对吗?我在'NoneType' object has no attribute num_accounts
收到了__del__
。为什么之前没有在__init__
函数中抱怨?
答案 0 :(得分:3)
来自the docs:
警告:由于调用
__del__()
方法的不稳定情况,将忽略执行期间发生的异常,并向sys.stderr
打印警告。 此外,当响应于被删除的模块而调用__del__()
时(例如,当完成程序的执行时),__del__()
方法引用的其他全局变量可能已被删除或在被拆除的过程(例如进口机械关闭)。因此,__del__()
方法应该保持维持外部不变量所需的绝对最小值。从版本1.5开始,Python保证在删除其他全局变量之前,从其模块中删除名称以单个下划线开头的全局变量;如果不存在对此类全局变量的其他引用,这可能有助于确保在调用__del__()
方法时导入的模块仍然可用。
答案 1 :(得分:1)
当ea
超出范围时,您无法控制内存的释放方式。 Account
似乎是在此阶段对None
的引用
为什么你认为你需要一个__del__
方法?
答案 2 :(得分:1)
解释器退出时,Account
引用将被移除EvilAccount()
实例。结果,Account
现在是“无”。
一种解决方法是使用type(self)
而不是直接引用类名,但这会使每个类的计数 ,因此EvilAccount
会拥有它自己的计数器:
def __init__(self, ...):
# ...
type(self).num_accounts += 1
def __del__(self):
type(self).num_accounts -= 1
另一种方法是在调用Account
时检查__del__
是否仍然存在;只是抓住例外:
def __del__(self):
try:
Account.num_accounts -= 1
except AttributeError:
pass # Account has already been reaped
答案 3 :(得分:1)
其他人已经回答了为什么会这样,但是关于你应该做什么,试试这个:
import weakref
class classproperty(object):
def __init__(self, f):
self.f = f
def __get__(self, obj, owner):
return self.f(owner)
class Account(object):
_active = weakref.WeakSet()
@classproperty
def num_accounts(self):
return len(Account._active)
def __init__(self, name, balance):
self.name = name
self.balance = balance
Account._active.add(self)
def deposit(self, amt):
self.balance += amt
def withdraw(self, amt):
self.balance -= amt
def inquire(self):
return self.balance
>>> Account.num_accounts
0
>>> a = [Account('a', 0), Account('b', 0)]
>>> Account.num_accounts
2
>>> a.pop()
<__main__.Account object at 0x02F8D650>
>>> Account.num_accounts # Interactive session is holding onto the popped account in '_'
2
>>> Account.num_accounts # but now it has gone.
1
因此,不要计算存在多少个实例,只需保留所有当前实例的集合。 WeakSet
不会阻止它们被销毁,因此它只会准确地跟踪仍然存活的实例。
请注意,虽然在您认为丢失它们之后实例很容易停留:如果有什么抛出异常,那么堆栈帧中的所有局部变量将保持活动状态,直到抛出下一个异常为止。在这种情况下,您可能还需要一个明确的close()
方法,当有人关闭帐户并从活动集中明确删除实例时,您可以使用该方法。