2020年3月

Flask 中的上下文变量:

变量名上下文类别说明
current_app程序上下文指向处理请求的当前程序实例
g程序上下文替代python的全局变量,确保仅在当前请求中可用,用于存储全局数据,每次请求都会重设
request请求上下文封装客户端发出的请求报文数据
session请求上下文用于记住请求之间的数据,通过签名的Cookie实现

以上都是指向真实对象的代理,若需要获得原始对象,可以调用_get_current_object()方法。

以下代码保存了name这个值,并且可以供其他视图函数使用:

from flask import g

@app.before_request
def get_name():
    g.name = request.args.get('name')

@app.route('/hello')
def hello():
    return 'Hello, ' + g.name

完整的报错信息如下:

There was an error while executing VBoxManage, a CLI used by Vagrant
for controlling VirtualBox. The command and stderr is shown below.
Command: ["startvm", "7130e2d3-3630-4c6c-b306-e96282ec6426", "--type", "headless"]
Stderr: VBoxManage: error: RawFile#0 failed to create the raw output file X:/development/env/xenial64/ubuntu-xenial-16.04-cloudimg-console.log (VERR_FILE_NOT_FOUND)
VBoxManage: error: Details: code NS_ERROR_FAILURE (0x80004005), component ConsoleWrap, interface IConsole

解决方法:
在Vagrantfile中关于virtualbox的部分做如下配置:

  config.vm.provider "virtualbox" do |vb|
  #   Display the VirtualBox GUI when booting the machine
  #   vb.gui = true
  #
  #   # Customize the amount of memory on the VM:
    vb.memory = "1024"
    vb.cpus = 2
  # Basebox ubuntu/xenial64 comes with following Vagrantfile config and causes https://github.com/joelhandwell/ubuntu_vagrant_boxes/issues/1
  # vb.customize [ "modifyvm", :id, "--uart1", "0x3F8", "4" ]
  # vb.customize [ "modifyvm", :id, "--uartmode1", "file", File.join(Dir.pwd, "ubuntu-xenial-16.04-cloudimg-console.log") ]
  # following config will address the issue
    vb.customize [ "modifyvm", :id, "--uartmode1", "disconnected" ]

  end

1. HTTP请求

查询字符串
URL https://undefined.im/hello?name=Lily 中的?name=Lily即为查询字符串(query string)

一个完整的请求:

浏览器发送请求(request),报文(message)在浏览器和服务器之间传输,服务器根据请求返回的数据为响应报文(response message)。

常见的HTTP方法:

方法用途
GET获取资源
POST发送数据
PUT传输文件
DELETE删除资源
HEAD获得报文头部
OPTIONS询问支持的方法

Flask中的请求对象request

获取请求URL中的查询字符串参数:

request.args.get('name','默认值')
print request.args 
# {'name':value}

flask routes 命令
使用flask routes 命令可以查看所有路由

视图函数同时监听GET请求和POST请求:

@app.route('/api',methods=['GET','POST'])
def api():
    ...

转换器的使用:
转换器通过使用<转换器:变量名>(<int:age>)把变量转换成为整数。

@app.route('/api/<int:age>')
def api():
    ...

any 转换器:
any转换器可以设置可选值。

@app.route('/api/<any(blog,page,media):data>')
def api(data):
    ...

# 另外一种方式
dataType = ['blog','page','media']
@app.route('/api/<any(%s):data>'%str(data)[1:-1])
def api(data):
    ...

请求钩子
Flask 默认实现的5种请求钩子

钩子类型作用
before_first_request注册一个在第一个请求之前触发的函数
before_request注册一个在每个请求之前触发的函数
after_request注册一个如果没有异常抛出,则在请求之后触发的函数
teardown_request注册一个在每个请求之后触发的函数,如果发生异常,异常对象将作为参数
after_this_request在视图函数内注册一个在这个请求结束后出发的函数

注册一个钩子的基本示例

@app.before_request
def doSomething():
    pass
# 这个函数将在每个请求处理之前触发

2.HTTP 响应

视图函数可以返回最多由三个元素组成的元组:响应主体、状态码、首部字段。其中首部字段可以为字典,或是两元素元组组成的列表。

手工指定响应状态码

@app.route('/hello')
def hello():
    ...
    return 'hello'
    # 默认状态码为200

@app.route('/hello')
def hello():
    ...
    return 'hello',201
    # 指定状态码为201

重定向的方法

@app.route('/hello')
def hello():
    ...
    return redirect('https://notekit.cn/',302)
    # 重定向

@app.route('/hi')
def hi():
    ...
    return redirect(url_for('hello'))
    # 重定向到某个视图函数

搭建时花费了半天去解决过程中遇到的各种奇怪的坑,应当要记录下来。

virtualbox版本:6.0.14
vagrant 版本:2.2.6
box: ubuntu/xenial64

新建Box并导入

本地开发目录下(我的是X:development/env/xenial/):

vagrant init ubuntu/xenial64

会在目录下生成Vagrantfile,我做了如下配置:

Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/xenial64"
  config.vm.network "private_network", ip: "192.168.33.20"
  config.vm.synced_folder "./www", "/vagrant"
  config.vm.provider "virtualbox" do |vb|
    vb.memory = "1024"
  end
end

Python配置

Linux 下大都自带了python,使用起来非常的方便,这个box自带了python3.5.2,因此直接在它的基础上安装pip和pipenv即可。

~$ python
Python 3.5.2 (default, Oct  8 2019, 13:06:37)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>  

安装pip

sudo apt-get install python-pip

安装pipenv

pip install pipenv

创建虚拟Flask开发环境

在项目目录中执行:

pipenv install

pipenv 创建虚拟开发环境

搭建Flask开发环境

安装flask

pipenv shell
pipenv install flask

测试项目

在项目目录中创建app.py文件并保存。

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return '<h1>:)</h1> Hello! Flask.'

保存后执行:

pipenv run flask run --port=8080 --host=0.0.0.0 
# 如果已经在虚拟环境下(即已经使用pipenv shell激活了虚拟环境
# 执行 flask run --port=8080 --host=0.0.0.0 即可

如果没有出现报错信息,console会提示如下:

  • Environment: production
    WARNING: This is a development server. Do not use it in a production deployment.

Use a production WSGI server instead.

你服务器的访问地址为0.0.0.0:8080,打开主机的浏览器输入http://192.168.33.20:8080 即可看到下图:

helloflask.JPG

现在,你就可以专心学习Flask了。

/app.py

""" flask 模板渲染,参数传递 """

# 引入Flask类,引入模板渲染方法
from flask import Flask, render_template

# 传入
app = Flask(__name__)

# 定义入口方法
@app.route('/')
def index():
    # return 'Hello!! Flask :)'
    """ 渲染index.html 并传入参数 title """
    return render_template('index.html',title = 'Flask')

# 运行
if __name__ == '__main__':
    app.run(debug=True, port=80, host='0.0.0.0')

# 开启debug, 设置端口号,设置主机名

/templates/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ title }}</title>
</head>
<body>
     A page rendered by Flask with param 'title' = {{title}}
</body>
</html>

falsk是一款python下的轻量级Web框架,包含众多非常实用的插件,能够帮助你使用少量的代码构建功能完备的Web应用。

因为我是在Ubuntu 下做的开发,以下所有操作均在该系统下验证通过。
安装pip:

sudo apt install python-pip

安装flask框架:

pip install flask

一个简单的hello world

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return 'Hello!! Flask :)'

if __name__ == '__main__':
    app.run()

运行后报错:

ModuleNotFoundError: No module named 'flask'

排查:
因为同时安装了python2和python3,在安装pip时执行的sudo apt install python-pip是在python2下安装的pip,应当执行sudo apt install python3-pip
同样的,在安装flask扩展时应当执行pip3 install flask

我非常肯定以后不会用到python2,所以干脆就把它卸载了:

# 卸载python2.7
sudo apt-get remove python2.7

# 卸载python2.7及其依赖
sudo apt-get remove --auto-remove python2.7

#消除python2.7
sudo apt-get purge python2.7 or sudo apt-get purge --auto-remove python2.7

修复以上错误后运行代码,控制台给出了web访问地址:
屏幕截图 2020-03-15 17.12.44.png

在办公室的电脑里装了虚拟机跑ElementaryOS,安装好所有更新和驱动之后发现系统分辨率竟然不支持1600*900,网上搜索的解决方案是手动添加分辨率。
详细过程记录如下:

先放上一张1440*900分辨率的美图:

2020-03-09 17-27-11 的屏幕截图.png

查询1600*900分辨率下的有效扫描频率。
终端中输入:

cvt 1600 900

输出

"1600x900_60.00"  118.25  1600 1696 1856 2112  900 903 908 934 -hsync +vsync

然后输入以下命令新建分辨率

 sudo xrandr --newmode "1600x900_60.00"  118.25  1600 1696 1856 2112  900 903 908 934 -hsync +vsync 

给系统增加1600x900分辨率选项

sudo xrandr --addmode Virtual1 "1600x900_60.00"

添加开机启动脚本,实现永久设置正确的分辨率

sudo vim /etc/profile

在末尾增加如下脚本:

xrandr --newmode "1600x900_60.00"  118.25  1600 1696 1856 2112  900 903 908 934 -hsync +vsync 
xrandr --addmode Virtual1 "1600x900_60.00"

重新生成profile文件,让配置生效:

source /etc/profile

在系统设置-显示器中设置正确的分辨率,重启之后也会生效。

一入vscode深似海,即便是到了linux下,vscode依然是我首选的代码编辑器,除了真的很好用之外,关键是免费。
elementaryOS是基于Ubuntu18.08开发的,所以在ubuntu下安装vscode的方法同样适用与elementaryOS。

vscode for ubuntu界面

安装方法:

1.利用官方提供的Deb安装包

查看vscode官方安装说明:
https://code.visualstudio.com/docs/setup/linux,在Debian and Ubuntu based distributions这一段能找到deb包的下载地址,下载完成后:

sudo apt install ./<file>.deb

# If you're on an older Linux distribution, you will need to run this instead:
# sudo dpkg -i <file>.deb
# sudo apt-get install -f # Install dependencie

即可使用code了,真香啊。

2.借助第三方包管理器snap(不建议)

安装snap:

sudo apt update
sudo apt install snapd

安装vscode

sudo snap install --classic code # or code-insiders

为了更好的学习后端语言,在虚拟机里安装了elementaryOS,不得不说,界面做的确实漂亮,不过依然有很多坑,系统安装完成后的第一件事肯定是安装中文输入法咯。
以下命令在最新的elementaryOS(0.5.1)下测试通过:

卸载ibus

sudo apt-get remove ibus   // 卸载ibus
sudo apt-get remove scim 
sudo apt-get autoremove   // 删除依赖包
sudo apt-get -f install   // 修正安装过程中出现的依赖性关系

添加fcitx源

sudo add-apt-repository ppa:fcitx-team/nightly

出现以下问题:

add-apt-repository命令不存在
解决:

安装software-properties-common

apt install software-properties-common

更新软件源:

sudo apt-get update

出现以下问题:

E: 仓库 “http://ppa.launchpad.net/fcitx-team/nightly/ubuntu bionic Release” 没有 Release 文件。
N: 无法安全地用该源进行更新,所以默认禁用该源。
N: 参见 apt-secure(8) 手册以了解仓库创建和用户配置方面的细节。

解决:
进入 /etc/apt/sources.list.d目录后重命名fcitx-team-ubuntu-nightly-bionic.list

cd /etc/apt/sources.list.d
sudo mv fcitx-team-ubuntu-nightly-bionic.list   fcitx-team-ubuntu-nightly-bionic.list .bak

更新软件源后安装fcitx

sudo apt-get update
sudo apt-get install im-switch fcitx fcitx-config-gtk fcitx-sunpinyin fcitx-module-cloudpinyin fcitx-googlepinyin   

安装完成后配置默认输入法

sudo im-config

执行后会弹出可视化配置窗口,按照提示配置,重启完成后即可使用Ctrl + Space 切换输入法,享受输入中文的顺滑吧

类的继承

子类可以及成父类中的变量和方法

在继承前必须要导入包含父类的模块
from model import class

父类模块:phone.py

class Phone():

    def __init__(self, number, local):
        self.number = number
        self.local = local

    def calling(self):
        print('calling '+ str(self.number) +' from ' + self.local)

    def message(self):
        print('message to ' + self.number )

子类模块:mobile.py

# 引入父类模块
from phone import Phone

