Python 主机状态监控模块:psutil

这几天毕设需要加一个主机监控的功能,然后发现了一个 python 的跨平台的主机监控模块:psutil。

文档: https://pythonhosted.org/psutil/

Github: https://github.com/giampaolo/psutil

安装

pip install psutil

常用功能

因为毕设中只用到了 CPU,内存,磁盘这三个属性,但是这个库能获得的状态不止这些,还有网络,传感器和各种系统属性。只写下自己用到的,其他的文档介绍的挺全面。

CPU

psutil.cpu_times(percpu=False)

cpu_times 函数接受一个参数,percpu,默认为 False。

>>> psutil.cpu_times()
scputimes(user=33335.82, nice=0.0, system=35253.52, idle=405130.12)

执行之后将返回 user mode 和 kernel mode 的 CPU 时间(user,system),以及 CPU 空转的时间(idle)。

如果加上 percpu 参数,之后,将返回每个核心的信息:

>>> psutil.cpu_times(percpu=True)
[scputimes(user=12113.43, nice=0.0, system=14012.23, idle=92520.62), scputimes(user=4881.28, nice=0.0, system=4959.2, idle=108794.75), scputimes(user=11446.36, nice=0.0, system=11263.96, idle=95924.97), scputimes(user=4969.93, nice=0.0, system=5093.61, idle=108571.62)]

如果只是计算 CPU 的利用率的话,psutil.cpu_percent() 就够解决问题,cpu_percent 可以传递一个间隔参数,来计算一定间隔内的 CPU 利用率:

>>> psutil.cpu_percent(1)
13.7

内存

因为我只需要计算内存的使用率,所以只用到了 virtual_memory 方法。这个方法返回的参数很多,不过也是一目了然。

>>> psutil.virtual_memory()
svmem(total=17179869184L, available=4261830656L, percent=75.2, used=13366374400L, free=2298265600L, active=9087324160L, inactive=1963565056L, wired=2315485184L)

磁盘

disk_partitions 方法可以查看磁盘的使用情况,会返回一个 list,包含了比较全面的信息:

>>> psutil.disk_partitions()
[sdiskpart(device='/dev/disk1', mountpoint='/', fstype='hfs', opts='rw,local,rootfs,dovolfs,journaled,multilabel'), sdiskpart(device='/dev/disk0s3', mountpoint='/Volumes/Recovery HD', fstype='hfs', opts='rw,local,dovolfs,dontbrowse,journaled,multilabel')]

当然,以上只是提到了最简单的功能,还有很多使用的函数可以选择。

2017/06/05 15:49 下午 posted in  Python 黑魔法

BeautifulSoup 常用用法

安装

pip install beautifulsoup4

解析器需要单独安装,html 解析器:

pip install html5lib

更多安装相关查看文档: 安装解析器

解析

搜索引擎收录的中文文档有点老旧,直接使用会报错,但是解析是一件很简单的事情:

from bs4 import BeautifulSoup
soup = BeautifulSoup(html_txt, "html5lib")

定位

定位分两种,一个是直接根据标签:

soup.title
# <title>The Dormouse's story</title>

soup.title.name
# u'title'

soup.title.string
# u'The Dormouse's story'

soup.title.parent.name
# u'head'

soup.p
# <p class="title"><b>The Dormouse's story</b></p>

soup.p['class']
# u'title'

soup.a
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>

另一种是通过 find 方法,该方法支持查找,包括 id,class,标签:

soup.find_all('a')
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

soup.find(id="link3")
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
2017/04/01 11:42 上午 posted in  Python 黑魔法

requests 常用用法

安装

$ pip install requests

使用

GET:

payload = {'key1': 'value1', 'key2': 'value2'}
r = requests.get('https://github.com/timeline.json', params=payload)

POST

r = requests.post("http://httpbin.org/post", data=data)

其他方法:

r = requests.put("http://httpbin.org/put")
r = requests.delete("http://httpbin.org/delete")
r = requests.head("http://httpbin.org/get")
r = requests.options("http://httpbin.org/get")

响应

对于获取普通的 body,可以从 text 属性中获得:

import requests
r = requests.get('https://github.com/timeline.json')
r.text
u'[{"repository":{"open_issues":0,"url":"https://github.com/...

对于二进制数据,可以使用 r.content, 对于 json 的话,可以直接使用 r.json(),该方法会返回一个被解析的字典或列表。

无论是请求还是相应,用法比较符合直觉,更详细见文档: http://docs.python-requests.org/zh_CN/latest/user/quickstart.html

Session

虽然 http 是无状态的,但是请求往往是连续的,状态一般存储在 cookie 里。requests.Session 类便提供了一个 cookie 持续的请求方式,一般自定义 http client 都会继承自这个类。

用法:

>>> import requests
>>> s = requests.Session()
>>> s.get('http://httpbin.org/get')
<Response [200]>

# with
>>> with requests.Session() as s:
>>>     s.get('http://httpbin.org/get')
<Response [200]>

更多的参数可以参考相关文档:Session 更多参数

2017/03/15 11:42 上午 posted in  Python 黑魔法

Python 中的 id() 函数

在 python 中,有个 id() 函数,是用来获得一个 Object 的 id 的,在文档中介绍也非常简单:

id(object)

Return the “ identity ” of an object. This is an integer (or long integer) which is guaranteed to be unique and constant for this object during its lifetime. Two objects with non-overlapping lifetimes may have the same id() value.

这是 python 为每个 Object 在运行时定制了一个唯一 id,但是需要注意的是,当两个 Object 当生命域不重叠的时候,可能会有相同的 id。

