I'm sorry to ask such a basic question, but what's the Pythonic way to include the same if block that can conditionally return in multiple functions? Here's my setup:
def a():
if bool:
return 'yeehaw'
return 'a'
def b():
if bool:
return 'yeehaw'
return 'b'
I'd like to factor the common conditional out of the two functions, but I'm not sure how to do so.
Use a decorator or closure
def my_yeehaw(result):
def yeehaw():
if some_bool:
return 'yeehaw'
return result
return yeehaw
a = my_yeehaw('a')
b = my_yeehaw('b')
You could use a lambda that takes in a. bool and a default value to return if the condition is false:
check = lambda condition, default: 'yeehaw' if condition else default
def a():
return check(condition, 'a')
def b():
return check(condition, 'b')
I am new to python but I think you can use a default argument to send a or b based on what is passed to the function.
def a(x='a'):
if condition: #where condition can be True or False
return 'yeehaw'
return x
(note: my naming wasn't the best, consider that same_bool function might be better called identical_if_block(...) to follow your example
And I am also assuming bool_ is a parameter, though it could work as a global. But not as bool which, like any function object, is always Truthy
>>> bool(bool)
True
)
Use a function, as long as it doesn't need to return falsies.
def same_bool(bool_):
" works for any result except a Falsy"
return "yeehaw" if bool_ else None
def a(bool_):
res = same_bool(bool_)
if res:
return res
return 'a'
def b(bool_, same_bool_func):
#you can pass in your boolean chunk function
res = same_bool_func(bool_)
if res:
return res
return 'b'
print ("a(True):", a(True))
print ("a(False):", a(False))
print ("b(True, same_bool):", b(True,same_bool))
print ("b(False, same_bool):", b(False,same_bool))
output:
a(True): yeehaw
a(False): a
b(True, same_bool): yeehaw
b(False, same_bool): b
If you do need falsies, use a special guard value
def same_bool(bool_):
" works for any result"
return False if bool_ else NotImplemented
def a(bool_):
res = same_bool(bool_)
if res is not NotImplemented:
return res
return 'a'
You could also feed in "a" and "b" since they are constant results, but I assume that's only in your simplified example.
def same_bool(bool_, val):
return "yeehaw" if bool_ else val
def a(bool_):
return same_bool(bool_, "a")
I ended up liking the decorator syntax, as the functions that include the duplicative conditional logic have a good deal else going on in them:
# `function` is the decorated function
# `args` & `kwargs` are the inputs to `function`
def yeehaw(function):
def decorated(*args, **kwargs):
if args[0] == 7: return 99 # boolean check
return function(*args, **kwargs)
return decorated
#yeehaw
def shark(x):
return str(x)
shark(7)
Related
I have a decorator that just repeats a function a set number of times.
The decorator works completely fine, just one small issue.
When used on functions that don't return anything. It will print "None" after executing the function.
I know that when you print a function that doesnt return anything it just prints "None".
def repeat(r):
"""Decorator that repeats function 'r' number of times"""
def repetition(f):
def wrapper(*args, **kwargs):
for x in range(r):
rv = f(*args, **kwargs)
print(rv)
return wrapper
return repetition
#repeat(2)
def output(msg):
print(msg)
#repeat(3)
def addition(*args, result=0):
for arg in args:
result += arg
return result
output("Hello")
addition(1, 2, 3, 4, 5)
Is it possible for me to have an if statement that checks if the function returns None and operates off of that? I would like to add a condition to the decorator so if a function returns nothing it wont print it, therefore stopping it from printing "None" every time it's used on a function that returns nothing.
For Example:
if function() == None:
do something
Just Check the rv result:
def repeat(r):
"""Decorator that repeats function 'r' number of times"""
def repetition(f):
def wrapper(*args, **kwargs):
for x in range(r):
rv = f(*args, **kwargs)
if rv is not None:
print(rv)
else:
## Do domething if the function returns None
return wrapper
return repetition
Instead of your
print(rv)
use
if rv is not None:
print(rv)
The None value is a singleton, so instead of comparing its value with common operators ==, != (which is possible, but not recommended), compare it with is or is not operators.
Note:
In Python 3.8+ you may use the walrus operator (:=) for assigning a value and compare immediately using it in just one expression:
if (rv := f(*args, **kwargs)) is not None:
print(rv)
I have functions, that return validator functions, simple example:
def check_len(n):
return lambda s: len(s) == n
Is it possible to add a decorator, that prints out a message, in case the check evaluates to false?
Something like this:
#log_false_but_how
def check_len(n):
return lambda s: len(s) == n
check_one = check_len(1)
print(check_one('a')) # returns True
print(check_one('abc')) # return False
Expected output:
True
validator evaluated to False
False
I've tried creating an annotation, but can only access the function creation with it.
One way would be to define the functions like this:
def log_false(fn):
def inner(*args):
res = fn(*args)
if not res:
print("validation failed for {}".format(fn.__name__))
return res
return inner
#log_false
def check_one(s):
return check_len(1)(s)
But this way we lose the dynamic creation of validation functions.
You're doing the validation in the wrong place. check_len is a function factory, so res is not a boolean - it's a function. Your #log_false decorator has to wrap a validator function around each lambda returned by check_len. Basically you need to write a decorator that decorates the returned functions.
def log_false(validator_factory):
# We'll create a wrapper for the validator_factory
# that applies a decorator to each function returned
# by the factory
def check_result(validator):
#functools.wraps(validator)
def wrapper(*args, **kwargs):
result = validator(*args, **kwargs)
if not result:
name = validator_factory.__name__
print('validation failed for {}'.format(name))
return result
return wrapper
#functools.wraps(validator_factory)
def wrapper(*args, **kwargs):
validator = validator_factory(*args, **kwargs)
return check_result(validator)
return wrapper
Result:
#log_false
def check_len(n):
return lambda s: len(s) == n
check_one = check_len(1)
print(check_one('a')) # prints nothing
print(check_one('abc')) # prints "validation failed for check_len"
So here's an extension to this question: https://stackoverflow.com/a/37568895/2290820
on how to optionally Enable or Disable Decorator on a Function.
On those lines, I came up with something like this to make decorator get invoked on a recursive call:
def deco(f):
def fattr(attr):
f.attr = attr
def closure(*args):
f(*args)
f.unwrap = f
f.closure = closure
return f
return fattr
#deco
def printa(x):
if x > 1:
print x
return printa(x-1)
else:
print x
return
printa({1:1})(5)
# do the same call w/o deocorator
def finta(x):
if x > 1:
print x
return finta(x-1)
else:
print x
return
finta(5) # this works
to experiment with decorators on a recursive function. Clearly, printa recursive version is not behaving the way it should be.
I could do
g = printa({1:1})
g.closure(5)
to turn on the decorator option or not use that option. Anyway, regardless of good or bad design, How can I make decorator get invoked on a recursive call?
In your deco you have an assignment f.attr = attr that "eats" your argument after first recursive call. Your should modify your recursive call this way:
def deco(f):
def fattr(attr):
f.attr = attr
def closure(*args):
f(*args)
f.unwrap = f
f.closure = closure
return f
return fattr
#deco
def printa(x):
if x > 1:
print x
return printa(None)(x-1) # None will be assigned to f.attr
else:
print x
return
printa({1:1})(5)
5
4
3
2
1
I am writing a function which gets users from database and returns a list of user objects.
Function signature is as given below:
def select_users(self,userid,firstname,lastname,emailid,tenants,groups):
result = self.authservice.select_users(userid,firstname,lastname,emailid,tenants,groups)
In this function, I call select_users method of authservice object which will return a list of custom user objects. But if any of input parameters has '' value then it must be converted to None because self.authservice.select_users cannot handle empty strings. I can check each element value and convert it to None if it is empty, but I want it to be generic and reusable. If I could write a different function which can give me updated list of input parameters it would be very helpful. Please let me know how do I do that?
I would write a generic decorator, like this
def convert_empty_to_none(func):
def inner_function(*args, **kwargs):
args = (None if item == "" else item for item in args)
kwargs = {k:(None if v == "" else v) for k, v in kwargs.items()}
return func(*args, **kwargs)
return inner_function
#convert_empty_to_none
def test_function(a, b, c):
print a, b, c
test_function("", "", "")
Output
None None None
Evil way:
def select_users(self, *args):
new_args = [(None if arg == '' else arg) for arg in args]
result = self.authservice.select_users(*new_args)
Decorator solution is also pretty evil: changing function arguments to spare writing a couple of function calls doesn't seem such a great idea.
In the real life I would go with the explicit:
def never_blank(s):
return None if s == '' else s
def select_users(self, userid,firstname,lastname,emailid,tenants,groups):
result = self.authservice.select_users(userid,never_blank(firstname),never_blank(lastname),emailid,
never_blank(tenants),groups)
Tedious? Sure. Clean? Yep. Will bite you in the ass in the future? Nope.
Create a function and use it like a function type decorator
def sanitize(func):
def handler(*args, **kwargs):
args = (e if e != '' else None for e in args)
kwargs = {k:(v if v != '' else None) for k, v in kwargs.items()}
return func(*args, **kwargs)
return handler
#sanitize
def select_users(self,userid,firstname,lastname,emailid,tenants,groups):
result = self.authservice.select_users(userid,firstname,lastname,emailid,tenants,groups)
Benifits
You do not need to modify the signature
The caller would still have clear idea, what parameters the function expects
Generic and can be used for any function call
Is a decorator, so can easily be used in a non-intrusive fashion
You could use a decorator to create a generic wrapper that will replace every empty string with None.
def none_for_empty_string(func):
def wrapper(*args, **kwargs):
args = tuple(arg if arg != '' else None for arg in args)
kwargs = {k : v if v != '' else None for k, v in kwargs.iteritems()}
return func(*args, **kwargs)
return wrapper
#none_for_empty_string
def select_users(self,userid,firstname,lastname,emailid,tenants,groups):
...
Consider the following:
def funcA():
some process = dynamicVar
if dynamicVar == 1:
return dynamicVar
else:
print "no dynamicVar"
def main():
outcome = funcA()
If the 'some process' part results in a 1, the var dynamicVar is passed back as outcome to the main func. If dynamicVar is anything but 1, the routine fails as no arguments are being return.
I could wrap the outcome as a list:
def funcA():
outcomeList = []
some process = dynamicVar
if dynamicVar == 1:
outcomeList.append(dynamicVar)
return outcomeList
else:
print "no dynamicVar"
return outcomeList
def main():
outcome = funcA()
if outcome != []:
do something using dynamicVar
else:
do something else!
or maybe as a dictionary item. Each of the 2 solutions I can think of involve another set of processing in the main / requesting func.
Is this the 'correct' way to handle this eventuality? or is there a better way?
What is the proper way of dealing with this. I was particularly thinking about trying to catch try: / except: errors, so in that example the uses are reversed, so something along the lines of:
def funcA():
some process = dynamicVar
if dynamicVar == 1:
return
else:
outcome = "no dynamicVar"
return outcome
def main():
try:
funcA()
except:
outcome = funcA.dynamicVar
In Python, all function that do not return a value will implicitly return None. So you can just check if outcome is not None in main().
I believe when you write a function, it's return value should be clear and expected. You should return what you say you will return. That being said, you can use None as a meaningful return value to indicate that the operation failed or produced no results:
def doSomething():
"""
doSomething will return a string value
If there is no value available, None will be returned
"""
if check_something():
return "a string"
# this is being explicit. If you did not do this,
# None would still be returned. But it is nice
# to be verbose so it reads properly with intent.
return None
Or you can make sure to always return a default of the same type:
def doSomething():
"""
doSomething will return a string value
If there is no value available, and empty string
will be returned
"""
if check_something():
return "a string"
return ""
This handles the case with a bunch of complex conditional tests that eventually just fall through:
def doSomething():
if foo:
if bar:
if biz:
return "value"
return ""