Frone's Blog

技术小窝


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 公益404

python3比多线程和多进程还好的新模块 —— 协程Coroutine

发表于 2019-04-12 | 分类于 python | 阅读次数:

引子

最近把所有系统的python3 版本都更新到了python3.7,然后更新了一下代码,发现这个版本改动还是很大的,之前更多还是在使用python2.7做ETL或者操作一些API,没想到python的变化如此之大,看来自己还是太落伍了。于是在知乎和官网上找资料学习了下,看到一篇讲协程的文章很受启发,以后应该会较多使用这个功能,之前使用的多进程多线程效果都不明显,而协程应该是一个python的效率利器。

前言

多进程和多线程除了创建的开销大之外还有一个难以根治的缺陷,就是处理进程之间或线程之间的协作问题,因为是依赖多进程和多线程的程序在不加锁的情况下通常是不可控的,而协程则可以完美地解决协作问题,由用户来决定协程之间的调度。

总所周知,Python因为有GIL(全局解释锁)这玩意,不可能有真正的多线程的存在,因此很多情况下都会用multiprocessing实现并发,而且在Python中应用多线程还要注意关键地方的同步,不太方便,用协程代替多线程和多进程是一个很好的选择,因为它吸引人的特性:主动调用/退出,状态保存,避免cpu上下文切换等…

协程

基本概念

协程,又称作Coroutine,通过 async/await 语法进行声明,是编写异步应用的推荐方式。

从字面上来理解,即协同运行的例程,它是比是线程(thread)更细量级的用户态线程,特点是允许用户的主动调用和主动退出,挂起当前的例程然后返回值或去执行其他任务,接着返回原来停下的点继续执行。等下,这是否有点奇怪?我们都知道一般函数都是线性执行的,不可能说执行到一半返回,等会儿又跑到原来的地方继续执行。但一些熟悉python(or其他动态语言)的童鞋都知道这可以做到,答案是用yield语句。其实这里我们要感谢操作系统(OS)为我们做的工作,因为它具有getcontext和swapcontext这些特性,通过系统调用,我们可以把上下文和状态保存起来,切换到其他的上下文,这些特性为coroutine的实现提供了底层的基础。操作系统的Interrupts和Traps机制则为这种实现提供了可能性,因此它看起来可能是下面这样的:

1
2
3
4
5
6
7
8
9
10
>>> import asyncio

>>> async def main():
... print('hello')
... await asyncio.sleep(1)
... print('world')

>>> asyncio.run(main())
hello
world

理解生成器(generator)

学过生成器和迭代器的同学应该都知道python有yield这个关键字,yield能把一个函数变成一个generator,与return不同,yield在函数中返回值时会保存函数的状态,使下一次调用函数时会从上一次的状态继续执行,即从yield的下一条语句开始执行,这样做有许多好处,比如我们想要生成一个数列,若该数列的存储空间太大,而我们仅仅需要访问前面几个元素,那么yield就派上用场了,它实现了这种一边循环一边计算的机制,节省了存储空间,提高了运行效率。

运行协程

  1. asyncio.run() 函数用来运行最高层级的入口点 “main()” 函数

  2. 等待一个协程。以下代码段会在等待 1 秒后打印 “hello”,然后 再次 等待 2 秒后打印 “world”:

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

    async def say_after(delay, what):
    await asyncio.sleep(delay)
    print(what)

    async def main():
    print(f"started at {time.strftime('%X')}")

    await say_after(1, 'hello')
    await say_after(2, 'world')

    print(f"finished at {time.strftime('%X')}")

    asyncio.run(main())
  1. asyncio.create_task() 函数用来并发运行作为 asyncio 任务 的多个协程。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    async def main():
    task1 = asyncio.create_task(
    say_after(1, 'hello'))

    task2 = asyncio.create_task(
    say_after(2, 'world'))

    print(f"started at {time.strftime('%X')}")

    # Wait until both tasks are completed (should take
    # around 2 seconds.)
    await task1
    await task2

    print(f"finished at {time.strftime('%X')}")

可等待对象

如果一个对象可以在 await 语句中使用,那么它就是 可等待 对象。许多 asyncio API 都被设计为接受可等待对象。

可等待 对象有三种主要类型: 协程, 任务 和 Future.

协程

Python 协程属于 可等待 对象,因此可以在其他协程中被等待:

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

async def nested():
return 42

async def main():
# Nothing happens if we just call "nested()".
# A coroutine object is created but not awaited,
# so it *won't run at all*.
nested()

# Let's do it differently now and await it:
print(await nested()) # will print "42".

asyncio.run(main())

重要

在本文档中 “协程” 可用来表示两个紧密关联的概念:

  • 协程函数: 定义形式为 async def 的函数;
  • 协程对象: 调用 协程函数 所返回的对象。

asyncio 也支持旧式的 基于生成器的 协程。

任务

任务 被用来设置日程以便 并发 执行协程。

当一个协程通过 asyncio.create_task() 等函数被打包为一个 任务,该协程将自动排入日程准备立即运行:

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

async def nested():
return 42

async def main():
# Schedule nested() to run soon concurrently
# with "main()".
task = asyncio.create_task(nested())