Object's Identity

对于任何一个 Object 来说,都有一个 id,如果多个变量指向了同一个静态常量,那么这几个变量讲拥有同样的 id。

>>> s1 = "abc"
>>> s2 = "abc"
>>> s1 == s2
True
>>> id(s1)
4390873208
>>> id(s2)
4390873208
>>> s1 is s2
True

如果两个不同的变量(常量),肯定将有不同的 id。

>>> id("abc")
4390873208
>>> id("abcd")
4391753216

is 比较

在 python 中,有两种常用的比较方式,一个是 == 比较,一个是 is 比较,前者是用来比较值是不是相等,后者是判断是不是属于同一个对象。其实,在 python 中,is 比较便是通过 id() 来实现的。当两个 Objec 拥有相同的 id 时,便会认为这是两个相同的对象。

因此,可以认为:object1 is object2 等价于 id(object1) == id(object2)

例外

今天在 v2ex 看到一个好玩的帖子(https://www.v2ex.com/t/336021 ),提到了 id([1]) == id([2]) 会返回 True

而我跑了一组数据,结果也是很有意思:

>>> id([1]) 
4391579520 
>>> id([2]) 
4391579520 
>>> a = [1] 
>>> b = [1] 
>>> id([1]) 
4391757944 
>>> id([2]) 
4391757944 
>>> id(a) 
4391579520 
>>> id(b) 
4391641672 
>>> id([1]) 
4391787072
>>> id([2]) 
4391787072  

因为在文档中已经提到了,两个不同生命域的对象可能会有相同的 id,因此出现这种情况也不例外,毕竟 [1][2],现在的确是两个不同的生命域的常量,因为这两个常量没有被任何变量引用,当使用结束之后,便会被回收掉,因此会出现两个不同的常量出现相同 id 的情况。

当给他们添加引用,会发现这个 id 就被确定到这个一个 [1] 常量上了,这是因为 这个 [1] 的生命域并没有结束(还被引用不能被回收),因此,新的 list object 不会再使用这个 id 而是换了新的 id。

2018.11.16 更新

今天又看到一种新的验证方式,定义如下类:

class WTF(object):
  def __init__(self): print("I")
  def __del__(self): print("D")

is 比较时,两个对象先创建再比较:

In [2]: WTF() is WTF()
I
I
D
D
Out[2]: False

id 比较时,当对象的 id 被获得之后,对象就会被注销,新对象恰好会分配在原内存空间,因此得到 True。

In [3]: id(WTF()) == id(WTF())
I
D
I
D
Out[3]: True
2017/01/21 19:22 下午 posted in  Python 黑魔法

flask如何优雅的处理异常

如果使用flask编写RESTful风格的程序,不可避免的就是异常的处理问题。flask默认的异常处理方式实在不敢恭维,如果想达到能友好的返回错误信息,自己还要多做不少工作。

什么算优雅的返回错误信息呢?举个例子,下面的代码是获取User token的一段代码,如果密码验证失败将会向前端反馈错误信息,而反馈是通过抛出异常:

def get_user_token(self, data):
  valid(get_token, data)

  with get_session() as session:
  if "username" in data:
      user = session.query(User).filter_by(user_name=data['username']).first()
  elif "email" in data:
      user = session.query(User).filter_by(email=data['email']).first()
  else:
    user = None
    if not user or not user.valid_passwd(data['password']):
      raise APIException("get_token_error", "can't get the token with a wrong user password", 400)

  expiration = data['expiration'] if "expiration" in data else 86400
  return {
    "token": user.generate_auth_token(expiration=expiration),
    "id": user.id,
  }

那么可以在上层逻辑捕获这个异常,并把异常的信息包装一下优雅的反馈给前端,比如这样处理:

try:
  get_user_token(data)
except APIExcepting as e:
  return json.dumps({
    "id": e.error_id,
    "message": e.message,
    "code": e.code,
  })

当然,这只是最简单的包装,而且是http状态码写在了response的body里,而最好应该直接返回相应状态码的response。

每个下层逻辑的异常由上层来处理是很常规的办法,但是有的异常是可以直接抛到最上层的,比如参数错误,密码错误等,这些异常差的只是一个友好的显示,如果有办法对这些异常自动封装一个友好的反馈显示,那么就省不少事情。

而利用flask是可以实现这一点的,flask有一个叫errorhandler的东西,它可以捕捉特定的异常,然后根据这个异常进行自定义操作。那么,我们就可以创建一个可以直接抛到最上层的异常,由errorhandler监听捕捉这个异常,把捕捉到的异常封装成友好的response反馈出去就可以了。

首先是写一个可以被抛到最上层的异常:

class APIException(Base):
    def __init__(self, error_id, message, code=500):
        super(Base, self).__init__()
        self.raw_message = message
        self.error_id = error_id
        self.code = code
        self.message = message

    def to_dict(self):
        result = {
            "id": self.error_id,
            "code": self.code,
            "message": self.message,
        }
        return result

这个异常使用起来也很简单raise APIException(error_id, message, status_code)就可以了,而to_dict方法可以构建一个符合REST风格的错误信息返回body。下面就差一个错误处理句柄来把APIEception的实例封装成友好的response了。

@app.errorhandler(APIException)
def handle_api_exception(error):
    from flask import jsonify
    response = jsonify(error.to_dict())
    response.status_code = error.code
    return response

这样就有了一个处理APIException的句柄,当有这个异常抛出的话,它便会返回一个友好的body,而且状态码也会是在raise时制定的状态码。

2016/05/15 12:29 下午 posted in  Python 黑魔法