Tornado异步笔记(三)— 持久连接 KeepAlive 简介

 tornado  Tornado异步笔记(三)— 持久连接 KeepAlive 简介已关闭评论
12月 062018
 

网上看到的3篇连载关于tornado异步的文章,写的很好,虽然有些内容有些老了,但不影响文章的借鉴意义,强烈推荐:

转自:https://www.jianshu.com/p/3cc234198567

HTTP 持久连接

HTTP通信中,client和server一问一答的方式。HTTP是基于TCP的应用层协议,通常在发送请求之前需要创建TCP连接,然后在收到响应之后会断开这个TCP连接。这就是常见的http短连接。既然有短连接,那么也有长连接。

HTTP协议最初的设计是无连接无状态的方式。为了维护状态,引入了cookie和session方式认证识别用户。早期的web开发中,为了给用户推送数据,通常使用所谓的长连接。那时的长连接还是基于短连接的方式实现,即通过client的轮询查询,在用户层面看起来连接并没有断开。随着技术的发展,又出现了Websockt和MQTT等通信协议。Websockt和MQTT则是全双工的通信协议。

相比全双工实现的长连接,我们还会在web开发中遇到伪长连接。即HTTP协议中的keepalive模式。因为HTTP设计是无连接设计,请求应答结束之后就关闭了TCP连接。在http通信中,就会有大量的新建和销毁tcp连接的过程,那怕是同一个用户同一个客户端。为了优化这种方式,HTTP提出了KeepAlive模式,即创建的tcp连接后,传输数据,server返回响应之后并不会关掉tcp连接,下一次http请求就能复用这个tcp连接。

这是一种协商式的连接,毕竟每次的http发送数据的时候,还是要单独为每个请求发送header之类的信息。相比全双工的websocket,一旦创建了连接,下一次就不需要再发送header,直接发送数据即可。因此描述http的keepalive应该是持久连接(HTTP persistent connection )更准确。

keepalive 简介

HTTP的keepalive模式提供了HTTP通信的时候复用TCP连接的协商功能。http1.0默认是关闭的,只有在http的header加入Connection: Keep-Alive才能开启。而http1.1则正相反,默认就打开了,只有显示的在header里加入Connection: close才能关闭。现在的浏览器基本都是http1.1的协议,能否使用长连接,权看服务器的支持状况了。下图说明了开启keepalive模式的持久连接与短连接的通信示意图

短连接与持久连接,图片来源网络