# "task" can now be used to cancel "nested()", or
# can simply be awaited to wait until it is complete:
await task

asyncio.run(main())

Future 对象

Future 是一种特殊的 低层级 可等待对象,表示一个异步操作的 最终结果。

当一个 Future 对象 被等待,这意味着协程将保持等待直到该 Future 对象在其他地方操作完毕。

在 asyncio 中需要 Future 对象以便允许通过 async/await 使用基于回调的代码。

通常情况下 没有必要 在应用层级的代码中创建 Future 对象。

Future 对象有时会由库和某些 asyncio API 暴露给用户,用作可等待对象:

1
2
3
4
5
6
7
8
async def main():
await function_that_returns_a_future_object()

# this is also valid:
await asyncio.gather(
function_that_returns_a_future_object(),
some_python_coroutine()
)

一个很好的返回对象的低层级函数的示例是 loop.run_in_executor()。

并发运行任务

awaitable asyncio.gather(*aws, loop=None, return_exceptions=False)

并发 运行 aws 序列中的 可等待对象。

如果 aws 中的某个可等待对象为协程,它将自动作为一个任务加入日程。

如果所有可等待对象都成功完成,结果将是一个由所有返回值聚合而成的列表。结果值的顺序与 aws 中可等待对象的顺序一致。

如果 return_exceptions 为 False (默认),所引发的首个异常会立即传播给等待 gather() 的任务。aws序列中的其他可等待对象 不会被取消 并将继续运行。

如果 return_exceptions 为 True,异常会和成功的结果一样处理,并聚合至结果列表。

如果 gather() 被取消,所有被提交 (尚未完成) 的可等待对象也会 被取消。

如果 aws 序列中的任一 Task 或 Future 对象 被取消,它将被当作引发了 CancelledError 一样处理 — 在此情况下 gather() 调用 不会 被取消。这是为了防止一个已提交的 Task/Future 被取消导致其他 Tasks/Future 也被取消。

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
import asyncio

async def factorial(name, number):
f = 1
for i in range(2, number + 1):
print(f"Task {name}: Compute factorial({i})...")
await asyncio.sleep(1)
f *= i
print(f"Task {name}: factorial({number}) = {f}")

async def main():
# Schedule three calls *concurrently*:
await asyncio.gather(
factorial("A", 2),
factorial("B", 3),
factorial("C", 4),
)

asyncio.run(main())

# Expected output:
#
# Task A: Compute factorial(2)...
# Task B: Compute factorial(2)...
# Task C: Compute factorial(2)...
# Task A: factorial(2) = 2
# Task B: Compute factorial(3)...
# Task C: Compute factorial(3)...
# Task B: factorial(3) = 6
# Task C: Compute factorial(4)...
# Task C: factorial(4) = 24

爬虫例子

使用爬虫爬取豆瓣top250

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
from lxml import etree
from time import time
import asyncio
import aiohttp

url = "https://movie.douban.com/top250"
header = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36",
"content-type": "text/plain;charset=UTF-8",
}


async def fetch_content(url):
# await asyncio.sleep(1) # 防止请求过快 等待1秒
async with aiohttp.ClientSession(
headers=header, connector=aiohttp.TCPConnector(ssl=False)
) as session:
async with session.get(url) as response:
return await response.text()


async def parse(url):
page = await fetch_content(url)
html = etree.HTML(page)

xpath_movie = '//*[@id="content"]/div/div[1]/ol/li'
xpath_title = './/span[@class="title"]'
xpath_pages = '//*[@id="content"]/div/div[1]/div[2]/a'
xpath_descs = './/span[@class="inq"]'
xpath_links = './/div[@class="info"]/div[@class="hd"]/a'

pages = html.xpath(xpath_pages) # 所有页面的链接都在底部获取
fetch_list = []
result = []

for element_movie in html.xpath(xpath_movie):
result.append(element_movie)

for p in pages:
fetch_list.append(url + p.get("href")) # 解析翻页按钮对应的链接 组成完整后边页面链接

tasks = [fetch_content(url) for url in fetch_list] # 并行处理所有翻页的页面
pages = await asyncio.gather(*tasks)
# 并发 运行 aws 序列中的 可等待对象。
# 如果 aws 中的某个可等待对象为协程,它将自动作为一个任务加入日程。
# 如果所有可等待对象都成功完成,结果将是一个由所有返回值聚合而成的列表。结果值的顺序与 aws 中可等待对象的顺序一致。
for page in pages:
html = etree.HTML(page)
for element_movie in html.xpath(xpath_movie):
result.append(element_movie)

for i, movie in enumerate(result, 1):
title = movie.find(xpath_title).text
desc = (
"<" + movie.find(xpath_descs).text + ">"
if movie.find(xpath_descs) is not None
else None
)
link = movie.find(xpath_links).get("href")
print(i, title, desc, link)


async def main():
start = time()
for i in range(5):
await parse(url)
end = time()
print("Cost {} seconds".format((end - start) / 5))


if __name__ == "__main__":
asyncio.run(main())

参考文章

  1. 从0到1,Python异步编程的演进之路
  2. Python3 Async/Await解释
  3. 官方文档 协程与任务

