Syntatic sugar for wrapping functions with reusable logic
Decorators
Before decorators
defsquare(x: int):return x ** 2deflog(func):"""Simple decorator to print result"""defwrapper(x: int):
result = func(x)
print(f"The result is: {result}")
return result
return wrapper
square = log(square)
>>> square(2)
4
Decorators
With decorators (syntatic sugar)
@logdefsquare(x: int):return x ** 2
>>> square(2)
4
Decorators
Decorators with arguments
deflog_times(num_times: int=0):deflog(func):defwrapper(x: int):
result = func(x)
for index inrange(num_times):
print(f"({index}) The result is: {result}")
return result
return wrapper
return log
@log_times(num_times=3)defsquare(x: int):return x ** 2
Decorators
Decorators with arguments cont'd
>>> square(2)
(0) The result is: 4
(1) The result is: 4
(2) The result is: 4
Each time the .next() method of a generator-iterator is invoked, the code in the body of the generator-function is executed until a yield or return statement (see below) is encountered, or until the end of the body is reached.
Potential future topics
What's new in Python 3.10
Async in Python
...
Thank you for coming! Join us in January for
An Anything but Mundane
Intro to Machine Learning!
Why Python: Something I didn't cover last time
Zen of Python: What does "Pythonic" Python really mean? Where does it come from?
Pull up IDLE and do this...
Many "Pythonic" conventions come from the Zen of Python's philosophies
1: Using dict in comprehension
2: Creating dict from list using comprehension
Why is it better? What do you have to always do when you get a user from the second example?
Errors should never pass silently
BaseException covers many errors you shouldn't usually handle.
Show code example in `exception_handling_bad.py`
don't have to declare `square` multiple time...
Without generators, we would have to implement iterator logic ourselves