numpy学习笔记

发布在 Python

简介

Numpy(发音”南派”而不是”努屁”)是一款用来进行科学计算的python库,可以方便快速的计算平均值、最大值、最小值、方差、标准差、向量运行等。相关库还有
Pandas,Scypi等。

数组

Numpy中用数组来表示数据,和Python中的列表相比,除了提供了一些更快、更方便的函数外,最大的区别就在于前者同一数组内的元素类型必须一致,而后者则无此要求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
In [1]: import numpy as np
In [2]: np.array([1,2,3,4,5]) # 创建一个numpy数组
Out[2]: array([1, 2, 3, 4, 5])
In [3]: np.array([1,2,3,4,5]).dtype # 查看元素类型
Out[3]: dtype('int64')
In [4]: np.max([1,2,3,4,5]) # 最大值
Out[4]: 5
In [5]: np.min([1,2,3,4,5]) # 最小值
Out[5]: 1
In [6]: np.std([1,2,3,4,5]) # 标准差
Out[6]: 1.4142135623730951
In [7]: np.mean([1,2,3,4,5]) # 平均值
Out[7]: 3.0
In [8]: np.array([1,2,1.2,4,'a']) # 声明时候就不按套路出牌,可以发现最后都变成了字符串
Out[8]:
array(['1', '2', '1.2', '4', 'a'],
dtype='|S32')
In [9]: a = np.array([[1,2],[3,4]]) # 定义二维数组
In [10]: a[:,1] # 取第2列
Out[10]: array([2, 4])
In [11]: a[1,:] # 取第2行
Out[11]: array([3, 4])
In [12]: a[1,1] # 取某个值
Out[12]: 4
In [13]: a[1,1:] # 使用切片
Out[13]: array([4])

运算

Numpy中的运算都是向量运算,支持加、减、乘、除、指数运算以及与、或、非这类逻辑运算,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
In [35]: a=np.array([5,3,1])
In [36]: b=np.array([2,4,6])
In [37]: a+b
Out[37]: array([7, 7, 7])
In [38]: a-b
Out[38]: array([ 3, -1, -5])
In [39]: a*b
Out[39]: array([10, 12, 6])
In [40]: a/b
Out[40]: array([2, 0, 0])
In [41]: a**b
Out[41]: array([25, 81, 1])
In [42]: a&b
Out[42]: array([0, 0, 0])
In [43]: a|b
Out[43]: array([7, 7, 7])
In [44]: ~a
Out[44]: array([-6, -4, -2])

这里注意py2版本的除法默认是取结果整数部分的,另外需要注意Numpy中++=有些区别,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
In [67]: a = np.array([2,3,4])
In [68]: b = a
In [69]: a += 1
In [70]: b
Out[70]: array([3, 4, 5])
In [71]: a
Out[71]: array([3, 4, 5])
In [72]: a = a + 1
In [73]: a
Out[73]: array([4, 5, 6])
In [74]: b
Out[74]: array([3, 4, 5])

可以看出,对于+=操作是修改了数组本身的值,而+操作则是创建了一个新的数组赋值给了变量a。
+=操作的行为叫做原位运算(In-Place),+操作则是非原位运算(Not In-Place)。对于切片操作,Numpy的行为和Python不同:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
In [75]: a
Out[75]: array([4, 5, 6])
In [76]: e = [1,2,3]
In [77]: f = e[:2]
In [78]: f
Out[78]: [1, 2]
In [79]: f[0]= 9
In [80]: f
Out[80]: [9, 2]
In [81]: e
Out[81]: [1, 2, 3]
In [82]: a
Out[82]: array([4, 5, 6])
In [83]: g = a[:2]
In [84]: g
Out[84]: array([4, 5])
In [85]: g[0] = 8
In [86]: g
Out[86]: array([8, 5])
In [87]: a
Out[87]: array([8, 5, 6])

Python中切片操作创建了一个新的列表出来,改变新的列表值不会影响旧列表。而Numpy中的切片还是指向原来的值,会互相影响。

