分类目录归档:Python

这个冬天跟 Python 有一个约会

Dating-with-python-this-winter

visitor badge

这个冬天跟 Python 有一个约会——一个兴趣使然的 Python 课程。

前言

课程目的是让大家了解编程——最好是编程思想,掌握 Python 的基本操作。考虑到大家来自各种各样的专业,爬虫指定是不行,对大家没啥用,客户端或者后端也是对程序员才有用。

所以,学会 Python 基础之后,一个具体目的暂定是

学习数据处理、分析, 掌握 Numpy, Pandas 等框架。

起源

你为啥要搞这样的教学啊?

这次课程源自于某个北京研究生豆瓣群,有人问一个月适合学什么啊?我回答 Python,于是就成立了!随后,我想反正都是教,为什么不多拉点人呢,于是就去豆瓣拉人了,也去了即刻,区域也从北京到各地(云南、内蒙古、湖南……)。

如何上课?

课程分为三个部分

课前预习:大家可以试着完成,全凭自觉噢。

课中讲解:一个小时直播。哔哩哔哩直播

课后编程实践:需要自主完成作业,编程是实践技术,跟厨师和木匠一样,所以每次请你认真完成作业,一节课的作业截止日期一般是下一节课上完之后的一天。不再强制交作业了,如果需要催作业和批改作业服务,可以参与硬币计划,所有付费将捐给孩子们。

  • 免费:课程除了催作业和批改作业服务之外,视频、文档、群里答疑都是免费的。

  • 答疑:希望大家可以自主思考,有问题发到群里大家一起讨论。可以直接私信我,或者直接发群里交流,最后我们可以把这些答疑沉淀为文档。

  • 时间安排:开课后,一般隔两天上一节课,一天空闲是留给课前问题和课后编程的。

  • 硬币计划:目前我们还推出了硬币计划,你自行为孩子们进行捐款,可享受群主夺命催作业和批改作业服务。当然,不付费也可以参与课程和答疑,详情见下方链接:Github |博客

    欢迎加入噢!

  • 闻道有先后,术业有专攻。虽然是免费的,希望大家也能认真对待自己的时间和精力,如果觉得自己不能坚持学习,那应该提前退群,免得浪费时间。

课程目录和时间点

节数 文档和视频 直播时间(20:00) 课后作业期限(23:59)
0 Hello, world! Github | 博客 | 12月27日录播 12月27日 12月30日
1 变量和类型,用内存的视角看数据 Github | 博客 | 12月29日录播 12月29日 1月1日
2 字符串和数据结构 Github | 博客 | 1月1日录播 1月1日 1月5日
3 控制流 Github | 博客 | 1月3日录播 1月3日 1月7日
4 计算的本质 Github | 博客 | (1月8日点错了按钮,是没有录成视频,可惜……) 1月8日 无作业
5 函数与作用域 Github | 博客 | 1月11日录播 1月11日 1月14日
6 类与数据结构 Github | 博客 | 1月14日录播 1月14日 1月17日
7 面向对象的类设计 Github | 博客 | [1月17日录播]() 1月17日 1月20日
第 8 课 数据分析初步 Github | 博客 | 1月21日录播 1月21日 1月23日
第 9 课 NumPy 之 ndarray Github | 博客 | 1月24日录播 1月24日 1月26日
第 10 课 NumPy 计算和广播原理 Github | 博客 | 1月26日录播 1月26日 动手实现代码
第 11 课 Matplotlib 画图 Github | 录播 动手实现代码
第 12 课 Pandas (1) Github | 视频 动手实现代码
第 13 课 Pandas (2) Github

注意,后续机器学习与数据分析课程
https://github.com/xrandx/Dating-with-Machine-Learning

注意

如何参与

内容指引

  • 变量定义
  • 算术运算
  • for 循环语句,while 循环语句,goto 语句
  • 函数定义,函数调用
  • 递归
  • 静态类型系统
  • 类型推导
  • lambda 函数
  • 基于对象和面向对象
  • 垃圾回收与内存模型
  • 指针算术
  • ……

正则表达式-非捕获组的迷思 ( non-capturing group ) (?:)

(?:…)
正则括号的非捕获版本。 匹配在括号内的任何正则表达式,但该分组所匹配的子字符串不能在执行匹配后被获取或是之后在模式中被引用。

这解释真叫人头大。

例一

(aa|bb) 匹配 aabb

结果是

类型 范围 内容
Match 1
Full match 0-2 aa
Group 1. 0-2 aa
Match 2
Full match 2-4 bb
Group 1. 2-4 bb

例二

(aa|bb)+ 匹配 aabb

结果是

类型 范围 内容
Match 1
Full match 0-4 aabb
Group 1. 2-4 bb

第一个先匹配到 aa ,再匹配到 bb 。第二个直接匹配了 aabb 。
第一个和第二个的差别在于,用 + 重复匹配组,后果就是组只会保留最后一个迭代。

例三

我们再试试:

(?:aa|bb)+ 匹配 aabb

类型 范围 内容
Match 1
Full match 0-4 aabb

组保存的消失了?这就是非捕获组的用处,意思就是不保存组的内容到内存中,也就不会标号,于是只会得到匹配。

例四

这个用处是什么呢
先看看这个 Python 代码:

reg = r"(0\d{2})-(\d{7})"

s = "010-3838438"

