So first, you go through all_args
, but you don't use items()
.
This will first, make it so you don't have to type all_args[key]
,
and should run faster.
Also the only times that you use all_args[key]
it's wrapped in a type
.
As I like to promote laziness, you could use a generator comprehension to do this all in the for
line, if you wish to.
for key, value in ((i, type(j)) for i, j in all_args.items()):
if key in hints:
if value != hints[key]:
raise Exception('Type of {} is {} and not {}'.format(key, value, hints[key]))
Now the inside of the for loop is simple, for a little added complexity on the for line.
Exception is massive, I never ever ever want to see someone raise Exception
.
Let's say we have some code that raises almost any error inside a function that is wrapped in your wrapper.
What will happen if we want to catch an except for invalid type input?
Well the other function is never going to work right.
And it will mask so many bugs.
I use the following to 'test' your program.
@strict_types
def should_work_function() -> str:
return non_existant_value
@strict_types
def should_error_function() -> str:
raise SystemError('There was a problem.')
# Masks a bug
try:
should_work_function()
except Exception as e:
print(e)
# Prevents intended use of the function.
try:
should_work_function()
except Exception as e:
print(e)
print('Done without errors apart from input type errors. :D')
So what's the problem? If you look at the output then you will see a problem straight away.
name 'non_existant_value' is not defined
There was a problem.
Done without errors apart from input type errors. :D
To amend this I would recommend subclassing Exception
, you could try TypeError
,
but that might make some confusion as stopping a='abc';a['a']
will stop you exception too.
class StrictTypeError(Exception):pass
When you change your function, and my 'test', to use that exception the output of the above 'test' will show something like:
Traceback (most recent call last):
File "D:/programs/stack/strictTypes.py", line 35, in <module>
should_work_function()
File "D:/programs/stack/strictTypes.py", line 17, in type_checker
res = f(*args, **kwargs)
File "D:/programs/stack/strictTypes.py", line 28, in should_work_function
return non_existant_value
NameError: name 'non_existant_value' is not defined
Which is definitely better.
As this function should be as fast as possible,
as you want less overhead when calling all your functions.
you should aim at using the fastest methods.
Looking at one of your question, Is checking if key is in dictionary and getting it's value in the same “if” safe?
There was a small discussion on efficiency.
Which was your current method is the fastest,
unless most of the key's are in the dict.
If you were to blindly wrap the inner if
in a try
, then you have the potential of masking bugs.
To avoid this use else
as it only runs once try
finishes successfully.
This is if you have another potential IndexError
than that will force the except to run.
And so I would recommend changing the for loop to something like:
for key, value in ((i, type(j)) for i, j in all_args.items()):
try:
hint = hints[key]
except IndexError:
pass # Put @Caridorc's raise if you wish
else:
if value != hint:
raise StrictTypeError('Type of {} is {} and not {}'.format(key, value, hint))
Whilst the above is never going to raise IndexError
, apart from on the hints[key]
part, it is better to get into the habit of using the else
.
And your intent will easily be known if there is one statement in the try
.