The usual Python decorators iterators and generators

  • 2020-06-12 09:59:59
  • OfStack

While learning python, the three "nominators" can be a bit of a challenge for people with no programming experience in other languages. This blog explains the blogger's own understanding of decorators, iterators, and generators.

Why use decorators

What is a decorator? "Decoration" from the literal meaning to who is on a particular building according to the fixed way of thinking and style of beautification of 1 kind of behavior, the so-called "device" is a tool, for python decorator is to be able to without modifying the original code to add new functions, such as 1 software online, we need not to modify the source code and the way of change is called the circumstance also can add a new function, in python decorators can be used to realize, when writing the code also want to consider the scalability, below we look to see steps 1 1 1 python decorator.

A simple example introduces a parameter-free decorator

Look at a few simple lines of code. The code runs to sleep for 2 seconds before printing "hello boy!" :


import time
def foo():
 """ print """
 time.sleep(2)
 print("Hello boy!")
foo()

We now need to add a program timing function to it, but cannot modify the original code:


import time
def timmer(func):
 def wrapper():
  """ Timing function """
  time_start=time.time()
  func()
  time_end=time.time()
  print("Run time is %f "%(time_end-time_start))
 return wrapper
def foo():
 """ print """
 time.sleep(2)
 print("Hello boy!")
foo=timmer(foo)
foo()
# The results 
Hello boy!
Run time is 2.000446 

Look! We achieved this without modifying the original code, and because the function is also an object, we were able to pass the function foo as an argument to the function timmer.

In python, there is a simpler alternative to foo=timmer(foo), using @timmer, which in python is called grammar sugar.


import time
def timmer(func):
 def wrapper():
  """ Timing function """
  time_start=time.time()
  func()
  time_end=time.time()
  print("Run time is %f "%(time_end-time_start))
 return wrapper
@timmer  # Is equal to the  foo=timmer(foo)
def foo():
 """ print """
 time.sleep(2)
 print("Hello boy!")
foo()

Let's follow the execution process of the analysis function step by step:

1. Import time module


import time

2. Define timmer, which does not execute the code inside the function


def timmer(func):

3. Call the decorator, equivalent to foo=timer(foo), is the function foo as a parameter to the function timmer


@timmer

4. Run the function timmer, taking the parameter func=foo


def timmer(func):

5. In the function timmer, the function wrapper is defined, and the internal code of the function wrapper is not executed, and then the function wrapper is returned as the return value


return wrapper

6. Assigns the return value to foo, in step 3, foo=timmer(foo), remember


@timmer # Is equal to the  foo=timmer(foo)

7. Run function foo (), but the function is not the original function, can print foo, right, because before we gave the foo wrapper as return values, so perform foo here is in the execution wrapper, again in order to determine this 1 point, you can also print wrapper their memory address is the same, so are all pointing to one address space:


<function timmer.<locals>.wrapper at 0x00000180E0A8A950> # print foo The results of the 
<function timmer.<locals>.wrapper at 0x000001F10AD8A950> # print wrapper The results of the 
foo()

8. Run the function wrapper, record the start time, and execute the function func. In step 4, func is assigned by foo.


import time
def timmer(func):
 def wrapper():
  """ Timing function """
  time_start=time.time()
  func()
  time_end=time.time()
  print("Run time is %f "%(time_end-time_start))
 return wrapper
def foo():
 """ print """
 time.sleep(2)
 print("Hello boy!")
foo=timmer(foo)
foo()
# The results 
Hello boy!
Run time is 2.000446 

0

9. Record the end time, print the running time and finish the program.


Hello boy!
Run time is 2.000161 
 

There are reference decorators

In the previous example, the original function has no arguments. Now let's look at 1. When the original function has arguments, how to modify the decorator function?


import time
def timmer(func):
 def wrapper(*args,**kwargs):
  """ Timing function """
  start_time=time.time()
  res=func(*args,**kwargs)
  end_time=time.time()
  print("Run time is %f"%(end_time-start_time))
  return res
 return wrapper
@timmer 
def my_max(x,y):
 """ Returns the maximum of two values """
 res=x if x > y else y
 time.sleep(2)
 return res
res=my_max(1,2)
print(res)
# The results 
Run time is 2.000175

In this example, my_max has two positions that need to be passed in when the original function needs to pass in an argument. You only need to add two formal parameters to wrapper. Variable arguments (*args,**kwargs) can also be used in this example.

Let's take a look at a decorator with parameters:


import time
def timmer(func):
 def wrapper():
  """ Timing function """
  time_start=time.time()
  func()
  time_end=time.time()
  print("Run time is %f "%(time_end-time_start))
 return wrapper
def foo():
 """ print """
 time.sleep(2)
 print("Hello boy!")
foo=timmer(foo)
foo()
# The results 
Hello boy!
Run time is 2.000446 

3

If the decorator itself has parameters, an additional layer of embedded functions is needed. Let's analyze the execution process step by step:

