salto print parámetro linea imprimir función funcion end python decorator

python - linea - parámetro end de la función print



¿Cómo crear un decorador de Python que se pueda usar con o sin parámetros? (10)

Me gustaría crear un decorador de Python que se pueda usar con parámetros:

@redirect_output("somewhere.log") def foo(): ....

o sin ellos (por ejemplo, para redirigir la salida a stderr por defecto):

@redirect_output def foo(): ....

¿Es eso posible?

Tenga en cuenta que no estoy buscando una solución diferente al problema de la salida de redireccionamiento, es solo un ejemplo de la sintaxis que me gustaría lograr.


¿Has probado argumentos de palabras clave con valores predeterminados? Algo como

def decorate_something(foo=bar, baz=quux): pass


Basándose en la respuesta de Vartec:

imports sys def redirect_output(func, output=None): if output is None: output = sys.stderr if isinstance(output, basestring): output = open(output, ''w'') # etc... # everything else...


De hecho, el caso de advertencia en la solución de @ bj0 se puede verificar fácilmente:

def meta_wrap(decor): @functools.wraps(decor) def new_decor(*args, **kwargs): if len(args) == 1 and len(kwargs) == 0 and callable(args[0]): # this is the double-decorated f. # Its first argument should not be a callable doubled_f = decor(args[0]) @functools.wraps(doubled_f) def checked_doubled_f(*f_args, **f_kwargs): if callable(f_args[0]): raise ValueError(''meta_wrap failure: '' ''first positional argument cannot be callable.'') return doubled_f(*f_args, **f_kwargs) return checked_doubled_f else: # decorator arguments return lambda real_f: decor(real_f, *args, **kwargs) return new_decor

Aquí hay algunos casos de prueba para esta versión a meta_wrap de meta_wrap de meta_wrap .

@meta_wrap def baddecor(f, caller=lambda x: -1*x): @functools.wraps(f) def _f(*args, **kwargs): return caller(f(args[0])) return _f @baddecor # used without arg: no problem def f_call1(x): return x + 1 assert f_call1(5) == -6 @baddecor(lambda x : 2*x) # bad case def f_call2(x): return x + 1 f_call2(5) # raises ValueError # explicit keyword: no problem @baddecor(caller=lambda x : 100*x) def f_call3(x): return x + 1 assert f_call3(5) == 600


En general, puede dar argumentos predeterminados en Python ...

def redirect_output(fn, output = stderr): # whatever

Sin embargo, no estoy seguro si eso funciona con decoradores. No sé de ninguna razón por la que no lo haría.


Necesita detectar ambos casos, por ejemplo, utilizando el tipo del primer argumento, y en consecuencia devolver el contenedor (cuando se usa sin parámetro) o un decorador (cuando se usa con argumentos).

from functools import wraps import inspect def redirect_output(fn_or_output): def decorator(fn): @wraps(fn) def wrapper(*args, **args): # Redirect output try: return fn(*args, **args) finally: # Restore output return wrapper if inspect.isfunction(fn_or_output): # Called with no parameter return decorator(fn_or_output) else: # Called with a parameter return decorator

Cuando se utiliza la @redirect_output("output.log") , se llama a redirect_output con un solo argumento "output.log" , y debe devolver a un decorador que acepta la función para que se decore como un argumento. Cuando se usa como @redirect_output , se llama directamente con la función para decorarla como un argumento.

O en otras palabras: la sintaxis @ debe ir seguida de una expresión cuyo resultado es una función que acepta una función para ser decorada como su único argumento, y devolver la función decorada. La expresión en sí misma puede ser una llamada a función, que es el caso con @redirect_output("output.log") . Enrevesado, pero cierto :-)


Sé que esta es una vieja pregunta, pero realmente no me gustan las técnicas propuestas, así que quería agregar otro método. Vi que django usa un método realmente limpio en su decorador de django.contrib.auth.decorators en django.contrib.auth.decorators . Como puede ver en los documentos del decorador , se puede usar solo como @login_required o con argumentos, @login_required(redirect_field_name=''my_redirect_field'') .