当开启了持久连接,就不能使用返回EOF的方式来判断数据结尾了。对于静态和动态的数据,可以使用Conent-Lenght和Transfer-Encoding`来做应用层的区分。

requests与持久连接

了解了keeplive模式,接下来我们就来使用keepalive方式。服务器使用Tornado,tornado实现了keepalive的处理,客户端我们可以分别使用同步的requests和异步的AsyncHTTPClient。

先写一个简单的服务器:

micro-server.py

import tornado.httpserver 
import tornado.ioloop 
import tornado.web 
class IndexHandler(tornado.web.RequestHandler): 
def get(self, *args, **kwargs): 
        self.finish('It works')

app = tornado.web.Application(
handlers=[
('/', IndexHandler),
],
debug=True ) if __name__ == '__main__':
server = tornado.httpserver.HTTPServer(app)
server.listen(8000)
tornado.ioloop.IOLoop().instance().start()

requests 短连接

requests不愧是一个”for human” 的软件,实现一个http客户端非常简单。

import argparse 
import requests

url = 'http://127.0.0.1:8000' 
def short_connection(): 
    resp = requests.get(url)
    print(resp.text)

    resp = requests.get(url)
    print(resp.text) 
def long_connection(): 
    pass 
if __name__ == '__main__':
    ap = argparse.ArgumentParser()
    ap.add_argument("-t", "--type", default="short")
    args = ap.parse_args()
    type_ = args.type if type_ == 'short':
        short_connection() elif type_ == 'long':
        long_connection()

运行keepalive python requests-cli.py –type=short,可以看见返回了数据,同时通过另外一个神器wireshark抓包如下:

requests 短连接

从抓包的情况来看,两次http请求,一共创建了两次tcp的握手连接和挥手断开。每次发送http数据都需要先创建tcp连接,然后就断开了连接。通常是客户端发起的断开连接。

requests 持久连接

requests的官网也说明了,基于urllib3的方式,requests百分比实现了keepalive方式,只需要创建一个客户端session即可,代码如下:

def long_connection(): s = requests.Session()

    resp = s.get(url)
    print(resp.text)

    resp = s.get(url)
    print(resp.text)

    s.close()

再次通过抓包如下图:

requests 持久连接模式

可以看到,同样也是两次http请求,只创建了一次tcp的握手和挥手。两次http请求都基于一个tcp连接。再次查看包43,可以看到下图中的报文header指定了keepalive。

http请求的数据包

AsyncHTTPClient与持久连接

tornado是一个优秀高性能异步非阻塞(non-block)web框架。如果torando的handler中也需要请求别的三方资源,使用requests的同步网络IO,将会block住整个tornado的进程。因此tornado也实现了异步的http客户端AsyncHTTPClient。

短连接

使用AsyncHTTPClient也不难,但是想要使用其异步效果,就必须把其加入事件循环中,否则只有连接的创立,而没有数据的传输就退出了。

import tornado.httpclient
import tornado.ioloop 
import time

url = 'http://127.0.0.1:8000' 
def handle_response(response):
 if response.error:
        print("Error: %s" % response.error) else:
        print(response.body)

http_client = tornado.httpclient.AsyncHTTPClient()
http_client.fetch(url, handle_response)
http_client.fetch(url, handle_response)

运行上述代码,将会看到wirshark中,创建了两次TCP连接和断开了连接,并没有发送http数据。为了发送http数据,还需要加入tornado的事件循环。即在最后一行加入tornado.ioloop.IOLoop.instance().start()

再次运行,客户端正常收到了数据,抓包如下:

async http client 短连接

抓包的结果咋一看像是持久连接,仔细一看却有两次握手和挥手的操作。的确,客户端发送异步http请求的时候,创建了两个端口49989和49990两个tcp连接。因为是异步的请求,因此先创建了两个连接,然后才发送数据,发送数据的时候都是基于所创建的端口进行的。也就是没有使用持久连接。

持久连接

AsyncHTTPClient使用持久连接也很简单。现在流行微服务架构。通常提供给客户端的服务称之为网关,网关从各种微服务中调用获取数据,通信的方式中,同步的有http和rpc,异步的有mq之类的。而http通常都是使用持久连接的方式。

下面我们介绍一下在tornado server的handler中使用async client请求微服务的资源。

再写一个简单server

#!/usr/bin/env python 
# -*- coding:utf-8 -*- 
import tornado.gen 
import tornado.httpclient 
import tornado.httpserver 
import tornado.ioloop 
import tornado.web 
class AsyncKeepAliveHandler(tornado.web.RequestHandler):
  @tornado.web.asynchronous
  @tornado.gen.coroutine
    def get(self, *args, **kwargs):
        url = 'http://127.0.0.1:8000/'
        http_client = tornado.httpclient.AsyncHTTPClient()
    response = yield tornado.gen.Task(http_client.fetch, url) 
    print response.code print response.body
        self.finish("It works")

app = tornado.web.Application(
        handlers=[
            ('/async/keepalive', AsyncKeepAliveHandler)
        ],
        debug=True ) 
if __name__ == '__main__':
    server = tornado.httpserver.HTTPServer(app)
    server.listen(5050)
    tornado.httpclient.AsyncHTTPClient.configure("tornado.curl_httpclient.CurlAsyncHTTPClient")
    tornado.ioloop.IOLoop().instance().start()

然后我们请求5050端口的服务,也连接发送两次http请求:

(venv)☁  keepalive  curl http://127.0.0.1:5050/async/keepalive It works%                                                                                                                                                     (venv)☁  keepalive  curl http://127.0.0.1:5050/async/keepalive It works%

再看我们的抓包情况:

tornado handler使用持久连接

从图中可以看到,即使是两个请求,最终都是复用了断开为50784的tcp连接。

因为asynchttpclient默认使用的是SimpleAsyncHTTPClient,实现持久连接只需要配置一下tornado.httpclient.AsyncHTTPClient.configure(“tornado.curl_httpclient.CurlAsyncHTTPClient”)即可。当然,这个需要tornado的版本4.2以上,当前的版本是4.5。

CurlAsyncHTTPClient依赖于pycurl。pycurl又依赖libcurl。在安装pycurl的时候,可能会出现link的问题。例如ImportError: pycurl: libcurl link-time version (7.37.1) is older than compile-time version (7.43.0) 。 解决了link问题,如果是mac系统,安装的时候可能出现error: Setup script exited with error: command ‘cc’ failed,多半是由于xcode做鬼,这里有一个解决说明

AsyncHTTPClient设置成为keepalive模式是全局性的,比较tornado是单进程单线程的,访问三方或者微服务,都是一个客户端,所有的模式都是持久连接。

短连接与持久连接的应用场景

持久连接可以减少tcp连接的创建和销毁,提升服务器的处理性能。但是并不是所有连接都得使用持久连接。长短连接都有其使用场景。

既然持久连接在于连接的持久,因此对于频繁通信,点对点的就可以使用。例如网关和微服务之间。如果创建了持久连接,就必须在意连接的存活状态。客户端一般不会主动关闭,因此服务端需要维护这个连接状态,对于一些长时间没有读写事件发生的连接,可以主动断开,节省资源。

对于一些用完就走的场景,也不需要使用持久连接。而另外一些需要全双工通信,例如推送和实时应用,则需要真正的长连接,比如MQTT实现推送和websocket实现实时应用等。

总结

微服务大行其道,从微观来看,增加了更多的网络IO。而IO又是最耗时的操作。相比之下,程式的计算速度就显得没那么紧要了。优化网络IO才是提升性能的关键。一些频繁通信的场景,使用持久连接或长连接更能优化大量TCP连接的创建和销毁。

就Python的而言,Tornado的诞生就是为了解决网络IO的瓶颈。并且很多tornado及其三方库的问题,都能在github和stackoverflow找到作者的参与和回答。可见作者对项目的负责。由于tornado单线程的特性,因此做任何IO操作,都需要考虑是否block。幸好有AsyncHTTPClinet,既可以提供异步IO,也可以实现持久连接,当然,tornado也支持websocket。

Tornado异步笔记(二)— 异步客户端AsyncHTTPClient

 tornado  Tornado异步笔记(二)— 异步客户端AsyncHTTPClient已关闭评论
12月 062018
 

网上看到的3篇连载关于tornado异步的文章,写的很好,虽然有些内容有些老了,但不影响文章的借鉴意义,强烈推荐:

转自: https://www.jianshu.com/p/ff9cb6dea27a/


Tornado异步笔记(二)— 异步客户端
前面了解Tornado的异步任务的常用做法,姑且归结为异步服务。通常在我们的服务内,还需要异步的请求第三方服务。针对HTTP请求,Python的库Requests是最好用的库,没有之一。官网宣称:HTTP for Human。然而,在tornado中直接使用requests将会是一场恶梦。requests的请求会block整个服务进程。
上帝关上门的时候,往往回打开一扇窗。Tornado提供了一个基于框架本身的异步HTTP客户端(当然也有同步的客户端)— AsyncHTTPClient。
AsyncHTTPClient 基本用法
AsyncHTTPClient是 tornado.httpclinet 提供的一个异步http客户端。使用也比较简单。与服务进程一样,AsyncHTTPClient也可以callback和yield两种使用方式。前者不会返回结果,后者则会返回response。
如果请求第三方服务是同步方式,同样会杀死性能。
class SyncHandler(tornado.web.RequestHandler):
    def get(self, *args, **kwargs):

        url = ‘https://api.github.com/’
        resp = requests.get(url)
        print resp.status_code

        self.finish(‘It works’)

使用ab测试大概如下:
Document Path:          /sync
Document Length:        5 bytes

Concurrency Level:      5
Time taken for tests:   10.255 seconds
Complete requests:      5
Failed requests:        0
Total transferred:      985 bytes
HTML transferred:       25 bytes
Requests per second:    0.49 [#/sec] (mean)
Time per request:       10255.051 [ms] (mean)
Time per request:       2051.010 [ms] (mean, across all concurrent requests)
Transfer rate:          0.09 [Kbytes/sec] received

性能相当慢了,换成AsyncHTTPClient再测:
class AsyncHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self, *args, **kwargs):

        url = ‘https://api.github.com/’
        http_client = tornado.httpclient.AsyncHTTPClient()
        http_client.fetch(url, self.on_response)
        self.finish(‘It works’)

    @tornado.gen.coroutine
    def on_response(self, response):
        print response.code
        

qps 提高了很多
Document Path:          /async
Document Length:        5 bytes

Concurrency Level:      5
Time taken for tests:   0.162 seconds
Complete requests:      5
Failed requests:        0
Total transferred:      985 bytes
HTML transferred:       25 bytes
Requests per second:    30.92 [#/sec] (mean)
Time per request:       161.714 [ms] (mean)
Time per request:       32.343 [ms] (mean, across all concurrent requests)
Transfer rate:          5.95 [Kbytes/sec] received

同样,为了获取response的结果,只需要yield函数。
class AsyncResponseHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    @tornado.gen.coroutine
    def get(self, *args, **kwargs):

        url = ‘https://api.github.com/’
        http_client = tornado.httpclient.AsyncHTTPClient()
        response = yield tornado.gen.Task(http_client.fetch, url)
        print response.code
        print response.body

AsyncHTTPClient 转发
使用Tornado经常需要做一些转发服务,需要借助AsyncHTTPClient。既然是转发,就不可能只有get方法,post,put,delete等方法也会有。此时涉及到一些 headers和body,甚至还有https的waring。
下面请看一个post的例子, yield结果,通常,使用yield的时候,handler是需要 tornado.gen.coroutine。
headers = self.request.headers
body = json.dumps({‘name’: ‘rsj217’})
http_client = tornado.httpclient.AsyncHTTPClient()

resp = yield tornado.gen.Task(
    self.http_client.fetch, 
    url,
    method=”POST”, 
    headers=headers,
    body=body, 
    validate_cert=False)

AsyncHTTPClient 构造请求
如果业务处理并不是在handlers写的,而是在别的地方,当无法直接使用tornado.gen.coroutine的时候,可以构造请求,使用callback的方式。

body = urllib.urlencode(params)
req = tornado.httpclient.HTTPRequest(
  url=url, 
  method=’POST’, 
  body=body, 
  validate_cert=False)  

http_client.fetch(req, self.handler_response)

def handler_response(self, response):
    
    print response.code

用法也比较简单,AsyncHTTPClient中的fetch方法,第一个参数其实是一个HTTPRequest实例对象,因此对于一些和http请求有关的参数,例如method和body,可以使用HTTPRequest先构造一个请求,再扔给fetch方法。通常在转发服务的时候,如果开起了validate_cert,有可能会返回599timeout之类,这是一个warning,官方却认为是合理的。
AsyncHTTPClient 上传图片
AsyncHTTPClient 更高级的用法就是上传图片。例如服务有一个功能就是请求第三方服务的图片OCR服务。需要把用户上传的图片,再转发给第三方服务。
class ApiAccountUploadHandler(helper.BaseHandler):
    @tornado.gen.coroutine
    @helper.token_require
    def post(self, *args, **kwargs):
        upload_type = self.get_argument(‘type’, None)

        files_body = self.request.files[‘file’]
        
        new_file = ‘upload/new_pic.jpg’
        new_file_name = ‘new_pic.jpg’

        # 写入文件
        with open(new_file, ‘w’) as w:
            w.write(file_[‘body’])

        logging.info(‘user {} upload {}’.format(user_id, new_file_name))

        # 异步请求 上传图片
        with open(new_file, ‘rb’) as f:
            files = [(‘image’, new_file_name, f.read())]

        fields = ((‘api_key’, KEY), (‘api_secret’, SECRET))

        content_type, body = encode_multipart_formdata(fields, files)

        headers = {“Content-Type”: content_type, ‘content-length’: str(len(body))}
        request = tornado.httpclient.HTTPRequest(config.OCR_HOST,
                                                 method=”POST”, headers=headers, body=body, validate_cert=False)

        response = yield tornado.httpclient.AsyncHTTPClient().fetch(request)

def encode_multipart_formdata(fields, files):
    “””
    fields is a sequence of (name, value) elements for regular form fields.
    files is a sequence of (name, filename, value) elements for data to be
    uploaded as files.
    Return (content_type, body) ready for httplib.HTTP instance
    “””
    boundary = ‘———-ThIs_Is_tHe_bouNdaRY_$’
    crlf = ‘\r\n’
    l = []
    for (key, value) in fields:
        l.append(‘–‘ + boundary)
        l.append(‘Content-Disposition: form-data; name=”%s”‘ % key)
        l.append(”)
        l.append(value)
    for (key, filename, value) in files:
        filename = filename.encode(“utf8”)
        l.append(‘–‘ + boundary)
        l.append(
                ‘Content-Disposition: form-data; name=”%s”; filename=”%s”‘ % (
                    key, filename
                )
        )
        l.append(‘Content-Type: %s’ % get_content_type(filename))
        l.append(”)
        l.append(value)
    l.append(‘–‘ + boundary + ‘–‘)
    l.append(”)
    body = crlf.join(l)
    content_type = ‘multipart/form-data; boundary=%s’ % boundary
    return content_type, body

def get_content_type(filename):
    import mimetypes

    return mimetypes.guess_type(filename)[0] or ‘application/octet-stream’

对比上述的用法,上传图片仅仅是多了一个图片的编码。将图片的二进制数据按照multipart 方式编码。编码的同时,还需要把传递的相关的字段处理好。相比之下,使用requests 的方式则非常简单:

files = {}
f = open(‘/Users/ghost/Desktop/id.jpg’)
files[‘image’] = f
data = dict(api_key=’KEY’, api_secret=’SECRET’)
resp = requests.post(url, data=data, files=files)
f.close()
print resp.status_Code

总结
通过AsyncHTTPClient的使用方式,可以轻松的实现handler对第三方服务的请求。结合前面关于tornado异步的使用方式。无非还是两个key。是否需要返回结果,来确定使用callback的方式还是yield的方式。当然,如果不同的函数都yield,yield也可以一直传递。这个特性,tornado的中的tornado.auth 里面对oauth的认证。
大致就是这样的用法。

Tornado异步笔记(一)— 异步任务

 tornado  Tornado异步笔记(一)— 异步任务已关闭评论
12月 062018
 

网上看到的3篇连载关于tornado异步的文章,写的很好,虽然有些内容有些老了,但不影响文章的借鉴意义,强烈推荐:

转自:https://www.jianshu.com/p/31fae7dd05ba

高性能服务器Tornado
Python的web框架名目繁多,各有千秋。正如光荣属于希腊,伟大属于罗马。Python的优雅结合WSGI的设计,让web框架接口实现千秋一统。WSGI 把应用(Application)和服务器(Server)结合起来。Django 和 Flask 都可以结合 gunicon 搭建部署应用。
与 django 和 flask 不一样,tornado 既可以是 wsgi 应用,也可以是 wsgi 服务。当然,选择tornado更多的考量源于其单进程单线程异步IO的网络模式。高性能往往吸引人,可是有不少朋友使用之后会提出疑问,tornado号称高性能,实际使用的时候却怎么感受不到呢?
实际上,高性能源于Tornado基于Epoll(unix为kqueue)的异步网络IO。因为tornado的单线程机制,一不小心就容易写出阻塞服务(block)的代码。不但没有性能提高,反而会让性能急剧下降。因此,探索tornado的异步使用方式很有必要。
Tornado 异步使用方式
简而言之,Tornado的异步包括两个方面,异步服务端和异步客户端。无论服务端和客户端,具体的异步模型又可以分为回调(callback)和协程(coroutine)。具体应用场景,也没有很明确的界限。往往一个请求服务里还包含对别的服务的客户端异步请求。
服务端异步方式
服务端异步,可以理解为一个tornado请求之内,需要做一个耗时的任务。直接写在业务逻辑里可能会block整个服务。因此可以把这个任务放到异步处理,实现异步的方式就有两种,一种是yield挂起函数,另外一种就是使用类线程池的方式。请看一个同步例子:

class SyncHandler(tornado.web.RequestHandler):

    def get(self, *args, **kwargs):
        # 耗时的代码
        os.system(“ping -c 2 www.google.com”)
        self.finish(‘It works’)

使用ab测试一下:
ab -c 5 -n 5 http://127.0.0.1:5000/sync

Server Software:        TornadoServer/4.3
Server Hostname:        127.0.0.1
Server Port:            5000

Document Path:          /sync
Document Length:        5 bytes

Concurrency Level:      5
Time taken for tests:   5.076 seconds
Complete requests:      5
Failed requests:        0
Total transferred:      985 bytes
HTML transferred:       25 bytes
Requests per second:    0.99 [#/sec] (mean)
Time per request:       5076.015 [ms] (mean)
Time per request:       1015.203 [ms] (mean, across all concurrent requests)
Transfer rate:          0.19 [Kbytes/sec] received
qps 仅有可怜的 0.99,姑且当成每秒处理一个请求吧。
下面祭出异步大法:
class AsyncHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    @tornado.gen.coroutine
    def get(self, *args, **kwargs):

        tornado.ioloop.IOLoop.instance().add_timeout(1, callback=functools.partial(self.ping, ‘www.google.com’))

        # do something others

        self.finish(‘It works’)

    @tornado.gen.coroutine
    def ping(self, url):
        os.system(“ping -c 2 {}”.format(url))
        return ‘after’
尽管在执行异步任务的时候选择了timeout 1秒,主线程的返回还是很快的。ab压测如下:
Document Path:          /async
Document Length:        5 bytes

Concurrency Level:      5
Time taken for tests:   0.009 seconds
Complete requests:      5
Failed requests:        0
Total transferred:      985 bytes
HTML transferred:       25 bytes
Requests per second:    556.92 [#/sec] (mean)
Time per request:       8.978 [ms] (mean)
Time per request:       1.796 [ms] (mean, across all concurrent requests)
Transfer rate:          107.14 [Kbytes/sec] received

上述的使用方式,通过tornado的IO循环,把可以把耗时的任务放到后台异步计算,请求可以接着做别的计算。可是,经常有一些耗时的任务完成之后,我们需要其计算的结果。此时这种方式就不行了。车道山前必有路,只需要切换一异步方式即可。下面使用协程来改写:
class AsyncTaskHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    @tornado.gen.coroutine
    def get(self, *args, **kwargs):
        # yield 结果
        response = yield tornado.gen.Task(self.ping, ‘ www.google.com’)
        print ‘response’, response
        self.finish(‘hello’)

    @tornado.gen.coroutine
    def ping(self, url):
        os.system(“ping -c 2 {}”.format(url))
        return ‘after’
可以看到异步在处理,而结果值也被返回了。

Server Software:        TornadoServer/4.3
Server Hostname:        127.0.0.1
Server Port:            5000

Document Path:          /async/task
Document Length:        5 bytes

Concurrency Level:      5
Time taken for tests:   0.049 seconds
Complete requests:      5
Failed requests:        0
Total transferred:      985 bytes
HTML transferred:       25 bytes
Requests per second:    101.39 [#/sec] (mean)
Time per request:       49.314 [ms] (mean)
Time per request:       9.863 [ms] (mean, across all concurrent requests)
Transfer rate:          19.51 [Kbytes/sec] received

qps提升还是很明显的。有时候这种协程处理,未必就比同步快。在并发量很小的情况下,IO本身拉开的差距并不大。甚至协程和同步性能差不多。例如你跟博尔特跑100米肯定输给他,可是如果跟他跑2米,鹿死谁手还未定呢。
yield挂起函数协程,尽管没有block主线程,因为需要处理返回值,挂起到响应执行还是有时间等待,相对于单个请求而言。另外一种使用异步和协程的方式就是在主线程之外,使用线程池,线程池依赖于futures。Python2需要额外安装。
下面使用线程池的方式修改为异步处理:
from concurrent.futures import ThreadPoolExecutor

class FutureHandler(tornado.web.RequestHandler):
    executor = ThreadPoolExecutor(10)

    @tornado.web.asynchronous
    @tornado.gen.coroutine
    def get(self, *args, **kwargs):

        url = ‘www.google.com’
        tornado.ioloop.IOLoop.instance().add_callback(functools.partial(self.ping, url))
        self.finish(‘It works’)

    @tornado.concurrent.run_on_executor
    def ping(self, url):
        os.system(“ping -c 2 {}”.format(url))

再运行ab测试:
Document Path:          /future
Document Length:        5 bytes

Concurrency Level:      5
Time taken for tests:   0.003 seconds
Complete requests:      5
Failed requests:        0
Total transferred:      995 bytes
HTML transferred:       25 bytes
Requests per second:    1912.78 [#/sec] (mean)
Time per request:       2.614 [ms] (mean)
Time per request:       0.523 [ms] (mean, across all concurrent requests)
Transfer rate:          371.72 [Kbytes/sec] received

qps瞬间达到了1912.78。同时,可以看到服务器的log还在不停的输出ping的结果。
想要返回值也很容易。再切换一下使用方式接口。使用tornado的gen模块下的with_timeout功能(这个功能必须在tornado>3.2的版本)。
class Executor(ThreadPoolExecutor):
    _instance = None

    def __new__(cls, *args, **kwargs):
        if not getattr(cls, ‘_instance’, None):
            cls._instance = ThreadPoolExecutor(max_workers=10)
        return cls._instance

class FutureResponseHandler(tornado.web.RequestHandler):
    executor = Executor()

    @tornado.web.asynchronous
    @tornado.gen.coroutine
    def get(self, *args, **kwargs):

        future = Executor().submit(self.ping, ‘www.google.com’)

        response = yield tornado.gen.with_timeout(datetime.timedelta(10), future,
                                                  quiet_exceptions=tornado.gen.TimeoutError)

        if response:
            print ‘response’, response.result()

    @tornado.concurrent.run_on_executor
    def ping(self, url):
        os.system(“ping -c 1 {}”.format(url))
        return ‘after’

线程池的方式也可以通过使用tornado的yield把函数挂起,实现了协程处理。可以得出耗时任务的result,同时不会block住主线程。
Concurrency Level:      5
Time taken for tests:   0.043 seconds
Complete requests:      5
Failed requests:        0
Total transferred:      960 bytes
HTML transferred:       0 bytes
Requests per second:    116.38 [#/sec] (mean)
Time per request:       42.961 [ms] (mean)
Time per request:       8.592 [ms] (mean, across all concurrent requests)
Transfer rate:          21.82 [Kbytes/sec] received

qps为116,使用yield协程的方式,仅为非reponse的十分之一左右。看起来性能损失了很多,主要原因这个协程返回结果需要等执行完毕任务。
好比打鱼,前一种方式是撒网,然后就完事,不闻不问,时间当然快,后一种方式则撒网之后,还得收网,等待收网也是一段时间。当然,相比同步的方式还是快了千百倍,毕竟撒网还是比一只只钓比较快。
具体使用何种方式,更多的依赖业务,不需要返回值的往往需要处理callback,回调太多容易晕菜,当然如果需要很多回调嵌套,首先优化的应该是业务或产品逻辑。yield的方式很优雅,写法可以异步逻辑同步写,爽是爽了,当然也会损失一定的性能。
异步多样化
Tornado异步服务的处理大抵如此。现在异步处理的框架和库也很多,借助redis或者celery等,也可以把tonrado中一些业务异步化,放到后台执行。
此外,Tornado还有客户端异步功能。该特性主要是在于 AsyncHTTPClient的使用。此时的应用场景往往是tornado服务内,需要针对另外的IO进行请求和处理。顺便提及,上述的例子中,调用ping其实也算是一种服务内的IO处理。接下来,将会探索一下AsyncHTTPClient的使用,尤其是使用AsyncHTTPClient上传文件与转发请求。

AWS SSH使用证书登录时出现:Permission denied (publickey). (之前能够连接,而现在却不能连接)问题解决

 aws  AWS SSH使用证书登录时出现:Permission denied (publickey). (之前能够连接,而现在却不能连接)问题解决已关闭评论
12月 062018
 

使用ssh -i /path/my-key-pair.pem ec2-user@public-dns-hostname
出现:Permission denied (publickey). 

出现这样的错误,先确认下手头的私钥pem文件确实对应自己的EC2实例。然后可以通过下面几个步骤排查:

1. 登录所使用的用户名是否正确?
正确的用户名如下所示:
对于 Amazon Linux 2 或 Amazon Linux AMI,用户名称是 ec2-user。
对于 Centos AMI,用户名称是 centos。
对于 Debian AMI,用户名称是 admin 或 root。
对于 Fedora AMI,用户名为 ec2-user 或 fedora。
对于 RHEL AMI,用户名称是 ec2-user 或 root。
对于 SUSE AMI,用户名称是 ec2-user 或 root。
对于 Ubuntu AMI,用户名称是 ubuntu。
另外,如果 ec2-user 和 root 无法使用,请与 AMI 供应商核实。
例如,要使用 SSH 客户端连接到从 Amazon Linux 实例,请使用以下命令:
ssh -i /path/my-key-pair.pem ec2-user@public-dns-hostname

2. 如果登录时还看到类似以下的错误:
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0777 for ‘.ssh/my_private_key.pem’ are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
bad permissions: ignore key: .ssh/my_private_key.pem
Permission denied (publickey).

需要使用下面命令调整证书文件权限
[ec2-user ~]$ chmod 0400 .ssh/my_private_key.pem

3. 如果上面的步骤还是没解决问题,而且可能是之前能够连接,而现在却不能连接了,则可能是实例主目录的权限发生了更改。/home/ec2-user/.ssh/authorized_keys 的目录及文件权限必须限制为仅限所有者
具体操作如下(以 Amazon Linux 实例为例,用户名ec2-user):
chmod 600 /home/ec2-user/.ssh/authorized_keys
chmod 700 /home/ec2-user/.ssh
chmod 700 /home/ec2-user
(测试发现700权限的扩大到755也可以)

Done!!!

*******************************************************
附录1:打开AWS用户名密码登录方式,此处以root登录为例


1.首先 用密钥登陆 
2.给 root 设置密码 sudo passwd root
3.密码设置好后 切换到root用户 su root
4.修改ssh配置文件,允许密码登录
 vim /etc/ssh/sshd_config 
将 passwordAuthentication no 改为  passwordAuthentication yes
将PermitRootLogin 改为yes 此处表示允许root登录(当然如果使用其它用户登录可以保持这项为no,但记得赋予新用户有sudo权限方便修改恢复配置)

#其它参数说明:
# 是否让 sshd 去检查用户家目录或相关档案的权限数据,
# 这是为了担心使用者将某些重要档案的权限设错,可能会导致一些问题所致。
# 例如使用者的 ~.ssh/ 权限设错时,某些特殊情况下会不许用户登入
#StrictModes no

# 是否允许用户自行使用成对的密钥系统进行登入行为,仅针对 version 2。
# 至于自制的公钥数据就放置于用户家目录下的 .ssh/authorized_keys 内
#RSAAuthentication yes
#PubkeyAuthentication yes
#AuthorizedKeysFile      %h/.ssh/authorized_keys

5 重启 ssh service sshd restart

********************************************************
附录2:更新替换AWS密钥/证书(以 AWS ubuntu举例)


1.在aws后台生成并下载密钥,然后将密钥保存到自己电脑。

2.执行ssh-keygen -y,会提示要求输入密钥路径, 复制密钥路径,回车得到public key,例如
ssh-rsa AAAAB3NzaC1yc2EAAAADAQA231BAQCMASwerewg23XvhyGzydp0V234lP/fyfuhsHKMECZydc5ewytvTq0mqYTfjKBS++PeBpEL1Zx/ilEYCmgY6omTrIMtG8s1jf/lAk0l9++f2ldp/w2U86seARyRxVEexxxyuwJJDASDHNiEbshXQ6M49nUsE6tfETG3sFl+XDeva0lkNkssA4JDU+eivPRGma3XcBAXvsUsD8VkKQJvudrpJDSjjncdjYOVd2Wcrcj5Li8MmLvIkEX1pmqTT6O6oUfEtCdpVi4tCwTXV5ydU8UtjJDSGDFSJgbY9Unve4LgjgoWF677FdUpvVFD1NPoLH

3.利用以前的密钥/用户名密码登录服务器,将上面第二步的public key粘贴到~/.ssh/authorized_keys。 
比如: aws ubuntu系统默认用户名ubuntu位置在/home/ubuntu/.ssh/authorized_keys,如没有.ssh目录则新建目录, 
然后将旧的public key注释或者删除。

4.注意将上面3所在目录和文件修改为正确的权限
chmod 600 /home/ubuntu/.ssh/authorized_keys
chmod 700 /home/ubuntu/.ssh
chmod 700 /home/ubuntu

4.然后你就可以利用新的密钥文件(.pem后缀文件)登录服务器了 
例如:ssh -i /path/my-key-pair.pem ubuntu@public-dns-hostname

深入浅出JSON Web Token(JWT)

 JWT  深入浅出JSON Web Token(JWT)已关闭评论
12月 042018
 

转自:https://www.cnblogs.com/mantoudev/p/8994341.html

1. JWT 介绍

JSON Web Token(JWT)是一个开放式标准(RFC 7519),它定义了一种紧凑(Compact)且自包含(Self-contained)的方式,用于在各方之间以JSON对象安全传输信息。 这些信息可以通过数字签名进行验证和信任。 可以使用秘密(使用HMAC算法)或使用RSA的公钥/私钥对对JWT进行签名。

image

虽然JWT可以加密以提供各方之间的保密性,但我们将重点关注已签名的令牌。 签名的令牌可以验证其中包含的索赔的完整性,而加密令牌隐藏来自其他方的索赔。 当令牌使用公钥/私钥对进行签名时,签名还证明只有持有私钥的方是签名方。

我们来进一步解释一些概念:

  • Compact(紧凑)
    由于它们尺寸较小,JWT可以通过URL,POST参数或HTTP标头内发送。 另外,尺寸越小意味着传输速度越快。
  • Self-contained(自包含):
    有效载荷(Playload)包含有关用户的所有必需信息,避免了多次查询数据库。

2. JWT适用场景

  • Authentication(鉴权)
    这是使用JWT最常见的情况。 一旦用户登录,每个后续请求都将包含JWT,允许用户访问该令牌允许的路由,服务和资源。 单点登录是当今广泛使用JWT的一项功能,因为它的开销很小,并且能够轻松地跨不同域使用。
  • Information Exchange(信息交换)
    JSON Web Tokens是在各方之间安全传输信息的好方式。 因为JWT可以签名:例如使用公钥/私钥对,所以可以确定发件人是他们自称的人。 此外,由于使用标头和有效载荷计算签名,因此您还可以验证内容是否未被篡改。

3. JWT结构

在紧凑的形式中,JWT包含三个由点(.)分隔的部分,它们分别是:

  • Header
  • Payload
  • Signature

JWT结构通常如下所示:

xxxxx.yyyyy.zzzzz 

下面我们分别来介绍这三个部分:

Header通常由两部分组成:令牌的类型,即JWT。和常用的散列算法,如HMAC SHA256或RSA。
例如:

{ "alg": "HS256", "typ": "JWT" }

Header部分的JSON被Base64Url编码,形成JWT的第一部分。

Payload

这里放声明内容,可以说就是存放沟通讯息的地方,在定义上有3种声明(Claims):

  • Registered claims(注册声明):
    这些是一组预先定义的声明,它们不是强制性的,但推荐使用,以提供一组有用的,可互操作的声明。 其中一些是:iss(发行者),exp(到期时间),sub(主题),aud(受众)等。#Registered Claim Names#

  • Public claims(公开声明):
    这些可以由使用JWT的人员随意定义。 但为避免冲突,应在IANA JSON Web令牌注册表中定义它们,或将其定义为包含防冲突命名空间的URI。
  • Private claims(私有声明):
    这些是为了同意使用它们但是既没有登记,也没有公开声明的各方之间共享信息,而创建的定制声明。


    

 载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分

  • 标准中注册的声明
  • 公共的声明
  • 私有的声明 

     标注中注册的声明(建议不强制使用)

  • iss:jwt签发者
  • sub:jwt所面向的用户
  • aud:接收jwt的一方
  • exp:jwt的过期时间,这个过期时间必须大于签发时间
  • nbf:定义在什么时间之前,该jwt都是不可用的
  • iat:jwt的签发时间
  • jti:jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击 

    公共的声明:

       公共的声明可以添加任何的信息,一般添加用户的相关信息或其它业务需要的必要信息,但不建议添加敏感信息,因为该部分在客户端可解密;

     私有的声明

         私有的声明是提供者和消费者功能定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为名文信息。

Playload示例如下:

{ "sub": "1234567890", "name": "John Doe", "admin": true }

Playload部分的JSON被Base64Url编码,形成JWT的第二部分。

Notice:

请注意,对于已签名的令牌,此信息尽管受到篡改保护,但任何人都可以阅读。 除非加密,否则不要将秘密信息放在JWT的有效内容或标题元素中。这也是很多文章争论jwt安全性原因,不要用 JWT 取代 Server-side 的 Session状态机制。详情请阅读这篇文章:Stop Using Jwt For Sessions.

Signature

第三部分signature用来验证发送请求者身份,由前两部分加密形成。
要创建签名部分,您必须采用编码标头,编码有效载荷,秘钥,标头中指定的算法并签名。
例如,如果你想使用HMAC SHA256算法,签名将按照以下方式创建:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

3. JWT实践

JWT输出的是三个由点分隔的Base64-URL字符串,可以在HTML和HTTP环境中轻松传递,而与基于XML的标准(如SAML)相比,它更加紧凑。

以下JWT示例,它具有先前的标头和有效负载编码,并且使用秘钥进行签名。

image

我们可以使用jwt.io调试器来解码,验证和生成JWT:
image

4.JWT工作原理

在身份验证中,当用户使用他们的凭证成功登录时,JSON Web Token将被返回并且必须保存在本地(通常在本地存储中,但也可以使用Cookie),而不是在传统方法中创建会话 服务器并返回一个cookie。

关于存储令牌(Token)的方式,必须考虑安全因素。
参考: #Where to Store Tokens#

无论何时用户想要访问受保护的路由或资源,用户代理都应使用承载方案发送JWT,通常在请求头中的Authorization字段,使用Bearer schema:

Authorization: Bearer <token>

这是一种无状态身份验证机制,因为用户状态永远不会保存在服务器内存中。 服务器受保护的路由将在授权头中检查有效的JWT,如果存在,则允许用户访问受保护的资源。 由于JWT是独立的,所有必要的信息都在那里,减少了多次查询数据库的需求。

这使得我们可以完全依赖无状态的数据API,甚至向下游服务提出请求。 无论哪些域正在为API提供服务并不重要,因此不会出现跨域资源共享(CORS)的问题,因为它不使用Cookie。

image

Notice:

请注意,使用已签名的令牌,令牌中包含的所有信息都会暴露给用户或其他方,即使他们无法更改它。 在JWT中,不应该在Playload里面加入任何敏感的数据,比如像密码这样的内容。如果将用户的密码放在了JWT中,那么怀有恶意的第三方通过Base64解码就能很快地知道你的密码了。

5. 常见问题

① JWT 安全嗎?

Base64编码方式是可逆的,也就是透过编码后发放的Token内容是可以被解析的。一般而言,我们都不建议在有效载荷内放敏感讯息,比如使用者的密码。

② JWT Payload 內容可以被伪造嗎?

JWT其中的一个组成内容为Signature,可以防止通过Base64可逆方法回推有效载荷内容并将其修改。因为Signature是经由Header跟Payload一起Base64组成的。

是的,Cookie丢失,就表示身份就可以被伪造。故官方建议的使用方式是存放在LocalStorage中,并放在请求头中发送。

④ 空间及长度问题?

JWT Token通常长度不会太小,特别是Stateless JWT Token,把所有的数据都编在Token里,很快的就会超过Cookie的大小(4K)或者是URL长度限制。

⑤ Token失效问题?

  • 无状态JWT令牌(Stateless JWT Token)发放出去之后,不能通过服务器端让令牌失效,必须等到过期时间过才会失去效用。
  • 假设在这之间Token被拦截,或者有权限管理身份的差异造成授权Scope修改,都不能阻止发出去的Token失效并要求使用者重新请求新的Token。

6. JWT使用建议

  • 不要存放敏感信息在Token里。
  • Payload中的exp时效不要设定太长。
  • 开启Only Http预防XSS攻击。
  • 如果担心重播攻击(replay attacks )可以增加jti(JWT ID),exp(有效时间) Claim。
  • 在你的应用程序应用层中增加黑名单机制,必要的时候可以进行Block做阻挡(这是针对掉令牌被第三方使用窃取的手动防御)。

python3安装turtle提示错误:Command “python setup.py egg_info” failed with error code 1 错误解决

 python  python3安装turtle提示错误:Command “python setup.py egg_info” failed with error code 1 错误解决已关闭评论
12月 022018
 

 使用pip在python3.6.4安装turtle时出现下面错误:

Collecting turtle

  Using cached https://files.pythonhosted.org/packages/ff/f0/21a42e9e424d24bdd0e509d5ed3c7dfb8f47d962d9c044dba903b0b4a26f/turtle-0.0.2.tar.gz

    Complete output from command python setup.py egg_info:

    Traceback (most recent call last):

      File “<string>”, line 1, in <module>

      File “/tmp/pip-install-hpqxw6_s/turtle/setup.py”, line 40

        except ValueError, ve:

                         ^

    SyntaxError: invalid syntax

    —————————————-

Command “python setup.py egg_info” failed with error code 1 in /xxx/turtle/

其实仔细看是一个语法错误,可以根据上面的url(https://files.pythonhosted.org/packages/ff/f0/21a42e9e424d24bdd0e509d5ed3c7dfb8f47d962d9c044dba903b0b4a26f/turtle-0.0.2.tar.gz)自己先把包下载到本地,然后解压到本地再安装,提示的错误在解压的setup.py文件里面,

  1. 打开setup.py文件,第40行修改为 except (ValueError, ve):
    原来的是Python2的写法,没有括号,加了括号之后Python3就能用了。
  2. 用pip3安装: pip install -e turtle-0.0.2
    -e后面接上我们修改过setup.py文件的目录。
  3. 这样就搞定了。

Mac OS X 中一些常用的命令行技巧

 mac  Mac OS X 中一些常用的命令行技巧已关闭评论
12月 022018
 

挺好的文章,分享下:https://www.cnblogs.com/wwl188/p/5612505.html

一、网络设置相关

1、网卡的物理地址的动态重置

出于某些需求,例如网络中的 IP 地址或网络帐号与网卡物理地址绑定,使得多个设备无法切换上网,可尝试临时更改物理地址。不过,系统偏好设置是不能修改网卡物理地址的,这就是命令行的用武之地了。具体来说,若心中已有想修改的物理地址,例如 00:1f:3c:93:b5:99,可执行

$ sudo ifconfig en1 ether 00:1f:3c:93:b5:99

来临时修改网卡的物理地址;若没有物理地址,只是想临时启用尝试一下,可用 openssl 生成一个随机的物理地址:

$ openssl rand -hex 6 | sed 's/\(..\)/\1:/g; s/.$//'

当然,用 openssl 生成的物理地址可能会与网络中的网卡地址有冲突,在启用随机生成的网卡地址之前,最好先确认一下是否已有 IP 地址已绑定该物理地址:

$ arp -a

2、主机名、计算机名的重置

默认的主机名让人非常无语,特别是出现了一些与 locale 有关的特殊字符。通常情况下,这些并不可见,但是,频繁使用终端时,就会被这些莫名其妙的主机名恶心到了。因此,修改主机名成了当务之急,不过修改的方法很简单。顺次点击左上角的苹果图标->系统偏好设置->共享,接着修改主机电脑名与局域网主机名即可。话说回来,Unix 的命令行是强大的,因此,主机名与计算机名的修改也可以通过命令行修改。

$ sudo scutil --set ComputerName newnameformacosx
$ sudo scutil --set HostName newhostnameformacosx

好了,查看一下修改是否成功:

$ scutil --get ComputerName
$ scutil --get HostName

将主机名放入 /etc/hosts 文件

$ echo 127.0.0.1 newhostnameformacosx >> /etc/hosts

重启网络服务让上述修改立马生效:

$ sudo dscacheutil -flushcache $ sudo killall -HUP mDNSResponder

3、命令行下载工具的使用

众所周知,wget 是大多是 *nix 系统中标准的命令行下载工具。不过 Mac OS X 中并不自带 wget,取而代之的是命令行下载工具 curl。下面来介绍一下 curl 的基本用法,例如直接下载

$ curl -O http://wordpress.org/latest.zip

若想将下载文件重命名

$ curl -o wordpress-3.8.zip 'http://wordpress.org/latest.zip'

若想在下载过程中显示进度,可执行

$ curl -# -O http://wordpress.org/latest.zip

如果下载的 url 链接需要重定向,可试试下述方式

$ curl -L -o 'file.zip' 'http://example.com/download.php?fileID=foo'

若想节省带宽,可执行

$ curl -L -O --compressed 'http://example.com/large.report-tab.html'

若下载链接需要输入用户名与密码,可试试

$ curl ftp://james:123456@202.121.137.58:21/path/to/backup.tar.gz $ curl http://james:123456@202.121.137.58/file/path/data.tar.gz

若下载的是带加密的链接,例如下载链接由 SSL/HTTPS/SFTP 提供,可试试

$ curl --ftp-ssl -u james:123456 ftp://202.121.137.58:21/backups/07/07/2012/mysql.blog.sql.tar.gz $ curl -u james sftp://202.121.137.57/backups/data.tar.gz

4、系统更新与 Xcode.app 的命令行工具安装

通常,Mac OS X 上的软件更新是通过 App Store 完成,但是对于那些 geek 来说,可以试试下述命令:

$ sudo softwareupdate --list

会列出所有可能的更新,然后选择需要的更新进行安装

$ sudo softwareupdate --install SomeApp

若不想选择直接更新所有可能的更新,可执行

$ sudo softwareupdate -i -a

Xcode 是 Apple 推出的集成开发环境,它带有 Apple 改良过的 GCC 版本 llvm-gcc。安装它,非常容易,仅需去 App Store 下载 Xcode.app 即可。不过对于 Unix geek 来说,这是不能让人满意的。因为 Xcode.app 不自带命令行工具,需要单独安装它。在 Xcode 5.0 之前,命令行工具的安装非常简单,仅需打开 Xcode.app,然后点击偏好设置->下载->组件,然后选择 Xcode 命令行工具安装即可。但是自 Xcode 5.0 开始,安装 Xcode 的命令行工具,须在终端执行下述命令:

$ xcode-select --install

二、文件系统的相关操作

1、HFS 文件系统的扩展属性

在 Mac OS X 下的 hfs+ 文件系统上,文件经常会被附加上 Mac 专有的扩展属性。比如

$ ls -l
total 0 -rw-r--r--@ 1 daniel  staff  0 16 jul 19:28 1.txt -rw-r--r--  1 daniel  staff  0 16 jul 19:28 2.txt

会有 @ 标志。这个属性是用户在 Finder 里对文件进行任意操作时就会附加上的。比如右键查看 Get Info,然后在 spotlight commands 里写上几个字再删掉,此时,该文件就会附带有 @ 属性。如果想手动去除掉文件的 @ 属性,可以使用命令 xattr。例如

$ xattr -l 1.txt
com.apple.metadata:kmditemfindercomment $ xattr -d com.apple.metadata:kmditemfindercomment 1.txt $ xattr -l 1.txt $ ls -al 1.txt

此时就没有特殊属性了。

2、文件或文件夹的复制、打包与解包

由于 Mac OS X 文件系统中的扩展属性,若将 Mac OS X 系统中的文件直接用打包释放到 Linux 系统中造成莫名奇妙的错误。这里就需要处理一下文件在复制、打包过程中的扩展属性。首先,Mac OS X 的 cp 命令有一个选项 -X,用它可在复制过程中忽略这些文件的扩展属性。因此,在打包前,先可复制出一份没有扩展属性的文件包,例如

$ cp -Xr /path/to/src - | tar czvf src.tar.gz -

当然,除去扩展属性之外,通常,文件直接打包作成中还会出现 Mac OS X 系统专有的隐藏文件,流入 _MACOSX、._Filename 与 .DS_Store 等。在打 zip 包时,可使用 -X 选项排除这些文件,例如

$ zip -r -X archive_name.zip folder_to_compress

不过,对于 *nix 系统中专有的打包命令 tar,可就没有 -X 选项这么好的东西了。好在 Mac OS X 还是提供了专门的方法,例如,在打 gzip 包时,可使用如下命令

$ COPYFILE_DISABLE=1 tar -zcvf archive_name.tar.gz folder_to_compress

打 bzip2 包也很简单:

$ COPYFILE_DISABLE=1 tar -jcvf archive_name.tar.bz2 folder_to_compress

对于其他格式的 tar 包照此处理。解包过程相对简单,这里附上一段与解包相关的 Bash 配置:

$ echo "# ignore useless invisible files or extended attributes on Mac OS X for cp and tar
export COPYFILE_DISABLE=true
# extract: Extract most know archives with one command
extract () {  if [ -f $1 ] ; then    case $1 in     *.tar.bz2)	tar xjf $1	  ;;     *.tar.gz)	 tar xzf $1	  ;;     *.bz2)		 bunzip2 $1	  ;;     *.rar)		 unrar e $1	  ;;     *.gz)		  gunzip $1		;;     *.tar)		 tar xf $1		;;     *.tbz2)		tar xjf $1	  ;;     *.tgz)		 tar xzf $1	  ;;     *.zip)		 unzip $1		 ;;     *.Z)			uncompress $1  ;;     *.7z)		  7z x $1		  ;;     *)	  echo "'$1' cannot be extracted via extract()" ;;    esac  else    echo "'$1' is not a valid file"  fi
}" >> ~/.bashrc

3、DMG 格式文件的制作以及 ISO 转换互转

DMG 格式是 Mac OS X 中常用的打包格式,创建 DMG 格式的文件,可直接利用系统自带的磁盘工具(Disk Utils)完成。不过这里稍微介绍一下命令行 hdiutil 的用法。创建 DMG 格式的文件

$ hdiutil create -size 100M -stdinpass -format UDZO -srcfolder folder_to_compress archive_name.dmg

其中可以选用的 DMG 格式有 UDZO(压缩格式,默认)、UDRO(只读格式)、UDBZ(Better compressed image)、UDRW(可读写格式)UDTO(DVD 格式)。若想修改 DMG 文件的大小,可执行

$ hdiutil resize 150M /path/to/the/diskimage

若想修改 DMG 格式中的加密口令,可执行

$ hdiutil chpass /path/to/the/diskimage

挂载 DMG 格式的文件可用

$ hdiutil attach archive_name.dmg

它的挂载点在 /Volumes 目录的同名目录下

$ ls -lah /Volumes/archive_name/

卸载 DMG 文件的方法是:

$ hdiutil eject /Volumes/archive_name/

将 ISO 格式的文件转为 DMG 格式的文件

$ hdiutil convert /path/imagefile.iso -format UDRW -o /path/convertedimage.dmg

将 DMG 格式的文件转为 ISO 格式的文件

$ hdiutil convert /path/imagefile.dmg -format UDTO -o /path/convertedimage.cdr $ hdiutil makehybrid /path/convertedimage.cdr -iso -joliet -o /path/convertedimage.iso

4、文件系统的挂载

大多数情况下,文件管理器 Finder 会自动发现诸如 USB 等外部设备,因此无须用户自行挂载分区;但是,有的时候,例如 Mac OS X 硬盘重新分区后,划出了 msdos 分区或者 ext4 等非 HFS 文件系统的分区,如果要读写这些分区,就需要手动挂载这些文件系统了。挂载文件系统的第一步是建立挂载点,例如

$ sudo mkdir /Volumes/Foo $ sudo mkdir /Volumes/bar

接着查看有哪些设备需要手动挂载并挂载它们

$ ls /dev/disk* $ sudo mount -t msdos /dev/disk1s1 /Volumes/Foo $ sudo mount -t hfs /dev/disk1s2 /Volumes/bar

最后,卸载这些不在需要的分区

$ diskutil eject /dev/disk1s1 $ diskutil eject /dev/disk1s2

4、文件或文件夹的隐藏属性

通常,为了保护 Mac OS X 系统中的文件不会被用户随意修改,文件管理器 Finder 是不会显示带隐藏属性的文件与系统文件。但是,有些时候,需要操作这些文件(夹), 此时就需要让 Finder 找到这些文件,方法也很简单。在 Finder 的菜单栏中点击 Go -> Go to folder,与之对应的,可使用快捷键  shift+command+G 。 当然,如果对于 Mac OS X 自带的文件管理器不满意,可安装第三方文件管理器,例如 totalfinder。其实,单就隐藏文件来说,Mac OS X 还提供了其他方法,下面逐一介绍它们。 

4.1 修改 Finder 配置文件 让 Finder 程序显示隐藏文件

$ defaults write com.apple.finder AppleShowAllExtensions TRUE

让 Finder 程序不显示隐藏文件

$ defaults write com.apple.finder AppleShowAllExtensions FALSE

Finder 的配置文件影响到整个文件系统,属于比较暴力的方法。

4.2、开启或关闭某些文件或文件夹的隐藏属性 用下面命令可以解除文件夹的隐藏属性:

$ chflags nohidden ~/Library

恢复文件夹的隐藏属性:

$ chflags hidden ~/Library

事实上,HFS+ 文件系统中文件和目录的隐藏属性也可通过 SetFile 命令来修改。例如,执行

$ mkdir -p ~/Sites
$ SetFile -a V ~/Sites

可开启文件夹 ~/Sites 的隐藏属性;而执行

$ SetFile -a v ~/Sites

可以关闭它的隐藏属性。

三、杂项

1、重置被遗忘的管理员密码

首先,在系统开机还未进入登录界面时按下  command+S 进入单用户模式。然后输入 

$ mount -rw /

以读写方式挂载文件系统;接着重置管理员 james 的密码

$ passwd james

完成后,输入

$ reboot

重启开机即可。

2、重建 Spotlight 数据库

比较简单的办法是用鼠标顺次点击系统偏好-> Spotlight ->重建数据库;对于命令行发烧友来说,可以试试

$ sudo mdutil -E /

3、清理缓存

$ sudo purge

真正实现tornado异步非阻塞(gen.coroutine,ThreadPoolExecutor,Celery)

 python, tornado  真正实现tornado异步非阻塞(gen.coroutine,ThreadPoolExecutor,Celery)已关闭评论
11月 292018
 

真正实现tornado异步非阻塞,来自:https://blog.csdn.net/u013038616/article/details/72821600

其中 Tornado 的定义是 Web 框架和异步网络库,其中他具备有异步非阻塞能力,能解决他两个框架请求阻塞的问题,在需要并发能力时候就应该使用 Tornado

但是在实际使用过程中很容易把 Tornado 使用成异步阻塞框架,这样对比其他两大框架没有任何优势而言,本文就如何实现真正的异步非阻塞记录。

以下使用的 Python 版本为 2.7.13 
平台为 Macbook
Pro 2016

使用 gen.coroutine 异步编程


Tornado 中两个装饰器:

  • tornado.web.asynchronous

  • tornado.gen.coroutine

asynchronous 装饰器是让请求变成长连接的方式,必须手动调用 self.finish() 才会响应

class MainHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
        # bad 
        self.write("Hello, world")

asynchronous 装饰器不会自动调用self.finish() ,如果没有没有指定结束,该长连接会一直保持直到 pending 状态。 

没有调用self.finish()
所以正确是使用方式是使用了 asynchronous 需要手动 finish

class MainHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
        self.write("Hello, world")
        self.finish()
coroutine 装饰器是指定改请求为协程模式,说明白点就是能使用 yield 配合 Tornado 编写异步程序。

Tronado 为协程实现了一套自己的协议,不能使用 Python 普通的生成器。

在使用协程模式编程之前要知道如何编写 Tornado 中的异步函数,Tornado 提供了多种的异步编写形式:回调、Future、协程等,其中以协程模式最是简单和用的最多。

编写一个基于协程的异步函数同样需要 coroutine 装饰器

@gen.coroutine
def sleep(self):
    yield gen.sleep(10)
    raise gen.Return([1, 2, 3, 4, 5])

这就是一个异步函数,Tornado 的协程异步函数有两个特点:
  • 需要使用 coroutine 装饰器

  • 返回值需要使用 raise
    gen.Return()
     
    当做异常抛出

返回值作为异常抛出是因为在 Python
3.2
之前生成器是不允许有返回值的。

使用过 Python 生成器应该知道,想要启动生成器的话必须手动执行 next() 方法才行,所以这里的 coroutine 装饰器的其中一个作用就是在调用这个异步函数时候自动执行生成器。

使用 coroutine 方式有个很明显是缺点就是严重依赖第三方库的实现,如果库本身不支持 Tornado 的异步操作再怎么使用协程也是白搭依然会是阻塞的,放个例子感受一下。

import time
import logging
import tornado.ioloop
import tornado.web
import tornado.options
from tornado import gen

tornado.options.parse_command_line()

class MainHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
        self.write(“Hello, world”)
        self.finish()

class NoBlockingHnadler(tornado.web.RequestHandler):
    @gen.coroutine
    def get(self):
        yield gen.sleep(10)
        self.write(‘Blocking Request’)

class BlockingHnadler(tornado.web.RequestHandler):
    def get(self):
        time.sleep(10)
        self.write(‘Blocking Request’)

def make_app():
    return tornado.web.Application([
        (r”/”, MainHandler),
        (r”/block”, BlockingHnadler),
        (r”/noblock”, NoBlockingHnadler),
    ], autoreload=True)

if __name__ == “__main__”:
    app = make_app()
    app.listen(8000)
    tornado.ioloop.IOLoop.current().start()


为了显示更明显设置了 10

当我们使用 yield
gen.sleep(10)
 
这个异步的 sleep 时候其他请求是不阻塞的。 
非阻塞效果图
当使用 time.sleep(10) 时候会阻塞其他的请求。 
阻塞效果图
这里的异步非阻塞是针对另一请求来说的,本次的请求该是阻塞的仍然是阻塞的。

gen.coroutine Tornado
3.1
后会自动调用 self.finish() 结束请求,可以不使用 asynchronous 装饰器。

所以这种实现异步非阻塞的方式需要依赖大量的基于 Tornado 协议的异步库,使用上比较局限,好在还是有一些可以用的异步库

基于线程的异步编程


使用 gen.coroutine 装饰器编写异步函数,如果库本身不支持异步,那么响应任然是阻塞的。

Tornado 中有个装饰器能使用 ThreadPoolExecutor 来让阻塞过程编程非阻塞,其原理是在 Tornado 本身这个线程之外另外启动一个线程来执行阻塞的程序,从而让 Tornado 变得阻塞。

futures Python3 是标准库,但是在 Python2 中需要手动安装 
pip
install futures

测试代码

import logging
import tornado.ioloop
import tornado.web
import tornado.options
from tornado import gen
from tornado.concurrent import run_on_executor
from concurrent.futures import ThreadPoolExecutor

tornado.options.parse_command_line()

class MainHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
        self.write(“Hello, world”)
        self.finish()

class NoBlockingHnadler(tornado.web.RequestHandler):
    executor = ThreadPoolExecutor(4)

    @run_on_executor
    def sleep(self, second):
        time.sleep(second)
        return second

    @gen.coroutine
    def get(self):
        second = yield self.sleep(5)
        self.write(‘noBlocking Request: {}’.format(second))

def make_app():
    return tornado.web.Application([
        (r”/”, MainHandler),
        (r”/noblock”, NoBlockingHnadler),
    ], autoreload=True)

if __name__ == “__main__”:
    app = make_app()
    app.listen(8000)
    tornado.ioloop.IOLoop.current().start()

ThreadPoolExecutor 是对标准库中的 threading 的高度封装,利用线程的方式让阻塞函数异步化,解决了很多库是不支持异步的问题。 

这里写图片描述
但是与之而来的问题是,如果大量使用线程化的异步函数做一些高负载的活动,会导致该 Tornado 进程性能低下响应缓慢,这只是从一个问题到了另一个问题而已。

所以在处理一些小负载的工作,是能起到很好的效果,让 Tornado 异步非阻塞的跑起来。

但是明明知道这个函数中做的是高负载的工作,那么你应该采用另一种方式,使用 Tornado 结合 Celery 来实现异步非阻塞。

基于 Celery 的异步编程

Celery 是一个简单、灵活且可靠的,处理大量消息的分布式系统,专注于实时处理的任务队列,同时也支持任务调度。 
Celery
并不是唯一选择,你可选择其他的任务队列来实现,但是 Celery Python 所编写,能很快的上手,同时 Celery 提供了优雅的接口,易于与 Python
Web
框架集成等特点。

Tornado 的配合可以使用 tornado-celery ,该包已经把 Celery 封装到 Tornado 中,可以直接使用。

实际测试中,由于 tornado-celery 很久没有更新,导致请求会一直阻塞,不会返回

解决办法是:

celery 降级到 3.1
pip install celery==3.1 
pika 降级到 0.9.14
pip install pika==0.9.14

import time
import logging
import tornado.ioloop
import tornado.web
import tornado.options
from tornado import gen

import tcelery, tasks

tornado.options.parse_command_line()
tcelery.setup_nonblocking_producer()


class MainHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
        self.write("Hello, world")
        self.finish()


class CeleryHandler(tornado.web.RequestHandler):
    @gen.coroutine
    def get(self):
        response = yield gen.Task(tasks.sleep.apply_async, args=[5])
        self.write('CeleryBlocking Request: {}'.format(response.result))


def make_app(): 
    return tornado.web.Application([
        (r"/", MainHandler),
        (r"/celery-block", CeleryHandler),
    ], autoreload=True)

if __name__ == "__main__":
    app = make_app()
    app.listen(8000)
    tornado.ioloop.IOLoop.current().start()
import os
import time
from celery import Celery
from tornado import gen

celery = Celery("tasks", broker="amqp://")
celery.conf.CELERY_RESULT_BACKEND = os.environ.get('CELERY_RESULT_BACKEND', 'amqp')

@celery.task
def sleep(seconds):
    time.sleep(float(seconds))
    return seconds

if __name__ == "__main__":
    celery.start()

这里写图片描述

Celery Worker 运行在另一个进程中,独立于 Tornado 进程,不会影响 Tornado 运行效率,在处理复杂任务时候比进程模式更有效率。

总结


方法

优点

缺点

可用性

gen.coroutine

简单、优雅

需要异步库支持

★★☆☆☆

线程

简单

可能会影响性能

★★★☆☆

Celery

性能好

操作复杂、版本低

★★★☆☆

目前没有找到最佳的异步非阻塞的编程模式,可用的异步库比较局限,只有经常用的,个人编写异步库比较困难。

推荐使用线程和 Celery 的模式进行异步编程,轻量级的放在线程中执行,复杂的放在 Celery 中执行。当然如果有异步库使用那最好不过了。

Python
3
中可以把 Tornado 设置为 asyncio 的模式,这样就使用
兼容
asyncio 模式的库,这应该是日后的方向。

Reference


celery  [‘selərɪ]  详细X

基本翻译
n. [园艺] 芹菜

网络释义
Celery: 芹菜
celery salt: 香芹盐
celery sticks: 芹菜杆

ubuntu 16.04安装shadowsocks(ss) gui 客户端

 ubuntu  ubuntu 16.04安装shadowsocks(ss) gui 客户端已关闭评论
11月 282018
 

如何安装ss服务器请自行搜索,本文只讲解在ubuntu 16.04/14.04下ss gui客户端安装,请依次执行: 

1. 安装 shadowsocks-qt5

sudo add-apt-repository ppa:hzwhuang/ss-qt5
sudo apt-get update
sudo apt-get install shadowsocks-qt5 

2. 配置ss服务器连接

打开shadowsocks-qt5, 然后通过菜单 连接->添加->手动 , 添加服务器ip,端口,密码,加密方式,本地地址,本地端口(如:1080)等, 保存后连接


3. 配置本机网络连接

在系统设置中,选择网络 –> 网络代理 –> 方法手动 –> Socks主机 127.0.0.1   1080。应用到整个系统即可

使用Matplotlib一步步绘制函数图像

 Matplotlib, python  使用Matplotlib一步步绘制函数图像已关闭评论
11月 232018
 

文章循序渐进、清晰明了的介绍了使用matplotlib画一个函数的过程,分享下:https://www.jianshu.com/p/b1dee69767d3

matplotlib 是 python 最著名的绘图库,它提供了一整套和 matlab 相似的命令 API,十分适合交互式进行绘图。

它的文档相当完备,并且 Gallery 页面 中有上百幅缩略图,打开之后都有源程序。因此如果你需要绘制某种类型的图,只需要在这个页面中浏览/复制/粘贴一下,基本上都能搞定。

这是要实现的效果图,利用 Matplotlib 绘制函数 y = x2 的图像。

image

代码实现

import matplotlib.pyplot as plt import numpy as np # 定义 x 变量的范围 (-3,3) 数量 50  x=np.linspace(-3,3,50)
y=x**2 # Figure 并指定大小 plt.figure(num=3,figsize=(8,5)) # 绘制 y=x^2 的图像,设置 color 为 red,线宽度是 1,线的样式是 -- plt.plot(x,y,color='red',linewidth=1.0,linestyle='--') # 设置 x,y 轴的范围以及 label 标注 plt.xlim(-1,2)
plt.ylim(-2,3)
plt.xlabel('x')
plt.ylabel('y') # 设置坐标轴刻度线  # Tick X 范围 (-1,2) Tick Label(-1,-0.25,0.5,1.25,2) 刻度数量 5 个 new_ticks=np.linspace(-1,2,5)
plt.xticks(new_ticks) # Tick Y 范围(-2.2,-1,1,1.5,2.4) ,Tick Label (-2.2, -1, 1, 1.5, 2.4) 别名(下面的英文) plt.yticks([-2.2,-1,1,1.5,2.4],
          [r'$really\ bad$',r'$bad$',r'$normal$',r'$good$',r'$really\ good$']) # 设置坐标轴 gca() 获取坐标轴信息 ax=plt.gca() # 使用.spines设置边框:x轴;将右边颜色设置为 none。 # 使用.set_position设置边框位置:y=0的位置;(位置所有属性:outward,axes,data) ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none') # 移动坐标轴 # 将 bottom 即是 x 坐标轴设置到 y=0 的位置。 ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('data',0)) # 将 left 即是 y 坐标轴设置到 x=0 的位置。 ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data',0)) # 设置标签 ax.set_title('y = x^2',fontsize=14,color='r') # 显示图像 plt.show()

首先一幅 Matplotlib 的图像组成部分介绍。

image

基本构成

在 matplotlib 中,整个图像为一个 Figure 对象。在 Figure 对象中可以包含一个或者多个 Axes 对象。每个 Axes(ax) 对象都是一个拥有自己坐标系统的绘图区域。

所属关系如下(图片来自网络):

image

图上:Title 为图像标题,Axis 为坐标轴, Label 为坐标轴标注,Tick 为刻度线,Tick Label 为刻度注释。

各个对象关系可以梳理成以下内容(图片来自网络):

image

Matplotlib 绘图步骤分析

image

注意

这里不讲解 matplotlib 的安装,我使用的是 Anaconda 的 notebook,因此,可以直接导入 matplotlib 的module,想要安装 Anaconda 的请查看的写的另一篇文章。墙裂推荐 Anaconda | 安利 Python IDE

如果说不想使用 Anaconda,可以看看这篇推荐文章,教你如何安装 matplotlib(视频和文章教程)— 莫烦 Python — Matplotlib

上面虽然贴了全部的代码,有 matplotlib 基础的人一看便懂,但是对于初学者来说,就比较抓脑了。

一、matplotlib 实现简单图像

image

使用import导入模块matplotlib.pyplot,并简写成plt使用import导入模块numpy,并简写成np。

#导入 module import matplotlib.pyplot as plt import numpy as np

使用np.linspace定义 x:范围是 (-3,3),个数是50,仿真一维数据组 (x,y) 表示曲线。

# 定义 x 变量的范围 (-3,3) 数量 50  x=np.linspace(-3,3,50)
y=x**2 

使用plt.figure定义一个图像窗口,使用plt.plot画 (x,y) 曲线,使用plt.show显示图像。

#绘制及显示 plt.figure()
plt.plot(x, y)
plt.show()

最终代码

import matplotlib.pyplot as plt import numpy as np # 定义 x 变量的范围 (-3,3) 数量 50  x=np.linspace(-3,3,50)
y=x**2 plt.figure()
plt.plot(x, y)
plt.show()

运行结果

image

看到这个,是不是和最终效果图差距很大,没关系,我们接着往下看。

二、设置 Figure 图像

使用plt.figure定义一个图像窗口:编号为 3,大小为 (8, 5)。使用plt.plot画(x,y)曲线,曲线的颜色属性(color)为红色。曲线的宽度(linewidth)为1.0。曲线的类型(linestyle)为虚线, 使用plt.show显示图像。

plt.figure(num=3,figsize=(8,5))
plt.plot(x,y,color='red',linewidth=1.0,linestyle='--')

最终代码

import matplotlib.pyplot as plt import numpy as np # 定义 x 变量的范围 (-3,3) 数量 50  x=np.linspace(-3,3,50)
y=x**2 # Figure 并指定大小 plt.figure(num=3,figsize=(8,5)) # 绘制 y=x^2 的图像,设置 color 为 red,线宽度是 1,线的样式是 -- plt.plot(x,y,color='red',linewidth=1.0,linestyle='--')

plt.show()

运行效果

image

三、设置坐标轴

设置 x,y 轴坐标范围及标注 label

使用plt.xlim设置x坐标轴范围:(-1, 2); 使用plt.ylim设置 y 坐标轴范围:(-2, 3);

使用plt.xlabel设置 x 坐标轴名称:’x’; 使用plt.ylabel设置 y 坐标轴名称:’I am y’;

代码实现

import matplotlib.pyplot as plt import numpy as np # 定义 x 变量的范围 (-3,3) 数量 50  x=np.linspace(-3,3,50)
y=x**2 # Figure 并指定大小 plt.figure(num=3,figsize=(8,5)) # 绘制 y=x^2 的图像,设置 color 为 red,线宽度是 1,线的样式是 -- plt.plot(x,y,color='red',linewidth=1.0,linestyle='--') # 设置 x,y 轴的范围以及 label 标注 plt.xlim(-1,2)
plt.ylim(-2,3)
plt.xlabel('x')
plt.ylabel('y')

plt.show()

运行结果

image

设置 x, y 刻度范围及刻度标注

设置 x 轴:使用np.linspace定义范围以及个数:范围是(-1,2); 个数是 5。

使用plt.xticks设置 x 轴刻度:范围是(-1,2); 个数是5。

设置 y 轴:使用plt.yticks设置 y 轴刻度以及名称:刻度为 [-2, -1.8, -1, 1.22, 3];对应刻度的名称为 [‘really bad’,’bad’,’normal’,’good’, ‘really good’]。

# 设置坐标轴刻度线  # Tick X 范围 (-1,2) Tick Label(-1,-0.25,0.5,1.25,2) 刻度数量 5 个 new_ticks=np.linspace(-1,2,5)
plt.xticks(new_ticks) # Tick Y 范围(-2.2,-1,1,1.5,2.4) ,Tick Label (-2.2, -1, 1, 1.5, 2.4) 别名(下面的英文) plt.yticks([-2.2,-1,1,1.5,2.4],
          [r'$really\ bad$',r'$bad$',r'$normal$',r'$good$',r'$really\ good$'])

最终代码

import matplotlib.pyplot as plt import numpy as np # 定义 x 变量的范围 (-3,3) 数量 50  x=np.linspace(-3,3,50)
y=x**2 # Figure 并指定大小 plt.figure(num=3,figsize=(8,5)) # 绘制 y=x^2 的图像,设置 color 为 red,线宽度是 1,线的样式是 -- plt.plot(x,y,color='red',linewidth=1.0,linestyle='--') # 设置 x,y 轴的范围以及 label 标注 plt.xlim(-1,2)
plt.ylim(-2,3)
plt.xlabel('x')
plt.ylabel('y') # 设置坐标轴刻度线  # Tick X 范围 (-1,2) Tick Label(-1,-0.25,0.5,1.25,2) 刻度数量 5 个 new_ticks=np.linspace(-1,2,5)
plt.xticks(new_ticks) # Tick Y 范围(-2.2,-1,1,1.5,2.4) ,Tick Label (-2.2, -1, 1, 1.5, 2.4) 别名(下面的英文) plt.yticks([-2.2,-1,1,1.5,2.4],
          [r'$really\ bad$',r'$bad$',r'$normal$',r'$good$',r'$really\ good$']) # 显示图像 plt.show()

运行效果,注意看 x, y 轴刻度以及标注变化

image

四、移动坐标轴

隐藏右边框和上边框:

  1. 使用plt.gca获取当前坐标轴信息。

  2. 使用.spines设置边框:右侧边框;使用.set_color设置边框颜色:默认白色;

    使用.spines设置边框:上边框;使用.set_color设置边框颜色:默认白色;

调整坐标轴至中心(将 left 边框调整到 x=0 处,将 bottom 边框调整到 y=0 处):

  1. 使用.xaxis.set_ticks_position设置 x 坐标刻度数字或名称的位置:bottom。(所有位置:top,bottom,both,default,none)。
  2. 使用.spines设置边框:x 轴;使用.set_position设置边框位置:y=0 的位置;(位置所有属性:outward,axes,data)
# 设置坐标轴 gca() 获取坐标轴信息 ax=plt.gca() # 使用.spines设置边框:x轴;将右边颜色设置为 none。 # 使用.set_position设置边框位置:y=0的位置;(位置所有属性:outward,axes,data) ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none') # 移动坐标轴 # 将 bottom 即是 x 坐标轴设置到 y=0 的位置。 ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('data',0)) # 将 left 即是 y 坐标轴设置到 x=0 的位置。 ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data',0))

最终代码

import matplotlib.pyplot as plt import numpy as np # 定义 x 变量的范围 (-3,3) 数量 50  x=np.linspace(-3,3,50)
y=x**2 # Figure 并指定大小 plt.figure(num=3,figsize=(8,5)) # 绘制 y=x^2 的图像,设置 color 为 red,线宽度是 1,线的样式是 -- plt.plot(x,y,color='red',linewidth=1.0,linestyle='--') # 设置 x,y 轴的范围以及 label 标注 plt.xlim(-1,2)
plt.ylim(-2,3)
plt.xlabel('x')
plt.ylabel('y') # 设置坐标轴刻度线  # Tick X 范围 (-1,2) Tick Label(-1,-0.25,0.5,1.25,2) 刻度数量 5 个 new_ticks=np.linspace(-1,2,5)
plt.xticks(new_ticks) # Tick Y 范围(-2.2,-1,1,1.5,2.4) ,Tick Label (-2.2, -1, 1, 1.5, 2.4) 别名(下面的英文) plt.yticks([-2.2,-1,1,1.5,2.4],
          [r'$really\ bad$',r'$bad$',r'$normal$',r'$good$',r'$really\ good$']) # 设置坐标轴 gca() 获取坐标轴信息 ax=plt.gca() # 使用.spines设置边框:x轴;将右边颜色设置为 none。 # 使用.set_position设置边框位置:y=0的位置;(位置所有属性:outward,axes,data) ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none') # 移动坐标轴 # 将 bottom 即是 x 坐标轴设置到 y=0 的位置。 ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('data',0)) # 将 left 即是 y 坐标轴设置到 x=0 的位置。 ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data',0)) # 显示图像 plt.show()

运行结果

image

五、设置标题

使用 .set_title 设置标题,声明标题为 y = x2,字体大小 14,颜色为 red。

# 设置标签 ax.set_title('y = x^2',fontsize=14,color='r')

最终代码

import matplotlib.pyplot as plt import numpy as np # 定义 x 变量的范围 (-3,3) 数量 50  x=np.linspace(-3,3,50)
y=x**2 # Figure 并指定大小 plt.figure(num=3,figsize=(8,5)) # 绘制 y=x^2 的图像,设置 color 为 red,线宽度是 1,线的样式是 -- plt.plot(x,y,color='red',linewidth=1.0,linestyle='--') # 设置 x,y 轴的范围以及 label 标注 plt.xlim(-1,2)
plt.ylim(-2,3)
plt.xlabel('x')
plt.ylabel('y') # 设置坐标轴刻度线  # Tick X 范围 (-1,2) Tick Label(-1,-0.25,0.5,1.25,2) 刻度数量 5 个 new_ticks=np.linspace(-1,2,5)
plt.xticks(new_ticks) # Tick Y 范围(-2.2,-1,1,1.5,2.4) ,Tick Label (-2.2, -1, 1, 1.5, 2.4) 别名(下面的英文) plt.yticks([-2.2,-1,1,1.5,2.4],
          [r'$really\ bad$',r'$bad$',r'$normal$',r'$good$',r'$really\ good$']) # 设置坐标轴 gca() 获取坐标轴信息 ax=plt.gca() # 使用.spines设置边框:x轴;将右边颜色设置为 none。 # 使用.set_position设置边框位置:y=0的位置;(位置所有属性:outward,axes,data) ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none') # 移动坐标轴 # 将 bottom 即是 x 坐标轴设置到 y=0 的位置。 ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('data',0)) # 将 left 即是 y 坐标轴设置到 x=0 的位置。 ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data',0)) # 设置标签 ax.set_title('y = x^2',fontsize=14,color='r') # 显示图像 plt.show()

运行结果

image

最后,附上 matplotlib 整体结构图(图片来自网络):