Python中的更多特殊功能

113 阅读9分钟

Python是一种了不起的编程语言!它是开发人工智能和机器学习应用程序的最流行语言之一。凭借非常容易学习的语法,Python有一些特殊的功能,使其区别于其他语言。在本教程中,我们将谈论Python编程语言的一些特殊属性。

完成本教程后,你将知道。

  • 用于列表和字典理解的结构
  • 如何使用 zip 和枚举函数
  • 什么是函数上下文和装饰器
  • Python 中生成器的作用是什么?

让我们开始吧。

教程概述

本教程分为4个部分,它们是:

  1. 列表和字典的理解
  2. Zip和枚举函数
  3. 函数上下文和装饰器
  4. Python中的生成器,以Keras生成器为例

导入部分

本教程中使用的库在下面的代码中被导入。

from tensorflow import keras
from tensorflow.keras.preprocessing.image import ImageDataGenerator
import numpy as np
import matplotlib.pyplot as plt
import math

列表理解

列表理解提供了一种简短的语法,用于从现有列表中创建新的列表。例如,假设我们需要一个新的列表,其中每个新项是旧项乘以3。一种方法是使用for 循环,如下所示。

original_list = [1, 2, 3, 4]
times3_list = []

for i in original_list:
        times3_list.append(i*3)
print(times3_list)
[3, 6, 9, 12]

使用列表理解的较短方法只需要一行代码。

time3_list_awesome_method = [i*3 for i in original_list]
print(time3_list_awesome_method)
[3, 6, 9, 12]

你甚至可以根据一个特殊的标准来创建一个新的列表。例如,如果我们希望只有偶数被添加到新的列表中。

even_list_awesome_method = [i for i in original_list if i%2==0]
print(even_list_awesome_method)
[2, 4]

也可以有一个与上述相关的else 。例如,我们可以保留所有的偶数,并将奇数替换为零。

new_list_awesome_method = [i if i%2==0 else 0 for i in original_list]
print(new_list_awesome_method)
[0, 2, 0, 4]

列表理解也可以用来替换嵌套的循环。例如下面这个。

colors = ["red", "green", "blue"]
animals = ["cat", "dog", "bird"]
newlist = []
for c in colors:
    for a in animals:
        newlist.append(c + " " + a)
print(newlist)
['red cat', 'red dog', 'red bird', 'green cat', 'green dog', 'green bird', 'blue cat', 'blue dog', 'blue bird']

可以这样做,在列表理解的内部有两个 "for"。

colors = ["red", "green", "blue"]
animals = ["cat", "dog", "bird"]

newlist = [c+" "+a for c in colors for a in animals]
print(newlist)

语法

列表理解的语法如下。

newlist = [expression for item in iterable if condition == True] 。

或者

newList = [expression if condition == True else expression for item in iterable]。

字典理解

字典理解与列表理解类似,只是现在我们有(键,值)对。这里有一个例子;我们将通过将字符串 'number' 连接到每个值来修改字典的每个值。

original_dict = {1: 'one', 2: 'two', 3: 'three', 4: 'four'}
new_dict = {key:'number ' + value for (key, value) in original_dict.items()}
print(new_dict)
{1: 'number one', 2: 'number two', 3: 'number three', 4: 'number four'}

同样,条件式也是可以的。我们可以根据新字典中的一个标准来选择添加 (key, value) 对。

#Only add keys which are greater than 2
new_dict_high_keys = {key:'number ' + value for (key, value) in original_dict.items() if key>2}
print(new_dict_high_keys)

# Only change values with key>2
new_dict_2 = {key:('number ' + value if key>2 else value) for (key, value) in original_dict.items() }
print(new_dict_2)
{3: 'number three', 4: 'number four'}
{1: 'one', 2: 'two', 3: 'number three', 4: 'number four'}

Python 中的枚举器和 Zip

在 Python 中,迭代器被定义为任何可以返回其所有项目的数据结构,一次一个。这样你就可以使用一个for 循环来进一步逐一处理所有的项目。Python有两个额外的结构,使for 循环更容易使用,即:enumerate()zip()

枚举

在传统的编程语言中,你需要一个循环变量来遍历一个容器的不同值。在Python中,这被简化为让你访问一个循环变量和一个可迭代对象的值。enumerate(x) 函数返回两个可迭代对象。一个可迭代对象从0到len(x)-1不等。另一个是迭代对象,其值等于x的项。下面是一个例子。

name = ['Triangle', 'Square', 'Hexagon', 'Pentagon']

# enumerate returns two iterables
for i, n in enumerate(name):
    print(i, 'name: ', n)
0 name:  Triangle
1 name:  Square
2 name:  Hexagon
3 name:  Pentagon

默认情况下,enumerate从0开始,但如果我们指定的话,我们可以从其他数字开始。这在某些情况下是很有用的,比如说。

data = [1,4,1,5,9,2,6,5,3,5,8,9,7,9,3]
for n, digit in enumerate(data[5:], 6):
    print("The %d-th digit is %d" % (n, digit))
The 6-th digit is 2
The 7-th digit is 6
The 8-th digit is 5
The 9-th digit is 3
The 10-th digit is 5
The 11-th digit is 8
The 12-th digit is 9
The 13-th digit is 7
The 14-th digit is 9
The 15-th digit is 3

Zip

Zip允许你创建一个图元的可迭代对象。Zip将多个容器(m_1,m_2,ldots,m_n)(m\_1, m\_2, \\ldots, m\_n)作为参数,并通过从每个容器中配对一个项目来创建第i个元组。第i个元组就是(m_1i,m_2i,ldots,m_ni)(m\_{1i}, m\_{2i}, ldots, m\_{ni})。如果传递的对象有不同的长度,那么形成的元组总数的长度等于传递对象的最小长度。

下面是同时使用zip()enumerate() 的例子。

sides = [3, 4, 6, 5]
colors = ['red', 'green', 'yellow', 'blue']
shapes = zip(name, sides, colors)

# Tuples are created from one item from each list
print(set(shapes))

# Easy to use enumerate and zip together for iterating through multiple lists in one go
for i, (n, s, c) in enumerate(zip(name, sides, colors)):
    print(i, 'Shape- ', n, '; Sides ', s)
{('Triangle', 3, 'red'), ('Square', 4, 'green'), ('Hexagon', 6, 'yellow'), ('Pentagon', 5, 'blue')}
0 Shape-  Triangle ; Sides  3
1 Shape-  Square ; Sides  4
2 Shape-  Hexagon ; Sides  6
3 Shape-  Pentagon ; Sides  5

函数上下文

Python 允许嵌套函数,你可以在一个外层函数中定义一个内部函数。在Python中,有一些与嵌套函数有关的很棒的特性。

  • 外层函数可以向内层函数返回一个句柄
  • 即使外层函数结束执行,内层函数也会保留它的所有环境和它的局部变量,以及它的外层函数中的变量。

下面给出了一个例子,并在注释中作了解释。

def circle(r):
    area = 0
    def area_obj():
        nonlocal area
        area = math.pi * r * r
        print("area_obj")
    return area_obj    

def circle(r):
    area_val = math.pi * r * r
    def area():
        print(area_val)
    return area    

# returns area_obj(). The value of r passed is retained
circle_1 = circle(1)
circle_2 = circle(2)

# Calling area_obj() with radius = 1
circle_1()
# Calling area_obj() with radius = 2
circle_2()
3.141592653589793
12.566370614359172

Python中的装饰器

装饰器是Python的一个强大特性。你可以使用装饰器来定制一个类或一个函数的工作。可以把它们看作是一个应用于另一个函数的函数。使用带有@ 符号的函数名来定义被装饰函数上的装饰器函数。装饰器以一个函数作为参数,给人以很大的灵活性。

考虑下面这个函数square_decorator() ,它接受一个函数作为参数,同时也返回一个函数。

  • 内部嵌套的函数square_it()需要一个参数arg.
  • square_it()函数将该函数应用于arg ,并将结果平方化。
  • 我们可以将诸如sin 这样的函数传递给square_decorator() ,它反过来会返回sin2(x)sin^2(x)
  • 你也可以编写你自己的自定义函数,并使用特殊的@符号对其使用square_decorator() 函数,如下图所示。函数plus_one(x) 返回x+1 。这个函数由square_decorator() 来装饰,因此,我们得到(x+1)2(x+1)^2
def square_decorator(function):
    def square_it(arg):
        x = function(arg)
        return x*x
    return square_it

size_sq = square_decorator(len)
print(size_sq([1,2,3]))

sin_sq = square_decorator(math.sin)
print(sin_sq(math.pi/4))