Define the function auth


import time
def timmer(func):
 def wrapper():
  """ Timing function """
  time_start=time.time()
  func()
  time_end=time.time()
  print("Run time is %f "%(time_end-time_start))
 return wrapper
def foo():
 """ print """
 time.sleep(2)
 print("Hello boy!")
foo=timmer(foo)
foo()
# The results 
Hello boy!
Run time is 2.000446 

4

2. To invoke the interpreter, first run the function auth(filetype='file')


import time
def timmer(func):
 def wrapper():
  """ Timing function """
  time_start=time.time()
  func()
  time_end=time.time()
  print("Run time is %f "%(time_end-time_start))
 return wrapper
def foo():
 """ print """
 time.sleep(2)
 print("Hello boy!")
foo=timmer(foo)
foo()
# The results 
Hello boy!
Run time is 2.000446 

5

3. Run the function auth, which defines a function auth2 and returns it as a return value, then @auth (filetype='file') is the same as @auth2, which is the same as index=auth2(index)


import time
def timmer(func):
 def wrapper():
  """ Timing function """
  time_start=time.time()
  func()
  time_end=time.time()
  print("Run time is %f "%(time_end-time_start))
 return wrapper
def foo():
 """ print """
 time.sleep(2)
 print("Hello boy!")
foo=timmer(foo)
foo()
# The results 
Hello boy!
Run time is 2.000446 

6

auth2(index) executes, func=index, defines the function wrapper, and returns it. At this time, index is equal to wrapper


def wrapper(*args,**kwargs):
return wrapper

5. When index is run, that is, wrapper, run the internal code of the function, filetype=="file", prompt the user to output the user name and password to determine whether the input is correct, if correct, then execute the function func(), equal to execute the original index and print


if filetype == "file":
    username=input("Please input your username:")
    passwd=input("Please input your password : ")
    if passwd == '123456' and username == 'Frank':
     print("Login successful")
     func()

6. Run the results test


import time
def timmer(func):
 def wrapper():
  """ Timing function """
  time_start=time.time()
  func()
  time_end=time.time()
  print("Run time is %f "%(time_end-time_start))
 return wrapper
def foo():
 """ print """
 time.sleep(2)
 print("Hello boy!")
foo=timmer(foo)
foo()
# The results 
Hello boy!
Run time is 2.000446 

9

Decorators can also be superimposed:


import time
#
def timmer(func):
 def wrapper():
  """ Timing function """
  time_start=time.time()
  func()
  time_end=time.time()
  print("Run time is %f "%(time_end-time_start))
  # print("---",wrapper)
 return wrapper
def auth(filetype):
 def auth2(func):
  def wrapper(*args,**kwargs):
   if filetype == "file":
    username=input("Please input your username:")
    passwd=input("Please input your password : ")
    if passwd == '123456' and username == 'Frank':
     print("Login successful")
     func()
    else:
     print("login error!")
   if filetype == 'SQL':
    print("No SQL")
  return wrapper
 return auth2
@timmer
@auth(filetype='file') # First to return 1 a auth2 == " @auth2 == "  index=auth2() == "  index=wrapper
def index():
 print("Welcome to China")
index()

# The test results 
Please input your username:Frank
Please input your password : 123456
Login successful
Welcome to China
Run time is 7.966267

Annotation to optimize


import time
def timmer(func):
 def wrapper():
  """ Calculate program running time """
  start_time=time.time()
  func()
  end_time=time.time()
  print("Run time is %s:"%(end_time-start_time))
 return wrapper
@timmer
def my_index():
 """ Print welcome """
 time.sleep(1)
 print("Welcome to China!")
my_index()
print(my_index.__doc__)

# The results 
Welcome to China!
Run time is 1.0005640983581543:
 Calculate program running time 

When we use the decorator, we don't change the code itself, but when we run it, like in the example above, running my_index is actually running wrapper, if we print comments for my_index, we print comments for wrapper(), how do we optimize it?

wraps can be imported into module functools, as shown below:


import time
from functools import wraps
def timmer(func):
 @wraps(func)
 def wrapper():
  """ Calculate program running time """
  start_time=time.time()
  func()
  end_time=time.time()
  print("Run time is %s:"%(end_time-start_time))
 return wrapper
@timmer
def my_index():
 """ Print welcome """
 time.sleep(1)
 print("Welcome to China!")
my_index()
print(my_index.__doc__)
# The results 
Welcome to China!
Run time is 1.0003223419189453:
 Print welcome 

Thus, on the surface, nothing has changed in the original function.

Why use iterators

In python, iterator can be used to describe the advantages and disadvantages of iterator 1. If you don't understand it, you can skip it first. After reading this blog, I believe you will understand the meaning:

Advantages:

Iterators do not depend on indexes when they are evaluated, so they can iterate over objects that do not have indexes, such as dictionaries and files

