If the first function calls a second function, is it possible to have the second function return a value for the first function?
def check_type(x):
if type(x) not in [int, float]:
return None
def square(x):
check_type(x)
return x**2
print(square(2))
>>> 4
print(square('2'))
>>> Error but should return None
I know that I can restructure my code in such a way to make this possible by using if then statements but I wanted to see if Python had anything built in already to save lines of code.
From a conceptual point of view, you might want to switch the order of the functions. Instead of square using check_type to check its arguments, you can make check_type accept square as the callback to call with sanitized arguments if everything passes.
A naive approach might be as follows:
def check_type(x, hook):
return hook(x) if type(x) in [int, float] else None
def square(x):
return x**2
print(check_type(2, square))
# 4
print(check_type('2', square))
# None
This is not super elegant, but it is a design improvement over your original approach in a number of ways. For one thing, the function square is free to focus on doing it's job instead of worrying about sanitizing arguments, which helps encapsulation. For another, it makes type checking more consistent across functions, because you can do it in one place instead of having to remember to do it in each function.
As far as elegance goes, python provides a mechanism for doing this quite cleanly, called decorators. A decorator is a function that accepts a function or class as input, and returns a replacement object. This is usually used to wrap functions with extra functionally, exactly like you are trying to do.
A first pass decorator implementation would look like this:
from functools import wraps
def check_type(func):
@wraps(f)
def wrapper(x):
return func(x) if type(x) in [int, float] else None
return wrapper
@check_type
def square(x):
return x**2
print(square(2))
# 4
print(square('2'))
# None
Notice that the wrapper function that the decorator returns to replace square is itself decorated. functools.wraps modifies the function object by updating the name and other attributes to make wrapper be harder to distinguish from the original square.
On an unrelated note, I probably wouldn't allow my function to return a value on inappropriate input. When you return None, you are forcing the caller to check for None vs a valid value, effectively defeating the purpose of having a type checker in the first place. Instead, an exception is a better design choice because it doesn't punt the responsibility down the line:
def check_type(func):
@wraps(f)
def wrapper(x):
if type(x) in [int, float]
raise TypeError ('Expected one thing but got the other')
return func(x)
return wrapper
As a final point, you may want to reconsider how you do type checking, or whether you do it at all. For example, you can extend most of the built-in classes. If you want to allow such extensions, a better phrasing might be
if isinstance(x, (int, float)):
Or you can use abstract base classes registered in the numbers module. This will allow you to handle things like numpy scalars, decimals, etc:
if isinstance(x, numbers.Number):
Finally, you may want to consider doing the most pythonic thing, and passing through anything that supports whatever operation you need. That means not doing any type checking at all. If you pass an instance of something with no __pow__ method, the interpreter will raise a TypeError for you in square.
If you want to save lines of code (not saying that's always a virtuous goal), you can use boolean short circuiting for things like this:
def check_type(x):
return type(x) in [int, float] or None
def square(x):
return check_type(x) and x**2
print(square(2))
# 4
print(square('2'))
# None
This will return the value for check_type(x) if it's falsey, otherwise the value square()
Having said that, it seems the pythonic way is to not do the type check, but catch the error:
def square(x):
try:
return x**2
except TypeError:
return None
This will allow your function to work on other types you might not have predicted, but allow exponents. For example, complex numbers: square(2+1j) which returns None in the first example, but (3+4j) in the second.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With