django的使用2-模型

[TOC]

模型

模型准确且唯一的描述了数据。它包含您储存的数据的重要字段和行为。一般来说,每一个模型都映射一张数据库表。有如下几个点:

  • 每个模型都是一个 Python 的类,这些类继承 django.db.models.Model
  • 模型类的每个属性都相当于一个数据库的字段。
  • 利用这些,Django 提供了一个自动生成访问数据库的 API;

example

定义了一个 Person 模型,拥有 first_name 和 last_name:

1
2
3
4
5
from django.db import models

class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)

first_name 和 last_name 是模型的 字段。每个字段都被指定为一个类属性,并且每个属性映射为一个数据库列。

上面的 Person 模型会创建一个如下的数据库表:

1
2
3
4
5
CREATE TABLE myapp_person (
"id" serial NOT NULL PRIMARY KEY,
"first_name" varchar(30) NOT NULL,
"last_name" varchar(30) NOT NULL
);

该表的名称规则 myapp_person = app名称_class名称,可修改。
id 字段会被自动添加,可修改。
创建数据表的语法是根据配置文件setting.py中设置的数据类型来定。

字段类型

模型中每一个字段都应该是某个 Field 类的实例, Django 利用这些字段类来实现以下功能:

  • 字段类型用以指定数据库数据类型(如:INTEGER, VARCHAR, TEXT)。
  • 在渲染表单字段时默认使用的 HTML 视图。
  • 基本的有效性验证功能,用于 Django 后台和自动生成的表单。

Django 内置了数十种字段类型;一起来看看常见的:

  • BooleanField

一个 true/false 字段。

  • CharField

一个字符串字段,适用于小到大的字符串。

  • DateField

一个日期,在 Python 中用一个 datetime.date 实例表示。

  • DateTimeField

一个日期和时间,在 Python 中用一个 datetime.datetime 实例表示。

  • TimeField

一个时间,在 Python 中用 datetime.time 实例表示。

  • DecimalField

一个固定精度的十进制数,在 Python 中用一个 Decimal 实例来表示。

  • EmailField

实际是一个 CharField,使用 EmailValidator 来检查该值是否为有效的电子邮件地址。

  • FloatField

在 Python 中用一个 float 实例表示的浮点数。

  • IntegerField

一个整数。从 -2147483648 到 2147483647 的值在 Django 支持的所有数据库中都是安全的。

  • TextField

一个大的文本字段。该字段的默认表单部件是一个 Textarea。

  • URLField

URL 的 CharField,由 URLValidator 验证。

关系字段

Django 还定义了一组表示关系的字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ForeignKey

from django.db import models

class Car(models.Model):
manufacturer = models.ForeignKey(
'Manufacturer',
on_delete=models.CASCADE,
)
# ...

class Manufacturer(models.Model):
# ...
pass

字段选项(字段值)

  • null
  • blank
  • choice
  • default
  • primary_key
  • unique
  • unique_for_date
  • unique_for_month
  • unique_for_year

more:https://docs.djangoproject.com/zh-hans/2.2/ref/models/fields/

关联关系

显然,关系型数据库的强大之处在于各表之间的关联关系。 Django 提供了定义三种最常见的数据库关联关系的方法:多对一,多对多,一对一。

多对一关联

定义一个多对一的关联关系,使用 django.db.models.ForeignKey 类。就和其它 Field 字段类型一样,只需要在模型中添加一个值为该类的属性。

例如,如果一个 Car 模型有一个制造者 Manufacturer –就是说一个 Manufacturer 制造许多辆车,但是每辆车都仅有一个制造者– 那么使用下面的方法定义这个关系:

1
2
3
4
5
6
7
8
9
from django.db import models

class Manufacturer(models.Model):
# ...
pass

class Car(models.Model):
manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
# ...

多对多关联

定义一个多对多的关联关系,使用 django.db.models.ManyToManyField 类。就和其他 Field 字段类型一样,只需要在模型中添加一个值为该类的属性。

例如:如果 Pizza 含有多种 Topping (配料) – 也就是一种 Topping 可能存在于多个 Pizza 中,并且每个 Pizza 含有多种 Topping –那么可以这样表示这种关系:

1
2
3
4
5
6
7
8
9
from django.db import models

class Topping(models.Model):
# ...
pass

class Pizza(models.Model):
# ...
toppings = models.ManyToManyField(Topping)

一对一关联

使用 OneToOneField 来定义一对一关系。就像使用其他类型的 Field 一样:在模型属性中包含它。

例如: 位置餐厅 其实可以是一对一的关系。

字段命名限制

  • 一个字段的名称不能是 Python 保留字,因为这会导致 Python 语法错误。
  • 一个字段名称不能包含连续的多个下划线,原因在于 Django 查询语法的工作方式。
  • 字段名不能以下划线结尾,原因同上。

模型属性

模型当中最重要的属性是 Manager。它是 Django 模型和数据库查询操作之间的接口,并且它被用作从数据库当中 获取实例,如果没有指定自定义的 Manager 默认名称是 objects。Manager 只能通过模型类来访问,不能通过模型实例来访问。

模型方法

在模型中添加自定义方法会给的对象提供自定义的“行级”操作能力。与之对应的是类 Manager 的方法意在提供“表级”的操作,模型方法应该在某个对象实例上生效。

重写之前定义的模型方法

还有一个 模型方法的集合,包含了一些可能自定义的数据库行为。尤其是这两个最有可能定制的方法 save() 和 delete()。

1
2
3
4
5
6
7
8
9
10
11
from django.db import models

class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()

def save(self, *args, **kwargs):
do_something()
super().save(*args, **kwargs) # Call the "real" save() method.
do_something_else()

执行自定义 SQL

另一个常见的模式是在模型方法和模块方法中编写自定义 SQL 语句。例如

1
Person.objects.raw('SELECT id, first_name, last_name, birth_date FROM myapp_person')

模型继承

模型继承在 Django 中与普通类继承在 Python 中的工作方式几乎完全相同

Django 有三种可用的集成风格。

  • 常见情况下,仅将父类用于子类公共信息的载体,因为不会想在每个子类中把这些代码都敲一遍。这样的父类永远都不会单独使用,所以 抽象基类 是需要的。
  • 若继承了一个模型(可能来源其它应用),且想要每个模型都有对应的数据表,客官这边请 多表继承。
  • 最后,若只想修改模型的 Python 级行为,而不是以任何形式修改模型字段, 代理模型 会是的菜。

抽象基类

抽象基类在你要将公共信息放入很多模型时会很有用。编写你的基类,并在 Meta 类中填入 abstract=True。该模型将不会创建任何数据表。当其用作其它模型类的基类时,它的字段会自动添加至子类。

一个例子:

1
2
3
4
5
6
7
8
9
10
11
from django.db import models

class CommonInfo(models.Model):
name = models.CharField(max_length=100)
age = models.PositiveIntegerField()

class Meta:
abstract = True

class Student(CommonInfo):
home_group = models.CharField(max_length=5)

Student 模型拥有3个字段: nameagehome_groupCommonInfo 模型不能用作普通的 Django 模型,因为它是一个抽象基类。它不会生成数据表,也没有管理器,也不能被实例化和保存。

多表继承

Django 支持的第二种模型继承方式是层次结构中的每个模型都是一个单独的模型。每个模型都指向分离的数据表,且可被独立查询和创建。继承关系介绍了子类和父类之间的连接。比如:

1
2
3
4
5
6
7
8
9
10
from django.db import models

class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)

class Restaurant(Place):
serves_hot_dogs = models.BooleanField(default=False)
serves_pizza = models.BooleanField(default=False)

代理模型

使用 多表继承 时,每个子类模型都会创建一张新表。这一般是期望的行为,因为子类需要一个地方存储基类中不存在的额外数据字段。不过,有时候你只想修改模型的 Python 级行为——可能是修改默认管理器,或添加一个方法。

代理模型就像普通模型一样申明。你需要告诉 Django 这是一个代理模型,通过将 Meta 类的 proxy 属性设置为 True。

例如,假设你想为 Person 模型添加一个方法。你可以这么做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from django.db import models

class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)

class MyPerson(Person):
class Meta:
proxy = True

def do_something(self):
# ...
pass

多重继承

和 Python 中的继承一样,Django 模型也能继承自多个父类模型。请记住,Python 的命名规则这里也有效。第一个出现的基类(比如 Meta )就是会被使用的那个;举个例子,如果存在多个父类包含 Meta,只有第一个会被使用,其它的都会被忽略。

1
2
3
4
5
6
7
8
9
10
class Article(models.Model):
article_id = models.AutoField(primary_key=True)
...

class Book(models.Model):
book_id = models.AutoField(primary_key=True)
...

class BookReview(Book, Article):
pass

参考:

django的使用1-简介

[TOC]

简介

Django 是一个用python语言写的web开发框架,最初被设计用于具有快速开发需求的新闻类站点,目的是要实现简单快捷的网站开发。
著名的MVC模式:所谓MVC就是把web应用分为模型(M),控制器(C),视图(V)三层;他们之间以一种插件似的,松耦合的方式连接在一起。

模型负责业务对象与数据库的对象(ORM),视图负责与用户的交互(页面),控制器(C)接受用户的输入调用模型和视图完成用户的请求。

Django的MTV模式本质上与MVC模式没有什么差别,也是各组件之间为了保持松耦合关系,只是定义上有些许不同,Django的MTV分别代表:

  • Model(模型):负责业务对象与数据库的对象(ORM)
  • Template(模版):负责如何把页面展示给用户
  • View(视图):负责业务逻辑,并在适当的时候调用Model和Template

此外,Django还有一个url分发器,它的作用是将一个个URL的页面请求分发给不同的view处理,view再调用相应的Model和Template

Django安装与基本使用

Django安装

1
pip install django

创建项目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
django-admin startproject your-project-name

# example -----------------------------------------------------------
(dj) alex@Alex:~/github$ django-admin startproject my_project
(dj) alex@Alex:~/github$ tree my_project
my_project
├── manage.py
└── my_project
├── asgi.py
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py

这些目录和文件的用处是:

  • manage.py: 一个让你用各种方式管理 Django 项目的命令行工具
  • my_project/ 目录包含你的项目,它是一个纯 Python 包。它的名字就是当你引用它内部任何东西时需要用到的 Python 包名。
  • my_project/init.py:一个空文件,告诉 Python 这个目录应该被认为是一个 Python 包。
  • my_project/settings.py:Django 项目的配置文件。
  • my_project/urls.py:Django 项目的 URL 声明,路由配置。
  • my_project/asgi.py:作为你的项目的运行在 ASGI 兼容的 Web 服务器上的入口。
  • my_project/wsgi.py:作为你的项目的运行在 WSGI 兼容的Web服务器上的入口。

创建应用

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
python manage.py startapp app01

# example -----------------------------------------------------------
(dj) alex@Alex:~/github$ cd my_project
(dj) alex@Alex:~/github/my_project$ ls
manage.py my_project
(dj) alex@Alex:~/github/my_project$ python manage.py startapp app01
(dj) alex@Alex:~/github/my_project$ tree
.
├── app01
│   ├── admin.py
│   ├── apps.py
│   ├── __init__.py
│   ├── migrations
│   │   └── __init__.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── manage.py
└── my_project
├── asgi.py
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py

启动项目

1
2
3
4
5
6
7
8
python manage.py runserver

# example -----------------------------------------------------------
(dj) alex@Alex:~/github/my_project$ python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

这里省略了host 和port的设置。python manage.py runserver 0.0.0.0:8000

manage.py 命令

项目中大量关于项目和应用的管理都可以通过 python manage.py ... 来处理。

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
(dj) alex@Alex:~/github/my_project$ python manage.py 

Type 'manage.py help <subcommand>' for help on a specific subcommand.

Available subcommands:

[auth]
changepassword
createsuperuser

[contenttypes]
remove_stale_contenttypes

[django]
check
compilemessages
createcachetable
dbshell
diffsettings
dumpdata
flush
inspectdb
loaddata
makemessages
makemigrations
migrate
sendtestemail
shell
showmigrations
sqlflush
sqlmigrate
sqlsequencereset
squashmigrations
startapp
startproject
test
testserver

[sessions]
clearsessions

[staticfiles]
collectstatic
findstatic
runserver

Model(模型)

Django使用一种叫对象关系映射器的技术,来处理数据和模型之间的的关系。

简单粗暴的理解是Django的模型就类似于MySQL的table。一个class就是描述一个table;而一个实例,就是table里面的一条数据;而类的方法就是对数据的处理。

设计模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
文件:my_project/my_project/models.py

from django.db import models

class Reporter(models.Model):
full_name = models.CharField(max_length=70)

def __str__(self):
return self.full_name

class Article(models.Model):
pub_date = models.DateField()
headline = models.CharField(max_length=200)
content = models.TextField()
reporter = models.ForeignKey(Reporter, on_delete=models.CASCADE)

def __str__(self):
return self.headline

这里定义了一个reporter的table, 其中有一个字段full_name.

应用数据模型

接下来,运行 Django 命令行实用程序以自动创建数据库表:

1
2
$ python manage.py makemigrations
$ python manage.py migrate

这里他会根据 my_project/settings.py 里面配置的数据连接,然后创建数据库和表。

享用便捷的 API

这里的意思就是Model有很多个方法,这些方法可以很方便的对数据进行处理。下面通过manage.py shell来演示一下:

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
python manage.py shell
>>> from news.models import Article, Reporter
>>> Reporter.objects.all()
<QuerySet []>

# Create a new Reporter.
>>> r = Reporter(full_name='John Smith')
>>> r.save()
>>> r.id
1
>>> Reporter.objects.all()
<QuerySet [<Reporter: John Smith>]>
>>> r.full_name
'John Smith'
>>> Reporter.objects.get(id=1)
<Reporter: John Smith>


# Create an article.
>>> from datetime import date
>>> a = Article(pub_date=date.today(), headline='Django is cool',
... content='Yeah.', reporter=r)
>>> a.save()
>>> Article.objects.all()
<QuerySet [<Article: Django is cool>]>
>>> r = a.reporter
>>> r.full_name
'John Smith'
>>> r.article_set.all()
<QuerySet [<Article: Django is cool>]>
>>> r.delete()

URLconf

简单来说,URLconf 就是路由。将不同的请求连接到不同的 View(视图) 上面。可以有很多种匹配方式。下面这个 URLconf 适用于前面 Reporter/Article 的例子:

1
2
3
4
5
6
7
8
9
10
11
文件:my_project/my_project/urls.py

from django.urls import path

from . import views

urlpatterns = [
path('articles/<int:year>/', views.year_archive),
path('articles/<int:year>/<int:month>/', views.month_archive),
path('articles/<int:year>/<int:month>/<int:pk>/', views.article_detail),
]

View(视图)

通常来说,一个视图的工作就是:从参数获取数据,装载一个模板,然后将根据获取的数据对模板进行渲染。下面是一个 year_archive 的视图样例:

1
2
3
4
5
6
7
8
9
10
文件:my_project/app01/views.py

from django.shortcuts import render

from .models import Article

def year_archive(request, year):
a_list = Article.objects.filter(pub_date__year=year)
context = {'year': year, 'article_list': a_list}
return render(request, 'news/year_archive.html', context)

Template(模版)

上面的代码加载了 news/year_archive.html 模板。

模板就是要返回给用户的页面框架,这个模板可以传递很多参数,比如一个博客的首页。它需要很多条文章标题来丰富这个页面,所以传递的参数就可以是一系列文章的标题。 这样模板和内容就独立开来了。最后吧模板和内容加载一起渲染一下,就可以返回到浏览器给用户看了。

例如上面的模板可以是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
{% extends "base.html" %}

{% block title %}Articles for {{ year }}{% endblock %}

{% block content %}
<h1>Articles for {{ year }}</h1>

{% for article in article_list %}
<p>{{ article.headline }}</p>
<p>By {{ article.reporter.full_name }}</p>
<p>Published {{ article.pub_date|date:"F j, Y" }}</p>
{% endfor %}
{% endblock %}

这个模板有一些语法,比如这个模板其实是继承了base.html模板,里面的for轮训,if判断。变量传递等。

参考:

Python线程、进程和协程详解

[TOC]

Python被人诟病最多的大概就是性能差,在这里讲一下 Python 的多进程,多线程与协程。首先声明这不是教程,看完这篇文章,大概能够对 Python 的多进程与多线程有一定的了解。

解释器环境:python3.5.1
我们都知道python网络编程的两大必学模块socket和socketserver,其中的socketserver是一个支持IO多路复用和多线程、多进程的模块。一般我们在socketserver服务端代码中都会写这么一句:

1
server = socketserver.ThreadingTCPServer(settings.IP_PORT, MyServer)

ThreadingTCPServer这个类是一个支持多线程和TCP协议的socketserver,它的继承关系是这样的:

1
2
class ThreadingTCPServer(ThreadingMixIn, TCPServer):
pass

右边的TCPServer实际上是它主要的功能父类,而左边的ThreadingMixIn则是实现了多线程的类,它自己本身则没有任何代码。
MixIn在python的类命名中,很常见,一般被称为“混入”,戏称“乱入”,通常为了某种重要功能被子类继承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class ThreadingMixIn:
daemon_threads = False

def process_request_thread(self, request, client_address):
try:
self.finish_request(request, client_address)
self.shutdown_request(request)
except:
self.handle_error(request, client_address)
self.shutdown_request(request)

def process_request(self, request, client_address):
t = threading.Thread(target = self.process_request_thread,
args = (request, client_address))
t.daemon = self.daemon_threads
t.start()

ThreadingMixIn类中,其实就定义了一个属性,两个方法。在process_request方法中实际调用的正是python内置的多线程模块threading。这个模块是python中所有多线程的基础,socketserver本质上也是利用了这个模块。

一、线程

线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不独立拥有系统资源,但它可与同属一个进程的其它线程共享该进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。就绪状态是指线程具备运行的所有条件,逻辑上可以运行,在等待处理机;运行状态是指线程占有处理机正在运行;阻塞状态是指线程在等待一个事件(如某个信号量),逻辑上不可执行。每一个应用程序都至少有一个进程和一个线程。线程是程序中一个单一的顺序控制流程。在单个程序中同时运行多个线程完成不同的被划分成一块一块的工作,称为多线程。

以上那一段,可以不用看!举个例子,厂家要生产某个产品,在它的生产基地建设了很多厂房,每个厂房内又有多条流水生产线。所有厂房配合将整个产品生产出来,某个厂房内的所有流水线将这个厂房负责的产品部分生产出来。每个厂房拥有自己的材料库,厂房内的生产线共享这些材料。而每一个厂家要实现生产必须拥有至少一个厂房一条生产线。那么这个厂家就是某个应用程序;每个厂房就是一个进程;每条生产线都是一个线程。

1.1 普通的多线程

在python中,threading模块提供线程的功能。通过它,我们可以轻易的在进程中创建多个线程。下面是个例子:

1
2
3
4
5
6
7
8
9
10
11
12
import threading
import time

def show(arg):
time.sleep(1)
print('thread'+str(arg))

for i in range(10):
t = threading.Thread(target=show, args=(i,))
t.start()
print('main thread stop')

上述代码创建了10个“前台”线程,然后控制器就交给了CPU,CPU根据指定算法进行调度,分片执行指令。
下面是Thread类的主要方法:
start 线程准备就绪,等待CPU调度
setName 为线程设置名称
getName 获取线程名称
setDaemon 设置为后台线程或前台线程(默认)
如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止。如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止。
join 逐个执行每个线程,执行完毕后继续往下执行,该方法使得多线程变得无意义。
run 线程被cpu调度后自动执行线程对象的run方法

1.2 自定义线程类

对于threading模块中的Thread类,本质上是执行了它的run方法。因此可以自定义线程类,让它继承Thread类,然后重写run方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import threading
class MyThreading(threading.Thread):
def __init__(self,func,arg):
super(MyThreading,self).__init__()
self.func = func
self.arg = arg

def run(self):
self.func(self.arg)

def f1(args):
print(args)

obj = MyThreading(f1, 123)
obj.start()

1.3 线程锁

CPU执行任务时,在线程之间是进行随机调度的,并且每个线程可能只执行n条代码后就转而执行另外一条线程。由于在一个进程中的多个线程之间是共享资源和数据的,这就容易造成资源抢夺或脏数据,于是就有了锁的概念,限制某一时刻只有一个线程能访问某个指定的数据。

1.3.1 未使用锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import threading
import time
NUM = 0
def show():
global NUM
NUM += 1
name = t.getName()
time.sleep(1) # 注意,这行语句的位置很重要,必须在NUM被修改后,否则观察不到脏数据的现象。
print(name, "执行完毕后,NUM的值为: ", NUM)

for i in range(10):
t = threading.Thread(target=show)
t.start()

print('main thread stop')

上述代码运行后,结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
main thread stop
Thread-1 执行完毕后,NUM的值为: 10
Thread-2 执行完毕后,NUM的值为: 10
Thread-4 执行完毕后,NUM的值为: 10
Thread-9 执行完毕后,NUM的值为: 10
Thread-3 执行完毕后,NUM的值为: 10
Thread-6 执行完毕后,NUM的值为: 10
Thread-8 执行完毕后,NUM的值为: 10
Thread-7 执行完毕后,NUM的值为: 10
Thread-5 执行完毕后,NUM的值为: 10
Thread-10 执行完毕后,NUM的值为: 10

由此可见,由于线程同时访问一个数据,产生了错误的结果。为了解决这个问题,python在threading模块中定义了几种线程锁类,分别是:
Lock 普通锁(不可嵌套)
RLock 普通锁(可嵌套)常用
Semaphore 信号量
event 事件
condition 条件

1.3.2 普通锁Lock和RLock

类名:Lock或RLock
普通锁,也叫互斥锁,是独占的,同一时刻只有一个线程被放行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import time
import threading

NUM = 10
def func(lock):
global NUM
lock.acquire() # 让锁开始起作用
NUM -= 1
time.sleep(1)
print(NUM)
lock.release() # 释放锁

lock = threading.Lock() # 实例化一个锁对象
for i in range(10):
t = threading.Thread(target=func, args=(lock,)) # 记得把锁当作参数传递给func参数
t.start()

以上是threading模块的Lock类,它不支持嵌套锁。RLcok类的用法和Lock一模一样,但它支持嵌套,因此我们一般直接使用RLcok类。

1.3.3 信号量(Semaphore)

类名:BoundedSemaphore
这种锁允许一定数量的线程同时更改数据,它不是互斥锁。比如地铁安检,排队人很多,工作人员只允许一定数量的人进入安检区,其它的人继续排队。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import time
import threading

def run(n):
semaphore.acquire()
print("run the thread: %s" % n)
time.sleep(1)
semaphore.release()

num = 0
semaphore = threading.BoundedSemaphore(5) # 最多允许5个线程同时运行
for i in range(20):
t = threading.Thread(target=run, args=(i,))
t.start()

1.3.4 事件(Event)

类名:Event
事件主要提供了三个方法 set、wait、clear。
事件机制:全局定义了一个“Flag”,如果“Flag”的值为False,那么当程序执行wait方法时就会阻塞,如果“Flag”值为True,那么wait方法时便不再阻塞。这种锁,类似交通红绿灯(默认是红灯),它属于在红灯的时候一次性阻挡所有线程,在绿灯的时候,一次性放行所有的排队中的线程。
clear:将“Flag”设置为False
set:将“Flag”设置为True

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import threading

def func(e,i):
print(i)
e.wait() # 检测当前event是什么状态,如果是红灯,则阻塞,如果是绿灯则继续往下执行。默认是红灯。
print(i+100)

event = threading.Event()
for i in range(10):
t = threading.Thread(target=func, args=(event, i))
t.start()

event.clear() # 主动将状态设置为红灯
inp = input(">>>")
if inp == "1":
event.set() # 主动将状态设置为绿灯

1.3.5 条件(condition)

类名:Condition
该机制会使得线程等待,只有满足某条件时,才释放n个线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import threading

def condition():
ret = False
r = input(">>>")
if r == "yes":
ret = True
return ret

def func(conn, i):
print(i)
conn.acquire()
conn.wait_for(condition) # 这个方法接受一个函数的返回值
print(i+100)
conn.release()

c = threading.Condition()
for i in range(10):
t = threading.Thread(target=func, args=(c, i,))
t.start()

上面的例子,每输入一次“yes”放行了一个线程。下面这个,可以选择一次放行几个线程。

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
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import threading

def run(n):
con.acquire()
con.wait()
print("run the thread: %s" %n)
con.release()

if __name__ == '__main__':
con = threading.Condition()
for i in range(10):
t = threading.Thread(target=run, args=(i,))
t.start()
while True:
inp = input('>>>')
if inp == "q":
break
# 下面这三行是固定语法
con.acquire()
con.notify(int(inp)) # 这个方法接收一个整数,表示让多少个线程通过
con.release()

1.5 全局解释器锁(GIL)

既然介绍了多线程和线程锁,那就不得不提及python的GIL,也就是全局解释器锁。在编程语言的世界,python因为GIL的问题广受诟病,因为它在解释器的层面限制了程序在同一时间只有一个线程被CPU实际执行,而不管你的程序里实际开了多少条线程。所以我们经常能发现,python中的多线程编程有时候效率还不如单线程,就是因为这个原因。那么,对于这个GIL,一些普遍的问题如下:

每种编程语言都有GIL吗?

以python官方Cpython解释器为代表….其他语言好像未见。

为什么要有GIL?

作为解释型语言,Python的解释器必须做到既安全又高效。我们都知道多线程编程会遇到的问题。解释器要留意的是避免在不同的线程操作内部共享的数据。同时它还要保证在管理用户线程时总是有最大化的计算资源。那么,不同线程同时访问时,数据的保护机制是怎样的呢?答案是解释器全局锁GIL。GIL对诸如当前线程状态和为垃圾回收而用的堆分配对象这样的东西的访问提供着保护。

为什么不能去掉GIL?

首先,在早期的python解释器依赖较多的全局状态,传承下来,使得想要移除当今的GIL变得更加困难。其次,对于程序员而言,仅仅是想要理解它的实现就需要对操作系统设计、多线程编程、C语言、解释器设计和CPython解释器的实现有着非常彻底的理解。

在1999年,针对Python1.5,一个“freethreading”补丁已经尝试移除GIL,用细粒度的锁来代替。然而,GIL的移除给单线程程序的执行速度带来了一定的负面影响。当用单线程执行时,速度大约降低了40%。虽然使用两个线程时在速度上得到了提高,但这个提高并没有随着核数的增加而线性增长。因此这个补丁没有被采纳。

另外,在python的不同解释器实现中,如PyPy就移除了GIL,其执行速度更快(不单单是去除GIL的原因)。然而,我们通常使用的CPython占有着统治地位的使用量,所以,你懂的。

在Python 3.2中实现了一个新的GIL,并且带着一些积极的结果。这是自1992年以来,GIL的一次最主要改变。旧的GIL通过对Python指令进行计数来确定何时放弃GIL。在新的GIL实现中,用一个固定的超时时间来指示当前的线程以放弃这个锁。在当前线程保持这个锁,且当第二个线程请求这个锁的时候,当前线程就会在5ms后被强制释放掉这个锁(这就是说,当前线程每5ms就要检查其是否需要释放这个锁)。当任务是可行的时候,这会使得线程间的切换更加可预测。

GIL对我们有什么影响?

最大的影响是我们不能随意使用多线程。要区分任务场景。

在单核cpu情况下对性能的影响可以忽略不计,多线程多进程都差不多。在多核CPU时,多线程效率较低。GIL对单进程和多进程没有影响。

在实际使用中有什么好的建议?

建议在IO密集型任务中使用多线程,在计算密集型任务中使用多进程。深入研究python的协程机制,你会有惊喜的。

更多的详细介绍和说明请参考下面的文献:

原文:Python’s Hardest Problem
译文:Python 最难的问题

1.6 定时器(Timer)

定时器,指定n秒后执行某操作。很简单但很使用的东西。

1
2
3
4
5
6
from threading import Timer
def hello():
print("hello, world")
t = Timer(1, hello) # 表示1秒后执行hello函数
t.start()

通常而言,队列是一种先进先出的数据结构,与之对应的是堆栈这种后进先出的结构。但是在python中,它内置了一个queue模块,它不但提供普通的队列,还提供一些特殊的队列。具体如下:

queue.Queue :先进先出队列
queue.LifoQueue :后进先出队列
queue.PriorityQueue :优先级队列
queue.deque :双向队列

1.6.1 Queue:先进先出队列

这是最常用也是最普遍的队列,先看一个例子。

1
2
3
4
5
6
7
8
9
10
import queue
q = queue.Queue(5)
q.put(11)
q.put(22)
q.put(33)

print(q.get())
print(q.get())
print(q.get())

Queue类的参数和方法:

maxsize 队列的最大元素个数,也就是queue.Queue(5)中的5。当队列内的元素达到这个值时,后来的元素默认会阻塞,等待队列腾出位置。

1
2
3
4
def __init__(self, maxsize=0):
self.maxsize = maxsize
self._init(maxsize)

qsize() 获取当前队列中元素的个数,也就是队列的大小
empty() 判断当前队列是否为空,返回True或者False
full() 判断当前队列是否已满,返回True或者False

1
2
put(self, block=True, timeout=None)

往队列里放一个元素,默认是阻塞和无时间限制的。如果,block设置为False,则不阻塞,这时,如果队列是满的,放不进去,就会弹出异常。如果timeout设置为n秒,则会等待这个秒数后才put,如果put不进去则弹出异常。

1
2
get(self, block=True, timeout=None)

从队列里获取一个元素。参数和put是一样的意思。
join()阻塞进程,直到所有任务完成,需要配合另一个方法task_done。

1
2
3
4
5
def join(self):
with self.all_tasks_done:
while self.unfinished_tasks:
self.all_tasks_done.wait()

task_done()表示某个任务完成。每一条get语句后需要一条task_done。

1
2
3
4
5
6
7
8
9
10
import queue
q = queue.Queue(5)
q.put(11)
q.put(22)
print(q.get())
q.task_done()
print(q.get())
q.task_done()
q.join()

1.6.2 LifoQueue:后进先出队列

类似于“堆栈”,后进先出。也较常用。

1
2
3
4
5
import queue
q = queue.LifoQueue()
q.put(123)
q.put(456)
print(q.get())

上述代码运行结果是:456

1.6.3 PriorityQueue:优先级队列

