1

我正在阅读 Python Essential Reference 第 4 版。我无法弄清楚如何解决以下代码中的问题

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_accountsin __del__。为什么它不在__init__函数中更早地抱怨?

4

4 回答 4

3

文档

警告:由于__del__()调用方法的环境不稳定,在执行期间发生的异常将被忽略,sys.stderr而是打印警告。此外,当__del__()响应于模块被删除而被调用时(例如,当程序执行完成时),该__del__()方法引用的其他全局变量可能已经被删除或正在被拆除(例如,导入机器关闭)。为此原因,__del__()方法应该做维护外部不变量所需的绝对最小值。从 1.5 版开始,Python 保证名称以下划线开头的全局变量会在其他全局变量被删除之前从其模块中删除;__del__()如果不存在对此类全局变量的其他引用,这可能有助于确保在调用方法时导入的模块仍然可用。

于 2013-05-03T07:40:29.323 回答
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()方法,当有人关闭帐户时可以使用该方法,并从活动集中显式删除实例。

于 2013-05-03T08:15:05.240 回答
1

ea超出范围时,您无法控制内存的释放方式。看来这是现阶段Account的参考None

为什么你认为你需要一种__del__方法?

于 2013-05-03T07:24:40.243 回答
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
于 2013-05-03T08:08:35.423 回答