索引数组

假设有2个数组的 长度一样,一个数组类型为整型,另一个数组的类型为布尔型,则

1
2
3
4
5
6
7
8
9
10
11
12
13
In [45]: a
Out[45]: array([5, 3, 1])
In [46]: c = np.array([True,False,True])
In [47]: a[c]
Out[47]: array([5, 1])
In [48]: a[a!=3]
Out[48]: array([5, 1])
In [49]: a != 3
Out[49]: array([ True, False, True], dtype=bool)

数组c为索引数组(index array),除此之外可以直接在数组索引中使用逻辑判断达到同样的效果。也就是说,对一个数组进行逻辑判断后
返回的结果是一个索引数组。

当使用Numpy处理二维数组时候,就会涉及到轴(axis)这个概念,轴用来控制行或者列的平均值、最大最小值、标准差等的计算:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
In [3]: a = np.array([[1,2],[3,4]])
In [4]: a
Out[4]:
array([[1, 2],
[3, 4]])
In [5]: a.mean()
Out[5]: 2.5
In [6]: a.mean(axis=0)
Out[6]: array([ 2., 3.])
In [7]: a.mean(axis=1)
Out[7]: array([ 1.5, 3.5])

这里声明了一个二维数组,当mean函数不添加任何参数时,默认计算了整个数据的平均值。当axis=0时则计算出每一列的平均值,axis=1时则计算每一行的平均值。
其他计算函数同理。

标准化

用于将数据进行标准化(standardized)处理,即(单值-平均数)/标准差,为啥要进行标准化处理呢?我觉得是为了减少两组变量之间由于取值区间差异过大而产生的问题。
比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
In [60]: a
Out[60]: array([ 1, 3, 5, 7, 9, 11])
In [61]: b
Out[61]: array([5000, 4000, 3000, 2000, 1000, 50])
In [63]: a_s = (a-a.mean())/a.std()
In [64]: b_s = (b-b.mean())/b.std()
In [69]: a_s
Out[69]:
array([-1.46385011, -0.87831007, -0.29277002, 0.29277002, 0.87831007,
1.46385011])
In [70]: b_s
Out[70]:
array([ 1.4694161 , 0.87968389, 0.28995167, -0.29978054, -0.88951276,
-1.44975836])

可以看出,在进行标准化后a和b的取值区间差异变得很小了。

相关系数

使用皮尔逊积矩相关系数来计算2个变量的相关度,这里需要注意的是2个变量之间需要有线性关系,变量是连续变量,变量符合正态分布。

计算皮尔逊相关系数可以分为2步:

  1. 将数据标准化
  2. 标准化的数据进行乘法运算,并取结果的平均值
1
2
3
4
def correlation(x, y):
tempx = (x-x.mean()) / x.std()
tempy = (y-y.mean()) / y.std()
return (tempx * tempy).mean()

另外Numpy中已经提供了函数corrcoef可以直接使用:

1
2
3
4
5
6
7
In [68]: correlation(a_s,b_s)
Out[68]: -0.99996549701548165
In [65]: np.corrcoef(a_s,b_s)
Out[65]:
array([[ 1. , -0.9999655],
[-0.9999655, 1. ]])

可以看出和自己编写的函数结果是一样的。

评论和分享

使用requests来模拟HTTP请求本来是一件非常轻松的事情,比如上传图片来说,简单的几行代码即可:

1
2
3
4
5
6
7
8
9
import requests
files = {'attachment_file': ('1.png', open('1.png', 'rb'), 'image/png', {})}
values = {'next':"http://www.xxxx.com/xxxx"}
r = requests.post('http://www.xxxx.com/upload', files=files, data=values) # 成功
r = requests.post('http://www.xxxx.com/upload', files=files, data=values) # 失败
r = requests.post('http://www.xxxx.com/upload', files=files, data=values) # 失败
r = requests.post('http://www.xxxx.com/upload', files=files, data=values) # 失败
r = requests.post('http://www.xxxx.com/upload', files=files, data=values) # 失败
...