re.findall(reg, s)

#  Out:[('010', '3838438')]

reg = r"(?:0\d{2})-(?:\d{7})"

s = "010-3838438"

re.findall(reg, s)

#  Out:['010-3838438']

上述差别如何发生?

第一个匹配的结果是

类型 范围 内容
Full match 0-11 010-3838438
Group 1. 0-3 010
Group 2. 4-11 3838438

而第二个匹配的结果是

类型 范围 内容
Full match 0-11 010-3838438

Python 自带的 re 模块函数:

re.findall
对 string 返回一个不重复的 pattern 的匹配列表, string 从左到右进行扫描,匹配按找到的顺序返回。如果样式里存在一到多个组,就返回一个组合列表;就是一个元组的列表(如果样式里有超过一个组合的话)。空匹配也会包含在结果里。

也就是从左到右,按正则表达式,不重复地匹配字符串,返回一个字符串列表。 如果正则表达式有一个或更多的组,返回组的列表,优先返回组。

第一个匹配里有两个组,所以只返回了组。

不得不说这个函数实现的很失败,为什么不把所有结果都返回?

最后一个例子

建议你自己猜一下输出结果:

s = 'exp=50;exp=51;'

re.findall(r'exp=(50|51);', s)

re.findall(r'exp=(?:50|51);', s)




#  Out:['50', '51']

#  Out:['exp=50;', 'exp=51;']

为什么出现这种差别?

前者有两个匹配,每个匹配有一个组。后者只有两个匹配。

因为 findall 总是尽力返回捕获的组,也就是括号内的字符串。(太蠢了)

使用了 (?:) ,它不会保存匹配的组。如果 findall 没有捕获到组,就会返回所有匹配的字符串。

Python 利器之 SQLAlchemy

参考

请参考官方文档:sqlalchemy

SQLAlchemy 是什么

相当于 Java 的 Hibernate 和 Mybatis (我更喜欢MybatisPlus :),是 Python 里数一数二的 ORM框架。
ORM框架,本质上就是程序员懒得写 SQL 语句或者实体对象代码,于是让对象代码生成 SQL 语句或者让 SQL 语句生成对象,形成所谓的对象关系映射的程序框架(Object Relational Mapping)

初始化

初始化填好数据库信息,连接数据库,生成引擎,建立会话就好。当然它还可以连接内存数据库 sqlite 。
echo参数可以控制是否显示数据库操作细节。

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

mysql_config = 'mysql+pymysql://%s:%s@%s:%s/%s?charset=utf8' % (username, password, host, port, database_name)
engine = create_engine(mysql_config, echo=False)
Base = declarative_base()
DbSession = sessionmaker(bind=engine, expire_on_commit=False)
session = DbSession()

通过 declarative_base ,框架会处理你的类声明,自动将你的类与数据库表绑定。
如下:User 继承 Base 。

最好在类内声明数据类型。 primary_key 参数代表是否为主键。
tablename 为数据表表名。

class User(Base):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    name = Column(String)
    fullname = Column(String)
    password = Column(String)

增加

user1 = User(name='lihua', age=18)
session.add(user1)

user2 = User(name='hanmeimei', age=18)
user3 = User(name='songwenhua', age=18)

session.add_all([user1, user2])
session.commit()
# commit 才能提交更改

查找

过滤器有 filter_by 和 filter ,前者用于简单的查询。
filter_by 只支持 = 运算。
filter 支持 ==, >, < ,还支持 like, has, in_, or_, and_ 等自带操作。
查找后要加 all(), first(), 或者 limit() 。

session.query(User).filter_by(id=1).first()
# 代表第一个
# desc(): 降序
session.query(User).order_by(User.id.desc()).all()
# asc():升序
session.query(User).order_by(Users.name.desc(),User.id.asc()).all()

#  例子
query.filter(Address.user == someuser)
query.filter(Address.user != someuser)
query.filter(Address.user == None)
# 名称中包含
query.filter(User.addresses.contains(someaddress))
# 用于多对一关系
query.filter(User.addresses.any(Address.email_address == 'bar'))

删除

User.query.filter(User.id == 123).delete()
session.commit()

修改

只需要查找之后,修改对象的值,commit 就可以了。

p = session.query(ParamClass).filter_by(id=1).first()
p.k1 = k1
session.commit()

flush 和 commit区别

flush 只是将语句发送到数据库内存,这种改变在这个会话可以看见,其他会话则不然。
commit 会提交所有更改,存至磁盘,其他会话也能查询到这种更改。

如何动态绑定表

接下来是重头戏了,由于业务需求,我需要根据情况,将同一类型的对象,存储在不同的表,也就是将数据存多个表,这些表的列名和结构都一致。
Python 的一个特性是可以动态生成类,利用 type() 不仅可以判断数据类型,还可以生成类:

type(类名, 父类的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))

由于框架是根据tablename来绑定表的,而且类的tablename是无法改变的。
于是可以用字典在一开始就把表名和类相关联,Model 是存粹的数据模型,Base 是 declarative_base 而来。这样就可以根据情况来匹配类了。

class User():
    id = Column(Integer, primary_key=True)
    name = Column(String)
    fullname = Column(String)
    password = Column(String)

table_mapper_dict[table_name] = type(
    table_name,
    (Model, Base),
    {'__tablename__': table_name}
)