class Mobile(Phone): # 继承的父类名写在括号内

    def __init__(self, number, local, url):
        self.url = url
        # 调用父类的构造方法
        super(Mobile,self).__init__(number, local) 
        
    
    def browser(self):
        print('browser to ' + self.url)
        super(Mobile,self).message() #调用父类方法

Mobile('152','636','baidu').calling() #调用继承的方法
Mobile('152','636','baidu').browser()
 

成员的可见性

为了提高封装性和安全性,需要根据实际情况对类的成员的可见性进行约束

class Test():
    
    def __init__(self,name='张三',course='英语'):
        self.name = name
        self.course = course
        self.score = 0
      

    def marking(self):
    
        print(self.name + '的' + self.course + '成绩为' + str(self.score))

s = Test(name='那谁')
s.score = 20 # score可以被外部调用并更改
s.marking() 
 

定义私有变量和方法

为了提高安全性,变量score需要设置为私有,禁止外部调用,设置私有变量和方法的可见性方法如下:
在名称前使用__即可声明一个私有变量或者方法。

class Test():
    
    def __init__(self,name='张三',course='英语'):
        self.name = name
        self.course = course
        self.__score = 0 #设置为私有
      

    def __marking(self):
        print(self.name + '的' + self.course + '成绩为' + str(self.__score))

    def showScore(self):
        print(self.__score)

s = Test(name='那谁')
print(s.__score)  #无法访问
s.showScore() 

可访问性

python通过修改变量名称实现属性和方法的私有性:

class Test():
    
    def __init__(self,name='张三',course='英语'):
        self.name = name
        self.course = course
        self.__score = 0
      

    def __marking(self):
        print(self.name + '的' + self.course + '成绩为' + str(self.__score))

    def showScore(self):
        print(self.__score)

s = Test(name='那谁')
s.__score = -1  # 新增了一个名为__score的变量,和s.score 不同
print(s.__dict__) 
# {'name': '那谁', 'course': '英语', '_Test__score': 0, '__score': -1} 
print(s._Test__score) # 魔术方法,强制访问私有变量 score

基本概念

:类是现实世界或者思维世界中的实体在计算机中的反应,它将数据以及对数据的操作封装在一起。
数据成员:用来表征对象特性的属性
成员方法:用来体现对象行为并且封装在类中的函数
类变量:类的变量,抽象层级
实例变量:对象的特征,现实层级
类和对象的关系:类是对某一类象的抽象化,通过类可以产生多个对象

类的声明

类名首字母应该大写,且遵循驼峰法C
定义一个类

class CivilEngineer():
    name = ''
    project = ''

    def __init__(self,name,project):
        self.name = name
        self.project = project


    def eat(self):
        print(self.name + '负责了' + self.project )

实例化

按照不同的属性通过实例化生成不同的对象。

person = Person()
person.showInfo()

构造函数:

通过构造函数可以根据参数生成不同的对象,构造函数在类被实例化时会被自动调用,返回值只能为None

    def __init__(self,name,project):
        self.name = name
        self.project = project

方法的定义

self和php中的$this以及JS中的this关键字作用相同,用来引入内部属性/数据成员或者方法。

    def showInfo(self):
        print(self.name + '今年' + str(self.age) + '岁' )

实例方法

def eat(self):
    print(self.name + '吃东西')

在实例方法的内部访问类变量

可以借助__class__访问类变量:

class Student():
    
    sum = 0 # 类变量
    
    def total(self):
        self.__class__.sum += 1 #操作类变量
        print(self.__class__.sum)


print(Student.sum) #获取类变量
Student().total() # 实例方法操作类变量

类方法

类方法主要用来操作类变量 声明方法如下

在定义方法前增加装饰器 @classmethod,参数列表中传入cls(可更改),类方法用来操作类变量

class Person:
    @classmethod
    def plus_sum(cls):
        pass

类方法对类变量的操作

class Person:
    sum = 0

    @classmethod
    def total(cls):
        print(cls.sum)

Person.total() # 类方法被类调用
Person().total() # 类方法被实例调用

从上面的代码中可以看出,类方法可以更加方便的引用并操作类变量,而不像实例方法那样需要借助__class

!!! 类方法可以被类和实例调用,但不建议使用实例对象调用类方法