不过我今天在调试一个django程序的时候却遇到了大坑————为了偷懒,我直接在ipython中执行了上述代码,第一次提交的时候一切正常,但第二次之后提交就怎么也通过不了django的form验证。

阅读全文

服务器推送事件(server-sent events,SSE)是一种除websocket、ajax简单轮寻外另一种实现服务器数据主动推送数据到浏览器的方式。

这里,举一个的例子来说明如何使用基于pyhon的服务端来实现,为了简单我使用flask框架来实现。关键点有2个:

  1. HTTP响应头中包含content-type:text/event-stream
  2. 流响应
阅读全文

python中闭包示例

发布在 Python

概念上的东西这里就不详细解释了,为什么需要闭包(Closures)呢?个人理解就是某些情况下你需要在外部访问函数内部的变量时,闭包就该大显身手了。闭包的一种典型表现就是函数内部定义了新的函数。这里举几个例子以记录使用闭包时的几个注意点,例子来源于这里,大神用javascript写的示例,这里改成python的,也顺便说说遇到的坑。

阅读全文

程序中很常见的一种场景就是根据某个控制变量的值来调用不同的函数或对象进行处理,某些语言中可以使用case语句进行处理,在python可以使用getattr函数甚至if…elif…else来处理,除此之外,也可以使用字典来实现相同的功能,比如下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
animals = []
number_of_felines = 0
def deal_cat():
global number_of_felines
print "meow"
animals.append('feline')
number_of_felines += 1
def deal_dog():
print 'bark'
animals.append('canine')
def deal_bear():
print "hug"
animals.append('ursine')
token = {'cat': deal_cat, 'dog': deal_dog, 'bear': deal_bear}
#words = ['cat', 'dog', 'bear']
words = ['cat', 'dog', 'bear', 'cat']
for one in words:
token[one]()
#return token[one]()
nf = number_of_felines
print 'we met %d feline%s' % (nf, 's'[nf == 1:])
print 'the animals we net were:', ' '.join(animals)

阅读全文

celery使用小记

发布在 Python

之前的博客有关于celery以及相关概念的介绍,不过那篇文章没有具体的使用示例,今天补充一下。

目前celery的版本是3.1.19

这里我使用rabbitmq作为broker以及banckend。

阅读全文

关于functools.wraps的作用

发布在 Python

今天看代码时候有这么一段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from functools import wraps
def http_basic_auth(func):
'''定义httpbasicauth装饰器'''
@wraps(func)
def _decorator(request, *args, **kwargs):
authorization = request.META.get('HTTP_AUTHORIZATION','')
if authorization:
authmeth, auth = authorization.split(' ', 1)
if authmeth.lower() == 'basic':
auth = auth.strip().decode('base64')
username, password = auth.split(':', 1)
user = authenticate(username=username, password=password)
if user and user.is_superuser:
login(request, user)
return func(request, *args, **kwargs)
return HttpResponseForbidden()
return _decorator

作用就是为django实现http认证的装饰器,并且使用超级管理员才可以使用被装饰的接口。至于什么是装饰器、闭包、作用域这里就不多说了,有兴趣的可以参考http://www.imooc.com/learn/581 讲解的非常详细。

那么,这个函数中的@wraps(func)又是做什么的呢?这里就涉及到了装饰器的一个小细节问题:被装饰后的函数本质上已经不是原来的函数了,所以原函数的某些信息比如:__name____doc__等值就变了。而@wraps()的作用就是把原函数的相关信息代入到新的函数中。

评论和分享

python脚本模拟tail命令

发布在 Python

今天有个需求,需要实时监控nginx日志中某些ip的访问情况。如果ip较少的话,直接使用tail、grep以及管道配合即可。不过需要监控100个左右的ip并进行相应处理的话,除了自己写脚本还真没想到别的办法。