Iterators are lazy computations and more memory efficient than lists

Disadvantages:

Unable to get iterator length, no list flexibility

You can only evaluate it backwards, not backwards

What is an iterator

So what is an iterator in python?

If the object has ___ 205en__ (), it is iteratable, with the iterator using the function next()

Let's look at a simple iterator:


my_list=[1,2,3]
li=iter(my_list)  #li=my_list.__iter__()
print(li)
print(next(li))
print(next(li))
print(next(li))
# The results 
<list_iterator object at 0x000002591652C470>
2

As can be seen, the built-in function iter can be used to convert the list into a list iterator. next() is used to obtain the value. Once the value is obtained, a value is taken.


my_list=[1,2,3]
li=iter(my_list)
while True:
 try:
  print(next(li))
 except StopIteration:
  print("Over")
  break
 else:
  print("get!")
# The results 
get!
get!
get!
Over

View iterable objects and iterator objects

The Iterable module can be used to determine whether an object is iterable:


from collections import Iterable
s="hello" # Define string 
l=[1,2,3,4] # Define a list 
t=(1,2,3) # Define a tuple 
d={'a':1} # Define a dictionary 
set1={1,2,3,4} # Define a collection 
f=open("a.txt") # Define text 
#  See if they are all iteratable 
print(isinstance(s,Iterable))
print(isinstance(l,Iterable))
print(isinstance(t,Iterable))
print(isinstance(d,Iterable))
print(isinstance(set1,Iterable))
print(isinstance(f,Iterable))
# The results 
True
True
True
True
True
True

By judging, we can be sure that the commonly used data types we know can be iterated.

The Iterator module can be used to determine whether an object is an iterator:


from collections import Iterable,Iterator
s="hello"
l=[1,2,3,4]
t=(1,2,3)
d={'a':1}
set1={1,2,3,4}
f=open("a.txt")
#  See if they are all iteratable 
print(isinstance(s,Iterator))
print(isinstance(l,Iterator))
print(isinstance(t,Iterator))
print(isinstance(d,Iterator))
print(isinstance(set1,Iterator))
print(isinstance(f,Iterator))
# The results 
False
False
False
False
False
True

You know that only the files are iterators, so you can use next() directly without needing to convert to iterators.

What is a generator

The producer is a function with yield

Let's look at a simple generator


def my_yield():
 print('first')
 yield 1
g=my_yield()
print(g)
# The results 
<generator object my_yield at 0x0000024366D7E258>

The generator is also an iterator


from collections import Iterator
def my_yield():
 print('first')
 yield 1
g=my_yield()
print(isinstance(g,Iterator))
# The results 
True

So you could use next()


print(next(g))
# The results 
first
1
 

The execution of a generator

Let's take a look at the following example to understand the execution process of production


def my_yield():
 print('first')
 yield 1
 print('second')
 yield 2
 print('Third')
 yield 3
g=my_yield()
next(g)
next(g)
next(g)
# The results 
first
second
Third

1. Define the generator my_yield and assign it to g


def my_yield():
g=my_yield()

2. Start the first execution of next(), start the producer function, print statement 1, pause when yileld is encountered, and return a 1, which will be shown here if you want to print the return value


 print('first')
 yield 1

3. Execute 2 more times and print the string (pause 1 time for each execution)


 print('second')
 yield 2
 print('Third')
 yield 3

4. If you add next() once more, the StopIteration anomaly will be reported

Each time the generator pauses, the state of the function is saved. Here's an example:


def foo():
 i=0
 while True:
  yield i
  i+=1
g=foo()
for num in g:
 if num < 10:
  print(num)
 else:
  break
# The results 

next() is implied in the for loop. Every next1 is paused once, and the if statement determines once, and then executes the next next. It can be seen that our while loop does not go on indefinitely, but the state is preserved.

Coroutines function

Let's look at the generator and execution results below


def eater(name):
 print('%s start to eat food'%name)
 while True:
  food=yield
  print('%s get %s ,to start eat'%(name,food))
 print('done')
e=eater('Frank')
next(e)
e.send('egg') # to yield send 1 Value and continue to execute the code 
e.send('tomato')
# The results 
Frank start to eat food
Frank get egg ,to start eat
Frank get tomato ,to start eat

send can be directly transmitted to yield. The function containing yield expression is also called the coroutine function.

When running the program, do not use send directly, you must first use next() initializer.

If there are more than one such function, we will go to next()1 each time. In case we forget this step, we can initialize it with the decorator:


def init(func):
 def wrapper(*args):
  res = func(*args)
  next(res)  #  So let's do it here next
  return res
 return wrapper
@init
def eater(name):
 print('%s start to eat food'%name)
 while True:
  food=yield
  print('%s get %s ,to start eat'%(name,food))
 print('done')
e=eater('Frank')
e.send('egg') 
e.send('tomato')

So if there are more generators in the program that need to be initialized, simply call the decorator.


Related articles: