sqlalchemy级联

relationship

relationship是sqlalchemy中的用于创建两个对象之间的关系的一个对象,跟原本的mysql没有太大关系,但是relationship一定要建立在具有外键关系的两个对象上

例如

1
2
3
4
5
6
7
8
9
10
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key = True)
name = Column(String(20))

class Article(Base):
__tablename__ = "article"
id = Column(Integer, primary_key = True)
user_id = Column(Integer, ForeignKey("user.id"))
user = relationship("User", order_by = "Article.id", backref = backref("article"), cascade = "save-update, merge, delete")

user和article之间是一对多的关系,由于article具有

user = relationship("User", order_by = "Article.id", backref = backref("article"), cascade = "save-update, merge, delete")

字段,所以article具有user属性,也即article.user可以获取article对应的user

同时因为

backref = backref("article")

使得user具有article属性,由外键关系知道user和article为一对多关系,所以,user.article类型为[]。

backref和back_populates
stackoverflow上对两者的解释

If you use backref you don’t need to declare the relationship on the second table

就是使用backref就不用再两个具有外键的类上都声明relationship。

backref

1
2
3
4
5
6
7
8
9
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
children = relationship("Child", backref="parent")

class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('parent.id'))

back_populates

1
2
3
4
5
6
7
8
9
10
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
children = relationship("Child", back_populates="parent")

class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('parent.id'))
parent = relationship("Parent", back_populates="children")

上面两者是等价的


cascade级联

例子中

cascade = "save-update, merge, delete"

属性用来表示这两个关联起来的对象的增删的时候之间的关系

cascade默认值为save-update, merge

为了表达更清晰,声明了relationship属性的类我们称之为子类,没有声明的我们称之为父类,使用上面的user和article例子
image_1cdi1seaqu2sntue61gq51la29.png-6.8kB

1.save-update

  • 当user被add到session中的时候,两个article也自动被add到session中了
  • 当article1被添加到session中时,user自动被添加进session,同时article2也会自动被添加进去

2.delete

  • 当删除子对象时,父对象也一起被删除,如果子对象有指向父对象的外键且这个外键指向这个被删除的父对象,那么这个外键设置为null
  • 当删除父对象时,子对象没有删除,如果子对象有指向父对象的外键且这个外键指向这个被删除的父对象,那么外键设置为null

3.delete-orphan

delete-orphan实在delete的基础上,加上当子对象被解除跟父对象的关系的时候就会被标志为删除

4.其他还有merge,refresh-expire,expunge那些不常用的就不要求理解了


测试代码

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
from sqlalchemy import Column, String, Integer, ForeignKey, create_engine
from sqlalchemy.orm import sessionmaker, relationship, backref
from sqlalchemy.ext.declarative import declarative_base


Base = declarative_base()

class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key = True)
name = Column(String(20))

class Article(Base):
__tablename__ = "article"
id = Column(Integer, primary_key = True)
user_id = Column(Integer, ForeignKey("user.id"))
user = relationship("User", order_by = "Article.id", backref = backref("article"), cascade = "save-update, merge, delete")


engine = create_engine('mysql+mysqlconnector://root:Gzm20125@localhost:3306/test')
DBSession = sessionmaker(bind = engine)

Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
# 创建session对象:

session = DBSession()

多维数组最短路径问题

题目
给定一个矩阵m,从左上角开始每次只能向右或者向下走,最后到达右下角的位置,路径上所有的数字累加起来就是路径和,返回所有路径中最小的路径和。
例子:
给定m如下:
1 3 5 9
8 1 3 4
5 0 6 1
8 8 4 0
路径1,3,1,0,6,1,0是所有路径中路径和最小的,所以返回12。

一般这种这种问题可以通过动态规划来解决,因为去年没选算法导论,一直对动态规划这个概念理解不是很清楚

维基百科上的解释为
动态规划(英语:Dynamic programming,简称DP)是一种在数学、管理科学、计算机科学、经济学和生物信息学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。

以往的斐波那契数列和背包问题都是用到动态规划的思想,只是一直知道这就是动态规划而已。