La forma en que lo hacen es bastante simple. kwarg un kwarg ( function=None ) antes de sus argumentos de decorador. Si el decorador se usa solo, la function será la función real que está decorando, mientras que si se llama con argumentos, la function será None .

Ejemplo:

from functools import wraps def custom_decorator(function=None, some_arg=None, some_other_arg=None): def actual_decorator(f): @wraps(f) def wrapper(*args, **kwargs): # Do stuff with args here... if some_arg: print(some_arg) if some_other_arg: print(some_other_arg) return f(*args, **kwargs) return wrapper if function: return actual_decorator(function) return actual_decorator

@custom_decorator def test1(): print(''test1'') >>> test1() test1

@custom_decorator(some_arg=''hello'') def test2(): print(''test2'') >>> test2() hello test2

@custom_decorator(some_arg=''hello'', some_other_arg=''world'') def test3(): print(''test3'') >>> test3() hello world test3

Encuentro este enfoque que django usa para ser más elegante y más fácil de entender que cualquiera de las otras técnicas propuestas aquí.


Sé que esta pregunta es antigua, pero algunos de los comentarios son nuevos, y si bien todas las soluciones viables son esencialmente las mismas, la mayoría de ellas no son muy claras ni fáciles de leer.

Al igual que la respuesta de Thobe, la única forma de manejar ambos casos es verificar ambos escenarios. La forma más fácil es simplemente verificar si hay un único argumento y es callabe (NOTA: se necesitarán verificaciones adicionales si su decorador solo toma 1 argumento y pasa a ser un objeto invocable):

def decorator(*args, **kwargs): if len(args) == 1 and len(kwargs) == 0 and callable(args[0]): # called as @decorator else: # called as @decorator(*args, **kwargs)

En el primer caso, usted hace lo que hace cualquier decorador normal, devuelve una versión modificada o envuelta de la función aprobada.

En el segundo caso, usted devuelve un decorador "nuevo" que de alguna manera usa la información transmitida con * args, ** kwargs.

Esto está bien y todo, pero tener que escribirlo para cada decorador que hagas puede ser bastante molesto y no tan limpio. En cambio, sería bueno poder modificar automágicamente nuestros decoradores sin tener que volver a escribirlos ... ¡pero para eso están los decoradores!

Con el siguiente decorador de decorador, podemos deocurar nuestros decoradores para que se puedan usar con o sin argumentos:

def doublewrap(f): '''''' a decorator decorator, allowing the decorator to be used as: @decorator(with, arguments, and=kwargs) or @decorator '''''' @wraps(f) def new_dec(*args, **kwargs): if len(args) == 1 and len(kwargs) == 0 and callable(args[0]): # actual decorated function return f(args[0]) else: # decorator arguments return lambda realf: f(realf, *args, **kwargs) return new_dec

Ahora, podemos decorar nuestros decoradores con @doublewrap, y trabajarán con y sin argumentos, con una advertencia:

Anoté arriba, pero debería repetir aquí, el cheque en este decorador hace una suposición sobre los argumentos que un decorador puede recibir (es decir, que no puede recibir un solo argumento invocable). Como ahora lo hacemos aplicable a cualquier generador, debe tenerse en cuenta o modificarse si se lo contradice.

Lo siguiente demuestra su uso:

def test_doublewrap(): from util import doublewrap from functools import wraps @doublewrap def mult(f, factor=2): ''''''multiply a function''s return value'''''' @wraps(f) def wrap(*args, **kwargs): return factor*f(*args,**kwargs) return wrap # try normal @mult def f(x, y): return x + y # try args @mult(3) def f2(x, y): return x*y # try kwargs @mult(factor=5) def f3(x, y): return x - y assert f(2,3) == 10 assert f2(2,5) == 30 assert f3(8,1) == 5*7


Se llama a un decorador de pitones de una manera fundamentalmente diferente dependiendo de si le das argumentos o no. La decoración es en realidad solo una expresión (sintácticamente restringida).