阿兹卡班安装及介绍

发表于 2019-04-12 | 分类于 调度 | 阅读次数:

简介

Azkaban是一种类似于Oozie的工作流控制引擎,可以用来解决多个Hadoop(或Spark等)离线计算任务之间的依赖关系问题。

也可以用其代替cron来对周期性任务进行调度,并且更为直观,可靠,同时提供了美观的可视化管理界面。

下文将对azkaban对spark离线任务调度进行简要说明。

azkaban由三部分构成

  • Relational Database(Mysql)
  • Azkaban Web Server
  • Azkaban Executor Server

Relational Database(Mysql)

azkaban将大多数状态信息都存于Mysql中,Azkaban Web Server 和 Azkaban Executor Server也需要访问DB。

Azkaban Web Server

提供了Web UI,是azkaband的主要管理者,包括 project 的管理,认证,调度,对工作流执行过程的监控等。

Azkaban Executor Server

调度工作流和任务,纪录工作流活任务的日志,之所以将AzkabanWebServer和AzkabanExecutorServer分开,主要是因为在某个任务流失败后,可以更方便的将重新执行。而且也更有利于Azkaban系统的升级。

可调度任务类型

  • linux命令
  • 脚本
  • java程序
  • hadoop MR
  • hive

创建任务流

创建任务

只需要创建一个.job结尾的文本文件,通过dependencies字段指定依赖的上层执行脚本

1
2
3
# foo.job
type=command
command=echo "Hello World"
1
2
3
4
# bar.job
type=command
dependencies=foo
command=echo bar

上传任务流

将所有job文件打包成一个zip文件上传即可

1
zip -q foo.job.zip foo.job

参考资料

  1. 使用Azkaban调度Spark任务
  2. Azkaban 3的安装与配置
  3. 2018工作流引擎比较:Airflow、Azkaban、Conductor、Oozie和 Amazon Step Functions
  4. 英文版

阿里云ESC安装mysql

发表于 2019-04-12 | 分类于 阿里云 | 阅读次数:

准备工作

1
2
3
4
5
lsb_release -a # 查看系统版本

yum -y update # 更新系统软件

wget https://dev.mysql.com/get/mysql80-community-release-el7-2.noarch.rpm # 下载rpm包

开始安装

1
2
3
4
5
rpm -Uvh mysql80-community-release-el7-2.noarch.rpm

yum repolist all | grep mysql # 查看安装mysql配置文件

sudo yum install mysql-community-server

启动服务

1
2
3
4
5
6
7
8
9
service mysqld start

service mysqld status

ps -aux|grep mysql # 查看进程
netstat -anp|grep `pid` # 查看进程对应端口

# 通过端口查看进程
netstat -nap | grep 端口号

Mysql服务启动后发生如下事件

  1. mysql server初始化

  2. An SSL certificate and key files are generated in the data directory.

  3. The validate_password plugin is installed and enabled.

  4. A superuser account 'root'@'localhost' is created. A password for the superuser is set and stored in the error log file. To reveal it, use the following command:

    1
    2
    3
    4
    5
    6
    7
    grep 'temporary password' /var/log/mysqld.log

    # 可以使用如下方法更新密码
    shell> mysql -uroot -p'*****'
    mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY 'MyNewPass4!';
    mysql> update user set Host = '%' where user ='root'; # 开启远程登录
    mysql> flush privileges;

安全相关设置

  1. 在ECS管理页面添加安全组规则

  1. 添加3306 入口规则,无需启用防火墙

image-20190402183207794

参考资料

  1. 阿里云ECS服务器自建数据库的一些坑
  2. 阿里云ECS服务器CentOS7上安装MySql服务

  3. https://dev.mysql.com/doc/mysql-yum-repo-quick-guide/en/

  4. 用户管理

2018年度总结

发表于 2019-02-02 | 分类于 随笔 | 阅读次数:

前言

当你老了,回顾一生,就会发觉:什么时候出国读书,什么时候决定做第一份职业、何时选定了对象而恋爱、什么时候结婚,其实都是命运的巨变。只是当时站在三岔路口,眼见风云千樯,你作出选择的那一日,在日记上,相当沉闷和平凡,当时还以为是生命中普通的一天。

——陶杰 《杀鹌鹑的少女》

在2019年到来的时候,看到了这么一句话,感触颇深,生命就死这么组成的,开始的时候我们太年轻,意识不到,就要依赖父母的帮助。但是如果父母帮不上忙呢,那就真的看命了,年轻的固执如果没有本钱那就是作死吧。

读书

2018年做的比较好的就是读书了,阅读量显著提升了很多,也买了不少书,有纸质书(技术类偏多,还有少量管理书籍和休闲书籍)。今年发现中信出版社出版的书籍质量还是很高的,当然还是专注在管理方面,虽然工作中用到的不是很多,但是个人提升还是作用不小。

读书看电影还是用豆瓣记录比较合适,豆瓣评分还是比较客观的,而且书很全,也是对自己监督的一个工具。

mark

mark

