tensorflow的学习笔记--反向传播

反向传播

训练模型参数,在所有的参数上用梯度下降,是NN模型在训练数据上的损失函数最小。

  1. 损失函数(loss): 预测值(y)与已知答案(y’)的差距

  2. 均方误差MSE: $$MSE(y’,y)=\sum_{i=0}^n(y-y’)^2/n$$

    使用tensorflow表示:
    loss=tf.reduce_mean(tf.square(y'-y))

  3. 反向传播训练方法,以减小loss值为优化目标。 有以下几种方法:

1
2
3
4
train_step=tf.train.GradientDescentOptimizer(learnig_rate).minimize(loss) 
train_step=tf.train.MomentumOptimizer(learnig_rate,momentum).minimize(loss)
train_step=tf.train.AdamOptimizer(learnig_rate).minimize(loss)

其中leaning_rate代表学习率,决定每次更新的幅度。

实现代码

实现一个训练模型:

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
#coding:utf-8
import tensorflow as tf
import numpy as np
BATCH_SIZE=8
seed=23455


#基于seed产生随机数
rng=np.random.RandomState(seed)
#随机数返回32行2列的矩阵,表示32组,体积和重量,作为输入的数据集
X=rng.rand(32,2)

# 从X这个32x2的矩阵中,取出一行,判断如果和小于1 给Y复制1,如果不小于1 给Y赋值0
# 作为输入数据集的标签(正确答案)

Y=[[int(x0+x1<1)] for(x0,x1) in X]

print("X:",X)
print("Y:",Y)

# 定义神经王珞丹额输入和输出,定义前向的传播过程

x=tf.placeholder(tf.float32,shape=(None,2))
y_=tf.placeholder(tf.float32,shape=(None,1))

w1=tf.Variable(tf.random_normal([2,3],stddev=1,seed=1))
w2=tf.Variable(tf.random_normal([3,1],stddev=1,seed=1))

a=tf.matmul(x,w1)
y=tf.matmul(a,w2)

#定义损失函数
loss=tf.reduce_mean(tf.square(y-y_))
train_step=tf.train.GradientDescentOptimizer(0.001).minimize(loss)
# train_step=tf.train.MomentumOptimizer(learnig_rate,momentum).minimize(loss)
# train_step=tf.train.AdamOptimizer(learnig_rate).minimize(loss)

# 生成会话,训练STEPS轮
with tf.Session() as sess:
init_op=tf.global_variables_initializer()
sess.run(init_op)
# 输出目前(未经训练)的参数值
print("w1:",sess.run(w1))
print("w1:",sess.run(w2))
print("\n")

#训练模型
STEPS=3000
for i in range(STEPS):
start=(i*BATCH_SIZE)%32
end=start+BATCH_SIZE
sess.run(train_step,feed_dict={
x:X[start:end],y_:Y[start:end]
})
if i%500==0:
total_loss=sess.run(loss,feed_dict={x:X,y_:Y})
print("After %d training steps,loss on all data is %s"%(i,total_loss))

print("w1:",sess.run(w1))
print("w1:",sess.run(w2))
print("\n")

tensorflow的学习笔记--前向传播

输入参数

在上面一篇博客提到的一个简单的模型:

tensorflow

为了能够得到Y,需要准确的知道$$w_1,w_2$$的值,一般都是先随机给一个值,后面利用样本进行训练,得到准确的值。例如使用随机方法赋初值:

w=tf.Variable(tf.random_normal([2,3],stddev=2,mean=0,seed=1))

其中:random_normal代表随机正态分布,[2,3]产生2x3的矩阵,stddev=2代表标准差是2,mean=0均值为0,seed=1随机种子。(标准差,均值,随机种子可以不写)

除了random_normal方法外还有几个其他的生成函数:

truncated_normal:去掉过大偏离点的正态分布,如果生成的数据超过了平均值两个标准差,数据将重新生成。

random_uniform:平均分布

tf.zeros:生成全0数组,tf.zeros([3,2],int32) 生成[[0,0],[0,0],[0,0]]
tf.ones:生成全1数组,tf.zeros([3,2],int32) 生成[[1,1],[1,1],[1,1]]
tf.fill:全定值数组,tf.zeros([3,2],6) 生成[[6,6],[6,6],[6,6]]
tf.constant:直接给值,tf.constant([3,2,1]) 生成[3,2,1]

神经网络的实现过程

  1. 准备数据集,提取特征,作为输入喂给神经网络

  2. 搭建NN结构,从输入到输出(先搭建计算图,再用会话执行)
    (NN前向传播算法===>>计算输出)

  3. 大量特征数据喂给NN,迭代优化NN参数
    (NN反向传播算法====>>优化参数训练模型)

  4. 使用训练好的模型预测和分类

前向传播

比如生产一批零件,将体积$$x_1$$和重量$$x_2$$为特征输入到NN,通过NN后输出一个值。 具体的预测结果如下:

tensorflow

具体的Y值的计算是:$$Y=a_{11}w_{11}+ a_{12}w_{21}+a_{13}w_{31}$$;

我们把上面的过程用tensorflow表示出来,先定义几个变量:

输入参数X的权重矩阵
$$
W=\left[
\begin{matrix}
w_{11} & w_{12} & w_{13} \
w_{21} & w_{22} & w_{23}
\end{matrix}
\right]
$$

隐藏层的矩阵
$$
a=\left[
\begin{matrix}
a_{11} & a_{12} & a_{13}
\end{matrix}
\right] =XW
$$

隐藏层到输入结果的矩阵
$$
‘W=\left[
\begin{matrix}
w_{11} \
w_{12} \
w_{13} \
\end{matrix}
\right]
$$

由此可以得到:

a=tf.matmaul(X,W)

Y=tf.matmaul(a,'W)

分析过程

  1. 变量初始化,计算图节点,运算都需要sesion

  2. 变量初始化:在session.run函数中,使用tf.global_variables_initializer()

    1
    2
    init_op=tf.global_variables_initializer()
    sess.run(init_op)
  3. 计算图节点运算:在sess.run函数中写入待运算的节点
    sess.run(y)

  4. 使用tf.placeholder占位,在sess.run函数中用feed_dict喂数据

    喂一组数据:

    1
    2
    x=tf.placeholder(tf.float32,shape=(1,2))
    sess.run(y,feed_dict={x:[[0.5,0.6]]})

    喂多组数据:

    1
    2
    x=tf.placeholder(tf.float32,shape=(None,2))
    sess.run(y,feed_dict={x:[[0.5,0.6],[0.5,0.6]]})

使用代码实现上面的分析过程(喂入多组数据):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#coding:utf-8
# 两层神经网络
import tensorflow as tf

# 定义输入和参数
# 用placeholder实现喂数据
x=tf.placeholder(tf.float32,shape=(None,2))
w1=tf.Variable(tf.random_normal([2,3],stddev=1,seed=1))#正态分布随机数
w2=tf.Variable(tf.random_normal([3,1],stddev=1,seed=1))

#定义前向传播过程
a=tf.matmul(x,w1)
y=tf.matmul(a,w2)

# 计算结果
with tf.Session() as sess:
init_op=tf.global_variables_initializer()
sess.run(init_op)
print ("y in sj3 is:\n",sess.run(y,
feed_dict={
x:[[0.7,0.5],[0.2,0.3],[0.3,0.4],[0.4,0.5]]
}))

打印结果:
[[3.0904665] [1.2236414] [1.7270732] [2.2305048]]

tensorflow的学习笔记--初步认识tensorflow

几个概念

TensorFlow是一个基于数据流编程(dataflow programming)的符号数学系统,被广泛应用于各类机器学习(machine learning)算法的编程实现,由谷歌公司开发并开源免费使用。

在接触到的智能机器中,我们都需要先输入一段抽象的数据(语音,图片等),然后机器识别结果,输出我们想要的内容。

在机器诞生的前期,需要对机器进行训练和学习,使他有能力去认识识别的样本,然后由样本来预测其他的结果。

tensorflow中使用张量代表数据(可以简单理解为参数),使用计算图来搭建神经网络,使用会话执行计算图,优化对应的权重。

首先我们先介绍张量:

  • 张量 多维数组和列表。对于不同维数的张量有不同的名称和表示方法:

    标量: 一个数字,比如:1,2,3
    向量: 一个数组,[1,2,3]
    矩阵: 二位数组,[[1,2],[1,3],[2,3]]
    张量: 多维数组

tensorflow的数据的类型很多,与日常编程的数据类型也有点相似之处,先不一一介绍,先看看怎么使用tensorflow(使用pip命令安装对应的依赖模块)

1
2
3
4
5
6
7
import tensorflow as ts

a=ts.constant([1.0,2.0])# 定义常数
b=ts.constant([3.0,4.0])

result=a+b
print(result)

打印结果:Tensor("add:0", shape=(2,), dtype=float32)

add表示节点名
0 第0个输出
shape 维度
(2,)一维数组,长度为2
dtype 数据类型

从上面的打印结果可以看出,result不是一个具体的结果,而是一个具体的计算过程。

简单的模型

看一个简单的数据模型

tensorflow

其中:$$Y=XW=w_1x_1+w_2x_2$$

具体使用tensorflow实现代码如下:

1
2
3
4
5
6
7
import tensorflow as ts

x=ts.constant([[1.0,2.0]])# 一行两列
w=ts.constant([[3.0],[4.0]]) # 两行一列

y=ts.matmul(x,w)
print(y)

打印结果:Tensor("MatMul:0", shape=(1, 1), dtype=float32)

获得运算结果

计算图的值,需要用到sesion,具体代码:

1
2
3
4
5
6
7
8
9
10
import tensorflow as ts

x=ts.constant([[1.0,2.0]])
w=ts.constant([[3.0],[4.0]])

y=ts.matmul(x,w)

with ts.Session() as sess:
print(sess.run(y))

打印结果:[[11.]]

具体的执行过程是:

$$
\left(\begin{matrix}
1.0 & 2.0
\end{matrix}\right)
\left( \begin{matrix}
3.0 \
4.0
\end{matrix}\right) =13+24=11
$$

vim配置python环境

配置tab为4个空格和显示行号:

vim ~/.vimrc 写入:
set ts=4
set nu

保存!

Python的学习笔记--画图

海龟绘图

海龟绘图画图比较简单,主要使用python的turtle模块, 就是通过编程指挥一个小海龟在屏幕上前进和左转右转。

forward: 向前走,可以指定一个距离
left:左转,指定一个角度
right:右转,指定一个角度
circle:画圆
reset:重置

根据上面的说明我们可以简单的绘制一个五角星:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import turtle
t = turtle.Pen()
for x in range(5):
t.forward(100)
t.right(145)
turtle.done()
```

![海龟绘图](/img/assets/42/01_marked.png)

绘制螺旋阵:

``` python
import turtle
import time
turtle.pensize(2)
turtle.bgcolor("black")
turtle.tracer(False)
turtle.color("white")
for x in range(400):
turtle.forward(2*x)
turtle.left(60)
turtle.tracer(True)
turtle.done()

螺旋阵
更多的方法的说明可以看官方教程

Java多线程消费一个list

在项目中,常常会需要处理一个list数据列表,使用多线程来加速数据的处理。

需要保证两点:

  1. 能灵活控制线程的数量
  2. 能够同步的完成一批list的数据

可以使用信号量和线程池,具体实现代码如下:

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
public static <T> void startWithMultiThread(List<T> list, int nThread, Consumer<T> func) {
if (CollectionUtils.isEmpty(list)) {
return;
}
if (nThread <= 0) {
return;
}
if (func == null) {
return;
}
if (CollectionUtils.isEmpty(list)) {
return;
}
Semaphore semaphore = new Semaphore(nThread);//定义几个许可
ExecutorService executorService = Executors.newFixedThreadPool(nThread);//创建一个固定的线程池
for (T obj : list) {
try {
semaphore.acquire();
executorService.execute(() -> {
try {
func.accept(obj);
semaphore.release();
} catch (Exception ex) {

}
});
} catch (InterruptedException e) {

}
}
executorService.shutdown();
}

Java多线程总结

线程和进程

对于进程和线程的概念可以简单的理解成一个包含关系,例如:一个人个体可以称为社会的一个进程,人可以同时做很多事情,这个称之为线程

CPU一次只能执行一个指令,操作系统为了保证同一时刻多个程序同时执行, 把每次执行的指令过程分成若干时间片(timeslice),每一个程序都会在指定的时间片上运行一段时间后,然后保存运行的上下文资源,来保证下次执行。

由于进程对于资源的需求比较多,保存和恢复都会需要很多时间,CPU每次执行的单位都是线程

所以单核的CPU的执行其实本质都是单线程.

例如我们同时运行A、B、C三个程序:

程序执行

**疑问:**如果是多线程本质还是单线程执行为什么我们还要使用多线程?

因为在程序执行的过程的中,CPU的执行速度大于内存,也远远大于磁盘IO的运算,如果一个程序CPU执行完成后,要等待磁盘和内存的读取。在等待期间,CPU处于空闲的状态,这样就导致的资源的浪费。

多线程的引入是在CPU存在空闲的时间片的时候,能够有指令被执行,不必再等待其他的执行。

疑问: 如何控制线程的执行先后?

CPU的实行被划分成时间片来执行,所以线程能否被调度,本质是能否抢到时间片。

既然是抢时间片,就存在随机性,所以线程本身的调度时间我们无法完全控制。(可以采用让出时间片来控制,但也不是根本上解决调度顺序)

Java中的线程


Thread使用

在Java中,使用Thread来创建线程,使用start的方法来启动线程(此处并不是真正的启动)。我们可以简单的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) {
Thread th=new Thread(()->{
System.out.println("a");
});
th.start();

try {
Thread.currentThread().join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

Thread中有stop,interrupt,join等方法。其中stop不推荐使用。interrupt表示中断线程执行,join等待当前线程执行完成。

Fork/Join

Fork/Join框架是Java7提供的一个用于并行执行任务的框架,利用递归把总任务分割成若干个小任务,然后把每个任务的执行结果汇总到总任务

ForkJoin程序执行

我们使用forkJoin框架计算1000的加和,具体使用代码:

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
    private static final Integer MAX = 200;
static class SumForkJoinTask extends RecursiveTask<Integer> {
// 子任务开始计算的值
private Integer startValue;

// 子任务结束计算的值
private Integer endValue;

public SumForkJoinTask(Integer startValue , Integer endValue) {
this.startValue = startValue;
this.endValue = endValue;
}
@Override
protected Integer compute() {
if(endValue - startValue < MAX) {
System.out.println(String.format("02.执行任务=>start:%s,end:%s",startValue,endValue));
Integer totalValue = 0;
for(int index = this.startValue ; index <= this.endValue ; index++) {
totalValue += index;
}
return totalValue;
}
else {
SumForkJoinTask subTask1 = new SumForkJoinTask(startValue, (startValue + endValue) / 2);
subTask1.fork();
SumForkJoinTask subTask2 = new SumForkJoinTask((startValue + endValue) / 2 + 1 , endValue);
subTask2.fork();
System.out.println(String.format("01.拆分任务=>start:%s,end:%s",startValue,endValue));
return subTask1.join() + subTask2.join();
}
}
}
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool();
ForkJoinTask<Integer> taskFuture = pool.submit(new SumForkJoinTask(1,1001));
try {
Integer result = taskFuture.get();
System.out.println("result = " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace(System.out);
}
}

打印结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
01.拆分任务=>start:1,end:1001
01.拆分任务=>start:1,end:251
02.执行任务=>start:1,end:126
02.执行任务=>start:127,end:251
01.拆分任务=>start:252,end:501
02.执行任务=>start:252,end:376
02.执行任务=>start:377,end:501
01.拆分任务=>start:502,end:1001
01.拆分任务=>start:502,end:751
01.拆分任务=>start:1,end:501
01.拆分任务=>start:752,end:1001
02.执行任务=>start:502,end:626
02.执行任务=>start:752,end:876
02.执行任务=>start:627,end:751
02.执行任务=>start:877,end:1001
result = 501501

wait和notity

waitnotify是线程的阻塞和通知,可以实现线程间的通信。具体的流程图如下:

程序执行

具体使用代码如下:

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
 public class LockWait {

static volatile List<String> itemContainer = new ArrayList<>();
static Object obj = new Object();

public static void main(String[] args) {
Thread th1 = new Thread(() -> {
synchronized (obj) {
for (int i = 0; i < 10; i++) {
System.out.println("th1添加元素");
itemContainer.add(String.valueOf(i));
if (itemContainer.size() == 5) {
System.out.println("th1线程发出通知");
obj.notify();
}
}
}
});

Thread th2 = new Thread(() -> {
synchronized (obj) {
System.out.println("进入th2线程");
if (itemContainer.size() != 5) {
try {
System.out.println("th2线程开始等待");
obj.wait();
System.out.println("th2线程等待结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("th2线程结束");
}
}

});

th2.start();
th1.start();
}
}

具体可以查看Java多线程通信lock和wait

waitnotify释放锁的情况,wait 不释放锁,notify释放锁。

线程池

创建和销毁线程需要耗费CPU的资源,为了不必要的浪费,可以把线程进行池化管理,这就是线程池。

在Java中有四个类型的线程池,分别是:

newFixedThreadPool:初始化一个固定线程数的线程池,即使没有任务线程也会驻留在内存中。

newCachedThreadPool: 初始化一个缓存线程池,不控制线程数据量,当没有任务执行的,超时会自动释放。在使用时,要注意线程数量和创建线程的开销。

newSingleThreadExecutor:初始化只有一个线程的线程池, 如果该线程异常结束,会重新创建一个新的线程继续执行任务,唯一的线程可以保证所提交任务的顺序执行。

newScheduledThreadPool: 初始化的线程池可以在指定的时间内周期性的执行所提交的任务。

下面newFixedThreadPool使用的方法,其他的用法类似。

1
2
3
4
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.execute(()->{

});

Future和FutureTask的使用

Future是一个interface,FutureTask是其中的一个实现类, 多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。

CountDownLatch

具体使用方法:

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
public class FutureTaskObj {
public static void main(String[] args) {
TaskObj task = new TaskObj();
FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
Thread thread = new Thread(futureTask);
thread.start();
System.out.println("创建Task完成");
System.out.println("主线程继续执行");

try {
System.out.println("运行结果" + futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}

System.out.println("所有任务执行完毕");
}
}

class TaskObj implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("子线程在进行计算");
Thread.sleep(1000);
int sum = 0;
for (int i = 0; i < 100; i++)
sum += i;
return sum;
}
}

Future也可以使用线程池的方法启动,具体代码如下:

1
2
3
4
5
ExecutorService executor = Executors.newCachedThreadPool();
TaskObj task = new TaskObj();
FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
executor.submit(futureTask);
executor.shutdown();

其他几个类的使用


CountDownLatch

CountDownLatch 是等待线程执行完,在进行执行,具体的执行逻辑:

CountDownLatch

具体的执行代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void main(String[] args) {
try {
CountDownLatch countDownLatch = new CountDownLatch(5);

for (int i = 0; i < 5; i++) {
int finalI = i;
Thread th=new Thread(()->{
System.out.println(String.format("执行第:%s个线程",finalI));
countDownLatch.countDown();
});
th.start();
}

countDownLatch.await();
System.out.println("执行完成");
} catch (InterruptedException e) {
e.printStackTrace();
}
}

打印结果:

1
2
3
4
5
6
执行第:0个线程
执行第:1个线程
执行第:3个线程
执行第:2个线程
执行第:4个线程
执行完成

CyclicBarrier

CyclicBarrier栅栏的意思,线程数达到某个值时,再继续执行。

CountDownLatch

具体代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void main(String[] args) {
try {
CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
for (int i = 0; i < 3; i++) {
int finalI = i;
Thread th = new Thread(() -> {
try {
System.out.println(String.format("执行第%s个线程", finalI));
cyclicBarrier.await();
System.out.println(String.format("第%s个线程执行完成", finalI));
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
});
th.start();
}
} catch (Exception e) {
e.printStackTrace();
}
}

打印结果是:

1
2
3
4
5
6
执行第0个线程
执行第1个线程
执行第2个线程
第0个线程执行完成
第2个线程执行完成
第1个线程执行完成

Semaphore

Semaphore 称为信号量,是指定几个数量线程通过。

CountDownLatch

具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(2);
for (int i = 0; i < 10; i++) {
try {
System.out.println(String.format("获得第%s个许可", i));
semaphore.acquire();
System.out.println(String.format("第%s个许可获得成功", i));
int finalI = i;
Thread th = new Thread(() -> {
System.out.println(String.format("执行第%s个线程", finalI));
semaphore.release();
System.out.println(String.format("第%s个线程执行完成", finalI));
});
th.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

执行打印结果:

1
2
3
4
5
6
7
8
9
10
11
12
获得第0个许可
第0个许可获得成功
获得第1个许可
第1个许可获得成功
获得第2个许可
执行第0个线程
第0个线程执行完成
第2个许可获得成功
获得第3个许可
执行第1个线程
第1个线程执行完成
....

分析上面的结果,可以发现只有两个线程同时执行,等一个线程释放了,另一个线程才能执行完成。

LockSupport

LockSupportSemaphore类似,相当于只有一个许可的信号量Semaphore semaphore = new Semaphore(1),具体的实现逻辑:

LockSupport

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
try {
int finalI = i;
Thread th = new Thread(() -> {
System.out.println(String.format("执行第%s个线程", finalI));
LockSupport.park();
System.out.println(String.format("第%s个线程执行完成", finalI));
});
th.start();
Thread.sleep(1000);
LockSupport.unpark(th);
Thread.sleep(1000);
System.out.println("主线程执行完成");
} catch (Exception e) {
e.printStackTrace();
}
}
}

打印结果:

1
2
3
4
5
6
7
8
执行第0个线程
第0个线程执行完成
主线程执行完成
执行第1个线程
第1个线程执行完成
主线程执行完成
执行第2个线程
第2个线程执行完成

Python的学习笔记--枚举

枚举

python中,枚举使用Enum模块。 具体使用如下:

1
2
3
4
5

from enum import Enum

Week=Enum("Week",('Mon','Tue','Wen','Thu','Fri','Sta','Sun'))

在枚举中可以自动为下面的变量赋值,我们可以使用迭代把数据显示出来:

1
2
3
4
5
from enum import Enum
Week=Enum("Week",('Mon','Tue','Wen','Thu','Fri','Sta','Sun'))

for k, v in Week.__members__.items():
print(k, ':', v, ',', v.value)

打印结果:

1
2
3
4
5
6
7
Mon : Week.Mon , 1
Tue : Week.Tue , 2
Wen : Week.Wen , 3
Thu : Week.Thu , 4
Fri : Week.Fri , 5
Sta : Week.Sta , 6
Sun : Week.Sun , 7

如果像自定义枚举的值,可以使用继承的方法,自定义一个枚举:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from enum import Enum, unique

class Week(Enum):
Sun = 0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6

for k, v in Week.__members__.items():
print(k, ':', v, ',', v.value)

打印结果:

1
2
3
4
5
6
7
Sun : Week.Sun , 0
Mon : Week.Mon , 1
Tue : Week.Tue , 2
Wed : Week.Wed , 3
Thu : Week.Thu , 4
Fri : Week.Fri , 5
Sat : Week.Sat , 6

可以使用@unique装饰器来保证值的唯一。

1
2
3
4
5
6
7
8
9
10
11
12
from enum import Enum, unique

@unique
class Week(Enum):
Sun = 0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
Sat = 6

上面的代码会报错:TypeError: Attempted to reuse key: 'Sat'

对于枚举的使用可以用Week.Sun取枚举的值,Week.Sun.value来取对应的整型的值。

Python的学习笔记--面向对象编程

模块


随着项目增大,功能越来越复杂,为了提高代码的复用性和降低功能间的耦合,提出了模块的概念。

python中有很多的模块,使用时候直接用import导入对应的包就可以使用。

1
import sys

这样就导入了一个sys模块,我们可以在代码中直接使用:

1
2
3
4
5
import sys
def func():
print (sys.argv)

func()

打印结果是当前的文件所在的路径。

解释sys的作用:
sys.argv 用list来存储命令行传过来的参数。第一个参数是.py文件名称,例如:

python3 xxx.py 运行结果就是['xxxx.py']

python3 xxxx.py abc 运行结果:['xxxx.py', 'abc']

在python的模块中,有一个特殊的变量__name__,这个变量的作用就是如果当前运行的是当前文件,这个变量的值就是__main__,所以如果想对一个模块使用测试方法,可以利用这个变量:

1
2
if __name__=="__main__":
func()

为了防止模块的命名有冲突,python 又引入了的概念,包其实可以简单的认为就是一个文件的路径,例如有如下目录结构:

1
2
3
4
5
6
A
|- abc.py

B
|- abc.py

上面的两个abc.py没有冲突。

我们也可以自己定义一个模块,比如:

mymodule文件:

1
2
def func_print():
print("my module")

对module的使用:

1
2
import mymodule 
mymodule.func_print()

如果模块的名称过长,可以使用as 关键字定义其他的别名。

1
2
import mymodule as m 
m.func_print()

如果在包下面的模块 可以使用包名.模块名,例如,我们可以使用 import modules.moduleA方式导入指定的包。


面向对象变成对象中重要的一个组成,在python中是使用关键字class定义类。

比如我们想定义一个动物类:

1
2
3
class Animal:
name="fantuan"

我们创建实例的时候,直接使用Animal(),具体使用如下:

1
2
animal=Animal()
print (animal.name)

上面的Animal的类中的name属性可以通过实例和类名都可以调用。因为python在实例上找不到的属性都会沿着实例的类寻找。例如代码:

1
2
3
4
5
animalA=Animal()
animalB=Animal()
print(Animal.name)
print (animalA.name)
print (animalB.name)

都能打印出name属性的值。

python支持动态属性的定义

1
2
animalA.type="哺乳类"
print(animalA.type)

如果创建一个类的对象的时候,如果需要初始化变量,可以使用__init__方法。

1
2
3
4
5
6
7
8
9
10
class Animal:
def __init__(self,name,type):
self.name=name
self.type=type

animal=Animal("dog","犬科")

print(animal.name)
print(animal.type)

__init__方法的第一个参数永远是self(类似Java和C++中的this指针)。

如果定义了__init__方法,在定义实例的时候就不能传递空的参数了。

在类中定义方法的时候,第一个参数的是self,其他与正常的方法相同。

访问限制

在类的成员中,需要控制类的访问,在Java或者C++ 使用private,public等来管理成员的访问权限。

在python中,使用两个下划线__来表示私有变量。

如果我们使用动态赋值给对象赋值了一个__name属性,那python会怎么处理?

1
2
3
4
5
6
class Animal:
__name="dog"

a=Animal()
a.__name=2
print(a.__name )

打印的结果是 2

python会把这个变量当成一个普通的变量与内部定义的私有变量不同,可以被正常的访问。

在内部定义的私有变量会被转化成其它名称,具体的名称根据python版本而不同。

在通常的代码中,我们能看到一些_name 等一些单个下划线开头的变量,这中变量是可以正常被访问,但一般不要那么做,可以当成私有变量。

继承和多态


我们定义了一个Animal的类:

1
2
3
4
5
6
7
8
9
class Animal:
def eat(self):
print("eat")

class Dog(Animal):
pass

d=Dog()
d.eat()

d中有了eat方法,是从基类继承而来。

对于python弱类型语言,天生就是多态,也不需要继承关系。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class WorkerA:
def do(self):
print("WorkA Working")

class WorkerB:
def do(self):
print("WorkB Working")


def doWork(obj):
obj.do()

workerA=WorkerA()
workerB=WorkerB()
doWork(workerA)
doWork(workerB)

打印结果:

1
2
WorkA Working
WorkB Working

Python的学习笔记--装饰器

python的函数也属于一个对象,可以有一个变量来代替,例如前面说过的一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
f=abs
print(f(-10))
```

如果我们在不改变原来函数的前提下,想扩展一个函数的功能。可以重新定义一个函数,把要原函数作为参数传递给`增强函数`,在调用函数前执行一些动作。

```python
def revert(a):
return abs(a)

def new_func(func):
print("do something")
return func

f=new_func(revert)
print(f(-10))

打印结果是:

1
2
do something
10

对于上面的代码,new_func就是装饰器,可以使用@对其进行简化:

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
def new_func(func):
print("do something")
return func

@new_func
def revert(a):
return abs(a)

print(revert(-10))

```

这样可以省去一个变量的定义,也维持了原来的方法名称。


上面的装饰器有点过于简单,因为我们只能在`return` 语句前增加内容,想在方法后面增加一个确不行,为了能够实现更强大的装饰器,我们可以对其做修改,再增加一层函数:

``` python
def new_func(func):
def inner(a):
print("do something")
res=func(a)
print("something is done")
return res
return inner

@new_func
def revert(a):
print ("processing")
return abs(a)

print(revert(-10))

打印结果:

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
do something
processing
something is done
10
```

上面这个装饰器基本满足了我们的需求,但同样还有一个问题,上面的装饰器,只能传递一个参数,所以还是不够一般性。

我们可以使用函数的默认值的方法来解决这个问题,官方给的是`*args 和 **kwargs`(tuple和dict类型),修改如下:

``` python
def new_func(func):
def inner(*args,**kw):
print("do something")
res=func(*args,**kw)
print("something is done")
return res
return inner
@new_func
def revert(a):
print ("processing")
return abs(a)

print(revert(-10))

我们再增加一个方法:

1
2
3
4
5
6
@new_func
def add(x,y):
print ("processing")
return x+y;
print(add(1,2))

打印结果是:

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
do something
processing
something is done
3
```

装饰器也可以传递参数,但是需要更深层次的函数:

``` python
def new_func(text):
print ("call method:",text)
def wapper(func):
def inner(*args,**kw):
print("do something")
res=func(*args,**kw)
print("something is done")
return res
return inner
return wapper

@new_func("revert")
def revert(a):
print ("processing")
return abs(a)

print(revert(-10))

运行结果:

1
2
3
4
5
call method: revert
do something
processing
something is done
10
Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×