抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

背景

随着写的爬虫变多,从单文件变为多文件,一些方法也都单独写成工具类。在优化数据库执行的时候,忽然想到python有没有连接池,可以像Java一样,控制下连接数。这样我没有人为close,一旦资源池紧张,也不会影响。让连接池自己去关掉一些空闲连接给新的连接请求 。

在写的时候考虑到我mysql和sqlite3都在使用,可否都写到配置文件中?其实都写在py文件也没问题,只不过这样写的多了,相当于定义了很多变量,且看上去不是那么高级,在写完ini以后想到,我的配置文件可否也像java中properties一样来读取?

ini有很多现成的lib可用,properties没有,需要自己实现,看过几篇文章的思路后,自己来实现。

问题

配置文件如下

1
2
3
4
5
6
7
8
9
10
# mysql
database.mysql.host_mysql=ip
database.mysql.port_mysql=3306
database.mysql.database_mysql=dbname
database.mysql.user_mysql=username
database.mysql.password_mysql=password
database.mysql.charset_mysql=utf8

# sqlite3
database_sqlite3=amc.db

思路很简单,读取文件,然后首字母”#”开头的是注释忽略,非”#”开头且是x=y这种格式,带有”=”的认为是配置行。一般到这里逐行读取放到一个字典就行了,但是java中往往properties配置是支持 a.b=c这种格式的。

于是识别为配置行之后,再去按照”=”切割,右侧是值,左侧是key,当key中含有”.”的话,就按照”.”切割。

大致流程如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
初始 a.b.c=1, key=a.b.c  value=1, 一个空字典{}
|
|
|
{'a': {}} # 当前得到的dict, value=1, 还有b.c (key=b=c), 调用self
|
|
|
{'a': {'b': {}}} # 当前得到的dict, value=1, 还有c (key=c), 调用self
|
|
|
{'a': {'b': {'c': 1}}} # 得到当前dict, 传过来的参数key中已经没有'.'了,
# 说明递归完了, 最终value带入dict, 返回dict

而写的get方法原理有点类似上面,支持 a.b.c去获取value

于是写下如下代码

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
# coding: utf-8
Author: ryan
Time: 2019/08/13 0:01
"""
像java一样读取 *.properties配置
传递值支持a.b.c
"""

class PropertiesRead:

def __init__(self, path):
self.path = path
self.property_dict = self.get_property()

def property_dict_treat(self, key_name, value, property_dict):
"""
递归
:param key_name:
:param value:
:param property_dict:
:return:
"""
if key_name.find('.') > 0:
key = key_name.split('.')[0]
key_name_temp = key_name[len(key) + 1:]
property_dict.setdefault(key, {})
self.property_dict_treat(key_name_temp, value, property_dict[key])
else:
property_dict[key_name] = value

def get_property(self):
"""
组成字典
:return:
"""
property_dict = {}
with open(self.path, 'r', encoding="UTF-8") as property_buffer:
for line in property_buffer.readlines():
line = line.strip().replace('\n', '')
if line and line.find('=') and not line.startswith('#'):
sp = line.split('=')
key = sp[0].strip()
value = sp[1].strip()
self.property_dict_treat(key, value, property_dict)
return property_dict

def get(self, key, temp_property=None):
"""
Give key name then return value.
:param key:
:param temp_property: 用来存储取多层字典值时中间过程的value
:return:
"""
if not temp_property:
temp_property = self.property_dict
if key.find('.') > 0:
temp_key = key[len(key.split('.')[0])+1:]
temp_property = temp_property[key.split('.')[0]]
self.get(temp_key, temp_property) # 问题出在此
return temp_property.get(key)

if name == "main":
filepath = "aaa.properties"
pr = PropertiesRead(filepath)
print(pr.get('database.mysql.host_mysql'))

打印出来是None,就很奇怪,首先看temp_property得到的dict是什么,于是60后加一条print(temp_property)

1
2
3
4
5
6
7
8
9
10
{'mysql': {'host_mysql': 'ip', 'port_mysql': '3306', 'database_mysql': 'dbname', 
'user_mysql': 'username', 'password_mysql': 'password', 'charset_mysql': 'utf8'}}

{'host_mysql': 'ip', 'port_mysql': '3306', 'database_mysql': 'dbname',
'user_mysql': 'username', 'password_mysql': 'password', 'charset_mysql': 'utf8'}

完整字典是
{'database': {'mysql': {'host_mysql': 'ip', 'port_mysql': '3306', 'database_mysql':
'dbname', 'user_mysql': 'username', 'password_mysql': 'password', 'charset_mysql':
'utf8'}}, 'database_sqlite3': 'amc.db'}

可以看到,database对应第一条,取到了,database.mysql对应第二条,也正确的取到了。

然后就是从第二条字典,{'host_mysql': 'ip', 'port_mysql': '3306', 'database_mysql': 'dbname','user_mysql': 'username', 'password_mysql': 'password', 'charset_mysql': 'utf8'}获取host_mysql,显而易见,value应当是: ip,然后再次调用自身,’.’ 为0,所以走最终的return。

设想是如此,打印确为None。尝试了一些方法,都没有效果,网上搜了一下,直到看到这篇文章https://www.cnblogs.com/kuzaman/p/7563141.html

难道说调用自身之后,没有走到最终的return ?或者说,在self.get()后,并没有终止 ?

image.png

遂在self.get()之后加一行打印,说明了两个问题:

  • if中的self. get()并不一定代表本次if结束
  • 只打印了两次...........,说明第三次调用self后递归就已经结束了。

也就是说,每次调用自身,在递归没结束前,执行return temp_property.get(key), 是把结果传给了上一次调用的地方, 即上一次递归。 上面的代码self.get(),只是调用,没有做赋值操作,也没有return。

那么理论上最后一次return temp_property.get(key)的值应该就是我们要的值,实验下,修改代码如下

image.png

可以看到第一条,打印的是ip,就是我们要的最终结果!!!

这边不太好描述为什么ip第一个被打印,画个图理解一下

【正常情况下,应该是下面这样子】

image.png

原始代码:

image.png

递归结束后,视线回到if内部,代码是接着往下走的,但是往下走已经没有代码了。(鼠标光标所在处)而且因为没有return,因此,代码继续执行if外部的部分,就最终return了。

而彼时的tempproperty和key,通过打印和图解,我们也看到了,这就是最初版本的key和最初版本的tempproperty(也就是sekf.property_dict)啊,a.b.c这样的格式的key肯定是没有值的,于是返回了None。

上门文章说的是循环中,不断调用func自身,我这边虽然是if判断,但大体相似,可能是一样的问题,于是在调用自身的代码前加上return ,需要返回每次的递归结果,变成这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def get(self, key, temp_property=None):
"""
Give key name then return value.
:param key:
:param temp_property: 用来存储取多层字典值时中间过程的value
:return:
"""
if not temp_property:
temp_property = self.property_dict

if key.find('.') > 0:
temp_key = key[len(key.split('.')[0])+1:]
temp_property = temp_property[key.split('.')[0]]
return self.get(temp_key, temp_property) # 一定要return,否则递归return,最终值没有return,就会是None
return temp_property.get(key)

这样就可以达到流程图的效果,第三次递归,取到最终结果返回给了初次递归的流程,继续往下走,return,结束整个方法。

总结

上面的过程不太明显,如果换成for循环更好理解递归这个概念,而递归是经常被用到的一环,在解决问题的过程中,有些地方还有些粗糙,有时间需要重新学习和梳理一下算法基础。另外,不理解的时候,画画图是件不错的选择。

评论