今年看完的没看完的书应该不止这些,微信读书应该是最多的,在双十一的时候还买了季卡,看《沙海》电视剧的时候读了一段时间《盗墓笔记》,应该看了三本。然后在看了央视的《一本好书》之后看完了《三体》,这是我相当喜欢的一套书,好像找回了小时候看凡尔纳科幻小说的感觉,我还看了大刘的中篇科幻集《时间移民》,然后完结了一直没看完的《1Q84 BOOK3》,以上是小说类的。

在工作方面受亮叔影响看了很多数据类的书籍,的确深入学习是我比较需要的,主要在数据仓库和数据驱动这两块,既要有技术底层,又要有理论能力,会干能说才吃香。看完了《数据的本质》,《用数据讲故事》,在看《数据仓库工具箱》,《大数据之路》。

管理类的书籍看了《精力管理》《格鲁夫给经理人的第一课》《稀缺》还买了很多没有看,要加油消化掉。

电影

电影就不一一赘述了,院线看了些有的没的,用新买的玩客云下了不少没上映的和不上映的,还是对惊悚悬疑类的比较感冒,温子仁是个不错的导演,那么经典的《saw》也是他导演的啊。。。

印象比较深刻的电影有《海王》《此房是我造》《升级》

技术学习

在技术方面专注在了数仓和阿里云平台上,重点使用了maxcompute和dataworks两款产品。

技术上收获的更多是广度吧,流程和目的更加清晰了,知道怎么样能够叫高效、低成本的完成一件事情。

生活

生活应该是改变最多的地方,2018年举办了婚礼,完成了人生的重要一步。不过生活和自己预计的差了很多,的确家庭环境影响很大。一直把自己家定位在平均水平左右,看来我错了,中国的平均水平低是因为中国有广大的贫困人口,农民、山区的人年收入可能就万把块。要是把自己定位在平均水平就觉得OK,那就是真的不能OK了。

结婚之后认识到了很多问题,结婚的时候和三叔聊过之后就更清晰了,我的父母和我弟弟的父母都是失败的父母,典型的草根失败人民,没赚到什么钱,对家庭也没什么帮助,这应该就是草根中的草根了吧,我最难受的一点就是跟父母没法深入沟通,真的很痛苦、无助,他们完全理解不了现在这个时代,也理解不了你。

可能抱怨有点多了吧,我能做的就是接受吧,还有沉默。或许更应该想清楚自己要成为什么样的人,过怎么样的一生,比生儿育女、家庭美满更重要,失败的人生又怎么能孕育幸福的家庭呢,不该说的不敢去说,和谐二字涵盖了太多,那就活着吧。

你好 2019

2019我34了,不确定性在互联网这个行业里变多了,需要做一些真正自己应该做的事情了。不忘初心,做回自己吧,希望这一年过的开心。

airflow EmailOperator 发送邮件 附件文件名丢失或乱码问题

发表于 2018-08-30 | 分类于 任务调度 | 阅读次数:

开始使用airflow

最早开始使用airflow是因为公司的服务过度 依赖crontab了,完全通过时间进行任务调度,而且不便于追踪任务运行情况,也不好处理任务之间的依赖关系。更不要说管理多服务器的crontab问题了。

于是自己进行软件选型,查阅相关资料。因为本身是做数据工作的,不希望只是一个简简单单的crontab功能升级,于是选择了airflow。

airflow的主要特点如下:

  • 我们可以将一些常做的巡检任务,定时脚本(如 crontab ),ETL处理,监控等任务放在 airflow 上集中管理,甚至都不用再写监控脚本,作业出错会自动发送日志到指定人员邮箱,低成本高效率地解决生产问题。
  • Airflow 适用于调度作业较为复杂,特别是各作业之间的依赖关系复杂的情况​

备选是cronsun

  • cronsun 是一个分布式任务系统,单个结点和 *nix 机器上的 crontab 近似。支持界面管理机器上的任务,支持任务失败邮件提醒,安装简单,使用方便,是替换 crontab 一个不错的选择)

具体关于任务调度可以参考之前转发的一篇文章,浅谈工作流调度系统

使用 EmailOperator 模块发送邮件

当然写本篇文章的目的是解决使用airflow时遇到的发送邮件问题,我在使用EmailOperator发送xlsx附件的时候遇到了附件文件名丢失问题,有的客户端可以正常接收(例如mac的邮件),但是服务器端和Foxmail则遇到了问题,因为之前使用smtp发送邮件的时候遇到过类似问题,所以决定去airflow的源代码中一探究竟。
附件文件名丢失

修改源代码处理文件名问题

查看源代码追踪到了问题,EmailOperator使用了 from airflow.utils.email import send_email,通过email这个类完成邮件的最终发送,所以讲问题定位到了这里。
第84行

1
2
part['Content-Disposition'] = 'attachment; filename="%s"' % basename
part['Content-ID'] = '<%s>' % basename

修改为

1
2
3
# update by frone due to email attachment reason
part.add_header('Content-Disposition', 'attachment', filename=('utf-8', '', basename))
part.add_header('Content-ID','<%s>'%basename)

最终成功解决问题,可以正常使用airflow的邮件功能了。
问题得到解决

集合分区问题

发表于 2018-08-06 | 分类于 算法 | 阅读次数:

本文根据PuLP文档翻译而来,原文请参考
https://pythonhosted.org/PuLP/CaseStudies/a_set_partitioning_problem.html

集合分区问题


集合分区问题确定如何将一个集合(S)中的项目划分为更小的子集。S中的所有项目必须包含在一个且仅包含一个分区中。相关问题是:

  • 集合分包- 所有项目必须包含在零个或一个分区中;
  • 集合覆盖 - 所有项目必须至少包含在一个分区中。

在这个案例研究中,婚礼策划师必须确定婚礼的客人座位分配。为了模拟这个问题,桌子被建模为分区,邀请参加婚礼的嘉宾被建模为S的元素。婚礼策划者希望最大化所有桌子的总幸福感。
这里写图片描述

可以通过显式枚举每个可能的子集来建模集合划分问题。虽然这种方法确实变得难以处理大量项目(不使用列生成),但它确实具有以下优点:分区的目标函数系数可以是非线性表达式(如幸福),并且仍然可以解决此问题使用线性编程。

首先,我们使用allcombinations()生成所有可能的桌座位列表。

1
2
#create list of all possible tables
possible_tables = [tuple(c) for c in pulp.allcombinations(guests, max_table_size)]

然后我们创建一个二进制变量,如果表将在解决方案中将为1,否则为零。

1
2
3
4
5
#create a binary variable to state that a table setting is used
x = pulp.LpVariable.dicts('table', possible_tables,
lowBound = 0,
upBound = 1,
cat = pulp.LpInteger)

我们创造LpProblem然后制作目标函数。请注意,此脚本中使用的幸福函数很难以任何其他方式进行建模。

1
2
3
seating_model = pulp.LpProblem("Wedding Seating Model", pulp.LpMinimize)

seating_model += sum([happiness(table) * x[table] for table in possible_tables])

我们指定解决方案中允许的表总数

1
2
#specify the maximum number of tables
seating_model += sum([x[table] for table in possible_tables]) <= max_tables, \

这组约束通过保证将guest虚拟机分配给一个表来定义集合分区问题。

1
2
3
4
#A guest must seated at one and only one table
for guest in guests:
seating_model += sum([x[table] for table in possible_tables
if guest in table]) == 1, "Must_seat_%s"%guest

完整的文件可以在这里找到wedding.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
47
48
"""
A set partitioning model of a wedding seating problem

Authors: Stuart Mitchell 2009
"""

import pulp

max_tables = 5
max_table_size = 4
guests = 'A B C D E F G I J K L M N O P Q R'.split()

def happiness(table):
"""
Find the happiness of the table
- by calculating the maximum distance between the letters
"""
return abs(ord(table[0]) - ord(table[-1]))

#create list of all possible tables
possible_tables = [tuple(c) for c in pulp.allcombinations(guests,
max_table_size)]

#create a binary variable to state that a table setting is used
x = pulp.LpVariable.dicts('table', possible_tables,
lowBound = 0,
upBound = 1,
cat = pulp.LpInteger)

seating_model = pulp.LpProblem("Wedding Seating Model", pulp.LpMinimize)

seating_model += sum([happiness(table) * x[table] for table in possible_tables])

#specify the maximum number of tables
seating_model += sum([x[table] for table in possible_tables]) <= max_tables, \
"Maximum_number_of_tables"

#A guest must seated at one and only one table
for guest in guests:
seating_model += sum([x[table] for table in possible_tables
if guest in table]) == 1, "Must_seat_%s"%guest

seating_model.solve()

print("The choosen tables are out of a total of %s:"%len(possible_tables))
for table in possible_tables:
if x[table].value() == 1.0:
print(table)

PulP线性优化(三)python编码

发表于 2018-08-03 | 分类于 算法 | 阅读次数:

本文根据PuLP文档翻译而来,原文请参考
https://pythonhosted.org/PuLP/main/basic_python_coding.html

基本的Python编码


在本课程中,您将学习Python中的基本编程,但也可以在Internet上免费获得优秀的Python语言参考资料。您可以下载Dive Into Python这本书, 或者 在Python网站上有许多Python 初学者指南。点击以下链接:

  • BeginnersGuide /非程序员
  • BeginnersGuide /程序员

取决于您当前的编程知识水平。下面的代码部分假定了基本编程原理的知识,并主要关注特定于Python编程的语法。

注意:>>>表示Python命令行提示符。

Python中的循环


for循环

一般格式是:

1
2
3
为 变量 在 序列:
#some命令
#other for循环之后的命令

请注意,格式(缩进和新行)控制for循环的结束,而循环的开头是冒号:。

观察下面的循环,这类似于您将在课程中使用的循环。变量i通过字符串列表依次变为每个字符串。顶部是.py文件中的代码,底部显示输出

1
2
3
4
5
#以下代码演示了一个列表,其中包含字符串
ingredientslist = [ “Rice” ,“Water” ,“Jelly” ]
for i in ingredientslist :
print i
print “不再在循环中”

输出

1
2
3
4
Rice
Water
Jelly
No longer in the loop

while循环

这些类似于for循环,除了它们继续循环,直到指定的条件不再为真。没有告诉while循环通过任何特定的序列。

