Python如何通过上下文管理器强制对象实例化?

8
我希望通过类上下文管理器来强制实例化对象。因此,直接实例化对象将变得不可能。
我已经实现了这个解决方案,但是从技术上讲,用户仍然可以实例化对象。
class HessioFile:
    """
    Represents a pyhessio file instance
    """
    def __init__(self, filename=None, from_context_manager=False):
        if not from_context_manager:
            raise HessioError('HessioFile can be only use with context manager')

同时还有上下文管理器:

@contextmanager
def open(filename):
    """
    ...
    """
    hessfile = HessioFile(filename, from_context_manager=True)

有更好的解决方案吗?

防止类被实例化的原因是什么? - Tryph
避免用户在__init__中打开文件后不关闭文件。 - Jean Jacquemier
4个回答

10
如果您认为您的客户将遵循基本的Python编码原则,那么您可以保证只有在上下文中才会调用类中的方法。
您的客户不应该显式地调用__enter__,因此如果已经调用了__enter__,则知道您的客户使用了with语句,因此处于上下文环境内(将调用__exit__)。
您只需要一个布尔变量来帮助您记住是否处于上下文内部或外部。
class Obj:
    def __init__(self):
        self._inside_context = False

    def __enter__(self):
        self._inside_context = True
        print("Entering context.")
        return self

    def __exit__(self, *exc):
        print("Exiting context.")
        self._inside_context = False

    def some_stuff(self, name):
        if not self._inside_context:
            raise Exception("This method should be called from inside context.")
        print("Doing some stuff with", name)

    def some_other_stuff(self, name):
        if not self._inside_context:
            raise Exception("This method should be called from inside context.")
        print("Doing some other stuff with", name)


with Obj() as inst_a:
    inst_a.some_stuff("A")
    inst_a.some_other_stuff("A")

inst_b = Obj()
with inst_b:
    inst_b.some_stuff("B")
    inst_b.some_other_stuff("B")

inst_c = Obj()
try:
    inst_c.some_stuff("c")
except Exception:
    print("Instance C couldn't do stuff.")
try:
    inst_c.some_other_stuff("c")
except Exception:
    print("Instance C couldn't do some other stuff.")

这将会输出:
Entering context.
Doing some stuff with A
Doing some other stuff with A
Exiting context.
Entering context.
Doing some stuff with B
Doing some other stuff with B
Exiting context.
Instance C couldn't do stuff.
Instance C couldn't do some other stuff.

由于您可能有许多希望“保护”不被外部上下文调用的方法,因此您可以编写一个装饰器来避免重复代码以测试您的布尔值:

def raise_if_outside_context(method):
    def decorator(self, *args, **kwargs):
        if not self._inside_context:
            raise Exception("This method should be called from inside context.")
        return method(self, *args, **kwargs)
    return decorator

然后更改您的方法为:
@raise_if_outside_context
def some_other_stuff(self, name):
    print("Doing some other stuff with", name)

3

我建议采取以下方法:

class MainClass:
    def __init__(self, *args, **kwargs):
        self._class = _MainClass(*args, **kwargs)

    def __enter__(self):
        print('entering...')
        return self._class

    def __exit__(self, exc_type, exc_val, exc_tb):
        # Teardown code
        print('running exit code...')
        pass


# This class should not be instantiated directly!!
class _MainClass:
    def __init__(self, attribute1, attribute2):
        self.attribute1 = attribute1
        self.attribute2 = attribute2
        ...

    def method(self):
        # execute code
        if self.attribute1 == "error":
            raise Exception
        print(self.attribute1)
        print(self.attribute2)


with MainClass('attribute1', 'attribute2') as main_class:
    main_class.method()
print('---')
with MainClass('error', 'attribute2') as main_class:
    main_class.method()

这将输出:
entering...
attribute1
attribute2
running exit code...
---
entering...
running exit code...
Traceback (most recent call last):
  File "scratch_6.py", line 34, in <module>
    main_class.method()
  File "scratch_6.py", line 25, in method
    raise Exception
Exception

2

据我所知,没有。通常情况下,如果python中存在的话,你可以找到一种调用它的方法。上下文管理器本质上是一种资源管理方案...如果除了管理器外没有别的用例,也许上下文管理可以集成到类的方法中?建议查看标准库中的atexit模块。它允许您注册清理函数,就像上下文管理器处理清理一样,但您可以将其捆绑到类中,以便每个实例化都有一个已注册的清理函数。可能会有帮助。

值得注意的是,无论付出多少努力,都无法防止人们在使用您的代码时犯傻瓜错误。您最好的选择通常是尽可能地使人们能够以明智的方式使用您的代码。


1
你可以想出一些笨拙的方法来尝试强制执行它(比如检查调用堆栈以禁止直接调用对象,设置布尔属性在__enter__中,然后在允许实例上的其他操作之前进行检查),但这最终会变得混乱不堪,难以理解和向他人解释。
无论如何,你也应该确信,如果需要的话,人们总会找到绕过它的方法。Python并没有真正限制你的手脚,如果你想做一些愚蠢的事情,它会让你做;成年人,对吧?
如果你需要强制执行,最好将其作为文档提示提供。这样,如果用户选择直接实例化并触发不需要的行为,那么这是他们没有遵循你的代码指南的错。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接