量化百科

Python-Numpy模块tile函数[源码解析]

由iquant创建,最终由iquant 被浏览 17 用户

tile

n. 瓷砖,瓦片vt. 铺以瓦;铺以瓷砖

1.前言

**函数格式tile(A,reps)**A和reps都是array_like类型A参数几乎所有类型都可以:array, list, tuple, dict, matrix这些序列化类型以及Python中基本数据类型int,float,string,bool类型。reps的参数可以是tuple,list, dict, array, int, bool.但不可以是float, string, matrix(多维度的ndarray数组)类型。

tile函数的功能是重复某个数组。比如tile(A,reps),他的作用就是把A重复reps次,所以也可以理解为什么参数reps不能是float或者string,而matrix为什么不行,后面就会看到为什么matrix不能作为参数。

其实如果可以使用Python广播的话没有必要使用tile函数。下面就是通过源码来简单分析分析他的运作,以及如何简单的使用他。

2.tile源码分析

我们这里通过原码tile源码来分析tile函数,为什么能实现A进行复制的功能。

def tile(A, reps):
    try:
        tup = tuple(reps)
    except TypeError:
        tup = (reps,)
    d = len(tup)
    if all(x == 1 for x in tup) and isinstance(A, _nx.ndarray):
        # Fixes the problem that the function does not make a copy if A is a
        # numpy array and the repetitions are 1 in all dimensions
        return _nx.array(A, copy=True, subok=True, ndmin=d)
    else:
        # Note that no copy of zero-sized arrays is made. However since they
        # have no data there is no risk of an inadvertent overwrite.
        c = _nx.array(A, copy=False, subok=True, ndmin=d)
    if (d < c.ndim):
        tup = (1,)*(c.ndim-d) + tup
    shape_out = tuple(s*t for s, t in zip(c.shape, tup))
    n = c.size
    if n > 0:
        for dim_in, nrep in zip(c.shape, tup):
            if nrep != 1:
                c = c.reshape(-1, n).repeat(nrep, 0)
            n //= dim_in
    return c.reshape(shape_out)

1.首先一个函数体头部

#定义了函数体的名称tile
#参数:
#------A:进行操作的对象
#------reps:重复的次数     
def tile(A, reps):

2.对reps进行处理

try:
   tup = tuple(reps)
except TypeError:
   tup = (reps,)

这个地方就很好理解了,他是把传进来的reps转换成Python的元组类型。我们一个一个类型的来看。

import numpy as np

print("list to tuple:",tuple([1,2]))
print("dict to tuple:",tuple({'A':1,'B':2}))
print("ndarray to tuple:",tuple(np.array([1,2])))
# print("int to tuple:",tuple(1))#error抛出TypeError异常执行tup = (reps,)
# print("bool to tuple:",tuple(True))##error抛出TypeError异常执行tup = (reps,)
#不可以作为reps参数的类型
# print("float to tuple:",tuple(1.2))#error抛出TypeError异常执行tup = (reps,)
print("string to tuple:",tuple('12'))
print("matrix to tuple:",tuple(np.array([[1,2],[3,4]])))

![](data:image/svg+xml;utf8,<svg%20xmlns='http://www.w3.org/2000/svg' width='383' height='165'></svg>)

Result

可以看出不可以作为reps参数的类型在这里也并执行通过,所以这个地方并不是不能使用一些类型作为reps的根源。

![](data:image/svg+xml;utf8,<svg%20xmlns='http://www.w3.org/2000/svg' width='461' height='253'></svg>)

reps可以为的参数类型

![](data:image/svg+xml;utf8,<svg%20xmlns='http://www.w3.org/2000/svg' width='597' height='192'></svg>)

reps不可以为的参数类型

其实使用tuple函数转换成元组失败是因为tuple函数他需要的参数是一个可迭代的类型,如果不是的话就会抛出Typeerror的异常,抛出异常在源码中就会把值直接放入元组的第一个位置,这里的(reps,)也是一个元组类型。其实抛出异常对应的无非就是标量值,像int,True以及不能作为参数的float。

3.获取元组的长度

#这个其实很好理解
#要注意len((reps,))就是reps的元素个数
d = len(tup)
print(len((True,)))#1

对应上面的分析,这里无非也就是两种情况:

  1. 像int,True这样的标量值,他们被转换成的元素是(value,),这种形式的,所以获取长度肯定得到的是1;
  2. 剩下的一些序列化的参数,他们的len长度>=1,不确定,这就需要看这些参数中有多少个元素。

4.判断语句

if all(x == 1 for x in tup) and isinstance(A, _nx.ndarray):
        # Fixes the problem that the function does not make a copy if A is a
        # numpy array and the repetitions are 1 in all dimensions
        return _nx.array(A, copy=True, subok=True, ndmin=d)
    else:
        # Note that no copy of zero-sized arrays is made. However since they
        # have no data there is no risk of an inadvertent overwrite.
        c = _nx.array(A, copy=False, subok=True, ndmin=d)

这里有几个函数需要注意:

  1. all()函数。all() 函数用于判断给定的可迭代参数 iterable 中的所有元素是否有 0、''、False 或者 iterable 为空。如果没有这些返回 True,否则返回 False。

  2. _nx.array(A, copy=False, subok=True, ndmin=d)函数,简单来说就是创建一个ndarray数组。

  3. copy参数:bool,可选。如果为True(默认值),那么对象被复制。否则,副本将仅当__array__返回副本。

  4. subok参数:bool,可选。如果为True,则子类将被传递,通过,否则返回数组将被迫成为一个基类数组(默认)。

  5. ndmin:int,可选。指定结果数组应有尺寸的最小数目。

  6. isinstance(A, _nx.ndarray)函数。isinstance(object, classinfo)函数就是判断object对象类型是否是classinfo类型相同,相同则返回True,否则返回False。

    #可以把这个先看成是import numpy as np import numpy.core.numeric as _nx print(_nx.ndarray)

    #从输出可以看出,isinstance(A, _nx.ndarray)判断A是不是ndarray类型的数据 ''' <class 'numpy.ndarray'> '''

这里的all(x == 1 for x in tup)就是为什么matrix不能使用的地方。

import numpy as np

reps = np.array([[1,2],[3,4]])
print(all((x == 1 for x in reps)))

'''
Traceback (most recent call last):
  File "G:/Python源码/numpy_test/numpy_test.py", line 1763, in <module>
    print(all((x == 1 for x in reps)))
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
'''

然后我们来分析这个判断语句把那些情况筛选出去了:

  1. all(x == 1 for x in tup) and isinstance(A, _nx.ndarray)。通过上面的分析,我们可以知道,如果传入的tup中的值都是1并且(and)我们的A是ndarray数组类型的话,直接return _nx.array(A, copy=True, subok=True, ndmin=d)。直接返回的是A这个ndarray数组,当然如果维度小于d的话,会自动增加d维度。
  2. 如果不是上面那种情况的话。执行c = _nx.array(A, copy=False, subok=True, ndmin=d)。ndmin参数说的就是c的ndim最少就是d,当然这里很自然的就会有三种情况:
  3. c.ndim == d**(也就是ndmin)**
  4. c.ndim > d**[这个时候会继续执行5中的判断语句]**
  5. c.ndim < d(这种情况不可能发生,因为指定了ndmin = d)[所以不考虑]

5.判断语句

if (d < c.ndim):
    tup = (1,)*(c.ndim-d) + tup

这是什么样的情况呢?因为c.ndim也就是c的维度比d也就是元组中的元素个数不匹配,或者说是要进行重复的A的维度和reps重复次数不匹配,这样可想而知是不可以的,所以加入了一个进行处理的代码。

import numpy as np
import numpy.core.numeric as _nx

A = np.array([[1,2],[3,4]])
tup = (2,)
d = len(tup)
c = _nx.array(A, copy=False, subok=True, ndmin=d)
if (d < c.ndim):
    tup = (1,)*(c.ndim-d) + tup

print(d)#1
print(c.ndim)#2
print(tup)#(1, 2)

![](data:image/svg+xml;utf8,<svg%20xmlns='http://www.w3.org/2000/svg' width='352' height='151'></svg>)

result

6.形成最终的shape_out

shape_out = tuple(s*t for s, t in zip(c.shape, tup))

因为我们在第五步的时候,已经将我们的c的ndim与我们的tup的维度匹配。我们把shape属性和我们需要进行重复次数的tup中对应的元素相乘形成新的数组,这个结果作为我们最终的shape。

