我想确保只在“with”语句中实例化该类。
即。这个没关系:
with X() as x:
...
而这不是:
x = X()
我如何确保此类功能?
答案 0 :(得分:12)
据我所知,没有直接的方法。但是,在调用对象中的实际方法之前,您可以使用布尔标志来检查是否调用了__enter__
。
class MyContextManager(object):
def __init__(self):
self.__is_context_manager = False
def __enter__(self):
print "Entered"
self.__is_context_manager = True
return self
def __exit__(self, exc_type, exc_value, traceback):
print "Exited"
def do_something(self):
if not self.__is_context_manager:
raise Exception("MyContextManager should be used only with `with`")
print "I don't know what I am doing"
与with
一起使用时,
with MyContextManager() as y:
y.do_something()
你会得到
Entered
I don't know what I am doing
Exited
但是,当您手动创建对象并调用do_something
时,
x = MyContextManager()
x.do_something()
你会得到
Traceback (most recent call last):
File "/home/thefourtheye/Desktop/Test.py", line 22, in <module>
x.do_something()
File "/home/thefourtheye/Desktop/Test.py", line 16, in do_something
raise Exception("MyContextManager should be used only with `with`")
Exception: MyContextManager should be used only with `with`
注意:这不是一个可靠的解决方案。在调用任何其他方法之前,有人可以单独直接调用__enter__
方法,在这种情况下可能永远不会调用__exit__
方法。
如果您不想在每个功能中重复检查,您可以将它设为装饰器,就像这样
class MyContextManager(object):
def __init__(self):
self.__is_context_manager = False
def __enter__(self):
print "Entered"
self.__is_context_manager = True
return self
def __exit__(self, exc_type, exc_value, traceback):
print "Exited"
def ensure_context_manager(func):
def inner_function(self, *args, **kwargs):
if not self.__is_context_manager:
raise Exception("This object should be used only with `with`")
return func(self, *args, **kwargs)
return inner_function
@ensure_context_manager
def do_something(self):
print "I don't know what I am doing"
答案 1 :(得分:8)
确保在with
子句中构造实例没有万无一失的方法,但您可以在__enter__
方法中创建一个实例并返回该实例而不是self
;这是将分配给x
的值。因此,您可以将X
视为在__enter__
方法中创建实际实例的工厂,例如:
class ActualInstanceClass(object):
def __init__(self, x):
self.x = x
def destroy(self):
print("destroyed")
class X(object):
instance = None
def __enter__(self):
# additionally one can here ensure that the
# __enter__ is not re-entered,
# if self.instance is not None:
# raise Exception("Cannot reenter context manager")
self.instance = ActualInstanceClass(self)
def __exit__(self, exc_type, exc_value, traceback):
self.instance.destroy()
return None
with X() as x:
# x is now an instance of the ActualInstanceClass
当然这仍然是可重用的,但每个with
语句都会创建一个新实例。
当然,您可以手动调用__enter__
,或者获取对ActualInstanceClass
的引用,但更多的是滥用而不是使用。
对于更有气味的方法,X()
在调用时确实会创建XFactory
实例,而不是X
实例;反过来,当用作上下文管理器时,创建ActualX
实例,它是X
的子类,因此isinstance(x, X)
将返回true。
class XFactory(object):
managed = None
def __enter__(self):
if self.managed:
raise Exception("Factory reuse not allowed")
self.managed = ActualX()
return self.managed
def __exit__(self, *exc_info):
self.managed.destroy()
return
class X(object):
def __new__(cls):
if cls == X:
return XFactory()
return super(X, cls).__new__(cls)
def do_foo(self):
print("foo")
def destroy(self):
print("destroyed")
class ActualX(X):
pass
with X() as x:
print(isinstance(x, X)) # yes it is an X instance
x.do_foo() # it can do foo
# x is destroyed
newx = X()
newx.do_foo() # but this can't,
# AttributeError: 'XFactory' object has no attribute 'do_foo'
您可以更进一步,让XFactory
使用X
的特殊关键字参数创建一个实际的__new__
实例,但我认为它太黑魔法无法使用。< / p>
答案 2 :(得分:3)
不幸的是,你不能很干净。
上下文管理器需要使用__enter__
和__exit__
方法,因此您可以使用此方法在类上分配成员变量以检入代码。
class Door(object):
def __init__(self, state='closed'):
self.state = state
self.called_with_open = False
# When being called as a non-context manger object,
# __enter__ and __exit__ are not called.
def __enter__(self):
self.called_with_open = True
self.state = 'opened'
def __exit__(self, type, value, traceback):
self.state = 'closed'
def was_context(self):
return self.called_with_open
if __name__ == '__main__':
d = Door()
if d.was_context():
print("We were born as a contextlib object.")
with Door() as d:
print('Knock knock.')
有状态对象方法有一个很好的附加好处,即能够判断以后调用__exit__
方法,或者在以后的调用中干净地处理方法要求:
def walk_through(self):
if self.state == 'closed':
self.__enter__
walk()
答案 3 :(得分:2)
到目前为止,所有答案都没有提供(我认为)OP希望直接 (我认为)OP想要这样的事情:
>>> with X() as x:
... # ok
>>> x = X() # ERROR
Traceback (most recent call last):
File "run.py", line 18, in <module>
x = X()
File "run.py", line 9, in __init__
raise Exception("Should only be used with `with`")
Exception: Should only be used with `with`
这是我提出的,它可能不是很强大,但我认为它最接近OP的意图。
import inspect
import linecache
class X():
def __init__(self):
if not linecache.getline(__file__,
inspect.getlineno(inspect.currentframe().f_back)
).startswith("with "):
raise Exception("Should only be used with `with`")
def __enter__(self):
return self
def __exit__(self, *exc_info):
pass
只要with
在使用上下文管理器时与X()
位于同一行,这将提供与上面显示的完全相同的输出。
答案 4 :(得分:1)
这是一个装饰器,可以自动确保方法不在上下文管理器之外调用:
from functools import wraps
BLACKLIST = dir(object) + ['__enter__']
def context_manager_only(cls):
original_init = cls.__init__
def init(self, *args, **kwargs):
original_init(self, *args, **kwargs)
self._entered = False
cls.__init__ = init
original_enter = cls.__enter__
def enter(self):
self._entered = True
return original_enter(self)
cls.__enter__ = enter
attrs = {name: getattr(cls, name) for name in dir(cls) if name not in BLACKLIST}
methods = {name: method for name, method in attrs.items() if callable(method)}
for name, method in methods.items():
def make_wrapper(method=method):
@wraps(method)
def wrapper_method(self, *args, **kwargs):
if not self._entered:
raise Exception("Didn't get call to __enter__")
return method(self, *args, **kwargs)
return wrapper_method
setattr(cls, name, make_wrapper())
return cls
这是一个使用它的例子:
@context_manager_only
class Foo(object):
def func1(self):
print "func1"
def func2(self):
print "func2"
def __enter__(self):
print "enter"
return self
def __exit__(self, *args):
print "exit"
try:
print "trying func1:"
Foo().func1()
except Exception as e:
print e
print "trying enter:"
with Foo() as foo:
print "trying func1:"
foo.func1()
print "trying func2:"
foo.func2()
print "trying exit:"
这是作为this duplicate question的答案而写的。
答案 5 :(得分:0)
OP的问题was believed to be an XY problem和current chosen answer确实(太过分)了。
我真的不知道OP最初的“ X问题”,但我认为动机并不是从字面上阻止“ x = X()
分配工作”。相反,这可能会迫使API用户始终使用x
作为上下文管理器,以便始终触发其__exit__(...)
,这就是将class X
设计为首先要成为上下文管理员。至少,这就是把我带到这个问答环节的原因。
class Holder(object):
def __init__(self, **kwargs):
self._data = allocate(...) # Say, it allocates 1 GB of memory, or a long-lived connection, etc.
def do_something(self):
do_something_with(self._data)
def tear_down(self):
unallocate(self._data)
def __enter__(self):
return self
def __exit__(self, *args):
self.tear_down()
# This is desirable
with Holder(...) as holder:
holder.do_something()
# This might not free the resource immediately, if at all
def foo():
holder = Holder(...)
holder.do_something()
也就是说,在学习了这里的所有对话之后,我最终还是保持了Holder
类的原样,好了,我只是为tear_down()
添加了一个文档字符串:
def tear_down(self):
"""You are expect to call this eventually; or you can simply use this class as a context manager."""
...
毕竟,我们都同意这里的成年人...