vpa*_*pas 8 python with-statement
我想确保该类仅在"with"语句中实例化.
即这个是好的:
with X() as x:
...
Run Code Online (Sandbox Code Playgroud)
这不是:
x = X()
Run Code Online (Sandbox Code Playgroud)
我该如何确保这样的功能?
the*_*eye 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"
Run Code Online (Sandbox Code Playgroud)
当你使用它时with,
with MyContextManager() as y:
y.do_something()
Run Code Online (Sandbox Code Playgroud)
你会得到
Entered
I don't know what I am doing
Exited
Run Code Online (Sandbox Code Playgroud)
但是,当您手动创建对象并调用时do_something,
x = MyContextManager()
x.do_something()
Run Code Online (Sandbox Code Playgroud)
你会得到
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`
Run Code Online (Sandbox Code Playgroud)
注意:这不是一个可靠的解决方案.有人可以__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"
Run Code Online (Sandbox Code Playgroud)
没有万无一失的方法来确保在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
Run Code Online (Sandbox Code Playgroud)
当然这仍然是可重用的,但每个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'
Run Code Online (Sandbox Code Playgroud)
您可以更进一步,并XFactory创建一个X带有特殊关键字参数的实际实例__new__,但我认为它太黑魔法才有用.
所有的答案至今没有提供什么样的(我认为)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`
Run Code Online (Sandbox Code Playgroud)
这就是我想出的,它可能不是很健壮,但我认为它最接近 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
Run Code Online (Sandbox Code Playgroud)
只要with与X()使用上下文管理器时在同一行中,这将提供与我上面显示的完全相同的输出。
| 归档时间: |
|
| 查看次数: |
2468 次 |
| 最近记录: |