首先引出Python中自带的2个函数:

  1. file.seek(off, whence=0) 从文件中移动off个操作标记(文件指针),正往结束方向移动,负往开始方向移动。如果设定了whence参数,就以whence设定的起始位为准,0代表从头开始,1代表当前位置,2代表文件最末尾位置。
  2. file.tell() 返回文件指针的当前位置。

那么思路就比较清晰了,写一个死循环,循环内记录指针位置并赋值给seek函数:

1
2
3
4
5
6
7
8
9
10
with open("test.txt") as f:
f.seek(0,2) #把指针置尾
while 1:
cp = f.tell() #获取当前位置
#print cp
line = f.readline()
if line:
print line
else:
f.seek(cp)#将指针移动到本次循环的位置

一个简单的模拟tail命令的脚本就完成了,然后在github上发现这么一个程序,原理上都一样,不过作者对其进行了更好的封装:https://github.com/kasun/python-tail

另外在python2.7中while 1的效率比while True的高。

评论和分享

suds库使用小记

发布在 Python

虽说接触过的大部分接口都是以restfullapi的形式返回json数据,但最近有些接口是soap的,如果只是需要一个python的soap客户端的话,suds库是十分不错的一个选择,文档

使用pip安装即可,最基础的使用方法如下,示例来源官网:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from suds.client import Client
url = 'http://localhost:7080/webservices/WebServiceTestBean?wsdl'
client = Client(url)
print client
Suds - version: 0.3.3 build: (beta) R397-20081121
Service (WebServiceTestBeanService) tns="http://test.server.enterprise.rhq.org/"
Prefixes (1):
ns0 = "http://test.server.enterprise.rhq.org/"
Ports (1):
(Soap)
Methods:
addPerson(Person person, )
echo(xs:string arg0, )
getList(xs:string str, xs:int length, )
getPercentBodyFat(xs:string name, xs:int height, xs:int weight)
getPersonByName(Name name, )
hello()
testExceptions()
testListArg(xs:string[] list, )
testVoid()
updatePerson(AnotherPerson person, name name, )
Types (23):
Person
Name
Phone
AnotherPerson

其中methods就是实现各个功能的方法,types则定义了数据类型:

1
2
result = client.service.getPercentBodyFat('jeff', 68, 170)
print result

比如string,int等简单类型就不多说了,如果想知道Person类型是什么,则可以使用:

1
2
person = client.factory.create('Person')
print person

来查看具体细节:

1
2
3
4
5
6
7
8
9
10
(Person)=
{
phone = []
age = NONE
name(Name) =
{
last = NONE
first = NONE
}
}

同理phone,name都是一种类型,可以使用ractory.create方法进行创建并赋值:

1
2
3
4
5
6
7
8
9
10
phone = client.factory.create('Phone')
phone.npa = 202
phone.nxx = 555
phone.number = 1212
name = client.factory.create('Name')
name.first = 'Elmer'
name.last = 'Fudd'
person.name = name
person.age = 35
person.phone = [phone]

然后就可以调用相关的方法了:client.service.addPerson(person)

有些接口还需要设置soap:Header属性,使用client.set_options(soapheaders=(userid,password))

这里需要注意的就是如果header中有多个节点的话,要使用一个元组添加而不是调用多次set_options函数.

再比较有用的就是如果想看suds构造出的xml信息的话,使用

1
2
3
import logging
logging.basicConfig(level=logging.INFO)
logging.getLogger('suds.client').setLevel(logging.DEBUG)

就可以了,如果还想看更详细的信息,比如请求头,状态码等,还可以使用
logging.getLogger('suds.transport').setLevel(logging.DEBUG)
至于更高级的用法,比如安全认证,xml信息修改注入,多server调用等文档描述的也很详细.

其实soap接口的本质就是向某个url以post方式提交一个xml格式的字符串,当然http头信息也需要相应变化,比如:

1
2
3
4
headers = {"Content-Type": "text/xml; charset=utf-8",
"SOAPAction": "http://www.xxxx.com/service/xxxx",
"Host": "www.xxxx.com"
}

可以使用requests以及urllib2等库发送请求,得到结果使用正则或lxml进行处理.