1
2
3
4
5
6
i = 3
while i <= 15:
# some commands
i = i + 1 # a command that will eventually end the loop is naturally
required
# other commands after while loop

对于这个特定的简单while循环,最好做一个for循环,但它演示了语法。如果循环之前的迭代次数需要结束,while循环是有用的,是未知的。

if语句

这与上面的循环非常相似。键标识符是冒号:启动语句和缩进结束以结束它。

1
2
3
4
5
6
if j in testlist:
# some commands
elif j == 5:
# some commands
else:
# some commands

这里显示“elif”(else if)和“else”也可以在if语句之后使用。事实上,“else”可以在两个循环之后以相同的方式使用。

python中的数组类型


列表

列表只是一组变量组合在一起。范围函数通常用于创建整数列表,具有范围的一般格式(开始,停止,步骤)。start的默认值为0,步骤的默认值为1。

1
2
>>> range(3,8)
[3,4,5,6,7]

这是一个列表/序列。除了整数之外,列表中还可以包含字符串或整数,浮点数和字符串。它们可以通过循环(如下一节所示)或通过显式创建(如下所示)创建。请注意,print语句将向用户显示字符串/变量/ list / ….

1
2
3
4
5
>>> a = [5,8,"pt"]
>>> print a
[5,8,'pt']
>>> print a[0]
5

元组

元组与列表基本相同,但重要的区别在于它们一旦创建就无法修改。它们由以下人员分配:

X = (4 ,1 ,8 ,“字符串” ,[ 1 ,0 ],(“J” ,4 ,“○” ),14 )
元组可以在其中包含任何类型的数字,字符串,列表,其他元组,函数和对象。另请注意,元组中的第一个元素编号为元素“零”。访问此数据的方法是:

x [ 0 ]
4
x [ 3 ]
“string”

字典

字典是每个具有关联数据的引用键列表,其中该顺序根本不影响字典的操作。对于字典,键不是连续的整数(与列表不同),而是可以是整数,浮点数或字符串。这将变得清晰:

x = {} #创建一个新的空字典 - 注意表示创建字典的大括号
x [ 4 ] = “编程” #字符串“programming”被添加到字典x中,“ 4“因为它是参考
x [ ”游戏“ ] = 12
打印 x [ ”游戏“ ]
12
在字典中,引用键和存储的值可以是任何类型的输入。新词典元素在创建时添加(使用列表,您无法访问或写入列表中超出最初定义的列表维度的位置)。

成本 = { “鸡肉” : 1.3 , “牛肉” : 0.8 , “羊肉” : 12 }
打印 “肉类的成本”
为 我 在 成本:
打印 我
打印 成本[ 我]
成本[ “LAMB” ] = 5
打印 “更新肉类成本“
对于 我 的 成本:
打印 我的
打印 成本[ i ]
给

成本 的 肉类
鸡肉
1.3
羊肉
12和
牛肉
0.8
更新 成本 的 肉类
LAMB
5
鸡
1.3
羊肉
12
牛肉
0.8
在上面的示例中,使用大括号和冒号创建字典以表示将数据分配给字典键。变量i依次分配给每个键(与列表中的变量相同)

用于 我 在 范围(1 ,10 )
)。然后使用此键调用字典,并返回存储在该键名下的数据。使用词典的这些类型的for循环与使用PuLP在本课程中对LP进行建模高度相关。

List / Tuple / Dictionary语法

注意创建一个:

列表用方括号[];
元组用圆括号和逗号(,)完成;
字典是用括号{}完成的。
但是,在创建之后,当访问list / tuple / dictionary中的元素时,操作总是用方括号执行(即a [3]?)。如果a是列表或元组,则返回第四个元素。如果a是字典,它将返回使用引用键3存储的数据。

列表生成式

Python支持List Comprehensions,这是一种快速而简洁的方法,可以在不使用多行的情况下创建列表。在简单的情况下,它们很容易理解,并且您将在本课程的代码中使用它们。

a = [ i for i in range (5 )]
a
[0,1,2,3,4]
上面的语句将创建列表[0,1,2,3,4]并将其分配给变量“a”。

赔率 = [ i for i in range (25 ) if i %2 == 1 ]
赔率
[1,3,5,7,9,11,13,15,17,19,21,23 ]
上面的语句使用if语句和模数运算符(%),因此列表中只包含奇数:[1,3,5,…,19,21,23]。(注意:模数运算符从整数除法计算余数。)

fifths = [i for i in range(25) if i%5==0]
fifths
[0, 5, 10, 15, 20]
This will create a list with every fifth value in it [0,5,10,15,20]. Existing lists can also be used in the creation of new lists below:

a = [i for i in range(25) if (i in odds and i not in fifths)]
Note that this could also have been done in one step from scratch:

a = [i for i in range(25) if (i%2==1 and i%5==0)]
For a challenge you can try creating

a list of prime numbers up to 100, and
a list of all “perfect” numbers.
More List Comprehensions Examples

Wikipedia: Perfect Numbers.

Other important language features


Commenting in Python

使用“”“开始并结束评论部分,在文件顶部进行评论。整个代码中的注释是使用行开头的hash#符号完成的。