带有权重的队列,每个元素都是一个元组,前面的数字表示它的优先级,数字越小优先级越高,同样的优先级先进先出

1
2
3
4
5
6
q = queue.PriorityQueue()
q.put((1,"alex1"))
q.put((1,"alex2"))
q.put((1,"alex3"))
q.put((3,"alex3"))
print(q.get())

1.6.4 deque:双向队列

Queue和LifoQueue的“综合体”,双向进出。方法较多,使用复杂,慎用!

1
2
3
4
5
6
7
q = queue.deque()
q.append(123)
q.append(333)
q.appendleft(456)
q.pop()
q.popleft()

1.7 生产者消费者模型

利用多线程和队列可以搭建一个生产者消费者模型,用于处理大并发的服务。

在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。

为什么要使用生产者和消费者模式

在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。

什么是生产者消费者模式

生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

这个阻塞队列就是用来给生产者和消费者解耦的。纵观大多数设计模式,都会找一个第三者出来进行解耦,如工厂模式的第三者是工厂类,模板模式的第三者是模板类。在学习一些设计模式的过程中,如果先找到这个模式的第三者,能帮助我们快速熟悉一个设计模式。

以上摘自方腾飞的《聊聊并发——生产者消费者模式》

下面是一个简单的厨师做包子,顾客吃包子的例子。

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
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import time
import queue
import threading

q = queue.Queue(10)
def productor(i):
while True:
q.put("厨师 %s 做的包子!"%i)
time.sleep(2)

def consumer(k):
while True:
print("顾客 %s 吃了一个 %s"%(k,q.get()))
time.sleep(1)

for i in range(3):
t = threading.Thread(target=productor,args=(i,))
t.start()

for k in range(10):
v = threading.Thread(target=consumer,args=(k,))
v.start()

# 看看有几个厨师 (⊙o⊙) 有几个顾客(⊙o⊙), 是厨师不够,还是顾客少 ^_^

1.8 线程池

在使用多线程处理任务时也不是线程越多越好,由于在切换线程的时候,需要切换上下文环境,依然会造成cpu的大量开销。为解决这个问题,线程池的概念被提出来了。预先创建好一个较为优化的数量的线程,让过来的任务立刻能够使用,就形成了线程池。在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
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import queue
import time
import threading

class MyThreadPool:
def __init__(self, maxsize=5):
self.maxsize = maxsize
self._q = queue.Queue(maxsize)
for i in range(maxsize):
self._q.put(threading.Thread)

def get_thread(self):
return self._q.get()

def add_thread(self):
self._q.put(threading.Thread)

def task(i, pool):
print(i)
time.sleep(1)
pool.add_thread()

pool = MyThreadPool(5)
for i in range(100):
t = pool.get_thread()
obj = t(target=task, args=(i,pool))
obj.start()

上面的例子是把线程类当做元素添加到队列内。实现方法比较糙,每个线程使用后就被抛弃,一开始就将线程开到满,因此性能较差。下面是一个相对好一点的例子:

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import queue
import threading
import contextlib
import time

StopEvent = object() # 创建空对象

class ThreadPool(object):

def __init__(self, max_num, max_task_num = None):
if max_task_num:
self.q = queue.Queue(max_task_num)
else:
self.q = queue.Queue()
self.max_num = max_num
self.cancel = False
self.terminal = False
self.generate_list = []
self.free_list = []

def run(self, func, args, callback=None):
"""
线程池执行一个任务
:param func: 任务函数
:param args: 任务函数所需参数
:param callback: 任务执行失败或成功后执行的回调函数,回调函数有两个参数1、任务函数执行状态;2、任务函数返回值(默认为None,即:不执行回调函数)
:return: 如果线程池已经终止,则返回True否则None
"""
if self.cancel:
return
if len(self.free_list) == 0 and len(self.generate_list) < self.max_num:
self.generate_thread()
w = (func, args, callback,)
self.q.put(w)

def generate_thread(self):
"""
创建一个线程
"""
t = threading.Thread(target=self.call)
t.start()

def call(self):
"""
循环去获取任务函数并执行任务函数
"""
current_thread = threading.currentThread
self.generate_list.append(current_thread)

event = self.q.get()
while event != StopEvent:
func, arguments, callback = event
try:
result = func(*arguments)
success = True
except Exception as e:
success = False
result = None
if callback is not None:
try:
callback(success, result)
except Exception as e:
pass

with self.worker_state(self.free_list, current_thread):
if self.terminal:
event = StopEvent
else:
event = self.q.get()
else:
self.generate_list.remove(current_thread)

def close(self):
"""
执行完所有的任务后,所有线程停止
"""
self.cancel = True
full_size = len(self.generate_list)
while full_size:
self.q.put(StopEvent)
full_size -= 1

def terminate(self):
"""
无论是否还有任务,终止线程
"""
self.terminal = True

while self.generate_list:
self.q.put(StopEvent)

self.q.empty()

@contextlib.contextmanager
def worker_state(self, state_list, worker_thread):
"""
用于记录线程中正在等待的线程数
"""
state_list.append(worker_thread)
try:
yield
finally:
state_list.remove(worker_thread)

# How to use
pool = ThreadPool(5)

def callback(status, result):
# status, execute action status
# # result, execute action return value
# pass

def action(i):
print(i)

for i in range(30):
ret = pool.run(action, (i,), callback)

time.sleep(5)
print(len(pool.generate_list), len(pool.free_list))
print(len(pool.generate_list), len(pool.free_list))
# pool.close()
# pool.terminate()

二、进程

在python中multiprocess模块提供了Process类,实现进程相关的功能。但是,由于它是基于fork机制的,因此不被windows平台支持。想要在windows中运行,必须使用if name == ‘main:的方式,显然这只能用于调试和学习,不能用于实际环境。
(PS:在这里我必须吐槽一下python的包、模块和类的组织结构。在multiprocess中你既可以import大写的Process,也可以import小写的process,这两者是完全不同的东西。这种情况在python中很多,新手容易傻傻分不清。)
下面是一个简单的多进程例子,你会发现Process的用法和Thread的用法几乎一模一样。

1
2
3
4
5
6
7
8
9
10
from multiprocessing import Process

def foo(i):
print("This is Process ", i)

if __name__ == '__main__':
for i in range(5):
p = Process(target=foo, args=(i,))
p.start()

2.1 进程的数据共享

每个进程都有自己独立的数据空间,不同进程之间通常是不能共享数据,创建一个进程需要非常大的开销。

1
2
3
4
5
6
7
8
9
10
11
12
13
from multiprocessing import Process
list_1 = []
def foo(i):
list_1.append(i)
print("This is Process ", i," and list_1 is ", list_1)

if __name__ == '__main__':
for i in range(5):
p = Process(target=foo, args=(i,))
p.start()

print("The end of list_1:", list_1)

运行上面的代码,你会发现列表list_1在各个进程中只有自己的数据,完全无法共享。想要进程之间进行资源共享可以使用queues/Array/Manager这三个multiprocess模块提供的类。

2.1.1 使用Array共享数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from multiprocessing import Process
from multiprocessing import Array

def Foo(i,temp):
temp[0] += 100
for item in temp:
print(i,'----->',item)

if __name__ == '__main__':
temp = Array('i', [11, 22, 33, 44])
for i in range(2):
p = Process(target=Foo, args=(i,temp))
p.start()

对于Array数组类,括号内的“i”表示它内部的元素全部是int类型,而不是指字符i,列表内的元素可以预先指定,也可以指定列表长度。概括的来说就是Array类在实例化的时候就必须指定数组的数据类型和数组的大小,类似temp = Array(‘i’, 5)。对于数据类型有下面的表格对应:
‘c’: ctypes.c_char, ‘u’: ctypes.c_wchar,
‘b’: ctypes.c_byte, ‘B’: ctypes.c_ubyte,
‘h’: ctypes.c_short, ‘H’: ctypes.c_ushort,
‘i’: ctypes.c_int, ‘I’: ctypes.c_uint,
‘l’: ctypes.c_long, ‘L’: ctypes.c_ulong,
‘f’: ctypes.c_float, ‘d’: ctypes.c_double

2.1.2 使用Manager共享数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from multiprocessing import Process,Manager

def Foo(i,dic):
dic[i] = 100+i
print(dic.values())

if __name__ == '__main__':
manage = Manager()
dic = manage.dict()
for i in range(10):
p = Process(target=Foo, args=(i,dic))
p.start()
p.join()

Manager比Array要好用一点,因为它可以同时保存多种类型的数据格式。

2.1.3 使用queues的Queue类共享数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import multiprocessing
from multiprocessing import Process
from multiprocessing import queues

def foo(i,arg):
arg.put(i)
print('The Process is ', i, "and the queue's size is ", arg.qsize())

if __name__ == "__main__":
li = queues.Queue(20, ctx=multiprocessing)
for i in range(10):
p = Process(target=foo, args=(i,li,))
p.start()

这里就有点类似上面的队列了。从运行结果里,你还能发现数据共享中存在的脏数据问题。另外,比较悲催的是multiprocessing里还有一个Queue,一样能实现这个功能。

2.2 进程锁

为了防止和多线程一样的出现数据抢夺和脏数据的问题,同样需要设置进程锁。与threading类似,在multiprocessing里也有同名的锁类RLock, Lock, Event, Condition, Semaphore,连用法都是一样样的!(这个我喜欢)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from multiprocessing import Process
from multiprocessing import queues
from multiprocessing import Array
from multiprocessing import RLock, Lock, Event, Condition, Semaphore
import multiprocessing
import time

def foo(i,lis,lc):
lc.acquire()
lis[0] = lis[0] - 1
time.sleep(1)
print('say hi',lis[0])
lc.release()

if __name__ == "__main__":
# li = []
li = Array('i', 1)
li[0] = 10
lock = RLock()
for i in range(10):
p = Process(target=foo,args=(i,li,lock))
p.start()

2.3 进程池

既然有线程池,那必然也有进程池。但是,python给我们内置了一个进程池,不需要像线程池那样需要自定义,你只需要简单的from multiprocessing import Pool。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from multiprocessing import Pool
import time

def f1(args):
time.sleep(1)
print(args)

if __name__ == '__main__':
p = Pool(5)
for i in range(30):
p.apply_async(func=f1, args= (i,))
p.close() # 等子进程执行完毕后关闭线程池
# time.sleep(2)
# # p.terminate() # 立刻关闭线程池
p.join()

进程池内部维护一个进程序列,当使用时,去进程池中获取一个进程,如果进程池序列中没有可供使用的进程,那么程序就会等待,直到进程池中有可用进程为止。
进程池中有以下几个主要方法:
apply:从进程池里取一个进程并执行
apply_async:apply的异步版本
terminate:立刻关闭线程池
join:主进程等待所有子进程执行完毕,必须在close或terminate之后
close:等待所有进程结束后,才关闭线程池

三、协程

线程和进程的操作是由程序触发系统接口,最后的执行者是系统,它本质上是操作系统提供的功能。而协程的操作则是程序员指定的,在python中通过yield,人为的实现并发处理。
协程存在的意义:对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时。协程,则只使用一个线程,分解一个线程成为多个“微线程”,在一个线程中规定某个代码块的执行顺序。
协程的适用场景:当程序中存在大量不需要CPU的操作时(IO)。
在不需要自己“造轮子”的年代,同样有第三方模块为我们提供了高效的协程,这里介绍一下greenlet和gevent。本质上,gevent是对greenlet的高级封装,因此一般用它就行,这是一个相当高效的模块。
在使用它们之前,需要先安装,可以通过源码,也可以通过pip。

3.1 greenlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from greenlet import greenlet

def test1():
print(12)
gr2.switch()
print(34)
gr2.switch()

def test2():
print(56)
gr1.switch()
print(78)

gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()

实际上,greenlet就是通过switch方法在不同的任务之间进行切换。

3.2 gevent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from gevent import monkey; monkey.patch_all()
import gevent
import requests

def f(url):
print('GET: %s' % url)
resp = requests.get(url)
data = resp.text
print('%d bytes received from %s.' % (len(data), url))

gevent.joinall([
gevent.spawn(f, 'https://www.python.org/'),
gevent.spawn(f, 'https://www.yahoo.com/'),
gevent.spawn(f, 'https://github.com/'),
])

通过joinall将任务f和它的参数进行统一调度,实现单线程中的协程。代码封装层次很高,实际使用只需要了解它的几个主要方法即可。

参考:

https://www.jianshu.com/p/8682c675c7bb

pycharm profession active

[TOC]

profession的版本要比免费版多些功能,这里就不多介绍了,自己看官方.

1.授权服务器(License Server URLS):

**使用方法:**激活时选择License server 填入http://idea.imsxm.com 点击Active即可 active.jpg

how-to-active: when active,type the url in License server address input box,and then press the Active button:)

2.local proxy

如果第一种方法被拌掉了,那么下载这个实现本地proxy代理 [idea_active_proxy.exe]

open it and use http://localhost:8888 to active JB.when done,you can close it.

意思就是下载这个idea_active_proxy.exe,实现反向代理,然后把http://idea.imsxm.com 换成 http://localhost:8888 即可。

这样在新建项目的时候就可以看到除了pure Python意外的其他项目了。

new_project.png

这种方法还是有点不好,那就是每次你运行pycharm的时候都要先运行这个idea_active_proxy.exe

3.localhost

第三种就更好了.

首先在你的host文件中添加 0.0.0.0 account.jetbrains.com

然后就是打开pycharm,在那个激活页面选择 Activation code

active.jpg

复制通用注册码,激活即可.

1
EB101IWSWD-eyJsaWNlbnNlSWQiOiJFQjEwMUlXU1dEIiwibGljZW5zZWVOYW1lIjoibGFuIHl1IiwiYXNzaWduZWVOYW1lIjoiIiwiYXNzaWduZWVFbWFpbCI6IiIsImxpY2Vuc2VSZXN0cmljdGlvbiI6IkZvciBlZHVjYXRpb25hbCB1c2Ugb25seSIsImNoZWNrQ29uY3VycmVudFVzZSI6ZmFsc2UsInByb2R1Y3RzIjpbeyJjb2RlIjoiSUkiLCJwYWlkVXBUbyI6IjIwMTgtMTAtMTQifSx7ImNvZGUiOiJSUzAiLCJwYWlkVXBUbyI6IjIwMTgtMTAtMTQifSx7ImNvZGUiOiJXUyIsInBhaWRVcFRvIjoiMjAxOC0xMC0xNCJ9LHsiY29kZSI6IlJEIiwicGFpZFVwVG8iOiIyMDE4LTEwLTE0In0seyJjb2RlIjoiUkMiLCJwYWlkVXBUbyI6IjIwMTgtMTAtMTQifSx7ImNvZGUiOiJEQyIsInBhaWRVcFRvIjoiMjAxOC0xMC0xNCJ9LHsiY29kZSI6IkRCIiwicGFpZFVwVG8iOiIyMDE4LTEwLTE0In0seyJjb2RlIjoiUk0iLCJwYWlkVXBUbyI6IjIwMTgtMTAtMTQifSx7ImNvZGUiOiJETSIsInBhaWRVcFRvIjoiMjAxOC0xMC0xNCJ9LHsiY29kZSI6IkFDIiwicGFpZFVwVG8iOiIyMDE4LTEwLTE0In0seyJjb2RlIjoiRFBOIiwicGFpZFVwVG8iOiIyMDE4LTEwLTE0In0seyJjb2RlIjoiUFMiLCJwYWlkVXBUbyI6IjIwMTgtMTAtMTQifSx7ImNvZGUiOiJDTCIsInBhaWRVcFRvIjoiMjAxOC0xMC0xNCJ9LHsiY29kZSI6IlBDIiwicGFpZFVwVG8iOiIyMDE4LTEwLTE0In0seyJjb2RlIjoiUlNVIiwicGFpZFVwVG8iOiIyMDE4LTEwLTE0In1dLCJoYXNoIjoiNjk0NDAzMi8wIiwiZ3JhY2VQZXJpb2REYXlzIjowLCJhdXRvUHJvbG9uZ2F0ZWQiOmZhbHNlLCJpc0F1dG9Qcm9sb25nYXRlZCI6ZmFsc2V9-Gbb7jeR8JWOVxdUFaXfJzVU/O7c7xHQyaidCnhYLp7v32zdeXiHUU7vlrrm5y9ZX0lmQk3plCCsW+phrC9gGAPd6WDKhkal10qVNg0larCR2tQ3u8jfv1t2JAvWrMOJfFG9kKsJuw1P4TozZ/E7Qvj1cupf/rldhoOmaXMyABxNN1af1RV3bVhe4FFZe0p7xlIJF/ctZkFK62HYmh8V3AyhUNTzrvK2k+t/tlDJz2LnW7nYttBLHld8LabPlEEjpTHswhzlthzhVqALIgvF0uNbIJ5Uwpb7NqR4U/2ob0Z+FIcRpFUIAHEAw+RLGwkCge5DyZKfx+RoRJ/In4q/UpA==-MIIEPjCCAiagAwIBAgIBBTANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1KZXRQcm9maWxlIENBMB4XDTE1MTEwMjA4MjE0OFoXDTE4MTEwMTA4MjE0OFowETEPMA0GA1UEAwwGcHJvZDN5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxcQkq+zdxlR2mmRYBPzGbUNdMN6OaXiXzxIWtMEkrJMO/5oUfQJbLLuMSMK0QHFmaI37WShyxZcfRCidwXjot4zmNBKnlyHodDij/78TmVqFl8nOeD5+07B8VEaIu7c3E1N+e1doC6wht4I4+IEmtsPAdoaj5WCQVQbrI8KeT8M9VcBIWX7fD0fhexfg3ZRt0xqwMcXGNp3DdJHiO0rCdU+Itv7EmtnSVq9jBG1usMSFvMowR25mju2JcPFp1+I4ZI+FqgR8gyG8oiNDyNEoAbsR3lOpI7grUYSvkB/xVy/VoklPCK2h0f0GJxFjnye8NT1PAywoyl7RmiAVRE/EKwIDAQABo4GZMIGWMAkGA1UdEwQCMAAwHQYDVR0OBBYEFGEpG9oZGcfLMGNBkY7SgHiMGgTcMEgGA1UdIwRBMD+AFKOetkhnQhI2Qb1t4Lm0oFKLl/GzoRykGjAYMRYwFAYDVQQDDA1KZXRQcm9maWxlIENBggkA0myxg7KDeeEwEwYDVR0lBAwwCgYIKwYBBQUHAwEwCwYDVR0PBAQDAgWgMA0GCSqGSIb3DQEBCwUAA4ICAQC9WZuYgQedSuOc5TOUSrRigMw4/+wuC5EtZBfvdl4HT/8vzMW/oUlIP4YCvA0XKyBaCJ2iX+ZCDKoPfiYXiaSiH+HxAPV6J79vvouxKrWg2XV6ShFtPLP+0gPdGq3x9R3+kJbmAm8w+FOdlWqAfJrLvpzMGNeDU14YGXiZ9bVzmIQbwrBA+c/F4tlK/DV07dsNExihqFoibnqDiVNTGombaU2dDup2gwKdL81ua8EIcGNExHe82kjF4zwfadHk3bQVvbfdAwxcDy4xBjs3L4raPLU3yenSzr/OEur1+jfOxnQSmEcMXKXgrAQ9U55gwjcOFKrgOxEdek/Sk1VfOjvS+nuM4eyEruFMfaZHzoQiuw4IqgGc45ohFH0UUyjYcuFxxDSU9lMCv8qdHKm+wnPRb0l9l5vXsCBDuhAGYD6ss+Ga+aDY6f/qXZuUCEUOH3QUNbbCUlviSz6+GiRnt1kA9N2Qachl+2yBfaqUqr8h7Z2gsx5LcIf5kYNsqJ0GavXTVyWh7PYiKX4bs354ZQLUwwa/cG++2+wNWP+HtBhVxMRNTdVhSm38AknZlD+PTAsWGu9GyLmhti2EnVwGybSD2Dxmhxk3IPCkhKAK+pl0eWYGZWG3tJ9mZ7SowcXLWDFAk0lRJnKGFMTggrWjV8GYpw5bq23VmIqqDLgkNzuoog==

git常见问题

[TOC]

1.git删除文件

当我们想要删除某个文件,直接删除是没有用的.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@vultr hexo]# git status 
# On branch master
nothing to commit (working directory clean)
[root@vultr hexo]# rm source/_posts/test.txt
rm: remove regular empty file `source/_posts/test.txt'? y
[root@vultr hexo]# git status
# On branch master
# Changed but not updated:
# (use "git add/rm <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# deleted: source/_posts/test.txt
#
no changes added to commit (use "git add" and/or "git commit -a")
[root@vultr hexo]# git add .
[root@vultr hexo]# git commit -m "delete test file "
# On branch master
# Changed but not updated:
# (use "git add/rm <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# deleted: source/_posts/test.txt
#
no changes added to commit (use "git add" and/or "git commit -a")

直接使用rm 删除的仅仅是工作区的文件,并没有删除暂存区,所以commit 不会生效. 由于平时使用基本都是修改或者增加新文件,大家习惯用 git add 命令,而忽略了 git rm , 其实在git status 都有提示,仔细看看就知道了.

正确的做法应该是下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@vultr hexo]# git rm source/_posts/test.txt
rm 'source/_posts/test.txt'
[root@vultr hexo]# git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# deleted: source/_posts/test.txt
#
[root@vultr hexo]# git commit -m "delete test"
[master 63184bb] delete test
0 files changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 source/_posts/test.txt
[root@vultr hexo]# git status
# On branch master
# Your branch is ahead of 'origin/master' by 1 commit.
#
nothing to commit (working directory clean)
[root@vultr hexo]# git push origin master

2.假大空的ahead of commits

在部署的环境中,我们根本不会修改代码,但是当你用git status 查看的时候发现会有 Your branch is ahead of 'origin/master' by 8 commits. 这样的提示:

1
2
3
4
5
6
7
8
9
[root@vultr hexo]# git status 
# On branch master
# Your branch is ahead of 'origin/master' by 8 commits.
#
nothing to commit (working directory clean)
[root@vultr hexo]# ls
_config.yml db.json hexo.txt Makefile node_modules package.json package-lock.json public README.md scaffolds source themes
[root@vultr hexo]# git push origin master
Everything up-to-date

然而这都是幻象,当你git push 准备提交的时候发现,根本没有commit. 完全可以不用理会,如果你觉得碍眼,就git push 意思一下.

3. git clone ssh报错

ssh 获取代码的方式比https的方便,无需手动输入密码。因此如果有ssh的方式,大家都愿意用ssh。那么接下来看看我在Linux上部署ssh方式获取git代码的坑吧。

首先来看看最常见的报错

1
2
3
4
5
6
$git clone ssh://git@git.gitlab.com:2222/test/test_project.git
Permission denied (publickey).
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

这个错误很明显,要用gitlab或者GitHub 你需要把ssh key(也就是~/.ssh/id_rsa.pub) 文件内容告诉gitlab,你才有权限获取代码啊。

![add_ssh_key_](10-git problem/add_ssh_key.png)

什么? 还报这个错误啊?那接着往下看吧。

首先看看你的配置文件

1
2
3
4
5
$cat ~/.ssh/config 
# Added by Warewulf
Host *
IdentityFile ~/.ssh/cluster
StrictHostKeyChecking=no

原来我的配置里面认证使用的是 ~/.ssh/cluster 文件,那我吧cluster.pub 这个文件添加到ssh key可以吗?答案是不行的,warewulf技术认证使用的是dsa加密,而ssh key使用的是rsa加密。但是我又不能直接改掉这个cluster,改了它集群其他技术就用不了了,怎么办?增加一个认证文件就可以啦!

1
2
3
4
5
6
$cat .ssh/config 
# Added by Warewulf
Host *
IdentityFile ~/.ssh/cluster
IdentityFile ~/.ssh/id_rsa
StrictHostKeyChecking=no

结果so esay,然而排错的过程却是痛苦的。下面简单介绍一下稍好 一点的排错方式。

在添加完 ~/.ssh/id_rsa 之后还是报错的时候,你可以通过 ssh来调试

1
2
3
4
5
6
7
8
9
10
$ssh -v git@git.gitlab.com -p 2222
OpenSSH_6.6.1, OpenSSL 1.0.1e-fips 11 Feb 2013
debug1: Reading configuration data ~/.ssh/config
debug1: ~/.ssh/config line 2: Applying options for *
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: /etc/ssh/ssh_config line 56: Applying options for *
debug1: Connecting to git.gitlab.com [xxx.xx.xx.x] port 2222.
debug1: Connection established.
...
Permission denied (publickey).

注意这里,我用的gitlab搭建改了ssh的端口2222,犯过愚蠢的错误 ssh -v git@git.gitlab.com:2222 ,然后一直报错,找不到原因,但回过头来想,这只是一个ssh命令,端口应该用参数 -p

如果上面的命令报了Permission denied (publickey). 这样的错误那就是你添加的ssh key没有被系统找到。那我们就手动添加来试试。

1
2
3
4
5
6
7
8
9
10
$ssh-agent bash               #进入一个新的bash环境
$ssh-add ~/.ssh/id_rsa #手动添加ssh key
$ssh -v git@git.gitlab.com -p 2222 #再次尝试连接gitlab
...
Welcome to GitLab, xxx! # 到这里说明连接上了gitlab
debug1: client_input_channel_req: channel 0 rtype exit-status reply 0
debug1: client_input_channel_req: channel 0 rtype eow@openssh.com reply 0
debug1: channel 0: free: client-session, nchannels 1
Connection to git.gitlab.com closed.
...

到这一步发现,诶~/.ssh/id_rsa 这个文件没有被系统作为认证文件,手动添加之后就可以连接,那就可以从系统为什么没有添加这个认证文件入手了。就是上面说的配置文件~/.ssh/config /etc/ssh/ssh_config

关于配置文件的说明我也是网上找的,看我百度的ssh官网

Gitlab CI

[TOC]

GitLab CI CD

持续集成(Continuous Integration) , 合并存储库中的更改之前触发管道来构建,测试和验证新代码。就是先测试和验证代码,然后再合并。

持续交付(Continuous Delivery) 将CI验证的代码交付给您的应用程序。就是将合并的代码更新部署到线上。

CI/CD的优点:

持续集成

  • 尽快发现错误: 在开发人员重新思考的同时解决问题
  • 减少集成问题: 更小的问题更容易消化
  • 避免复杂的问题: 使团队更加自信地发展更快

持续交付

  • 确保每个更改都是可发布的: 在完成之前测试所有内容,包括部署
  • 降低每次发布的风险: 使发布简单
  • 更加频繁地交付价值: 可靠的部署意味着更多的发布
  • 紧密的客户反馈循环: 快速而频繁的客户对变更的反馈

参考:https://about.gitlab.com/product/continuous-integration/

GitLab Runner

GitLab Runner 是一个处理构建的应用程序。它可以单独部署,并通过API与GitLab CI / CD一起使用。为了让你提交的代码自动执行构建,测试和部署等功能,你需要有一个可以执行构建测试和部署的服务,Gitlab Runner就是这么一个服务。

install

  1. 添加官方的安装源
1
2
3
4
5
# For Debian/Ubuntu/Mint
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | sudo bash

# For RHEL/CentOS/Fedora
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh | sudo bash
  1. 安装gitlab-runner
1
2
3
4
5
# For Debian/Ubuntu/Mint
sudo apt-get install gitlab-runner

# For RHEL/CentOS/Fedora
sudo yum install gitlab-runner
  1. 修改配置,链接到Gitlab
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
Run the following command:
sudo gitlab-runner register

Enter your GitLab instance URL:

Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com )
https://gitlab.com

Enter the token you obtained to register the Runner:

Please enter the gitlab-ci token for this runner
xxx

Enter a description for the Runner, you can change this later in GitLab’s UI:

Please enter the gitlab-ci description for this runner
[hostname] my-runner

Enter the tags associated with the Runner, you can change this later in GitLab’s UI:

Please enter the gitlab-ci tags for this runner (comma separated):
my-tag,another-tag

Enter the Runner executor:

Please enter the executor: ssh, docker+machine, docker-ssh+machine, kubernetes, docker, parallels, virtualbox, docker-ssh, shell:
docker

If you chose Docker as your executor, you’ll be asked for the default image to be used for projects that do not define one in .gitlab-ci.yml:

Please enter the Docker image (eg. ruby:2.1):
alpine:latest

参考:

.gitlab-ci.yml

当gitlab-runner注册到了gitlab中,然后在代码库中加入.gitlab-ci.yml 文件,代码库中的代码就会根据.gitlab-ci.yml 文件的内容规则来执行构建,测试和部署。

.gitlab-ci.yml遵循yaml语法格式,配置规则参考这里

在gitlab上搭建hexo博客

[TOC]

这篇文章讲解怎样用hexo把你写的Markdown快速搭建成博客(呐,你正在看的这篇博客呢就是一个Markdown写出来的 ^_^ )。

hexo

1.什么是hexo?

Hexo 是一个快速、简洁且高效的博客框架。Hexo 使用 Markdown(或其他渲染引擎)解析文章,在几秒内,即可利用靓丽的主题生成静态网页。

2.安装前提

安装 Hexo 相当简单。然而在安装前,您必须检查电脑中是否已安装下列应用程序:

3.安装

1

就是这么简单!

4.使用

简单三部曲:

1
2
3
4
5
6
hexo init blog 
cd blog && npm install
hexo server
# 1.初始化一个hexo 取名blog
# 2.进入blog 构建要用到的模块包
# 3.启动服务, 打开浏览器输入:127.0.0.1:3000 看看吧

就这样,你没有写任何文档这个博客也就起来了。

这个初始化的hexo下面,目录结构URU型下:

1
2
3
4
5
6
7
8
.
├── _config.yml #配置信息
├── package.json #应用程序的信息,npm install就是安装这些应用,生成node_modules目录
├── scaffolds
├── source
| ├── _drafts
| └── _posts #资源文件
└── themes #主题

如果你想要把你的Markdown内容放入这个博客,简单,将你写的Markdown放入blog/source/_post 目录中即可。

5.生成器

上面的 hexo server 只是在你的本地主机上运行了一个临时的博客,方便你看效果,如果没有问题那你就把他生成静态文件,放到任何地方做成博客啦。那在生成静态文件的时候,有一些配置需要配好。

a._config.yml

hexo对配置文件有详细的说明,这里只需要介绍常用的几个

1
2
3
4
5
6
7
8
9
# Site
title: 网站标题
author: hooby
timezone: Asia/Shanghai

url: https://hooby.gitlab.io/hexo # 网址
root: /hexo #网站根目录

theme: maupassant #选择用什么主题,后面会说

b.主题

主题就是你网站的风格咯,上官网挑一个咯。然后下载到 thems目录下面,同时在_config.yml 文件中的theme字段写上目录名称即可。

1
2
3
4
5
# git submodule add https://github.com/tufu9441/maupassant-hexo.git themes/maupassant
# cat _config.yml
...
theme: maupassant
...

c.生成静态页面

进入blog,执行:

1
hexo g

就是这么简单!

静态页面文件在public 目录。

gitlab-pages

那如果你没有自己的服务器,也没有域名,没有关系啦,上gitlab申请一个账号,做一个自己的blog,这是我的blog。gitlab是提供代码管理的,同时提供的pages功能,这个功能就可以做blog。

当你把自己的代码和Markdown放在gitlab上面之后,创建一个文件 .gitlab-ci.yml ,内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
image: node:8.11.4

pages:
# before_script:
# # 拉取子模块
# - git submodule sync --recursive
# - git submodule update --init --recursive
variables:
GIT_SUBMODULE_STRATEGY: recursive

script:
- npm install
- ./node_modules/hexo/bin/hexo generate
artifacts:
paths:
- public
only:
- master

就这么简单。

上哪里去看你的blog?

在gitlab上面找到Settings –> Pages ,Access pages下面那个网址就是你的啦,进去看看吧。

keras回调函数Callbacks

[TOC]

回调函数使用

回调函数是一个函数的合集,会在训练的阶段中所使用。你可以使用回调函数来查看训练模型的内在状态和统计。你可以传递一个列表的回调函数(作为 callbacks 关键字参数)到 SequentialModel 类型的 .fit() 方法。在训练时,相应的回调函数的方法就会在各自的阶段被调用。

Callback

1
2
keras.callbacks.Callback()

用来组建新的回调函数的抽象基类。

BaseLogger

1
2
keras.callbacks.BaseLogger()

会积累训练轮平均评估的回调函数。

这个回调函数被自动应用到每一个 Keras 模型上面。

TerminateOnNaN

1
keras.callbacks.TerminateOnNaN()

当遇到 NaN 损失会停止训练的回调函数。

ProgbarLogger

1
2
keras.callbacks.ProgbarLogger(count_mode='samples')

会把评估以标准输出打印的回调函数。

参数

  • count_mode: “steps” 或者 “samples”。 进度条是否应该计数看见的样本或步骤(批量)。

触发

  • ValueError: 防止不正确的 count_mode

History

1
2
keras.callbacks.History()

把所有事件都记录到 History 对象的回调函数。

这个回调函数被自动启用到每一个 Keras 模型。History 对象会被模型的 fit 方法返回。

ModelCheckpoint

1
keras.callbacks.ModelCheckpoint(filepath, monitor='val_loss', verbose=0, save_best_only=False, save_weights_only=False, mode='auto', period=1)

在每个训练期之后保存模型。

filepath 可以包括命名格式选项,可以由 epoch 的值和 logs 的键(由 on_epoch_end 参数传递)来填充。

例如:如果 filepathweights.{epoch:02d}-{val_loss:.2f}.hdf5, 那么模型被保存的的文件名就会有训练轮数和验证损失。

EarlyStopping

1
2
keras.callbacks.EarlyStopping(monitor='val_loss', min_delta=0, patience=0, verbose=0, mode='auto')

当被监测的数量不再提升,则停止训练。

RemoteMonitor

1
2
keras.callbacks.RemoteMonitor(root='http://localhost:9000', path='/publish/epoch/end/', field='data', headers=None)

将事件数据流到服务器的回调函数。

需要 requests 库。 事件被默认发送到 root + '/publish/epoch/end/'。 采用 HTTP POST ,其中的 data 参数是以 JSON 编码的事件数据字典。

LearningRateScheduler

1
2
keras.callbacks.LearningRateScheduler(schedule, verbose=0)

学习速率定时器。

参数

  • schedule: 一个函数,接受轮索引数作为输入(整数,从 0 开始迭代) 然后返回一个学习速率作为输出(浮点数)。
  • verbose: 整数。 0:安静,1:更新信息。

TensorBoard

1
2
keras.callbacks.TensorBoard(log_dir='./logs', histogram_freq=0, batch_size=32, write_graph=True, write_grads=False, write_images=False, embeddings_freq=0, embeddings_layer_names=None, embeddings_metadata=None)

Tensorboard 基本可视化。

TensorBoard 是由 Tensorflow 提供的一个可视化工具。

这个回调函数为 Tensorboard 编写一个日志, 这样你可以可视化测试和训练的标准评估的动态图像, 也可以可视化模型中不同层的激活值直方图。

如果你已经使用 pip 安装了 Tensorflow,你应该可以从命令行启动 Tensorflow:

1
tensorboard --logdir=/full_path_to_your_logs

参数

  • log_dir: 用来保存被 TensorBoard 分析的日志文件的文件名。
  • histogram_freq: 对于模型中各个层计算激活值和模型权重直方图的频率(训练轮数中)。 如果设置成 0 ,直方图不会被计算。对于直方图可视化的验证数据(或分离数据)一定要明确的指出。
  • write_graph: 是否在 TensorBoard 中可视化图像。 如果 write_graph 被设置为 True,日志文件会变得非常大。
  • write_grads: 是否在 TensorBoard 中可视化梯度值直方图。 histogram_freq 必须要大于 0 。
  • batch_size: 用以直方图计算的传入神经元网络输入批的大小。
  • write_images: 是否在 TensorBoard 中将模型权重以图片可视化。
  • embeddings_freq: 被选中的嵌入层会被保存的频率(在训练轮中)。
  • embeddings_layer_names: 一个列表,会被监测层的名字。 如果是 None 或空列表,那么所有的嵌入层都会被监测。
  • embeddings_metadata: 一个字典,对应层的名字到保存有这个嵌入层元数据文件的名字。 查看 详情 关于元数据的数据格式。 以防同样的元数据被用于所用的嵌入层,字符串可以被传入。

ReduceLROnPlateau

1
2
keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.1, patience=10, verbose=0, mode='auto', epsilon=0.0001, cooldown=0, min_lr=0)

当标准评估已经停止时,降低学习速率。

当学习停止时,模型总是会受益于降低 2-10 倍的学习速率。 这个回调函数监测一个数据并且当这个数据在一定「有耐心」的训练轮之后还没有进步, 那么学习速率就会被降低。

1
2
3
reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.2,
patience=5, min_lr=0.001)
model.fit(X_train, Y_train, callbacks=[reduce_lr])

CSVLogger

1
2
keras.callbacks.CSVLogger(filename, separator=',', append=False)

把训练轮结果数据流到 csv 文件的回调函数。

支持所有可以被作为字符串表示的值,包括 1D 可迭代数据,例如,np.ndarray。

1
2
3
csv_logger = CSVLogger('training.log')
model.fit(X_train, Y_train, callbacks=[csv_logger])

参数

  • filename: csv 文件的文件名,例如 ‘run/log.csv’。
  • separator: 用来隔离 csv 文件中元素的字符串。
  • append: True:如果文件存在则增加(可以被用于继续训练)。False:覆盖存在的文件。

LambdaCallback

1
keras.callbacks.LambdaCallback(on_epoch_begin=None, on_epoch_end=None, on_batch_begin=None, on_batch_end=None, on_train_begin=None, on_train_end=None)

在训练进行中创建简单,自定义的回调函数。

这个回调函数和匿名函数在合适的时间被创建。 需要注意的是回调函数要求位置型参数,如下:

  • on_epoch_beginon_epoch_end 要求两个位置型的参数: epoch, logs
  • on_batch_beginon_batch_end 要求两个位置型的参数: batch, logs
  • on_train_beginon_train_end 要求一个位置型的参数: logs

参数

  • on_epoch_begin: 在每轮开始时被调用。
  • on_epoch_end: 在每轮结束时被调用。
  • on_batch_begin: 在每批开始时被调用。
  • on_batch_end: 在每批结束时被调用。
  • on_train_begin: 在模型训练开始时被调用。
  • on_train_end: 在模型训练结束时被调用。

参考

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85

'''
任务二:
使用猫狗数据各400张,构建一个小数据集分类模型,实现相应的训练预测,
使用keras的回调函数保存训练日志用于可视化训练过程
训练结果以acc,loss,val_acc,val_loss曲线表示
'''

from keras.preprocessing.image import ImageDataGenerator
from keras.models import Sequential
from keras.layers import Dense, Activation, Convolution2D, MaxPooling2D, Dropout,Flatten
from keras.callbacks import CSVLogger, TensorBoard

#sample directory
train_data='cat_and_dog/train'
test_data='cat_and_dog/test'


#model construction
model = Sequential()
model.add(Convolution2D(32,(3, 3), padding='same',input_shape=(256,256,3)))
model.add(Activation('relu'))
model.add(Convolution2D(32,( 3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Convolution2D(64, (3, 3), padding='same'))
model.add(Activation('relu'))
model.add(Convolution2D(64,(3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Flatten())
model.add(Dense(256))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(1))
model.add(Activation('sigmoid'))
# model.add(Activation('softmax'))

#compile model
model.compile(optimizer='sgd',
# loss='binary_crossentropy',
loss='mse',
metrics=['acc'],
# metrics=['mae','acc'],
)

#just for callbacks
csv_logger = CSVLogger('history_log.csv', separator=',', append=True)
visulization = TensorBoard(log_dir='cat_and_dog/tensorbord', histogram_freq=0, write_graph=True)

#generator construction
datagen = ImageDataGenerator(
rescale=1./255,
)

train_generator = datagen.flow_from_directory(
train_data,
target_size = (256, 256),
# classes = ['dogs', 'cats'],
class_mode = "binary",
# batch_size = 32,
# save_to_dir = None,
)
validation_generator = datagen.flow_from_directory(
test_data,
target_size = (256, 256),
# classes = ['dogs', 'cats'],
class_mode = "binary",
# batch_size = 32,
# save_to_dir = None,
)

model.fit_generator(
train_generator,
steps_per_epoch=25,
verbose=1,
epochs=70,
validation_data=validation_generator,
validation_steps=10,
callbacks=[csv_logger,visulization]
)

accloss

val_accval_loss

gitlab-pages配置说明

[TOC]

pages 介绍

gitlab-pages是gitlab-ci的一个功能. 它上传静态页面文件到gitlab中, gitlab就使用这些静态页面作为网站展示出来.

gitlab-pages 功能:

1
2
3
4
5
6
7
8
9
10
11
Create websites for your GitLab projects, groups, or user account.
# 为你的gitlab项目搭建网站

Use any static website generator: Jekyll, Middleman, Hexo, Hugo, Pelican, and more.
# 可以使用各种网站静态生成器

Connect your custom domain(s) and TLS certificates.
# 自定义域名 以及支持TLS证书安全保证

Host your static websites on GitLab.com for free, or on your own GitLab instance.
# 你的网站可以免费放到gitlab上

pages 配置

搭建pages必须满足两个条件

  1. 所有的静态内容必须放到public 目录下

  2. .gitlab-ci.yml 文件必须配置 artifacts 选项的paths 参数为public

官网描述

1
2
3
4
5
6
7
8
pages:
...
script:
- mv dist public
artifacts:
paths:
- public
...

pages 搭建示例

准备条件:

  1. gitlab服务
  2. gitlab-runner

集群搭建示例:

集群上已经搭建了gitlab和gitlab-runner,可以直接使用.

  1. 公司博客

.gitlab-ci.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pages:
script:
- "singularity exec /scratch/containers/user.simg npm install "
- "singularity exec /scratch/containers/user.simg hexo generate "
cache:
paths:
- node_modules/
artifacts:
paths:
- public
only:
- master
# cache选项 可以把node_modules缓存起来,下次执行script命令之前就会先加载cache目录,加快npm install

自动部署完成之后,打开项目setting/pages 下面的网址就是访问连接.

  1. Vue

vue-cli-project-template/config/index.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
...
build: {
// Template for index.html
index: path.resolve(__dirname, '../dist/vue-cli-project-template/index.html'),

// Paths
assetsRoot: path.resolve(__dirname, '../dist/vue-cli-project-template'),
assetsSubDirectory: 'static',
assetsPublicPath: '/vue-cli-project-template/',
...
}
# 注意:
# assetsRoot: path.resolve 决定你生成静态页面的路径,这个路径要对应到pages配置的public中

.gitlab-ci.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pages:
script:
- "singularity exec /scratch/containers/backend.simg npm install"
- "singularity exec /scratch/containers/backend.simg npm run build"
- "cp -r dist/vue-cli-project-template public"
- "cp public/index.html public/404.html"
cache:
paths:
- node_modules/
artifacts:
paths:
- public
only:
- master

gitlab官网搭建示例:

gitlab官网给我们免费提供了gitlab-runner, 所以也是可以直接使用.

  1. Vue

这边的vue对build做了修改

vue-cli-project-template/config/index.js:

1
2
3
4
5
6
7
8
9
10
11
  ...
build: {
// Template for index.html
index: path.resolve(__dirname, '../public/index.html'),

// Paths
assetsRoot: path.resolve(__dirname, '../public'),
assetsSubDirectory: 'static',
assetsPublicPath: '/vue-cli-project-template/',
...
}

.gitlab-ci.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pages:
image: node:8.11.1
script:
- "npm install"
- "npm run build"
# - "cp -r dist/vue-cli-project-template public"
- "cp public/index.html public/404.html"
cache:
paths:
- node_modules/
policy: pull
artifacts:
paths:
- public
only:
- master

keras图像预处理

[TOC]

class: ImageDataGenerator

生成批次的带实时数据增益的张量图像数据。数据将按批次无限循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
keras.preprocessing.image.ImageDataGenerator(featurewise_center=False,
samplewise_center=False,
featurewise_std_normalization=False,
samplewise_std_normalization=False,
zca_whitening=False,
zca_epsilon=1e-6,
rotation_range=0.,
width_shift_range=0.,
height_shift_range=0.,
shear_range=0.,
zoom_range=0.,
channel_shift_range=0.,
fill_mode='nearest',
cval=0.,
horizontal_flip=False,
vertical_flip=False,
rescale=None,
preprocessing_function=None,
data_format=K.image_data_format())

常用参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
rotation_range: 整数。随机旋转的度数范围。
width_shift_range: 浮点数(总宽度的比例)。随机水平移动的范围。
height_shift_range: 浮点数(总高度的比例)。随机垂直移动的范围。
shear_range: 浮点数。剪切强度(以弧度逆时针方向剪切角度)。
zoom_range: 浮点数 或 [lower, upper]。随机缩放范围。如果是浮点数,[lower, upper] = [1-zoom_range, 1+zoom_range]。
channel_shift_range: 浮点数。随机通道转换的范围。
fill_mode: {"constant", "nearest", "reflect" or "wrap"} 之一。输入边界以外的点根据给定的模式填充:
"constant": kkkkkkkk|abcd|kkkkkkkk (cval=k)
"nearest": aaaaaaaa|abcd|dddddddd
"reflect": abcddcba|abcd|dcbaabcd
"wrap": abcdabcd|abcd|abcdabcd
cval: 浮点数或整数。当 fill_mode = "constant" 时,用于边界之外的点的值。
horizontal_flip: 布尔值。随机水平翻转。
vertical_flip: 布尔值。随机垂直翻转。
rescale: 重缩放因子。默认为 None。如果是 None 或 0,不进行缩放,否则将数据乘以所提供的值(在应用任何其他转换之前)。

类的方法

类的方法我就用了两个:flowflow_from_directory

flow():

传入 Numpy 数据和标签数组,生成批次的 增益的/标准化的 数据。在生成的批次数据上无限制地无限次循环。

  • 参数
1
2
3
4
5
6
7
8
x: 数据。秩应该为 4。在灰度数据的情况下,通道轴的值应该为 1,在 RGB 数据的情况下,它应该为 3。
y: 标签。
batch_size: 整数(默认 32)。
shuffle: 布尔值(默认 True)。
seed: 整数(默认 None)。
save_to_dir: None 或 字符串(默认 None)。这使你可以最佳地指定正在生成的增强图片要保存的目录(用于可视化你在做什么)。
save_prefix: 字符串(默认 '')。 保存图片的文件名前缀(仅当 save_to_dir 设置时可用)。
save_format: "png", "jpeg" 之一(仅当 save_to_dir 设置时可用)。默认:"png"。
  • yields: 元组 (x, y),其中 x 是图像数据的 Numpy 数组,y 是相应标签的 Numpy 数组。生成器将无限循环。

flow_from_directory():

以目录路径为参数,生成批次的 增益的/标准化的 数据。在生成的批次数据上无限制地无限次循环。

  • 参数
1
2
3
4
5
6
7
8
9
10
11
12
directory: 目标目录的路径。每个类应该包含至少一个子目录。任何在子目录下的图像,都将被包含在生成器中。
target_size: 整数元组 (height, width),默认:(256, 256)。所有的图像将被调整到的尺寸。
color_mode: "grayscale", "rbg" 之一。默认:"rgb"。图像是否被转换成1或3个颜色通道。
classes: 可选的类的子目录列表(例如 ['dogs', 'cats'])。默认:None。如果未提供,类的列表将自动从“目录”下的子目录名称/结构中推断出来,其中每个子目录都将被作为不同的类(类名将按字典序映射到标签的索引)。包含从类名到类索引的映射的字典可以通过class_indices属性获得。
class_mode: "categorical", "binary", "sparse", "input" 或 None 之一。默认:"categorical"。决定返回的标签数组的类型:"categorical" 将是 2D one-hot 编码标签,"binary" 将是 1D 二进制标签,"sparse" 将是 1D 整数标签,"input" 将是与输入图像相同的图像(主要用于与自动编码器一起工作)。如果为 None,不返回标签(生成器将只产生批量的图像数据,对于 model.predict_generator(), model.evaluate_generator() 等很有用)。请注意,如果 class_mode 为 None,那么数据仍然需要驻留在 directory 的子目录中才能正常工作。
batch_size: 一批数据的大小(默认 32)。
shuffle: 是否混洗数据(默认 True)。
seed: 可选随机种子,用于混洗和转换。
save_to_dir: None 或 字符串(默认 None)。这使你可以最佳地指定正在生成的增强图片要保存的目录(用于可视化你在做什么)。
save_prefix: 字符串。 保存图片的文件名前缀(仅当 save_to_dir 设置时可用)。
save_format: "png", "jpeg" 之一(仅当 save_to_dir 设置时可用)。默认:"png"。
follow_links: 是否跟踪类子目录下的符号链接(默认 False)。
  • yields: 元组 (x, y),其中 x 是图像数据的 Numpy 数组,y 是相应标签的 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
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
'''
任务一:使用keras数据生成器,对猫狗数据进行数值处理和空间处理(旋转,平移等),
对处理后的数据做三项检查,即数据类型,大小,数值范围,最后随机可视化8个生成器处理后的数据
'''

from __future__ import print_function
from keras.preprocessing.image import ImageDataGenerator
import matplotlib.pyplot as plt

# 从目录中读取图像
datagen = ImageDataGenerator(
rotation_range=90,
width_shift_range=0.1,
height_shift_range=0.1,
zoom_range=0.5,
rescale=1./255,
# horizontal_flip=True,
# vertical_flip=False
)
gene = datagen.flow_from_directory(
'test',
# target_size=(1280, 720),
batch_size=8,
# save_to_dir = 'x_test',
# # save_prefix = 'cats_and_dogs',
# save_format = 'png'
)
data = next(gene)
print(data[0][0].shape)
for i in data[0] :
plt.imshow(i)
plt.show()

# 直接使用cifar10数据
from keras.datasets import cifar10
(x_train, y_train), (x_test, y_test) = cifar10.load_data()
cifar10_datagen = ImageDataGenerator(
rescale=1./255,
)
result = cifar10_datagen.flow(
x_train,
y_train,
batch_size=8,
)
data = next(result)
for i in data[0] :
print("data type:",type(i))
print("data shape:",i.shape)
print("max:",i.max())
print("min:",i.min())
plt.imshow(i)
plt.show()

参考