之前我由于某些原因不想使用suds就是这么做的,这种方法有2点需要注意:

  1. 构造xml字符串的编码问题,以及特殊符号转意:’&’转成’&amp;‘,”<”转成”&lt;
  2. 某些函数必须进行顺序调用(比如必须先调用登录函数才能调用查询函数…),注意把上一个调用结果的cookie带入到下一个调用中,建议使用requests库的session进行自动处理

评论和分享

python-memcached源码小窥

发布在 Python

以前经常使用python-memcached对内存进行操作,但应用都比较简单,最近需要有一个分布式缓存系统于是看了看关于twemproxy 以及 mcrouter 这两款分别由twitter和facebook开源的软件文档。这2个软件都能容易的扩展缓存节点以及自动删除问题节点,并且提供不同的算法把数据缓存到各个节点中。这时候我想起来使用python-memcached的时候,也可以使用多个节点,并且某个节点挂掉后并不影响整个缓存程序的使用,那么它是怎么将数据分配到不同的节点呢?以及怎么处理的故障节点呢?
python-memcached的源码只有一个文件,不管是get或者set,取得服务节点Ip的函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
_SERVER_RETRIES = 10 # how many times to try finding a free server.
def _get_server(self, key):
print(key)
if isinstance(key, tuple):
serverhash, key = key
else:
serverhash = serverHashFunction(key)
if not self.buckets:
return None, None
for i in range(Client._SERVER_RETRIES):
print(self.buckets)
print (serverhash)
print(serverhash % len(self.buckets))
server = self.buckets[serverhash % len(self.buckets)]
if server.connect():
print("(using server %s)" % server,)
return server, key
serverhash = str(serverhash) + str(i)
if isinstance(serverhash, six.text_type):
serverhash = serverhash.encode('ascii')
serverhash = serverHashFunction(serverhash)
return None, None
```
其中print语句是我添加的,为了更清晰的看出原因。首先我们创建一个对象并且设置一个值:
```python
In [4]: mc = meme.Client(['127.0.0.1:11211','192.168.0.202:11211','192.168.0.203:11211'])
In [5]: mc.set("asdf","asdf")
asdf
[<meme._Host object at 0x7f2df825c390>, <meme._Host object at 0x7f2df825c3d0>, <meme._Host object at 0x7f2df825c410>]
20777
2
(using server inet:192.168.0.203:11211)
Out[5]: True

可以看到,这个”asdf”被放到了节点3中。再来多几个数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
In [23]: mc.set("a","test")
a
[<meme._Host object at 0x7f2df825c390>, <meme._Host object at 0x7f2df825c3d0>, <meme._Host object at 0x7f2df825c410>]
26807
2
(using server inet:192.168.0.203:11211)
Out[23]: True
In [24]: mc.set("b","test")
b
[<meme._Host object at 0x7f2df825c390>, <meme._Host object at 0x7f2df825c3d0>, <meme._Host object at 0x7f2df825c410>]
29118
0
(using server inet:127.0.0.1:11211)
Out[24]: True
In [25]: mc.set("c","test")
c
[<meme._Host object at 0x7f2df825c390>, <meme._Host object at 0x7f2df825c3d0>, <meme._Host object at 0x7f2df825c410>]
1721
2
(using server inet:192.168.0.203:11211)
Out[25]: True
In [26]: mc.set("d","test")
d
[<meme._Host object at 0x7f2df825c390>, <meme._Host object at 0x7f2df825c3d0>, <meme._Host object at 0x7f2df825c410>]
6365
2
(using server inet:192.168.0.203:11211)
Out[26]: True
In [27]: mc.set("e","test")
e
[<meme._Host object at 0x7f2df825c390>, <meme._Host object at 0x7f2df825c3d0>, <meme._Host object at 0x7f2df825c410>]
28634
2
(using server inet:192.168.0.203:11211)
Out[27]: True
In [28]: mc.set("f","test")
f
[<meme._Host object at 0x7f2df825c390>, <meme._Host object at 0x7f2df825c3d0>, <meme._Host object at 0x7f2df825c410>]
30419
2
(using server inet:192.168.0.203:11211)
Out[28]: True
In [29]: mc.set("g","test")
g
[<meme._Host object at 0x7f2df825c390>, <meme._Host object at 0x7f2df825c3d0>, <meme._Host object at 0x7f2df825c410>]
468
0
(using server inet:127.0.0.1:11211)
Out[29]: True
In [30]: mc.set("h","test")
h
[<meme._Host object at 0x7f2df825c390>, <meme._Host object at 0x7f2df825c3d0>, <meme._Host object at 0x7f2df825c410>]
4459
1
(using server inet:192.168.0.202:11211)
Out[30]: True