@square_decorator
def plus_one(a):
    return a+1

a = plus_one(3)
print(a)
9
0.4999999999999999
16

Python中的生成器

Python中的生成器允许你生成序列。生成器不是写一个return 语句,而是通过多个yield 语句返回多个值。对函数的第一次调用从产量中返回第一个值。第二次调用会从yield中返回第二个值,以此类推。

生成器函数可以通过next().每次调用next() ,都会返回下一个yield 的值。下面是一个生成斐波那契数列的例子,直到一个给定的数字x

def get_fibonacci(x):
    x0 = 0
    x1 = 1
    for i in range(x):
        yield x0
        temp = x0 + x1
        x0 = x1
        x1 = temp


f = get_fibonacci(6)
for i in range(6):
    print(next(f))
0
1
1
2
3
5

Keras中数据生成器的例子

生成器的一个用途是Keras中的数据生成器。它之所以有用,是因为我们不想把所有的数据都保存在内存中,而是想在训练循环需要它的时候临时创建。请记住,在Keras中,神经网络模型是分批训练的,所以生成器是为了发出成批的数据。下面的函数来自我们之前的文章《使用CNN进行金融时间序列预测》。

def datagen(data, seq_len, batch_size, targetcol, kind):
    "As a generator to produce samples for Keras model"
    batch = []
    while True:
        # Pick one dataframe from the pool
        key = random.choice(list(data.keys()))
        df = data[key]
        input_cols = [c for c in df.columns if c != targetcol]
        index = df.index[df.index < TRAIN_TEST_CUTOFF]
        split = int(len(index) * TRAIN_VALID_RATIO)
        if kind == 'train':
            index = index[:split]   # range for the training set
        elif kind == 'valid':
            index = index[split:]   # range for the validation set
        # Pick one position, then clip a sequence length
        while True:
            t = random.choice(index)      # pick one time step
            n = (df.index == t).argmax()  # find its position in the dataframe
            if n-seq_len+1 < 0:
                continue # can't get enough data for one sequence length
            frame = df.iloc[n-seq_len+1:n+1]
            batch.append([frame[input_cols].values, df.loc[t, targetcol]])
            break
        # if we get enough for a batch, dispatch
        if len(batch) == batch_size:
            X, y = zip(*batch)
            X, y = np.expand_dims(np.array(X), 3), np.array(y)
            yield X, y
            batch = []

上面的函数是在pandas数据框架中随机选取一行作为起点,并将接下来的几行作为一个时间间隔的样本。这个过程重复多次,以收集许多时间间隔为一个批次。当我们收集到足够数量的时间间隔样本时,在上述函数的倒数第二行,使用yield 命令对该批次进行调度。你可能已经注意到,生成器函数没有返回语句。在这个例子中,该函数甚至会永远运行。这很有用,也很有必要,因为它允许我们的Keras训练过程随心所欲地运行许多epoch。

如果我们不使用生成器,我们将需要把数据帧转换为所有可能的时间间隔,并把它们保存在内存中用于训练循环。这将是大量的重复数据(因为时间间隔是重叠的),并占用大量的内存。

因为它很有用,所以Keras在库中预先定义了一些生成器函数。下面是ImageDataGenerator() 的例子。我们在x_train 中加载了32×32图像的cifar10 数据集。该数据通过flow() 方法连接到生成器。next() 函数返回下一批数据。在下面的例子中,有4次对next() 的调用。每次都返回8张图片,因为批次大小为8。

下面是整个代码,在每次调用next() 后都会显示所有的图像。

(x_train, y_train), _ = keras.datasets.cifar10.load_data()
datagen = ImageDataGenerator()
data_iterator = datagen.flow(x_train, y_train, batch_size=8)

fig,ax = plt.subplots(nrows=4, ncols=8,figsize=(18,6),subplot_kw=dict(xticks=[], yticks=[]))

for i in range(4):
    # The next() function will load 8 images from CIFAR
    X, Y = data_iterator.next()
    for j, img in enumerate(X):
        ax[i, j].imshow(img.astype('int'))

摘要

在本教程中,你发现了Python的特殊功能

具体来说,你学到了

  • 列表和字典理解的目的
  • 如何使用 zip 和 enumerate
  • 嵌套函数、函数上下文和装饰器
  • Python 中的生成器和 Python 中的 ImageDataGenerator