Python is fast and beautiful [v1.0.0][Iterator Generator Decorator]

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.wrapsto 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...")

Guess you like

Origin blog.csdn.net/dawei_yang000000/article/details/135217333