最短路径问题:
设一个数组dp[n][n],用来存从0,0点到这一点的最短路径和,对于每一点dp[i][j]的值,它等于min(dp[i-1][j], dp[i][j-1]) + arr[i][j],特别的,dp[0][0] = arr[0][0],对于第一行,dp[0][j] = dp[0][j - 1] + arr[0][j]对于第一列,dp[i][0] = dp[i-1][0] + arr[i][0]

代码

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

arr = sys.stdin.readline().strip().split(" ")
arr = [[int(i) for i in arr]]

num = len(arr[0])

for i in range(num-1):
new_arr = sys.stdin.readline().strip().split(" ")
new_arr = [int(i) for i in new_arr]
arr.append(new_arr)

dp = [[0 for i in range(num)] for i in range(num)]
dp[0][0] = arr[0][0]
for i in range(1,num):
dp[0][i] = dp[0][i-1] + arr[0][i]
for i in range(1,num):
dp[i][0] = dp[0][i-1] + arr[0][i]
for i in range(1,num):
for j in range(1,num):
dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + arr[i][j]

print(dp[num-1][num-1])

flask包调用中的的路径问题

前言

这篇短文写在我的腾讯一面搞砸之后,这次面试让我深刻认识到我一直是一个很功利的人。总结了一下一共有几个要点:

  • 看需求办事,有什么需求谷歌什么,查看文档相关部分,而其他的部分置之不理,全然没有深入学习的决心
  • 对一个框架没有深入了解,仅仅止步于工具化,也没有对框架有更深层的了解,这让我很惭愧
  • 做事没有从工程层面来思考问题,仅仅从个人角度思考问题

flask包调用过程中的路径问题

假设有如下情况,my_app是封装为包的一个flask app,app声明在my_app/__init__.py文件中,h.py是包内的一个辅助模块,在run_app.py中调用my_app这个包的flask app进行运行

  • my_app
    • static
    • templates
    • __init__.py
    • h.py
  • run_app.py

###在辅助模块h.py中加载static中的静态文件###
当情况是运行run_app.py,而h.py需要加载static中静态文件的时候,在h.py中使用的路径可以是两种情况:

  • 绝对路径
  • 相对路径 ./my_app/static/file_name

注意包调用情况下,如果包内非__init__模块要加载static内静态文件,需要使用从包外路径开始定位


###html内加载静态文件###
当情况是运行run_app.py,html内需要加载静态文件,路径必须是相对路径,形式为:/static/file_name


###实例文件夹###
flask除了提供 app.static_folder和app.template_folder之外
image_1c8h30e029l51fi92jdv9f1rbt9.png-7.8kB
flask还提供了instance_path
image_1c8h31ai6r1hidf187l10fo6q6m.png-4.3kB
instance_path也可以用来存放静态文件,但是要注意的是instance_path所在的位置

flask文档对instance_path位置的阐述如下:

  • 未安装的模块:
1
2
/myapp.py
/instance
  • 未安装的包
1
2
3
/myapp
/__init__.py
/instance
  • 已安装的包或模块:
1
2
$PREFIX/lib/python2.X/site-packages/myapp
$PREFIX/var/myapp-instance

$PREFIX 是你 Python 安装的前缀。这个前缀可以是 /usr 或者你的 virtualenv 的路径。你可以打印 sys.prefix 的值来查看前缀被设置成了什么

所以上面已经阐述得很清楚了,在我们包调用的情况下,instance_path是跟my_app并列的位置上的,所以可以在my_app/__init__.py内的app使用app.instance_path来加载位于与my_app并列的instance中的静态文件,如下:

  • instance
    • static_file_to_load
  • my_app
  • run.py

mysql的charset和collate

mysql的charset

charset用于给数据库确定使用哪种编码方式进行编码
image_1ccpviqo3i3d1tbs1fd22p470h9.png-76.1kB


mysql的collate

collate叫做数据库的校验,就是一种对字符串进行比较的规则
image_1ccpvon1q16g6b0v16dh1gkuh4qm.png-90.6kB

stackoverflow上对charset和collate的解释:

A character set is a set of symbols and encodings. A collation is a set of rules for comparing characters in a character set. Let’s make the distinction clear with an example of an imaginary character set.


mysql中常用的编码和校验方式

###utf-8和unicode###
unicode是确定字符的编码值,但是具体怎么用多大的空间来存储室没有确定的,在计算机系统中,存储在硬件的使用试用unicode进行编码,读出来进行读写是使用utf-8进行编码的

###utf-8和ascii###
ascii编码是使用7位来进行编码的,utf-8是小的字符用一字节,多的用到2字节到4字节,当时utf-8用一字节进行编码的时候和ascii一样。

###utf-8和latin1###
Latin1是ISO-8859-1的别名,顾名思义,1这种编码方式可以编码拉丁字母,ISO-8859-1收录的字符除ASCII收录的字符外,还包括西欧语言、希腊语、泰语、阿拉伯语、希伯来语对应的文字符号。如果用latin1来编码非拉丁字母的中文日文等就会出现乱码。latin1用一字节来存储一个字符。


mysql实践中的问题

问题阐述
mysql database的编码和校验方式会影响mysql连接器(比如我一直是用mysql.connector)在查询varchar的时候,将结果返回为bytearray的形式还是是正常的string类型。charset为utf-8时,varchar字段的查询结果返回类型为bytearray,虽然这个问题可以在直接使用mysql.conector的时候自己decode一下来解决,但是当mysql.connector配合sqlalchemy这样的orm一起使用的时候,问题就尤为明显,而且难以解决。在sqlalchemy配合mysql.connector中,query函数中传入varchar类型的字段作为查询的条件的时候,报错:typeerror:bytearray is not hashalbe。显示返回的查询类型是bytearray,并且这是不可哈希的类型,所以sqlalchemy的查询报错。

问题原因
这应该是mysql.connector 2版本自己的设计或者一个bug,因为详细可以看Connector/Python 2.0.0官方文档说明
image_1ccq0rujm13qo1ln46ulrl51c4d13.png-28.1kB
这里说明,connector版本1返回值的类型是正常的,python2返回时字符串,python3是bytes,但是到了connector版本2,无论哪个版本的python返回值一律都是bytearray

解决方案
据我观察

  • charset为utf-8,返回值类型为bytearray
  • charset为latin1,返回值类型为string
  • charset为gbk,返回值类型为string,而gbk可以对中文编码

所以得出上面所说的,数据库的charset可以影响connector的返回值类型,这很迷,谷歌搜到的解释很多事stackoverflow上的,对于这个问题也是众说纷纭,所以也不知道这算是一个bug还是connector本身的一个设计。上面这样解决也要考虑当存储非拉丁字符时会出现乱码的情况。

在创建database时确定它的charset和collate

CREATE DATABASE db_name CHARACTER SET latin1 COLLATE latin1_swedish_ci;

latin1_swedish_ci意为瑞典的一个拉丁字符比较方式,ci意为不计较大小写。

通过上面设置database的charset和collate的方式就可以让connector的varchar返回类型为string,sqlalchemy的query不报错了。


总结

在mysql.connector+sqlalchemy对mysql进行含有中文字段的row进行增删查找,可以设置数据库的charset为gbk。这样mysql.connector查询的返回值为string,sqlalchemy的query不会报错。

1
create database test2 DEFAULT CHARACTER SET gbk COLLATE gbk_chinese_ci;

测试代码

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

from sqlalchemy import Column, String, Integer, create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base

# 创建对象的基类:
Base = declarative_base()

# 定义User对象:
class User(Base):
__tablename__ = 'user'
id = Column(String(20), primary_key=True)
name = Column(String(20))

# 初始化数据库连接:
engine = create_engine('mysql+mysqlconnector://root:Gzm20125@localhost:3306/test')
# 创建DBSession类型:
DBSession = sessionmaker(bind=engine)

#Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
# 创建session对象:

session = DBSession()
# 创建新User对象:
new_user = User(id="5", name='Bob')
# 添加到session:
session.add(new_user)
# 提交即保存到数据库:
session.commit()
# 关闭session:
session.close()

# 创建Session:
session = DBSession()
# 创建Query查询,filter是where条件,最后调用one()返回唯一行,如果调用all()则返回所有行:
user = session.query(User).filter_by(id = "5").one()
# 打印类型和对象的name属性:
print('type:', type(user))
print('name:', user.name)
# 关闭Session:
session.close()

image_1ccq1cgd91bo6hl21le3fpo1d6v1g.png-8.3kB