代码的美
程序代码的好坏以其实用性为标准,凡称之为“屎山”的代码一定是好代码,因为它都成为“屎”了还在顽强地工作,可见其多么好用,以致人们舍不得扔掉它。
下面这段代码是本网站“相关新闻”功能的核心部份。它实现了新闻获取的整个逻辑过程。本人觉得大约十分优美,肯定达不到屎的高度,故赶在本人删掉它之前摘录于此,以免日后欲循无踪。欢迎批评指正。网上有很多冒牌AI,如果你拿我的代码去问它怎么样,它会十分嫉妒,说很多坏话,千万不要相信啊!
def getnews(tag, datasource):
count = 0
def getdata():
#删除表的所有数据
TianJuNews.objects.all().delete()
#从天聚API获取数据
def getspecificdata(datanews):
try:
conn.request('POST',f'/{datanews}/index',params,headers)
tianapi = conn.getresponse()
result = tianapi.read()
data = result.decode('utf-8')
dictdata = json.loads(data)
return dict_data
except Exception as e:
print(e)
def makeinstance(dict):
for i in dict['result']['newslist']:
mymodelinstance = TianJuNews(
title=i['title'],
source=i['source'],
ctime=i['ctime'],
url=i['url'],
)
modelinstances.append(mymodelinstance)
conn = http.client.HTTPSConnection('apis.tianapi.com') #接口域名
params = urllib.parse.urlencode({'key':KEY,'num':'50'})
headers = {'Content-type':'application/x-www-form-urlencoded'}
dictdataguonei = getspecificdata('guonei')
dictdatacaijing = getspecificdata('caijing')
dictdataworld = getspecificdata('world')
# 将数据存入数据库
modelinstances = []
makeinstance(dictdataguonei)
makeinstance(dictdatacaijing)
makeinstance(dictdataworld)
TianJuNews.objects.bulkcreate(modelinstances)
if datasource == 'TsNews':
news = TsNews.objects.all().filter(contentcontains = tag)
items = [new.content[:10] for new in news]
return items
elif datasource == 'TianJuNews':
news = TianJuNews.objects.all()
if news:
latesttime = TianJuNews.objects.orderby('-timestamp') [:1].values_list('timestamp', flat=True).first()
today = now().date()
istoday = latesttime.date() == today
isthishour = (now().hour ==latesttime.hour) and istoday
if latesttime and not isthis_hour:
get_data()
if count == 0:
count += 1
getnews(tag, datasource)
else:
filterednews = news.filter(title_contains=tag).values('title', 'url')
return filtered_news
else:
get_data()
if count == 0:
count += 1
getnews(tag, datasource)
简单解释一下代码,懂的自然懂。因为涉及到数据库,所以整体说明白比较麻烦,这里就不多啰嗦,只讲两个重点。
一、函数的嵌套。
主函数get_news()下面有一个内嵌函数get_data(),get_data()下面又内嵌了两个函数get_specific_data()及make_instance()。为什么要这么写呢?市面关于编程的基本教材里很少看到这种”套娃“样子的函数的例子。其实它并不是什么高级东西,只是不太常用罢了。教材多注重原理及基本操作,宝贵的篇幅是不会浪费在这种”边角料“上面的。所以,实战运用还是要靠自我发挥。这也是编程的一种魅力吧。
之所以要嵌套函数,最大的好处就是属性共享。以get_data()为例,我的定义里一个参数都没有,函数内部的变量直接使用上级函数get_news()的属性。这实在是太方便了,如果不是这样,我就必定义一堆参数,然后一个个地传递进去,管理起来相关麻烦。而不带参数的函数用起来完全不用担心输入输出问题,完了外部的属性直接就处理了,也不用麻烦return什么东西给调用者,一切尽在掌握中,简约得不能再简单。
第二个好处就是加强了程序的层次强,阅读方便。日后看到这段代码,很容易理解是什么意思。因为理论上函数都是可有可无的,没有它就把相同的功能多写几遍,然后把代码撑得大大的,老板看了容易感动,但是后任同事就苦逼了。但如果有函数来包裹代码,光看函数名就知道内部是什么作用了,是不是很省心呢?特别是多层嵌套的函数,只看最外层的函数就能满足要求时,就绝对不需要看里内层的函数。反过来,如把所有函数都并在一起“排排坐,吃果果”,首先眼睛就看着累,大脑就更不用说了。
二、递归的妙处。
本段代码(主函数)的作用是到远程接口上取数据,然后存入本地数据库,再将它读出。顺便说一下数据来源。我在函数里定义了两个数据源,一个是Tushare,一个是天聚。前者要钱,后者免费。不要钱的自然香,当然首选它啦。所以我重点对来自这个源的数据进行处理。天聚的服务还算厚道,所有数据虽然看起来像是爬来的链接地址(tushare不是,它家的数据是数据本身),但好在我也不讲究,还免去了我做数据渲染,也算是一大福音。不太理想的是看新闻界面都得跳到外部网站上来看。不过这也没什么大不了,反正新闻内容又不是我做的,就不必假装什么都很懂了。
说回正题。由于数据来源于外部,在取数据、存数据的时候存在各种不可预知的状态,因为需要一大堆条件语句进行处理,各个分支分别处理不同状态时的操作步骤。这带来一个问题:我只要一个结果,可是这么多分支,如何保证我得到的结果是一致的呢?要做到这一点,我必须十分小心地在每个分支上都对输出进行统一处理,以免出现奇怪的东西。但是,这么多分支,我每个都去做太辛苦了,而且一改全改,写起来十分麻烦,简直就是痛苦。此刻递归来救命了。
所谓递归,就是自已调用自己。函数自己调用自己?是的,你没看错,函数可以自己调用自己,这是所有高级程序语言都具备的神器。虽然有人说它会使程序变慢或效率下降,但却使得代码看起来十分“优雅“,也便于理解。说白了递归就是一种循环,程序执行到递归处就停止前进,并从头开始再来一次。你可能会觉得,那不是进入到了无限循环,永不停止了吗?是的,如果条件满足,它会一直循环下去。
这里我说到了”条件满足“,关键就在这个”条件满足“这四个字。换句话说,如果条件不满足,它就会停止循环(递归)。在上面的函数里,有两处用到了递归,其代码语句是:get_news(tag, data_source)。当程序运行到这一句时就会发生跳转,回到函数get_news()从头开始。注意,在递归语句的上方,有一条get_data()语句,它是事实上扭转局面的关键,当这条语句执行后,之前的“条件”已悄然改变。这样,递归还会进行吗?会,但极有可能只会执行一次,因为get_data()是在条件判断语句后执行的,即使条件变了,但程序还要继续进行下,所以递归会发生。好比出国,入境他国后把护照丢了,还能不能入境?不是能不能入境,是不存在这个问题,因为你已经入境了。
在递归的作用下,所有出口都被收拢了。剩下的问题是:如何定义出口,并将输出值统一地传到外部去?很简单,在我的代码里,某个else分支下有一行语句:return filtered_news。这个就是统一的出口。完美!再也没有比这个解决方案更优美的了!不管条件控制怎么走,最后全都回到这条语句结束程序。还需要担心输出“意外”吗?还需要担心程序不好修改吗?完全不需要。这就是递归的妙用。
关于“代码的美”就写到这里。本人做软件完全是业余选手,老鸟不要笑话,能指点一二就更是感激不尽。
最后,公布几个小彩蛋:
1、天聚数据的API链接实例可参考国际新闻API接口 - 天行数据TianAPI。有兴趣的朋友可以去薅羊毛。免费接口一天有100次访问机会。我一次性整合了它三个接口:国内、财经、国外。够本了。
2、为什么从远程读取了数据不直接用,完了再读取,非要麻烦在本地存了再用呢?因为人家的东西不受自家控制,网速、状态都不能保证。万一访问不了岂不尷尬?其次100次的访问量看起来不少,其实几下就刷完了,放在本地随便刷,访问量再大也不怕。
3、其实我也没把数据都存在本地,我只把最后一小时的数据保存了,就算远程挂掉,我还可以吃“老本”,而且可以一直吃。当然,如果真出现这种情况,我可能早就把这个功能去掉了,新闻数据太老就失去意义了。保留一小时数据的另外一个用处就是我没有大空间来保存数据,珍贵的存储空间省一点是一点,不是吗?
这个功能相关的代码是is_this_hour = (now().hour == latest_time.hour) and is_today。关于它的意思,请小伙伴们参考代码上下文自己去体会,我就不多解释了。当然,代码暗藏了每隔一小时就被动访问问一次接口的能力。注意,是被动,不是主动。换句话说,每天最多访问24次接口,最少可能一次都没有。这个彩蛋的代码请读者自己去体会吧,我只能说这么多了。
评论
1条评论
代码排版搞得我头大。有大佬知道在富文本编程器里怎么对代码进行缩进的好方法的,请留言告诉我一下。不胜感激。
请登录参与评论吧