Python: função padrão e gerenciador de contexto?

10

Em python, existem muitas funções que funcionam como funções padrão e gerenciadores de contexto. Por exemplo, open() pode ser chamado como:

my_file=open(filename,'w')

ou

with open(filename,'w') as my_file:

Ambos fornecem um objeto my_file que pode ser usado para fazer o que você precisa. Em geral, o posterior é preferível, mas há momentos em que se pode querer fazer o primeiro também.

Eu consegui descobrir como escrever um gerenciador de contexto, criando uma classe com funções __enter__ e __exit__ ou usando o decorador @contextlib.contextmanager em uma função e yield em vez de% código%. No entanto, quando faço isso, não consigo mais usar a função diretamente - usando o decorador, por exemplo, recebo um objeto return em vez do resultado desejado. É claro que, se eu fizesse isso como uma aula, acabaria de obter uma instância da classe do gerador, que eu presumo que seja essencialmente a mesma coisa.

Então, como posso projetar uma função (ou classe) que funcione como uma função, retornando um objeto ou um gerenciador de contexto, retornando um _GeneratorContextManager ou algo semelhante?

editar:

Por exemplo, digamos que eu tenha uma função como a seguinte (isso é ALTAMENTE simplificada):

def my_func(arg_1,arg_2):
    result=arg_1+arg_2
    return my_class(result)

Portanto, a função recebe vários argumentos, faz coisas com eles e usa o resultado desse material para inicializar uma classe, que retorna. O resultado final é que eu tenho uma instância de _GeneratorContextManager , assim como eu teria um objeto my_class se eu tivesse chamado file . Se eu quiser usar essa função como gerenciador de contexto, posso modificá-lo da seguinte forma:

@contextlib.contextmanager
def my_func(arg_1,arg_2):
    result=arg_1+arg_2 # This is roughly equivalent to the __enter__ function
    yield my_class(result)
    <do some other stuff here> # This is roughly equivalent to the __exit__function

O que funciona muito bem ao chamar como um gerenciador de contexto, mas não consigo mais uma instância de open ao chamar como uma função direta. Talvez eu esteja apenas fazendo algo errado?

Editar 2:

Observe que eu tenho controle total sobre my_class , incluindo a capacidade de adicionar funções a ele. A partir da resposta aceita abaixo, eu pude inferir que minha dificuldade resultou de um mal-entendido básico: Eu estava pensando que o que eu chamei ( my_class no exemplo acima) precisava ter as funções my_func e __exit__ . Isso não está correto. Na verdade, é apenas o que a função retorna ( __enter__ no exemplo acima) que precisa das funções para funcionar como um gerenciador de contexto.

    
por ibrewster 11.02.2016 в 22:59
fonte

2 respostas

1

A dificuldade que você vai encontrar é que para uma função ser usada tanto como um gerenciador de contexto ( with foo() as x ) quanto como uma função regular ( x = foo() ), o objeto retornado da função precisa ter ambos __enter__ e __exit__ methods… e não há uma ótima maneira - no caso geral - de adicionar métodos a um objeto existente.

Uma abordagem pode ser criar uma classe de wrapper que use __getattr__ para passar métodos e atributos ao objeto original:

class ContextWrapper(object):
    def __init__(self, obj):
        self.__obj = obj

    def __enter__(self):
        return self

    def __exit__(self, *exc):
        ... handle __exit__ ...

    def __getattr__(self, attr):
        return getattr(self.__obj, attr)

Mas isso causará problemas sutis porque não é exatamente igual ao objeto que foi retornado pela função original (por exemplo, isinstance testes falharão, alguns recursos internos como iter(obj) não funcionará como esperado, etc.).

Você também pode criar subclasses dinamicamente do objeto retornado, conforme demonstrado aqui: link :

class ObjectWrapper(BaseClass):
    def __init__(self, obj):
        self.__class__ = type(
            obj.__class__.__name__,
            (self.__class__, obj.__class__),
            {},
        )
        self.__dict__ = obj.__dict__

    def __enter__(self):
        return self

    def __exit__(self, *exc):
        ... handle __exit__ ...

Mas essa abordagem também tem problemas (como observado no post vinculado), e é um nível de mágica que eu pessoalmente não me sentiria confortável em apresentar sem uma justificativa forte .

Geralmente, prefiro adicionar métodos __enter__ e __exit__ explícitos ou usar um auxiliar como contextlib.closing :

with closing(my_func()) as my_obj:
    … do stuff …
    
por David Wolever 11.02.2016 / 23:27
fonte
1

Apenas para esclarecer: se você for capaz de alterar my_class , é claro que você adicionaria os descritores __enter__/__exit__ a essa classe.

Se você não conseguir alterar my_class (o que eu deduzi da sua pergunta), esta é a solução que eu estava me referindo:

class my_class(object):

    def __init__(self, result):
        print("this works", result)

class manage_me(object):

    def __init__(self, callback):
        self.callback = callback

    def __enter__(self):
        return self

    def __exit__(self, ex_typ, ex_val, traceback):
        return True

    def __call__(self, *args, **kwargs):
        return self.callback(*args, **kwargs)


def my_func(arg_1,arg_2):
    result=arg_1+arg_2
    return my_class(result)


my_func_object = manage_me(my_func) 

my_func_object(1, 1)
with my_func_object as mf:
    mf(1, 2)

Como decorador:

@manage_me
def my_decorated_func(arg_1, arg_2):
    result = arg_1 + arg_2
    return my_class(result)

my_decorated_func(1, 3)
with my_decorated_func as mf:
    mf(1, 4)
    
por apex-meme-lord 12.02.2016 / 06:30
fonte