En tu primer ejemplo:

@redirect_output("somewhere.log") def foo(): ....

se llama a la función redirect_output con el argumento dado, que se espera que devuelva una función de decorador, que a su vez se llama con foo como argumento, que (¡finalmente!) se espera que devuelva la función decorada final.

El código equivalente se ve así:

def foo(): .... d = redirect_output("somewhere.log") foo = d(foo)

El código equivalente para su segundo ejemplo se ve así:

def foo(): .... d = redirect_output foo = d(foo)

Así que puedes hacer lo que quieras pero no de una manera totalmente integrada:

import types def redirect_output(arg): def decorator(file, f): def df(*args, **kwargs): print ''redirecting to '', file return f(*args, **kwargs) return df if type(arg) is types.FunctionType: return decorator(sys.stderr, arg) return lambda f: decorator(arg, f)

Esto debería estar bien a menos que desee utilizar una función como argumento para su decorador, en cuyo caso el decorador asumirá erróneamente que no tiene argumentos. También fallará si esta decoración se aplica a otra decoración que no devuelve un tipo de función.

Un método alternativo es simplemente requerir que siempre se llame a la función de decorador, incluso si no tiene argumentos. En este caso, su segundo ejemplo se vería así:

@redirect_output() def foo(): ....

El código de función del decorador se vería así:

def redirect_output(file = sys.stderr): def decorator(file, f): def df(*args, **kwargs): print ''redirecting to '', file return f(*args, **kwargs) return df return lambda f: decorator(file, f)


Usar argumentos de palabras clave con valores predeterminados (como sugiere kquinn) es una buena idea, pero requerirá que incluya el paréntesis:

@redirect_output() def foo(): ...

Si desea una versión que funcione sin el paréntesis en el decorador, tendrá que considerar ambos escenarios en su código de decorador.

Si usabas Python 3.0, podrías usar argumentos de palabra clave solo para esto:

def redirect_output(fn=None,*,destination=None): destination = sys.stderr if destination is None else destination def wrapper(*args, **kwargs): ... # your code here if fn is None: def decorator(fn): return functools.update_wrapper(wrapper, fn) return decorator else: return functools.update_wrapper(wrapper, fn)

En Python 2.x esto puede ser emulado con trucos varargs:

def redirected_output(*fn,**options): destination = options.pop(''destination'', sys.stderr) if options: raise TypeError("unsupported keyword arguments: %s" % ",".join(options.keys())) def wrapper(*args, **kwargs): ... # your code here if fn: return functools.update_wrapper(wrapper, fn[0]) else: def decorator(fn): return functools.update_wrapper(wrapper, fn) return decorator

Cualquiera de estas versiones te permitiría escribir código como este:

@redirected_output def foo(): ... @redirected_output(destination="somewhere.log") def bar(): ...


Varias respuestas aquí ya abordan su problema muy bien. Con respecto al estilo, sin embargo, prefiero resolver esta situación de decorador usando functools.partial , como se sugiere en el Python Cookbook 3 de David Beazley:

from functools import partial, wraps def decorator(func=None, foo=''spam''): if func is None: return partial(decorator, foo=foo) @wraps(func) def wrapper(*args, **kwargs): # do something with `func` and `foo`, if you''re so inclined pass return wrapper

Mientras que sí, puedes hacer

@decorator() def f(*args, **kwargs): pass

sin funky workarounds, me parece extraño, y me gusta tener la opción de simplemente decorar con @decorator .

En cuanto al objetivo secundario de la misión, se trata el redireccionamiento de la salida de una función en esta publicación de desbordamiento de pila .

Si desea profundizar, consulte el Capítulo 9 (Programación de metadatos) en Python Cookbook 3 , que está disponible gratuitamente para leerlo en línea .

Parte de ese material está en vivo (más ¡más!) En el impresionante video de YouTube de Beazley, Metaprogramación de Python 3 .

Feliz codificación :)