7.tile函数的核心代码-对c的维度进行重复

n = c.size
if n > 0:
    for dim_in, nrep in zip(c.shape, tup):
        #nrep == 1就不需要进行复制了
        if nrep != 1:
            c = c.reshape(-1, n).repeat(nrep, 0)
        n //= dim_in

第六步实现了对最终输出结果的shape的形成。之后就是很重要的如何去进行复制呢?这里的c.size得到的结果是c中元素的个数:

  1. n = 0直接输出结果

  2. n > 0执行复制元素的代码。

    for dim_in, nrep in zip(c.shape, tup): #nrep == 1就不需要进行复制了 if nrep != 1: c = c.reshape(-1, n).repeat(nrep, 0) n //= dim_in

从上面的分析我们也可以知道,到这一步,我们的shape和tup中的元素个数是相互匹配的。

  1. 如果tup中的值为1,则直接跳出,也就是不进行任何的复制操作。
  2. 如果tup中的值不为1,则执行c = c.reshape(-1, n).repeat(nrep, 0);n //= dim_in这两句代码。
  3. 这里的 c.reshape(-1,n)直接把c中的全部元素变成是一个一行n列的一个数组。
  4. repeat(nrep, 0)函数会把c.reshape(-1,n)形成的哪个一行n列的数组复制nrep次,形成一个nrep行n列的数组。并且这里的0是参数axis的值,也就是行的方向进行重复。
  5. n //= dim_in执行。" / "就一定表示 浮点数除法,返回浮点结果;" // "表示整数除法。

8.返回结果

return c.reshape(shape_out)

3.示例代码

分析完了代码,看看怎么去使用。

1.A = array([1,2]);reps = (1,1)。1或者True(看成是1)...也是同样的步骤

从源码的分析上看:

  1. 首先一个函数体头部

  2. 对reps进行处理

  3. tup = (1,1)

  4. 获取元组的长度d = 2

  5. 判断语句

  6. if all(x == 1 for x in tup) and isinstance(A, _nx.ndarray)

  7. return _nx.array(A, copy=True, subok=True, ndmin=d)。这里因为A的ndim为1,d = 2,所以会把A扩展成ndim为2的数组。

    import numpy as np

    A = np.array([1,2]) reps = (1,1) print(np.tile(A,reps))

    '''[[1 2]]'''

2.A = array([[1,2],[3,4]]);reps = (1,2)。

从源码的分析上看:

  1. 首先一个函数体头部

  2. 对reps进行处理

  3. tup = (1,2)

  4. 获取元组的长度d == 2

  5. 判断语句

  6. if all(x == 1 for x in tup) and isinstance(A, _nx.ndarray),因为tup中含有非1的元素,所以all中返回False,执行else语句。

  7. c = _nx.array(A, copy=False, subok=True, ndmin=d),此时的c和A的shape相同。

  8. 判断语句

  9. if (d < c.ndim),因为d == c.ndim所以不执行。

  10. 形成最终的shape_out

  11. shape_out = tuple(s*t for s, t in zip(c.shape, tup)),这里会返回shape_out是(2,4)的shape。

  12. tile函数的核心代码-对c的维度进行重复

  13. n = c.size = 4

  14. for dim_in, nrep in zip(c.shape, tup)。因为c.shape是(2,2),tup是(1,2),

  15. 执行第一次的时候,n //= dim_in,4 //= 2 ==2。

  16. 所以当执行第二次循环的时候,nrep == 2!1,所以执行 c = c.reshape(-1, n).repeat(nrep, 0)

  17. c.reshap(-1,2)的结果是[[1 2][3 4]]。

  18. [[1 2][3 4]].repeat(2,0)的执行结果是[[1 2] [1 2] [3 4] [3 4]]

  19. 返回结果

  20. return c.reshape(shape_out),也就是array([[1,2,3,4],[1,2,3,4]]).reshape((2,4)),执行的结果是[[1 2 1 2] [3 4 3 4]]

    import numpy as np

    A = np.array([[1,2],[3,4]]) reps = (1,2)

    print(np.tile(A,reps))

    ''' [[1 2 1 2] [3 4 3 4]] '''

\

标签

PythonNumpyNumPy函数