Is it possible, and if so, advisable, and if so, what would be the recommended method for decorating a function that yields a value?
For example, consider this imaginary example I made up
def foobar_creator(func):
def wrapped(**kwargs):
res = func(**kwargs)
flag = True
for k,v in kwargs:
if res % v == 0:
flag = False
yield k
if flag:
yield res
return wrapped
#foobar_creator
def generic_yielder(**kwargs):
for i in xrange(sys.maxint):
yield i
for i in generic_yielder(foo=3, bar=5, foobar=15):
print i
A generator function, when called, returns an iterator object. If your decorator is itself a generator too, you'll need to loop over the wrapped result:
def foobar_creator(func):
def wrapped(**kwargs):
gen = func(**kwargs)
flag = True
for k, v in kwargs:
if res % v == 0:
flag = False
yield k
if flag:
for res in gen:
yield res
return wrapped
If you are using Python 3.3 or up, you can use delegation to hand control the wrapped generator, by using yield from:
if flag:
yield from gen
Instead of yielding every potential return value, why not yield only those that actually exist? Something like
def wrap(f, arg):
for x in f(arg):
yield x
(actual decorator syntax, handling of positional and keyword arguments, etc. is omitted for clarity.)
For the case in comment42684128, the solution is as simple as:
(v for v in f(<args>) if filter_condition(v))
As a decorator:
def yfilter(filter_condition):
def yfilter_p(f):
def wrapped(*args,**kwargs):
return (v for v in f(*args,**kwargs) if filter_condition(v))
return wrapped
return yfilter_p
The existing answers don't handle generators that yield and then return a value. For that, you need to return (yield from f()):
def dec(f):
def g():
return (yield from f())
return g
#dec
def f():
yield 'val'
return 'done'
Related
I'm learning decorators in Python, and I came across some trouble with a decorator I'm using:
#curry
def modulo(mod,f):
if mod:
#fun.wraps(f)
def wrapper(*args, **kw):
result = f(*args, **kw)
return tuple(r % mod for r in result)
else:
return f
return wrapper
The curry decorator is a simple currying, so I can call mod as an argument in the following:
def _fib(p=1,q=-1,mod=None):
''' Fibonacci sequence '''
#modulo(mod)
def next_fib(pair):
x,y = pair
return y, p*x - q*y
yield from ( y for x,y in iterate(next_fib,(1,0)) )
which works and looks nice and clean. However, say I wanted another [closely related] generator for Lucas sequences:
def _luc(p=1,q=-1,mod=None):
''' Lucas sequence '''
#modulo(mod)
def next_luc(pair):
x,y = pair
return y, p*x - q*y
yield from ( y for x,y in iterate(next_luc,(p-2,2)) )
If I call them together, I get some sort of collision:
>>> F = _fib()
>>> print(take(10,F))
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
>>> L = _luc()
>>> print(take(10,L))
" ... TypeError: cannot unpack non-iterable function object"
Called individually they work as expected, with the correct modular terms returned.
My question twofold:
is this a namespace collision where they are both referring to modulo()?
How would you go about doing something like this?
helper functions:
import itertools as it
import functools as fun
def curry(f):
argc = f.__code__.co_argcount
f_args = []
f_kwargs = {}
#fun.wraps(f)
def wrapper(*args, **kwargs):
nonlocal f_args, f_kwargs
f_args += args
f_kwargs.update(kwargs)
if len(f_args)+len(f_kwargs) == argc:
return f(*f_args, **f_kwargs)
else:
return wrapper
return wrapper
def iterate(f,x):
''' x, f(x), f(f(x)), f(f(f(x))), ... '''
return it.accumulate(it.repeat(x), lambda fx, _: f(fx))
def take(n,iterable):
return [x for x in it.islice(iterable,n)]
I found that extending the original modulo wrapper does the trick!
With thanks to Thomas_Breydo for suggesting to change up the curried function
def modulo(mod):
def wrapper(f):
#fun.wraps(f)
def deco(*args,**kwargs):
if mod:
return tuple(n%mod for n in f(*args,**kwargs) )
else:
return f(*args,**kwargs)
return deco
return wrapper
which resolves my issue.
I have to write a decorator def that takes a validator def as argument. If the validator returned true it should decorate main to execute some code and if it returned false it should print an error.
I have tried to write two def in decorator with an if statement to return two different defs but it is not working.
the functionality and the logic MUST be exactly like i said because of online judging (validation must be done outside of decorator)
Here's an example:
#define decorator...
def validator(x):
return x>=0
#decorator(validator)
def f(x):
return x**0.5
print(f(4)) #should print 2
print(f(-4)) #should print error
Here is what you can do
#define decorator...
def validator(x):
return x>=0
def deco(validator):
def decorator(func):
def wrapper_decorator(*args, **kwargs):
if validator(*args, **kwargs):
return func(*args, **kwargs)
else:
print("error")
return
return wrapper_decorator
return decorator
#deco(validator)
def f(x):
return x**0.5
print(f(4)) #should print 2
print(f(-4)) #should print error
The answers everyone has answered are basically correct. However for your case, you require an additional function that acts as a validator. Hence you can add in another outer def to take in the function of the validator and check if it returns True/False.
Decorators can be written as example
def hello_decorator(func):
def inner1(*args, **kwargs):
print("before Execution")
# getting the returned value
returned_value = func(*args, **kwargs)
print("after Execution")
# returning the value to the original frame
return returned_value
return inner1
# adding decorator to the function
#hello_decorator
def sum_two_numbers(a, b):
print("Inside the function")
return a + b
a, b = 1, 2
# getting the value through return of the function
print("Sum =", sum_two_numbers(a, b))
You can rewrite this code as
def limit_decorator(func):
def internal(arg):
if (arg >= 0):
return func(arg)
else:
raise Exception("false input")
return internal
#limit_decorator
def square_root(a):
return a * 0.5
a = -5
print("Sum =", square_root(a))
I would suggest to do the validation of x, using one layer on nested functions (basically merge the validator function into the decorator)
def deco(f):
def wrapper(x):
if x<=0:
return False
else:
return f(x)
return wrapper
#deco
def f(x):
return x**0.
f(1) #returns false
f(4) #returns 2.0
Try this:
def decorator(validator):
def subdecorator(function):
def actual_function(arg):
if not validator(arg):
raise ValueError(f"Bad data: {arg}")
return function(arg)
return actual_function
return subdecorator
i want to design a decorator to check any function annotation type and if it has similar type then run function.
can python do such this thing??
if python can, please help me!!
def foo (a:int):
if foo.__annotations__.get('a') == type(a):
pass
def boo (b:str):
if boo.__annotations__.get('b') == type(b):
pass
and another thing is annotations is a dict type, i want such this :
from type import FunctionType
def check (f:FunctionType):
result = True
k = [k for k in f.__annotations__.keys()]
v = [v for v in f.__annotations__.values()]
for i in range(len(v)):
if v[i] != type(k[i]): #but we don't access to the type of k[i] out of th f function
result = False
return result
If I understand the idea correctly, perhaps this code will help you:
from types import FunctionType
def check(f: FunctionType):
def wrapper(*args, **kwargs):
result = True
# check args
keys = tuple(f.__annotations__.keys())
for ar in enumerate(args):
if not isinstance(ar[1], f.__annotations__.get(keys[ar[0]])):
result = False
break
if result:
# check kwargs
for k, v in kwargs.items():
if not isinstance(v, f.__annotations__.get(k)):
result = False
break
if result:
f(*args, **kwargs)
return wrapper
Example usage:
#check
def foo(a: str, b: int = None):
print(f"a = {a}")
print(f"b = {b}")
# Example 1: a=324, b=32:
foo(234, b=32)
# result: function not executed
# Example 2: a="abc", b="zzz":
foo("abc", b="zzz")
# result: function not executed
# Example 3: a="qwe", b= not set:
foo("qwe")
# result: function executed, output:
# a = qwe
# b = None
# Example 4: a="abc", b=99:
foo("abc", 99)
# result: function executed, output:
# a = abc
# b = 99
The decorator checks the argument types, and if everything is in order, it executes the function, otherwise it does nothing.
something like this.
import inspect
import functools
def check(func):
msg = "Expected type {etype} for {para} got {got}"
para = inspect.signature(func).parameters
keys = tuple(para.keys())
#functools.wraps(func)
def wrapper(*args,**kwargs):
def do_check(anno,value,para):
if not isinstance(value, anno):
raise TypeError(msg.format(etype=anno,
para=para,
got=type(value)))
for i,value in enumerate(args):
anno = para[keys[i]].annotation
do_check(anno, value, keys[i])
for arg_name,value in kwargs.items():
anno = para[arg_name].annotation
do_check(anno, value, arg_name)
ret = func(*args,**kwargs)
if "return" in func.__annotations__:
anno = func.__annotations__["return"]
do_check(anno, ret, "return")
return ret
return wrapper
#check
def test(a:int,b:str) -> str:
return 'aaa'
#check
def test2(a:int,b:str) -> str:
return 123
import functools
def annotations_checker(func):
#functools.wraps(func)
def wrapper(*args,**kwargs):
TrueFalseChecker=True
keys=tuple(func.__annotations__.keys())
for key_num in range(len(keys)):
if func.__annotations__[keys[key_num]]!=type(args[key_num]):
break
else:
value=func(*args,*kwargs)
return value
return
return wrapper
and you can use #annotations_checker decorator for any python method / python function to check type annotations
like:
#annotations_checker
def test(str_example:str,int_example:int,second_str_example:str):
print("if you can see this, the args and annonations type are same!")
test(1,"2",3) #the function will not work
test("1",1,"testtest") #function will work
Consider the following (non-working) example code:
class MyGenerator:
def test_gen(self):
for i in range(1,5):
if i % 2:
self.foo(i)
else:
self.bar(i)
def foo(self, i):
yield i
def bar(self, i):
yield i**2
g = MyGenerator()
for i in g.test_gen():
print i
This will not work, because test_gen has no yield and is no longer a generator function. In this small example I could just return the values from foo and bar and put the yield into test_gen, however I have a case where that's not possible. How can I turn test_gen into a generator function again?
You need to loop over the results of the delegated generators and yield those:
def test_gen(self):
for i in range(1,5):
if i % 2:
for res in self.foo(i):
yield res
else:
for res in self.bar(i):
yield res
If you are using Python 3.3 or up, you'd use the yield from expression to do proper generator delegation:
def test_gen(self):
for i in range(1,5):
if i % 2:
yield from self.foo(i)
else:
yield from self.bar(i)
Both re-introduce yield into the function, once again making it a generator function.
why not just:
class MyGenerator:
def test_gen(self):
for i in range(1,5):
if i % 2:
yield next(self.foo(i))
else:
yield next(self.bar(i))
def foo(self, i):
yield i
def bar(self, i):
yield i**2
What would be the nice way to return something from an iterator one last time when it's exhausted. I'm using a flag, but this is rather ugly:
class Example():
def __iter__(self):
self.lst = [1,2,3]
self.stop = False # <-- ugly
return self
def next(self):
if self.stop: # <-- ugly
raise StopIteration
if len(self.lst) == 0:
self.stop = True
return "one last time"
return self.lst.pop()
Background: I'm fetching an unknown amount of strings from an external source and send them further down to the caller. When the process is over, I want to emit a string "x records processed". I have no control over calling code, so this must be done inside my iterator.
You could just yield from __iter__ which would turn it into a generator function (alternately you could just write a generator function as suggested by Dan). Just as a warning, this might be misleading to people that abuse the next method.
class Example():
def __iter__(self):
lst = [1,2,3]
for i in reversed(lst):
yield i
yield "one last time"
Maybe you can use a generator function instead:
def example():
lst = [1, 2, 3]
while lst:
yield lst.pop()
yield 'one last time'
Don't return one extra thing. That's a bad idea because it doesn't extend well. What if you want to do sum as well as count? Or hash as well as count? An iterator is a stateful object. Make use of that.
class Example( collections.Iterator ):
def __iter__(self):
self.lst = [1,2,3]
self.count = 0
return self
def next(self):
if self.lst:
self.count += 1
return self.lst.pop()
raise StopIteration()
Use it like this.
my_iter= iter(Example())
for item in my_iterprin:
print( item )
print( my_iter.count )
You could do something like this:
class Example():
def __iter__(self):
self.lst = [1, 2, 3]
return self
def next(self):
try:
return self.lst.pop()
except IndexError:
print "done iterating"
raise StopIteration
>>> for i in Example():
... print i
...
3
2
1
done iterating
In your actual code you will probably need to change the exception type that you are catching, but this format should still be applicable.