2015-02-14 1 views
7

Ich möchte sicherstellen, dass die Klasse nur innerhalb einer "mit" Anweisung instanziiert wird.Wie überprüft man, ob ein Objekt mit `with` Anweisung erstellt wird?

heißt dieses ist ok:

with X() as x: 
... 

und das ist nicht:

x = X() 

Wie ich eine solche Funktionalität sicherstellen kann?

+4

Und ** warum im Himmel Name ** würden Sie jemals das tun wollen? 'x = X()', 'mit x als Ergebnis_des_Eingebens:' (Erstellen des CM und dessen Verwendung in zwei separaten Zeilen) ist plötzlich kein gültiger Usecase mehr? Was wäre, wenn ich Kontextmanager in einem Mapping speichern möchte, um sie dynamisch auszuwählen? –

+1

Und was, wenn ich ein ['contextlib.ExitStack()'] (https://docs.python.org/3/library/contextlib.html#contextlib.ExitStack) verwenden möchte? mehrere Kontextmanager kombinieren? Es gibt verschiedene gute Anwendungsfälle, in denen ein Kontextmanager außerhalb einer 'with'-Anweisung erstellt wird, die Sie verhindern möchten. Versuchen Sie nicht, alle möglichen Programmierfehler auf Kosten des Programmierers zu beheben, die wissen, was sie tun. –

Antwort

2

Alle Antworten bisher nicht bieten, was (ich denke) OP will direkt.
(glaube ich) OP will etwas wie folgt aus:

>>> 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` 

Dies ist, was mit ich komme, ist es sehr robust nicht sein kann, aber ich denke, dass es am nächsten Absicht OPs.

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 

Dies wird genau die gleiche Ausgabe geben, wie ich oben so lange gezeigt, wie with mit X() in der gleichen Zeile ist, wenn Kontext-Manager.

+0

Ja, das ist was ich wollte. Vielen Dank. – vpas

+9

Und deshalb können wir keine schönen Dinge haben. –

+1

Funktioniert nur mit 'mit' das ist auf Modulebene –

9

Es gibt keinen direkten Weg, soweit ich weiß. Sie können jedoch ein boolesches Flag verwenden, um zu überprüfen, ob __enter__ aufgerufen wurde, bevor die tatsächlichen Methoden in den Objekten aufgerufen wurden.

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" 

Wenn Sie es mit with verwenden,

with MyContextManager() as y: 
    y.do_something() 

Sie

Entered 
I don't know what I am doing 
Exited 

bekommen Aber, wenn Sie manuell ein Objekt erstellen, und rufen Sie do_something,

x = MyContextManager() 
x.do_something() 

Sie

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` 

Hinweis bekommen: Dies ist keine feste Lösung. Jemand kann die Methode __enter__ direkt aufrufen, bevor andere Methoden aufgerufen werden, und die Methode __exit__ wird in diesem Fall möglicherweise nie aufgerufen.

Wenn Sie nicht, dass der Check in jeder Funktion wiederholen möchten, können Sie es ein Dekorateur machen, wie diese

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" 
+0

Sie könnten '__getattribute__' verwenden, um es wirklich für alles zu nageln –

7

Es gibt keine narrensichere Methode ist, um sicherzustellen, dass eine Instanz innerhalb einer with Klausel aufgebaut ist , aber Sie können eine Instanz in der __enter__-Methode erstellen und diese anstelle von self; Dies ist der Wert, der in x zugewiesen wird. So können Sie X als Fabrik betrachten, die in seiner __enter__ Methode, so etwas wie die eigentliche Instanz erstellt:

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 

Natürlich ist dies noch wiederverwendbar ist, aber jede with Aussage würde erstellen eine neue Instanz.

Natürlich kann man die __enter__ manuell aufrufen oder einen Verweis auf die ActualInstanceClass bekommen, aber es würde mehr von Missbrauch anstelle von Nutzen sein.


Für einen noch smellier Ansatz, der X() wenn eine XFactory Instanz ist tatsächlich schaffen genannt, statt einer X Instanz; und diese wiederum erstellt, wenn sie als Kontextmanager verwendet wird, die ActualX -Instanz, die die Unterklasse X ist, also isinstance(x, X) wird wahr zurückgeben.

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' 

Sie könnten dies weiter zu nehmen und haben XFactory eine tatsächliche X Instanz mit einem speziellen zu __new__ Schlüsselwort-Argumente erstellen, aber ich halte es auch schwarze Magie sein, nützlich zu sein.

+0

Clever! Diese Lösung riecht ein bisschen (da sie leise ein anderes Objekt zurückgibt), aber es ist sauberer als das, was ich mir vorstellen konnte. –

3

Leider können Sie nicht sehr sauber.

Kontextmanager benötigen die Methoden __enter__ und __exit__, damit Sie eine Membervariable für die Klasse zum Einchecken Ihres Codes zuweisen können.

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.') 

Der Stateful-Objekt Ansatz hat den netten zusätzlichen Vorteil, wenn die __exit__ Methode später sagen zu können, genannt wurde, oder sauber Methode Anforderungen in späteren Anrufen handhaben:

def walk_through(self): 
    if self.state == 'closed': 
     self.__enter__ 
    walk() 
1

Hier ist ein Dekorateur, der sicher Methoden automatisiert machen außerhalb eines Kontext-Manager nicht genannt:

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 

Und hier ist ein Beispiel dafür in Gebrauch:

@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:" 

Dieses geschrieben wurde als Antwort auf this duplicate question.

Verwandte Themen