有没有办法为继承类实现 __post_init__ 方法?

问题描述 投票:0回答:2

我想防止实例的用户在使用所述对象时错误地创建一个不存在的属性。

假设我有一个带有初始化和一些属性的类:

class Foo(obj):
   def __init__(self, a, b):
       self.a = a
       self.b = b

我想在阻止创建新属性的同时启用设置现有属性:

myobj = foo(1,2)
>> 
print(myobj.a)
>> 1
myobj.a = 2
myobj.c = 1
>> AttributeError: frozen instance

使用

__setattr__
覆盖和布尔值相当容易:

class Foo(obj):
   _is_frozen = False
   def __init__(self, a, b):
       self.a = a
       self.b = b
       self._is_frozen = True
 
   def __setattr__(self, name, value):
       if not self._is_frozen or hasattr(self, name):
           super().__setattr__(name, value)
       else:
           raise AttributeError("frozen instance") 

现在我挣扎的步骤是当一个新类继承 Foo 时。如果在调用

super().__init__()
之后必须定义新属性,则实例将被冻结。

我曾尝试使用装饰器制作元类,但母类的装饰器仍在

super().__init__()
调用中被调用,我无法定义我的新属性。

换句话说,有没有办法制作一种

__post_init__
方法(对数据类模块的引用),只有在所有初始化(类中的一个和继承类中的一个)被调用后才会被调用

python python-3.x decorator python-decorators metaclass
2个回答
3
投票

确实-这很棘手。

在基类的

__init__
上有一个装饰器,它会在
__init__
之后冻结实例,正如你提到的有状态问题 - 可以添加其他状态变量,或计算
__init__
深度,并使用一个元类(或
__init_subclass__
)来装饰子类中的所有
__init__
方法,这样它只会在退出最外面的
__init__
.

时进行冻结

但是有一种使用元类的更简单的方法:元类的

__call__
方法是调用类
__new__
然后在创建新实例时调用
__init__
。因此,只需将代码用于在自定义元类上调用
__post_init__
__call__
(或直接从中冻结实例)

class PostInitMeta(type):
    def __call__(cls, *args, **kw):
        instance = super().__call__(*args, **kw)  # < runs __new__ and __init__
        instance.__post_init__()  
        return instance
    
class Freezing(metaclass=PostInitMeta):
    _frozen = False
    def __post_init__(self):
        self._frozen = True
        
    def __setattr__(self, name, value):
        if self._frozen:
            raise AttributeError() # or do nothing, and return as you prefer
        super().__setattr__(name, value)
        
class A(Freezing):
    def __init__(self, a):
        self.a = a
        
class B(A):
    def __init__(self, a, b):
        super().__init__(a)
        self.b = b


并在交互模式下测试:

In [18]: b = B(2, 3)

In [19]: b.a
Out[19]: 2

In [20]: b.b
Out[20]: 3

In [21]: b.b = 5
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In [21], line 1
----> 1 b.b = 5



0
投票

使用

__slots__
有一个更简单的解决方案。

注意:有一些用例可能禁止使用

__slots__
,检查Using Slots

class Foo:
    __slots__ = ['a', 'b']
    def __init__(self, a, b):
        self.a = a
        self.b = b

foo = Foo(1,2)
foo.c = 3 # => attribute error - 'Foo' object has no attribute 'c'

class Bar(Foo):
    __slots__ = ['c']
    def __init__(self, a, b, c):
        super().__init__(a, b)
        self.c = 3

bar = Bar(1,2,3)
bar.d = 4 # => attribute error - 'Bar' object has no attribute 'd'

© www.soinside.com 2019 - 2024. All rights reserved.