静态方法

静态方法通过使用@staticmethod装饰器来声明

class Person:
    sum = 0

    @classmethod
    def total(cls):
        print(cls.sum)

    @staticmethod
    def add():
        print(Person.sum) #实例方法访问类变量
        print('static method')

Person.total() # 类调用类方法
Person.add()  # 类调用静态方法
Person().add() # 实例调用静态方法

python中不常使用静态方法,静态方法可以访问实例变量和类变量

函数基础知识

函数的作用:将常用功能封装起来,具有明确的功能,能够隐藏细节,减少代码复杂度,具有复用性,如下代码展示了一个函数:

a = 1.12356
print(round(a,2))
# round(number, ndigits=None)) // 四舍五入
# 控制台中输入 help(func) 可以快速查看内置函数信息
# 查看python 之禅 import this

函数的定义

语法:

def funcname(parameter_list):
    pass

parameter_list 可以没有;函数中使用return 返回值;如果没有return 则默认返回 None;函数执行到return后,即停止执行。

示例:

def add(x,y):
    result = x + y
    return result

 
def print(code):  
    print(code) 
#自定义函数不能和内置函数同名,自己调用自己 报错 Traceback

如何设置函数允许最大的递归层数

在文件头部声明:

import sys
sys.setrecursionlimit(10000)

形参和实参

def add(x,y):
    result = x + y
    return result
# x,y 形参

a = add(1,2)

# 1,2 实参

返回结果

同时返回多个结果

def damage(skill1,skill2):
     return skill1,skill2

同时接收多个参数

sk1_damages,sk2_damages = damage(3,6) # 序列解包方式 接收结果 
print(sk1_damages)

序列解包相关知识

a,b,c = 1,2,3
""" 
等同于
a = 1
b = 2
c = 3
"""
d = 1,2,3
# 等同于 
d = (1,2,3)
a,b,c = d
#另外一个例子
a=b=c=1
""" 
a = 1
b = 1
c = 1
"""

函数的参数

必须参数

函数参数列表中定义的参数必须传递,传递的参数参与到函数运算,必须参数在调用时必须考虑传入顺序。

def add(x,y)
    return x + y 
#在调用add函数时必须传入x,y,且按序
add(2,1)
# 关键字参数,调用时可忽略顺序
add(x=2,y=1)

默认参数

下面定义了一个不含默认参数的函数:

def print_student_info(name, gender, age, college):
    print('我叫' + name)
    print('我今年' + str(age) + '岁')
    print('我时' + gender + '生')
    print('我在' + college + '上学')
    print('-------------------------')

print_student_info('李小萌','女',17,'市三中')

下面定义了一个含有默认参数的函数
默认参数的定义:即给定了默认值的参数,定义和调用时默认参数需要放在参数列表最后

def print_student(name, gender='男', age=18, college='红星小学'):
    print('我叫' + name)
    print('我今年' + str(age) + '岁') 
    print('我时' + gender + '生')
    print('我在' + college + '上学')
    print('-------------------------')

print_student('李亮','女',12)

# 使用关键字传参
print_student('果果', age = 19)

调用时默认参数需要放在参数列表最后

print_student('丁丁',gender='女',17,college='大三鹿中学')
#  SyntaxError: positional argument follows keyword argument

包的概念

包可以看作是包含了很多模块的文件夹,声明包的方法是在目录中创建
__init__.py文件

模块

每个py文件都可以看做是一个模块,模块中可以包含类,方法(函数),且模块和模块之间可以相互引入。

命名空间

python中的命名空间表示为包名+.+模块名:

包content下的article模块:content.article

包的引入

# import as 用法
import content.article as artice 
#导入模块,引用模块中的变量date
import content.article
print(content.article.date)

# from module import a,def 用法 
# 导入变量title
from content.article import title
# 导入getText方法
from content.article import getText

!!! 当一个包或者包中的模块被引入时,__init__.py模块中的类,方法,变量将会被自动引用,这一特性同样可以用来解决批量引入时的问题。

统配符引入

from package.module import *
指定某模块下可以使用*号导出的元素,需要在模块中做如下声明:
__all__ = ['a','b'] // 只有a,b能通过*的方式引入,a,b可以时变量,也可以是模块