导入语句

在您打算使用PuLP进行建模的所有Python编码的顶部,您将需要import语句。此语句使您当前正在编写的模块中的另一个模块(程序代码文件)的内容可用,即您将需要调用的pulp.py中定义的函数和值可用。在本课程中,您将使用:

来自 纸浆 进口 *
星号表示您正在从纸浆模块中导入所有名称。现在可以调用在pulp.py中定义的函数,就好像它们是在您自己的模块中定义的那样。

functions

Python中的函数定义如下:(def是define的缩写)

高清 名(inputparameter1 , inputparameter2 , 。 。 。):

#function体

对于一个真实的例子,请注意,如果在函数定义中为输入分配了一个值,这是默认值,并且仅在没有传入其他值时才使用。输入参数的顺序(在定义中)不无论如何,只要调用该函数,就会以相应的顺序输入位置参数。如果使用关键字,参数的顺序根本不重要:

def string_appender (head = ‘begin’ , tail = ‘end’ , end_message = ‘EOL’ ):
result = head + tail + end_message
返回 结果

string_appender (’newbegin’ , end_message = ‘StringOver’ )
newbeginendStringOver
在上面的示例中,将打印函数调用的输出。head的默认值是’begin’,但是使用了’newbegin’的输入。使用了’end’尾部的默认值。并使用endmessage的输入值。请注意,必须将end_message指定为关键字参数,因为没有给出tail的值

类

要演示类在Python中的工作方式,请查看以下类结构。

类名是Pattern,它包含几个与Pattern类的任何实例(即Pattern)相关的类变量。功能是

在里面
函数,它创建Pattern类的实例,并使用self分配name和lengthsdict的属性。
str
函数定义了打印类实例时要返回的内容。
修剪
函数就像任何普通函数一样,除了所有类函数之外,self必须在输入括号中。
类 模式:
“”, “
上在SpongeRoll问题的特定图案的信息
”“”
成本 = 1
trimValue = 0.04
totalRollLength = 20
lenOpts = [ 5 , 7 , 9 ]

def init (self ,name ,lengths = None ):
self 。name = name
self 。lengthsdict = 字典(拉链(自我。lenOpts ,长度))

def str (self ):
返回 自我。名称

def trim (self ):
返回 模式。totalRollLength - 总和([ INT (我)* 自我。lengthsdict [ 我] 为 我 在 自我。lengthsdict ])
这个类可以用如下:

1
2
3
4
5
6
7
8
>>> Pattern.cost # The class attributes can be accessed without making an instance of the class
1
>>> a = Pattern("PatternA",[1,0,1])
>>> a.cost # a is now an instance of the Pattern class and is associated with Pattern class variables
1
>>> print a # This calls the Pattern.__str__() function
"PatternA"
>>> a.trim() # This calls the Pattern.trim() function. Note that no input is required.

函数定义中的self是隐含的输入

PulP线性优化(二)优化概念

发表于 2018-08-03 | 分类于 算法 | 阅读次数:

本文根据PuLP文档翻译而来,原文请参考
https://pythonhosted.org/PuLP/main/optimisation_concepts.html

线性编程


最简单的数学程序类型是线性程序。要使您的数学程序成为线性程序,您需要满足以下条件:

  • 决策变量必须是实变量;
  • 目标必须是线性表达;
  • 约束必须是线性表达式。

线性表达式是以下形式的表达式

其中$a_i$和b是已知的常数$x_i$是变量。求解线性程序的过程称为线性编程。线性编程通过修订的单纯形法(也称为原始单纯形法),双单纯形法或内点法进行。像cplex这样的解算器允许您指定使用哪种方法,但我们在此不再详述。

整数编程


整数程序几乎与线性程序相同,但有一个非常重要的例外。整数程序中的某些决策变量可能只需要包含整数值。变量称为整数变量。由于大多数整数程序包含连续变量和整数变量的混合,因此它们通常称为混合整数程序。虽然线性编程的变化很小,但对解决方案过程的影响是巨大的。整数程序可能是非常难以解决的问题,并且目前有许多研究发现解决整数程序的“好”方法。可以使用分支绑定过程来解决整数程序。

注意对于任何合理大小的MIP,解决方案时间随着整数变量数量的增加呈指数增长。

PulP线性优化(一)优化过程

发表于 2018-08-03 | 分类于 算法 | 阅读次数:

本文根据PuLP文档翻译而来,原文请参考
https://pythonhosted.org/PuLP/main/the_optimisation_process.html#getting-the-problem-description

优化过程


解决优化问题不是一个线性过程,但过程可分为五个一般步骤:

  • 获得问题描述
  • 制定数学公式
  • 处理数学公式
  • 执行一些后优化分析
  • 介绍解决方案和分析

但是,在此过程中通常存在“反馈循环”。例如,在制定和解决优化问题之后,您通常需要考虑解决方案的有效性(通常咨询提供问题描述的人员)。如果您的解决方案无效,您可能需要更改或更新您的配方,以包含您对实际问题的新理解。此过程显示在运筹学方法图中。
这里写图片描述
建模过程从明确定义的模型描述开始,然后使用数学方法来确定数学公式。接下来,建模者将数学公式输入到一些求解器软件中,例如Excel并求解该模型。最后,根据原始模型描述将解决方案转化为决策。