好像并没有什么规律,但有心的读者肯定发现了,程序的流程就是根据key算出一个hash值,再根据这个hash值对总节点数进行取余数。换言之,数据被存储在哪个节点完全是由key来决定的!在某些极端情况下,可能出现所有的数据都存储在同一个节点的情况。

用于hash的函数如下:

1
2
3
4
def cmemcache_hash(key):
return (
(((binascii.crc32(key) & 0xffffffff) >> 16) & 0x7fff) or 1)
serverHashFunction = cmemcache_hash

首先对key进行crc32操作,这里注意python2.x版本中crc计算后得到的是有符号整数(- 2^31—-2^31-1),所以需要使用位操作& 0xffffffff将其转成无符号整数,然后在向右位移16位截取高16位,再与0x7fff进行位操作将值变成正数。(比较疑惑,不知道这么做后key转换出来的值真的不会重复吗?算法渣渣啊….)

那么,如果一台节点挂了,似乎并没有影响整个缓存的使用啊?比如上面key=h的数据应该存放在第二个节点上,现在关闭第二个节点(192.168.0.202),再取值:

1
2
3
4
5
6
7
8
9
In [35]: mc.get('h')
h
[<meme._Host object at 0x7f2df825c390>, <meme._Host object at 0x7f2df825c3d0>, <meme._Host object at 0x7f2df825c410>]
4459
1
[<meme._Host object at 0x7f2df825c390>, <meme._Host object at 0x7f2df825c3d0>, <meme._Host object at 0x7f2df825c410>]
26423
2
(using server inet:192.168.0.203:11211)

可以看出,就算某个节点挂了,对于mc来说,backets还是有3个节点的,它并不会把失效节点从“池子”中移除。不过节点2链接不上,于是程序把上次得到的hash值于循环次数进行拼接后再hash,看其能否得到一个可以链接的节点。这次结果是得到了节点3并且可以链接,于是程序去节点3寻找key=h的值,但第三个节点并没有,所以返回none。换言之,即使节点1有key=h的值,程序也不会理会的。
那么,我再设置一个key=h的数据试试:

1
2
3
4
5
6
7
8
9
In [36]: mc.set("h","test")
h
[<meme._Host object at 0x7f2df825c390>, <meme._Host object at 0x7f2df825c3d0>, <meme._Host object at 0x7f2df825c410>]
4459
1
[<meme._Host object at 0x7f2df825c390>, <meme._Host object at 0x7f2df825c3d0>, <meme._Host object at 0x7f2df825c410>]
26423
2
(using server inet:192.168.0.203:11211)

结果不出所料,程序还是先去找节点2,发现链接不上就继续hash,直到找到一个能链接的节点为止。极端情况下就循环10次都没碰到可用节点的话,程序就认为所有节点都挂了。

这种逻辑下,假设程序经过2次hash后决定把数据存在节点3后,节点2恢复正常,再进行get操作会发现数据还是取不到的。

结论:

python-memcached无法动态进行节点的扩展或者删除,简单的应用或者只有一个缓存节点时,python-memcached还是很给力的,但如果需要的是一个分布式缓存集群的话,还是使用上面提到的那2个程序更高效、灵活。

评论和分享

Roy.S

微信公众号:hi-roy


野生程序员


China