Iterator
An iterator is an object that can remember the location of the traversal. It starts accessing from the first element of the collection until all elements have been accessed. Iterators can only go forward and not backward.
iterable object
You can use the data types of the For loop. One of them is a collection data type, such as list, tuple, dict, set, str, etc.; the other is a generator, and the objects that can directly act on the for loop are collectively called iterable objects. (Iterable), determine whether it is an iterable object:
>>> from collections import Iterable
>>> isinstance([], Iterable)
True
Returning True indicates that the object is iterable.
Iterator
Different from ordinary collection data types, generators can not only act on for loops, but can also be __next__()
continuously called by functions and return the next value until a StopIteration error is finally thrown. Then, __next__()
generators can be called by functions and continue to return the next value. The object is called an iterator (Iterator). Determine whether it is an iterator object:
>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
Generators are all Iterator objects, but although list, dict, and str are iterable objects, they are not iterators. You can use iter()
functions to turn iterable objects such as list, dict, and str into iterators.
>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True
Iterator class
Using a class as an iterator requires implementing two methods in the class __iter__()
. __next__()
The __iter__()
method returns a special iterator object. This iterator object implements __next__()
the method and identifies the completion __next__()
method of the iteration through the StopIteration exception (in Python 2, next( )) will return the next iterator object.
class MyNumbers:
def __iter__(self):
self.a = 1
return self
def __next__(self):
if self.a <= 20:
x = self.a
self.a += 1
return x
else:
raise StopIteration
myclass = MyNumbers()
myiter = iter(myclass)
for x in myiter:
print(x)
Builder
simple generator
>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> G = (x * x for x in range(10))
>>> G
<generator object <genexpr> at 0x000000D25E141B88>
>>> G.next()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'generator' object has no attribute 'next'
>>> G.__next__()
0
>>> G.__next__()
1
>>> G.__next__()
4
>>> G.__next__()
9
>>> G.__next__()
16
>>> G.__next__()
25
>>> G.__next__()
36
>>> G.__next__()
49
>>> G.__next__()
64
>>> G.__next__()
81
>>> G.__next__()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
parse
There are many ways to create a Generator. The first method is very simple. Just change [] to () in a list generation expression to create a Generator.
Generator saves the algorithm. Each time next() is called, the value of the next element is calculated until the last element is calculated. When there are no more elements, a StopIteration error is thrown.
In the code, we call the next() function and report an error, because the next() function was changed to __next__() after Python3
It is too cumbersome to use the __next__() function to let the generator generate data. Although the generator is not a list, we can still use a for loop
>>> G = (x * x for x in range(10))
>>> for e in G:
... print(e)
...
0
1
4
9
16
25
36
49
64
81
Generator advanced
For an ordered sequence, we can use a list generator to generate each element, and then use a for loop to iterate it. If the calculation algorithm is more complicated and cannot be achieved with a for loop similar to the list generation formula, a function can also be used to achieve it.
>>> def fibonacci(end):
... a, b, c = 0, 0, 1
... while a < end:
... print(c)
... b, c = c, b+c
... a = a+1
...
>>> fibonacci(10)
1
1
2
3
5
8
13
21
34
55
>>>
parse
Another way to define a generator. If a function definition contains the yield keyword, then the function is no longer an ordinary function, but a generator.
The fibonacci function defines the calculation rules of the Fibonacci sequence. It can start from the first element and calculate any subsequent elements. However, it is not a generator. It is only one step away from the generator. It only needs to be Change print© to yield© and
the execution flow of generator and function will be different. Functions are executed sequentially and return when encountering a return statement or the last line of function statements. The function that becomes a generator is executed every time next() is called. It returns when it encounters a yield statement. When it is executed again, it continues execution from the yield statement returned last time.
>>> def fibonacci(end):
... a, b, c = 0,0,1
... while a < end:
... yield(c)
... b,c = c, b+c
... a=a+1
...
>>> fibonacci(10)
<generator object fibonacci at 0x000000D25E2792A0>
>>> num = fibonacci(10)
>>> num.__next__()
1
>>> num.__next__()
1
>>> num.__next__()
2
>>> num.__next__()
3
>>> num.__next__()
5
>>> for i in fibonacci(10):
... print(i)
...
1
1
2
3
5
8
13
21
34
55
Decorator
define a function
>>> def print_name(name='davieyang'):
... return name
...
Execute function
>>> print(print_name)
<function print_name at 0x0000000CB357C268>
>>> print(print_name())
davieyang
>>> print(print_name.__name__)
print_name
>>>
Functions are also objects and can be passed around
>>> print_func = print_name
>>> print_func_f = print_name()
>>> print(print_func)
<function print_name at 0x0000000CB357C268>
>>> print(print_func_f)
davieyang
>>> print(print_func())
davieyang
More function object operations
>>> del print_name
>>> print(print_name)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'print_name' is not defined
>>> print(print_name())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'print_name' is not defined
>>> print(print_func)
<function print_name at 0x0000000CB357C268>
>>> print(print_func())
davieyang
>>> print(print_func_f())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object is not callable
>>> print(print_func_f)
davieyang
Define functions within functions
>>> def print_name(name='davieyang'):
... print("currently, you are inside the print_name() function")
... def say_hello():
... return "currently, you are inside the say_hello() function"
... def say_goodbye():
... return "currently, you are inside the say_goodbye() function"
... print(say_hello())
... print(say_goodbay())
... print("currently, you are back in the print_name() function")
...
>>> print_name()
currently, you are inside the print_name() function
currently, you are inside the say_hello() function
currently, you are inside the say_goodbye() function
currently, you are back in the print_name() function
>>> say_hello()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'say_hello' is not defined
>>>
Return function from function
>>> def print_name(name='davieyang'):
... print("currently, you are inside the print_name() function")
... def say_hello():
... return "currently, you are inside the say_hello() function"
... def say_goodbye():
... return "currently, you are inside the say_goodbye() function"
... if name == "davieyang":
... return say_hello
... else:
... return say_goodbye
... print(say_hello())
... print(say_goodbay())
... print("currently, you are back in the print_name() function")
...
>>> a = print_name()
>>> print(a)
<function say_hello at 0x7f2143c01500>
This shows that it has pointed to the say_hello function
Pass functions as parameters
>>> def print_name(name='davieyang'):
... print("currently, you are inside the print_name() function")
...
>>> def do_something_before_print_name(func):
... print("for doing something before executing print_name()")
... print(func())
...
>>> do_something_before_print_name(print_name)
for doing something before executing print_name()
currently, you are inside the print_name() function
function decorator
Essentially, a decorator is a higher-order function that returns a function. Decorators are often used to extend or enhance functions.
Connecting the previous and the following, a simple decorator
def a_new_decorator(a_func):
def wrapTheFunction():
print("I am doing some boring work before executing a_func()")
a_func()
print("I am doing some boring work after executing a_func()")
return wrapTheFunction
def a_function_requiring_decoration():
print("I am the function which needs some decoration to remove my foul smell")
a_function_requiring_decoration()
#outputs: "I am the function which needs some decoration to remove my foul smell"
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
#now a_function_requiring_decoration is wrapped by wrapTheFunction()
a_function_requiring_decoration()
#outputs:I am doing some boring work before executing a_func()
# I am the function which needs some decoration to remove my foul smell
# I am doing some boring work after executing a_func()
traditional writing
@a_new_decorator
def a_function_requiring_decoration():
"""Hey you! Decorate me!"""
print("I am the function which needs some decoration to "
"remove my foul smell")
a_function_requiring_decoration()
#outputs: I am doing some boring work before executing a_func()
# I am the function which needs some decoration to remove my foul smell
# I am doing some boring work after executing a_func()
@a_new_decorator代替了a_function_requiring_decoration=a_new_decorator(a_function_requiring_decoration)
However, there is a problem here. The __name__ attribute of the function a_function_requiring_decoration after adding the decorator is modified by the decorator. This is not what we expected.
>>> print(a_function_requiring_decoration.__name__)
wrapTheFunction
Python provides a simple way functools.wraps
to solve the problem of decorators modifying attributes
from functools import wraps
def a_new_decorator(a_func):
@wraps(a_func)
def wrapTheFunction():
print("I am doing some boring work before executing a_func()")
a_func()
print("I am doing some boring work after executing a_func()")
return wrapTheFunction
@a_new_decorator
def a_function_requiring_decoration():
"""Hey yo! Decorate me!"""
print("I am the function which needs some decoration to "
"remove my foul smell")
>>> print(a_function_requiring_decoration.__name__)
a_function_requiring_decoration
class decorator
class logit:
_logfile = 'out.log'
def __init__(self, func):
self.func = func
def __call__(self, *args):
log_string = self.func.__name__ + " was called"
print(log_string)
# Open the logfile and append
with open(self._logfile, 'a') as opened_file:
# Now we log to the specified logfile
opened_file.write(log_string + '\n')
# Now, send a notification
self.notify()
# return base func
return self.func(*args)
def notify(self):
# logit only logs, no more
pass
Use this class-level decorator
logit._logfile = 'out2.log'
@logit
def myfunc():
pass
myfunc()
# Output: myfunc1 was called
class email_logit(logit):
'''
Let’s subclass logit to add email functionality, from here, @email_logit works just like @logit but sends an email to the admin in addition to logging.
A logit implementation for sending emails to admins
when the function is called.
'''
def __init__(self, email='[email protected]', *args, **kwargs):
self.email = email
super(email_logit, self).__init__(*args, **kwargs)
def notify(self):
# Send an email to self.email
# Will not be implemented here
pass
More uses for decorators
import logging
from selenium import webdriver
from time import sleep
class DecoratorDemo:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
driver = webdriver.Chrome()
driver.get(url="http://www.baidu.com")
logging.warning("%s is running" % self.func.__name__)
self.notify()
return self.func(*args, **kwargs)
def notify(self):
print(self.notify.__name__ + " " + "was called....")
@DecoratorDemo
def test_access_baidu():
print("Baidu website is opened...")
sleep(5)
if __name__ == '__main__':
test_access_baidu()
# -*- coding: utf-8 -*-
import logging
import unittest
from functools import wraps
from selenium import webdriver
"""
被装饰的函数有可能是不带参数的,有可能是带参数的,装饰器应如何编写
"""
def starting_browser_none_parameter(func):
"""
starting_browser_none_parameter是一个装饰器,返回函数wrap_starting_browser
函数的进入和退出时,被称为一个横切面,这种编程方式被称为面向切面编程
"""
@wraps(func)
def wrap_starting_browser():
driver = webdriver.Chrome()
driver.get(url="http://www.baidu.com")
logging.warning("%s is running" % func.__name__)
return func()
return wrap_starting_browser
def starting_browser_with_simple_parameter(func):
"""
starting_browser是一个装饰器,返回一个函数
"""
@wraps(func)
def wrap_starting_browser(self):
"""
函数的参数定义是(*args, **kwargs),因此,wrap_starting_browser()函数可以接受任意参数的调用。
"""
driver = webdriver.Chrome()
driver.get(url="http://www.baidu.com")
logging.warning("%s is running" % func.__name__)
return func(self)
return wrap_starting_browser
def starting_browser_with_sophisticated_parameter(func):
@wraps(func)
def wrap_starting_browser(*args, **kwargs):
"""
函数的参数定义是(*args, **kwargs),因此,wrap_starting_browser()函数可以接受任意参数的调用。
*args是一个数组,**kwargs是一个字典
"""
driver = webdriver.Chrome()
driver.get(url="http://www.baidu.com")
logging.warning("%s is running" % func.__name__)
return func(*args, **kwargs)
return wrap_starting_browser
class TestDecorator(unittest.TestCase):
@starting_browser_with_simple_parameter
def test_access_baidu(self):
print("Baidu website is opened...")
# -*- coding: utf-8 -*-
import logging
import unittest
from functools import wraps
from selenium import webdriver
"""
装饰器还有更大的灵活性,例如带参数的装饰器,在上面的装饰器调用中,该装饰器接收唯一的参数就是执行业务的函数
装饰器的语法允许我们在调用时,提供其它参数,比如@decorator(parameter)。这样,就为装饰器的编写和使用提供了更大的灵活性
比如,我们可以在装饰器中指定日志的等级,因为不同业务函数可能需要的日志级别是不一样的
它实际上是对原有装饰器的一个函数封装,并返回一个装饰器。我们可以将它理解为一个含有参数的闭包
当我 们使用@starting_browser_with_parameter(browser_name='firefox')调用的时候
Python 能够发现这一层的封装,并把参数传递到装饰器的环境中
"""
def starting_browser_with_parameter(browser_name):
def decorator_with_parameter(func):
@wraps(func)
def wrap_starting_browser(*args, **kwargs):
if browser_name.lower() == 'chrome':
driver = webdriver.Chrome()
driver.get(url="http://www.baidu.com")
logging.warning("%s is running" % func.__name__)
elif browser_name.lower() == 'firefox':
driver = webdriver.Firefox()
driver.get(url="http://www.baidu.com")
logging.warning("%s is running" % func.__name__)
else:
driver = webdriver.Ie()
driver.get(url="http://www.baidu.com")
logging.warning("%s is running" % func.__name__)
return func(*args, **kwargs)
return wrap_starting_browser
return decorator_with_parameter
class TestDecorator(unittest.TestCase):
@starting_browser_with_parameter(browser_name='firefox')
def test_access_baidu(self):
print("Baidu website is opened...")