使用Python为您提供了建模过程的“快捷方式”。通过在Python中制定数学程序,您已经将它放入PuLP可以轻松使用的形式中,建模者可以调用许多求解器,例如CPLEX,COIN,gurobi,因此您无需将数学公式输入到求解器软件中。但是,您通常不会在配方中添加任何“硬”数字,而是使用数据文件“填充”模型,因此会涉及一些创建相应数据文件的工作。使用数据文件的优点是相同的模型可能会被不同的数据集使用多次。

建模过程


建模过程是优化过程的“整洁”简化。让我们更详细地考虑优化过程的五个步骤:

获取问题描述


此步骤的目的是提出正式,严谨的模型描述。通常,您会启动一个优化项目,其中包含问题和一些数据的抽象描述。通常,您需要花一些时间与提供问题的人(通常称为客户)交谈。通过与客户交谈并考虑可用的数据,您可以获得您习惯的更严格的模型描述。有时并非所有数据都相关,或者您需要询问客户是否可以提供其他数据。有时,可用数据的限制可能会显着改变您的模型描述和后续配方。

制定数学公式


在此步骤中,我们从问题描述中确定关键的可量化决策,限制和目标,并在数学模型中捕获它们的依赖。我们可以将制定过程分为4个关键步骤:

  • 确定特别关注单位的决策变量(例如:我们需要确定每个进程每周运行多少小时)。
  • 使用决策变量制定目标函数,我们可以构建最小化或最大化目标函数。目标函数通常反映给定决策变量值的总成本或总利润。
  • 制定约束,要么是合乎逻辑的(例如,我们不能用于负数小时),要么明确指出问题描述。同样,约束以决策变量表示。
  • 确定目标函数和约束所需的数据。要解决您的数学程序,您需要在目标函数和/或约束中使用一些“硬数”作为变量边界和/或变量系数。

解决数学程序


对于相对简单或易于理解的问题,通常可以将数学模型解决为最优性(即,识别出最佳可能的解决方案)。这是使用诸如修订的单纯形法或内点法之类的算法来完成的。然而,使用这些技术解决最优性需要很长时间的许多工业问题,因此使用不保证最优性的启发式方法来解决这些问题。

进行一些后优化分析


通常问题描述存在不确定性(无论是提供的数据的准确性,还是将来的数据值)。在这种情况下,我们的解决方案的稳健性可以通过执行后优化分析来检查。这涉及确定最佳解决方案在公式的各种变化下如何变化(例如,给定成本增加或特定机器失效会产生什么影响?)。这种分析对于制定战术或战略决策也很有用(例如,如果我们投资开设另一家工厂,这会对我们的收入产生什么影响?)。

此步骤(以及下一步)中的另一个重要考虑因素是数学程序解决方案的验证。您应该仔细考虑解决方案的变量值在原始问题描述方面的含义。确保它们对您,更重要的是您的客户有意义(这就是为什么下一步,提出解决方案和分析很重要)。

介绍解决方案和分析


优化过程中的关键步骤是解决方案的呈现和任何后优化分析。从数学程序的解决方案转换回简明易懂的摘要与从问题描述到数学公式的转换同样重要。通过优化生成的关键观察和决策必须以易于理解的方式呈现给客户或项目利益相关者。

您的演示文稿是实施数学计划所产生决策的关键第一步。如果决策及其后果(通常由数学计划约束决定)没有明确而明智地呈现,那么您的最佳决策将永远不会被使用。

这一步也是您未来建议其他工作的机会。这可能包括:

  • 定期检查数学公式的有效性;
  • 进一步分析您的解决方案,为您的客户寻找其他好处;
  • 寻找未来的优化机会。

python生成EXCEL报表乱码问题

发表于 2018-07-30 | 分类于 python | 阅读次数:

乱码问题

从数据库导出数据,整理后使用jaro.jaro_winkler_metric 进行文本距离的计算,计算之前需要使用decode(“utf-8”)方法进行编码转换,但是在结果数据存储成CSV是遇到了乱码问题,使用EXCEL打开就会乱码,以往都是直接使用file.open()方法进行txt文本的输出,不会出现编码问题。

感觉问题应该在excel端,需要加个header之类的

解决问题

后来在知乎找到了满意的答案。
使用csv写入内容,并在头部写入BOM信息 csvfile.write(codecs.BOM_UTF8) 顺利解决问题

1
2
3
4
5
6
7
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import csv
import codecs
with open('test.csv', 'wb') as csvfile:
csvfile.write(codecs.BOM_UTF8)
spamwriter = csv.writer(csvfile, dialect='excel') spamwriter.writerow(['测试'] * 5 + ['Baked Beans']) spamwriter.writerow(['Spam', 'Lovely Spam', 'Wonderful Spam'])

转自
作者:Nice2
链接:https://www.zhihu.com/question/34201726/answer/150221584

123

Frone Xie

29 日志
10 分类
45 标签
GitHub CSDN
© 2019 Frone Xie
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4
本站访客数 人数 本站总访问量 次