Openpyxl 是一个用来处理 Excel 格式文件的 Python 库,它能用来处理 Excel 2007 及以上版本的 excel 文件,也就是 .xlsx/.xlsm/.xltx/.xltm 格式的表格文件。
使用 pip 安装
pip install openpyxl
使用方法包括读和写,参考如下例子:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from openpyxl import Workbook, load_workbook
class ExcelWriter:
def __init__(self, name):
self.name = name
self.wb = Workbook()
def create_work_sheet(self, sheet_name, index):
ws = self.wb.create_sheet(sheet_name, index)
ws.sheet_properties.tabColor = "1072BA"
def write_row(self):
ws = self.wb.active
for row in range(1, 10): # 1 到 10 行
ws.append(range(10)) # 10 列
self.wb.save(self.name)
def set_cell_value(self, row, col, value):
ws = self.wb.active
ws.cell(row=row, column=col).value = value
def save(self):
self.wb.save(self.name)
class ExcelReader:
def __init__(self, path):
self.path = path
self.wb = load_workbook(path)
def get_all_sheetnames(self):
return self.wb.get_sheet_names
def get_all_rows(self, sheet_name):
"""
按行进行迭代
:param sheet_name:
:return:
"""
ws = self.wb.get_sheet_by_name(sheet_name) # 调用 get_sheet_by_name 如果 sheet name 不存在返回 None
if ws is None:
return
rows = ws.rows
for row in rows:
if row is None:
return
yield [col.value for col in row]
def get_cell_value(self, sheet_name, row, col):
ws = self.wb.get_sheet_by_name(sheet_name)
if ws is None:
return
return ws.cell(row=row, column=col).value
if __name__ == '__main__':
writer = ExcelWriter("sample.xlsx")
writer.write_row()
reader = ExcelReader('sample.xlsx')
sn = reader.get_all_sheetnames()
for data in reader.get_all_rows('Sheet'):
print data
openpyxl 还有很多重量级的功能,比如绘图等等,具体可参考文档。
Python 其他处理 Excel 的库
这一篇文章整理了一下目前互联网上免费,并且能够稳定发送邮件的服务,能够满足个人使用需求,能够通过 API 调用直接发送邮件的服务。
个人的一些要求有这些:
对于小型的应用,最开始的时候可以使用 Gmail SMTP 来发送邮件,免费,并且送达率由 Google 来保证,基本没有啥问题。
但是 Gmail SMTP 发送有数量限制:
如果你使用超过了 Gmail 的限制,那么 Google 会在没有任何警告的前提下停止 Gmail 账号的访问,需要等一整天之后才能再访问,所以如果你的服务每天会发送超过 500 封邮件的情况下最好还是不要使用 Gmail。
[[Elastic Email]] 是一家加拿大的公司。官网地址:
在官网上可以看到调用 API 发送邮件的价格大约在 1000 封邮件 $0.12 左右。当每天发送超过 100 封邮件的时候需要增加支付方式。这也就意味着当每天发送的邮件数量不超过 100 封邮件的时候完全免费。但一旦超过数量,就必须购买其服务。如果一个月超过 10000000 封邮件的时候可以联系客服给予优惠价。
[[Mailgun]] 提供了 HTTP API 和 SMTP 两种方式发送邮件。
免费服务每个月 10000 封 / 单个域名 限制,个人其实完全用不完。老账号似乎还有每个月 10000 封邮件的免费额度。但是 Mailgun 的 Free 账号,没有绑定信用卡已经无法添加域名了。
Mailgun 似乎调整了收费方式,对于新注册的用户,前三个月可以每个月有 5000 封邮件的免费额度。一旦超过 3 个月,就需要根据使用来选择套餐付费使用。
而 Mailgun 升级第一档付费的就要 35 美元一个月,并且也只能发送 5 万邮件一个月。
[[SendGrid]] 免费服务每天可以发送 100 封邮件,同样支持 API 和 SMTP 方式,还可以使用 Webhook 方式。
MXRoute 是 LowEndTalk 上一位名叫 jar 的管理员提供的邮件服务,在最便宜的时候可以以 15 美元的价格购买一年的服务,目前也在官网提供 199 美元终身的服务。
MXRoute 不允许发送垃圾邮件,并且对于发送邮件每个电子邮件地址每小时 300 封出站电子邮件。
如果你想以更低的价格使用 MXRoute 的服务,也可以联系我 以更低的价格出售 MXRoute 的服务。
Resend 是一个专为开发者而服务的发送邮件系统。提供每天 100 封,一个月 3000 封邮件的免费试用额度。
每个月 100 封邮件
Sparkpost 屏蔽了 xyz 域名。
Mailchimp 是一个电子邮件订阅的在线工具。
搜狐的服务,免费账号 50 封邮件 / 天 20 条短信 / 天。需要域名备案。
如果是通过 Amazon EC2 托管的程序,每个月前 62000 封邮件免费。
[[sendinblue]] 提供免费的发送额度,每天可以至多发送 300 封邮件。
mailtrap 给个人开发这提供免费 500 封测试邮件。
Mail baby 是一个邮件服务提供商。只需要保持每个月 1$ 的费用,每发送 1000 封邮件花费 20 美分。
Mail Space 最低的一个套餐是每个月 3.33$,年付。每个月可以发送 1000 封邮件。
Tutanota 是一家德国公司,可以支付每个月 1 欧元使用。
mailbox.org 也是一家德国公司,提供三档套餐:
Premium,25GB 邮件空间,50GB 云端存储空间,25 个 @mailbox.org 昵称,250 个自定义域名昵称,9 欧元/每月
ProtonMail 是一款加密电子邮箱。
免费版提供 500MB 空间,每天 150 封邮件限制,3 个文件夹/标签限制。
Mailchannels 是一家邮件发送服务提供商。
一个域名邮箱提供商。
Mailcheap 是一家提供邮件发送服务的供应商。
Migadu 是一个收费的域名邮箱服务提供商,看了一下价格还挺贵。
mailtrap 是一个发送邮件的服务,免费版本提供每个月 100 封的测试邮件。
Sendy 是一个电子邮件发送服务,宣称比 Amazon SES 便宜 100 倍。
关于树莓派的装机,配置,系统安装,网络配置等等网上有太多的叫教程,就不在一一介绍。这里主要想要整理一下在折腾过程中遇到的几个问题。一些细节很琐碎,记录下来备忘。我安装的是 Raspberry Pi 官方的系统,也就是 Debian 的衍生系统,所以绝大多数下面的内容在其他 Debian/Ubuntu/Linux Mint 系统上都可以操作。很多内容我在 Mint 下也都已经实现过。
树莓派到今天已经发布了很多代了,当时买的比较早,稍微和新一代比较一下
一图胜过千言
安装完成之后
sudo apt-get install raspi-config
sudo raspi-config
选择 Network -> interface
或者手工编辑 vim /lib/udev/rules.d/73-usb-net-by-mac.rules
:
ACTION=="add", SUBSYSTEM=="net", SUBSYSTEMS=="usb", NAME=="", \
ATTR{address}=="?[014589cd]:*", \
TEST!="/etc/udev/rules.d/80-net-setup-link.rules", \
IMPORT{builtin}="net_id", NAME="eth0"
修改其中的 NAME.
编辑 /etc/network/interfaces
:
auto lo
iface lo inet loopback
auto eth0
allow-hotplug eth0
iface eth0 inet dhcp
查看设备:
lsusb
扫描可见 SSID:
sudo iwlist wlan0 scan
编辑:
auto wlan0
allow-hotplug wlan0
iface wlan0 inet dhcp # DHCP 自动分配 IP
wpa-ssid yourssid # 要连接的 wifi 名称
wpa-psk yourpassword # 要连接的 wifi 密码
auto wlan0
allow-hotplug wlan0 # 允许热插拔(非必须配置)
iface wlan0 inet static # 采用静态 IP 分配的方式
address 192.168.2.249 # 为树莓派设置的 ip
netmask 255.255.255.0 # 子网掩码
gateway 192.168.2.1 # 网关地址
wpa-ssid yourssid # 要连接的 wifi 名称
wpa-psk yourpassword # 要连接的 wifi 密码
编辑 /etc/network/interfaces
:
auto wlan0
allow-hotplug wlan0
iface wlan0 inet dhcp
pre-up wpa_supplicant -B w -D wext -i wlan0 -c /etc/wpa_supplicant/wpa_supplicant.conf
post-down killall -q wpa_supplicant
编辑多个配置文件路径 /etc/wpa_supplicant/wpa_supplicant.conf
,或者可以用命令生成:
wpa_passphrase SSID password >> /etc/wpa_passphrase/wpa_passphrase.conf
或者手动编辑该文件:
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=CN
network={
ssid="xxxx" # wifi 名称
psk="xxxx" # wifi 密码
key_mgmt=WPA-PSK # 加密方式
}
network={
ssid="xxxx"
psk="xxxx"
key_mgmt=WPA-PSK
}
network={
ssid="xxx"
key_mgmt=NONE # 加密方式,不加密
}
network={
ssid="xxxx"
key_mgmt=NONE
wep_key0="xxxx" # wep 密码
}
network={
ssid="xxxx"
psk="xxxx"
key_mgmt=WPA-PSK
scan_ssid=1 # 如果你的无线接入点是隐藏的,该配置就是必须的
}
network={
ssid="xxxx"
psk="xxxx"
key_mgmt=WPA-PSK
priority=999 # priority 指连接优先级,数字越大优先级越高(不可以是负数)
}
启动网卡:
sudo ifup wlan0
sudo /etc/init.d/networking restart
查看详情:
sudo ifconfig -a
sudo iwconfig
运行:
sudo wpa_cli
在交互模式下可以使用这些命令:
再该命令下需要先创建 network, 设置 network SSID, 密码,加密方式,最后再 enable。具体可以 help 查看。
在安装完成之后可以使用国内的 sources.list 源,比如说 清华大学的
编辑 /etc/apt/sources.list
:
deb http://mirrors.tuna.tsinghua.edu.cn/raspbian/raspbian/ stretch main non-free contrib
deb-src http://mirrors.tuna.tsinghua.edu.cn/raspbian/raspbian/ stretch main non-free contrib
编辑 /etc/apt/sources.list.d/raspi.list
:
deb http://mirrors.tuna.tsinghua.edu.cn/raspberrypi/ stretch main ui
然后更新软件包,一般 update 用来同步本地 package 和 源的 package 索引, update 一定要在 upgrade 或者 dist-upgrade 之前。update 只是用来同步 package 的状态,只是相当于检查更新,而需要手动触发更新。
sudo apt-get update
更新系统, upgrade 用来更新本地安装过的所有 package 的新版本。
sudo apt-get upgrade
更加智能的更新系统,会用更加智能的方式解决包冲突
sudo apt-get dist-upgrade
最后更新 kernel 和 firmware:
sudo rpi-update
sudo apt-get instal ttf-wqy-microhei
sudo apt-get install scim-pinyin
sudo dpkg-reconfigure locales
不同系统有不同的文件系统,在 Windows 下绝大部分的文件系统都是 NTFS,当然我一直在用的移动硬盘也格式化成了 NTFS,那么在 Linux 下挂载 NTFS 格式的磁盘,需要借助 ntfs-3g
这个 Package。
NTFS-3G 是微软 NTFS 文件系统的一个开源实现,包括读写支持。
使用如下命令安装 NTFS-3G
sudo apt-get install ntfs-3g
然后使用 sudo fdisk -l
来查看当前系统能够识别的磁盘及分区。一般来说额外的硬盘应该会是类似 /dev/sda1
这样的标示。
使用 mount
命令来挂载 NTFS 磁盘。
sudo mount -t ntfs-3g /dev/sda1 /media/sda1
mount 命令默认会调用 /sbin/mount.ntfs ,它在安装了 ntfs-3g 之后被符号连接到 /bin/ntfs-3g。 确保本地挂载点文件夹存在,否则自己手工新建 mkdir -p /media/sda1
。
或者可以直接使用 ntfs-3g
命令
ntfs-3g /dev/sda1 /media/sda1
具体参考: Arch Linux Wiki
使用 frp , 在外网 VPS 上搞一个 server 。
在之前的文章 就曾说到使用 Samba 来在局域网共享文件,搭配 Android 上 ES Explorer,电视盒子基本上可以做到类似家庭共享的作用,所有的一切都看路由器能够不能带动了。这里再说一下基本配置。
使用如下命令安装
sudo apt-get install samba
修改配置文件 smb.conf
[Public]
comment = Public Storage # 共享文件夹说明
path = /home/pi/Public # 共享文件夹目录
read only = no # 不只读
create mask = 0777 # 创建文件的权限
directory mask = 0777 # 创建文件夹的权限
guest ok = yes # guest 访问,无需密码
browseable = yes # 可见
设置文件夹权限
sudo chmod -R 777 /home/pi/Public/
重启 Samba 服务
sudo samba restart
完成后局域网中的其他设备就可以通过,\\IP\
来访问共享的内容
很早之前,我也写过 Resilio Sync 的文章,那个时候还叫 BTSync。在笔记本上一直跑着,这些天我看树莓派负载也不高,跑一个 Resilio Sync 应该也还可以。
不过过程却有点繁复,国内似乎屏蔽了 Resilio Sync 的官网,连他的 Key 都无法下载下来,可以使用 proxychains 代理的方法(可以参考之前的文章),不过毕竟也稍微麻烦一点,不过后来发现,在官网下载一个可运行的 rslsync ,再配置一些 conf 文件就可以直接开跑。
在文件 /etc/apt/sources.list.d/resilio-sync.list
中写入:
deb [arch=armhf] http://linux-packages.resilio.com/resilio-sync/deb resilio-sync non-free
添加公钥
wget -qO - https://linux-packages.resilio.com/resilio-sync/key.asc | sudo apt-key add -
更新 index
sudo dpkg --add-architecture armhf
sudo apt-get update
安装
sudo apt-get install resilio-sync
安装完之后,如果想要修改 conf 文件,可以去相应的位置 /etc 下找,然后使用树莓派的 IP:8888 来访问 WEB 管理界面。
然后就是添加相应的 KEY 来同步文件了。具体可以参考我之前那篇文章。
Flask Admin 是 Flask 的一个管理插件,类似于 Django Admin 一样的存在,可以很方便的帮助开发者实现管理界面,并且能够提供一套和数据库对应的界面。
Flask-Admin 提供一个现成的 SQLAlchemy 模型接口。它以类执行并接受 2 个参数:模型类和数据库会话。
初始化 Flask Admin
from flask import Flask
from flask.ext.admin import Admin
app = Flask(__name__)
admin = Admin(app)
app.run()
运行这个程序并访问 http://localhost:5000/admin/ ,可以看到基础模板。
增加一个管理视图
from flask import Flask
from flask.ext.admin import Admin, BaseView, expose
class MyView(BaseView):
@expose('/')
def index(self):
return self.render('index.html')
app = Flask(__name__)
admin = Admin(app)
admin.add_view(MyView(name='Hello'))
app.run()
模型视图允许你为数据库中的每个模型增加专用的管理页面。可以自动为数据库中每一张表生成一个管理页面。
from flask.ext.admin.contrib.sqla import ModelView
admin = Admin(app)
admin.add_view(ModelView(User, db.session))
构造自己的 ModelView,要定制这些模型视图,有两个选择:一是覆盖 ModelView 类的公有属性,二是覆盖它的方法。
from flask.ext.admin.contrib.sqla import ModelView
# Flask and Flask-SQLAlchemy initialization here
class MyView(ModelView):
# Disable model creation
can_create = False
# Override displayed fields
column_list = ('login', 'email')
def __init__(self, session, **kwargs):
# You can pass name and other parameters if you want to
super(MyView, self).__init__(User, session, **kwargs)
admin = Admin(app)
admin.add_view(MyView(db.session))
from flask.ext.admin.contrib.fileadmin import FileAdmin
import os.path as op
# Flask setup here
admin = Admin(app)
path = op.join(op.dirname(__file__), 'static')
admin.add_view(FileAdmin(path, '/static/', name='Static Files'))
Flask 处理上传的文件非常简单,总结归纳可以分为三步:
<form>
标签被标记有 enctype=multipart/form-data
,并且在里面包含一个 <input type=file>
标签假设将上传的文件存放在 static/uploads
目录中。
werkzeug 库可以判断文件名是否安全,例如防止文件名是 /../test.png
, 安装
pip install werkzeug
具体代码:
from flask import Flask, request
from werkzeug.utils import secure_filename
import os
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'static/uploads/'
app.config['ALLOWED_EXTENSIONS'] = set(['png', 'jpg', 'jpeg', 'gif'])
# For a given file, return whether it's an allowed type or not
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1] in app.config['ALLOWED_EXTENSIONS']
@app.route('/')
def hello_world():
return 'hello world'
@app.route('/upload', methods=['POST'])
def upload():
upload_file = request.files['file']
if upload_file and allowed_file(upload_file.filename):
filename = secure_filename(upload_file.filename)
upload_file.save(os.path.join(app.root_path, app.config['UPLOAD_FOLDER'], filename))
return 'hello, '+request.form.get('name', 'little apple')+'. success'
else:
return 'hello, '+request.form.get('name', 'little apple')+'. failed'
if __name__ == '__main__':
app.run(debug=True)
app.config
中的 config 是字典的子类,可以用来设置自有的配置信息,也可以设置自己的配置信息。函数 allowed_file(filename)
用来判断 filename 是否有后缀以及后缀是否在app.config['ALLOWED_EXTENSIONS']
中。
这里使用了 werkzeug
自带的 secure_filename
方法,该方法会过滤所有非 ASCII 码,对于中文文件名处理就不怎么友好了,所以我们可以定义自己的 secure_filename
方法
def secure_filename(filename):
"""
确保文件名不包含 / -
:param filename:
:return:
"""
filename = re.sub('[" "\/\--]+', '-', filename)
filename = re.sub(r':-', ':', filename)
filename = re.sub(r'^-|-$', '', filename)
return filename
使用正则将文件名中的非法字符去除掉。
Flask 提供了 getlist
方法
for upload in request.files.getlist("file"):
filename = os.path.splitext(upload.filename)[0]
filename = secure_filename(filename)
save_name = hashlib.md5('video'+ str(time.time())).hexdigest()[:16]
dest = '/'.join([dest_folder, save_name])
print("Accept incoming file:", filename)
upload.save(dest)
模拟上传文件
import requests
files = {'file': open('01.jpg', 'rb')}
user_info = {'name': 'einverne'}
r = requests.post("http://127.0.0.1:5000/upload", data=user_info, files=files)
print r.text
要控制上产文件的大小,可以设置请求实体的大小,例如:
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 #16MB
如果要获取上传文件的内容可以:
file_content = request.files['image01'].stream.read()
在 template 目录下新建 upload.html
,确保在 HTML 表单中设置 enctype=”multipart/form-data” 属性
<form action="\{\{ url_for('.upload_file') \}\}" method=post enctype=multipart/form-data>
<div class="form-group">
<label for="exampleInputFile">上传文件</label>
<input type="file" name="file">
<p class="help-block">上传数据</p>
<button type="submit" class="btn btn-default">Submit</button>
</div>
</form>
在 views.py 中写 upload_file
方法处理上传逻辑
@crawler_control.route('/upload', methods=['POST'])
def upload_file():
if request.method == 'POST':
f = request.files['file']
if f and '.' in f.filename and f.filename.rsplit('.', 1)[1] in app.config['ALLOWED_EXTENSIONS']:
filename = secure_filename(f.filename)
path = op.join('/tmp', filename)
f.save(path)
return "Success"
return render_template_string("only support POST")
处理 JSON 时,请求和响应头的 Content-Type
设置为 application/json
。
from flask import Flask, request, Response
import json
app = Flask(__name__)
@app.route('/json', methods=['POST'])
def my_json():
print request.headers
print request.json
rt = {'info':'hello '+request.json['name']}
# add custom headers
response.headers.add('Server', 'python flask')
return Response(json.dumps(rt), mimetype='application/json')
if __name__ == '__main__':
app.run(debug=True)
模拟客户端请求
import requests, json
user_info = {'name': 'einverne'}
headers = {'content-type': 'application/json'}
r = requests.post("http://127.0.0.1:5000/json", data=json.dumps(user_info), headers=headers)
print r.headers
print r.json()
模拟 RESTful 接口
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'hello world'
@app.route('/user/<username>')
def user(username):
print username
print type(username)
return 'hello world'
@app.route('/user/<username>/friends')
def user_friends(username):
print username
print type(username)
return 'hello world'
if __name__ == '__main__':
app.run(debug=True)
使用内置类型装换
@app.route('/page/<int:num>')
def page(num):
print num
print type(num)
return 'hello world'
自动将 num 转换成 int 类型,目前 Flask 支持的转换有:
类别 | 解释 |
---|---|
string | 任何不带 slash(/) 的字符串 |
int | 整数 |
float | float |
path | 接受 slash(/) |
any | 所有 |
uuid | uuid string |
复杂的用法,比如定义一个范围
@app.route('/page/<int:num1>-<int:num2>')
def page(num1, num2):
print num1
print num2
return 'hello world'
自定义转换器
自定义的转换器是一个继承 werkzeug.routing.BaseConverter
的类,修改 to_python
和 to_url
方法即可。to_python
方法用于将url中的变量转换后供被 @app.route
包装的函数使用,to_url
方法用于 flask.url_for
中的参数转换。
下面是一个示例,将 HelloWorld/index.py
修改如下:
from flask import Flask, url_for
from werkzeug.routing import BaseConverter
class MyIntConverter(BaseConverter):
def __init__(self, url_map):
super(MyIntConverter, self).__init__(url_map)
def to_python(self, value):
return int(value)
def to_url(self, value):
return 'hi'
app = Flask(__name__)
app.url_map.converters['my_int'] = MyIntConverter
@app.route('/page/<my_int:num>')
def page(num):
print num
print url_for('page', num='123')
return 'hello world'
if __name__ == '__main__':
app.run(debug=True)
浏览器访问 http://127.0.0.1:5000/page/123 后,HelloWorld/index.py 的输出信息是:
123
/page/hi
上面例子只能提到的 url_for
方法用于构建URL,他的使用方法如下
from flask import Flask, url_for
app = Flask(__name__)
@app.route('/')
def hello_world():
pass
@app.route('/user/<name>')
def user(name):
pass
@app.route('/page/<int:num>')
def page(num):
pass
@app.route('/test')
def test():
print url_for('hello_world')
print url_for('user', name='einverne')
print url_for('page', num=1, q='hadoop mapreduce 10%3')
print url_for('static', filename='uploads/01.jpg')
return ''
if __name__ == '__main__':
app.run(debug=True)
输出
/
/user/einverne
/page/1?q=hadoop+mapreduce+10%253
/static//uploads/01.jpg
url for 后接方法名
Flask 是一个轻量级的基于 python 的 web 框架。
一般情况下,只要通过 pip 安装 Flask 即可:
pip install Flask
进入 python shell
>>> import flask
>>> print flask.__doc__
flask
~~~~~
A microframework based on Werkzeug. It's extensively documented
and follows best practice patterns.
:copyright: (c) 2015 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
>>> print flask.__version__
0.12.2
创建 index.py
文件,在文件中放入以下内容:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
app.run()
运行 python index.py
python index.py
* Running on http://127.0.0.1:5000/
变量 app 是一个 Flask 实例,当客户端访问根目录 /
时,将响应 hello_world()
函数返回的内容。这不是返回 Hello World! 这么简单,Hello World! 只是 HTTP 响应报文的实体部分,状态码等信息既可以由 Flask 自动处理,也可以通过编程来制定。
在创建 Flask 时使用额外的参数,具体可参考 __doc__
添加 run 函数参数:
app.run(debug=True)
默认情况下,Flask 使用 127.0.0.1
,端口为 5000
,通过如下方式更改:
app.run(host='0.0.0.0', port=80, debug=True)
绑定 80 端口需要 root 权限运行 index.py
。
from flask import Flask, request
app = Flask(__name__)
@app.route('/')
def hello_world():
params = request.args.items()
return params.__str__()
if __name__ == '__main__':
app.run()
另外可以通过 request.full_path
和 request.path
来获取客户端请求 url
from flask import Flask, request
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'hello world'
@app.route('/register', methods=['POST'])
def register():
print request.headers
print request.form
print request.form['name']
print request.form.get('name')
print request.form.getlist('name')
print request.form.get('nickname', default='little apple')
return 'welcome'
if __name__ == '__main__':
app.run(debug=True)
使用 requests
模拟客户端请求:
import requests
user_info = {'name': 'einverne', 'password': '123'}
r = requests.post("http://127.0.0.1:5000/register", data=user_info)
print r.text
构造访问 URL 时可能会遇到如下两种情况:
@app.route('/projects/')
def projects():
return 'The project page'
@app.route('/about')
def about():
return 'The about page'
解释:
Flask 下有 Flask-Restless 结合 SQLAlchemy 轻松实现 RESTful 。
因为 HTTP 协议是无状态的,服务器不知道用户上一次做了什么,这阻碍了交互式 Web 应用程序的实现,所以引入了 Cookie 和 Session,用来记录用户的状态。
要记住的一点是 Session 是服务端记录状态,而 Cookie 是客户端记录状态。
对于一个分布式应用来说服务端记录状态会涉及到大量的成本。
from flask import Flask, render_template_string, \
session, request, redirect, url_for
app = Flask(__name__)
app.secret_key = 'F12Zr47j\3yX R~X@H!jLwf/T'
@app.route('/')
def hello_world():
return 'hello world'
@app.route('/login')
def login():
page = '''
<form action="\{\{ url_for('do_login') \}\}" method="post">
<p>name: <input type="text" name="user_name" /></p>
<input type="submit" value="Submit" />
</form>
'''
return render_template_string(page)
@app.route('/do_login', methods=['POST'])
def do_login():
name = request.form.get('user_name')
session['user_name'] = name
return 'success'
@app.route('/show')
def show():
return session['user_name']
@app.route('/logout')
def logout():
session.pop('user_name', None)
return redirect(url_for('login'))
if __name__ == '__main__':
app.run(debug=True)
设置 session 过期时间
from datetime import timedelta
from flask import session, app
session.permanent = True
app.permanent_session_lifetime = timedelta(minutes=5)
from flask import Flask, request, Response, make_response
import time
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'hello world'
@app.route('/add')
def login():
res = Response('add cookies')
res.set_cookie(key='name', value='letian', expires=time.time()+6*60)
return res
@app.route('/show')
def show():
return request.cookies.__str__()
@app.route('/del')
def del_cookie():
res = Response('delete cookies')
res.set_cookie('name', '', expires=0)
# print res.headers
# print res.data
return res
if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True)
毕业面临人生第一次租房,而到现在从学校搬出来也已经有近一个月时间,期间工作有些繁忙,而有一些租房相关的笔记一直躺在WizNote中,直到今天回到家,才想到应该把我的经历记下来,提醒自己以后租房过程中避坑。
现在的社会是一个信息为主的社会,我们经常挂在嘴边说的一句话就是信息不对称能够产生利益,现在社会很多的金钱交易都是基于信息的不对称。而租房信息获取的途径也会直接导致以后一年的住房条件和心情。下面就讲讲我参考的几个主要站点和App。
首先就是自如,也是我周围同学使用最多的,大多数周围的同学都通过自如找到了房子,我也看过一些自如的房子,自如有如下特点
缺点:
在同学QQ群中第一次听说,立马下载安装注册之后,需要缴纳100元作为认证费用,只有缴纳费用之后才能看到发布者联系方式。可是缴纳认证费用之后连续联系的两家都已经将房屋出租出去。不及时更新房屋信息,并且在缴纳认证费用的界面没有任何提示。如果30天内没有签约,费用不退。
总结下:
强烈建议将该App 拉入黑名单。白白让我交了100块的认证。
房源多,整租单间价格和市场几乎就是市场均价,但是环境看机会。
类似链家。
同样也是一个找租房的应用,不过看官网和他应用的主要内容似乎是一家经营房产,买卖房屋为主业的公司,租房只是他们其中的一个副业,当然也可以在上面找找周围合适的房屋信息,个人感觉比那个鬼 107 间要靠谱一些,和自如相同也是有“房管”来管理租赁一个范围内的房屋,和自如类似。可以参考,毕竟最后没有在安居客租房因此也不能下结论。
如果你公司内网有租房信息,请一定要利用好,租房那天在外面跑了很多地方,顶着大太阳看了一个有一个房子,却只有在公司内网同事分享的一些房子靠谱一些。个人最后也在同学的推荐下找到了相对比较合适的房子。
其他 58,链家 ,豆瓣 之类,只是寥寥看了一下,并没有真正却联系,在看房的时候也了解到不乏有之前的房东在 58 上找到很好的房子,进而想要帮房东更快的租出房子而内网发布信息的。当然如果个人贸然出租请还是注意防骗。
这个社会的复杂性都在一直合同中变的扁平和简洁,而着又同样带来了合同的复杂性,合同越复杂带来的后续处理细节就越方便。我们找到房东直租,但房东是一位上了年龄的阿姨,合同也让我们自己带,网上,同学问了一圈,最后在住房和城市建设委员会的网站上找了一份租房合同模板 。但网站上给出的版本还是 08 年的版本,我们又找了一份 14 年的修订版 —- 北京市房屋租赁合同(自行成交版2014)
有了合同模板就好很多,按照上面的填空,填完也能够整个合同流程有一个大概的印象,应该注意的地方合同中大多都有提到,当然除了合同中提到的内容,涉及到金钱的地方要额外的注意。
对于标题中提出的问题,可以拆分来一步步解决。先来区分 MySQL 中的 KEY
和 INDEX
,之后问题就可以简化为 PRIMARY KEY,UNIQUE KEY 和 INDEX 的区别。而这三者也正好是索引的划分,主键索引,唯一索引和普通索引(INDEX)。
INDEX 可以用来提高数据库中查询数据的速度。INDEX 通常加在那些 JOIN, WHERE,和 ORDER BY 子句的列上。创建索引时,需要确保该索引是应用在 SQL 查询语句的条件(一般作为 WHERE 子句的条件)。实际上,索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录。
索引也有它的缺点:虽然索引提高了查询速度,却会降低更新表的速度,如对表进行 INSERT、UPDATE 和 DELETE。因为更新表时,MySQL 不仅要保存数据,还要保存一下索引文件。
KEY 通常是 INDEX 同义词,但实际上还是有区别的。如果关键字属性 PRIMARY KEY 在列定义中已给定,则 PRIMARY KEY 也可以只指定为 KEY。这么做的目的是与其它数据库系统兼容。PRIMARY KEY 是一个唯一 KEY,此时,所有的关键字列必须定义为 NOT NULL。如果这些列没有被明确地定义为 NOT NULL,MySQL 应隐含地定义这些列。
KEY 即键值,是关系模型理论中的一部份,比如有主键(PRIMARY KEY),外键(Foreign KEY)等,用于数据完整性检验和唯一性约束等。而 INDEX 则处于实现层面,可以对表的任意列建立索引,那么当建立索引的列处于 SQL 语句中的 Where 条件中时,就可以得到快速的数据定位,从而提高检索效率。至于 UNIQUE INDEX,则只是属于 INDEX 中的一种而已,建立了 UNIQUE INDEX 表示此列数据不可重复。
于是,在设计表的时候,KEY 只是要处于模型层面的,而当需要进行查询优化,则对相关列建立索引即可。
KEY 是数据库的物理结构,一般理解为键,包含两层含义,一是约束,偏重于约束和规范数据库的结构完整性,二是索引,辅助查询。
可见,key 是同时具有 constraint 和 index 的意义。
INDEX 是数据库的物理结构,一般翻译为索引,但只辅助查询,会在创建时占用另外的空间。索引分为前缀索引、全文索引等。索引只是索引,不会去约束索引字段的行为。
PRIMARY KEYs(主键) 和 UNIQUE KEYs(唯一键约束) 是类似的, PRIMARY KEY 通常是一列,也有可能多列,通常由他来决定一行数据 (row)。 一张表只能有一个 PRIMARY KEY,但可以有很多 UNIQUE KEY。 当给一列设置为 UNIQUE KEY 之后,不能有两行在该列上有相同的数据。 PRIMARY KEY 不允许有 NULL 值,但是 UNIQUE KEY 可以。
修改表 ALTER TABLE table_name ADD PRIMARY KEY(column_name, ...)
总结,相同点:
差异点:
PRIMARY KEY 不能有空值, UNIQUE KEY 可以有。如果 PRIMARY KEY 的 1 个或多个列为 NULL,在增加 PRIMARY KEY 时,列自动更改为 NOT NULL 。而 UNIQUE KEY 对列没有要求是通过参考索引实施的,如果插入的值均为 NULL,则根据索引的原理,全 NULL 值不被记录在索引上,所以插入全 NULL 值时,可以有重复的,而其他的则不能插入重复值。
alter table t add constraint uk_t_1 UNIQUE (a,b); insert into t (a ,b ) values (null,1); # 不能重复 insert into t (a ,b ) values (null,null);#可以重复
在 MySQL 中,对于一个 PRIMARY KEY 的列,MySQL 已经自动对其建立了 UNIQUE INDEX,无需重复再在上面建立索引了。
网上关于 PRIMARY KEY 和 UNIQUE INDEX 的一段解释:
Note that “PRIMARY” is called PRIMARY KEY not INDEX.
KEY is something on the logical level, describes your table and database design (i.e. enforces referential integrity …)
INDEX is something on the physical level, helps improve access time for table operations.
Behind every PK there is (usually) UNIQUE INDEX created (automatically).
建立索引会占用磁盘空间的索引文件。
CREATE INDEX IndexName ON mytable(username(length));
如果是 CHAR,VARCHAR 类型,length 可以小于字段实际长度;如果是 BLOB 和 TEXT 类型,必须指定 length。
在创建表时创建索引:
CREATE TABLE mytable(
ID INT NOT NULL,
username VARCHAR(15) NOT NULL,
INDEX [INDEXName] (username(length))
);
删除索引
DROP INDEX [INDEXName] ON mytable;