程序代码的好坏以其实用性为标准,凡称之为“屎山”的代码一定是好代码,因为它都成为“屎”了还在顽强地工作,可见其多么好用,以致人们舍不得扔掉它。下面这段代码是本网站“相关新闻”功能的核心部份。它实...

简单解释一下代码,懂的自然懂。因为涉及到数据库,所以整体说明白比较麻烦,这里就不多啰嗦,只讲两个重点。一、函数的嵌套。主函数get_news()下面有一个内嵌函数get_data(),get_d...

多用脑防止痴呆

一本正经地胡说八道比一棒子打不出一个屁要好

看更多博客
编程

代码的美

登录后可点赞

程序代码的好坏以其实用性为标准,凡称之为“屎山”的代码一定是好代码,因为它都成为“屎”了还在顽强地工作,可见其多么好用,以致人们舍不得扔掉它。

下面这段代码是本网站“相关新闻”功能的核心部份。它实现了新闻获取的整个逻辑过程。本人觉得大约十分优美,肯定达不到屎的高度,故赶在本人删掉它之前摘录于此,以免日后欲循无踪。欢迎批评指正。网上有很多冒牌AI,如果你拿我的代码去问它怎么样,它会十分嫉妒,说很多坏话,千万不要相信啊!

def getnews(tag, datasource):
 count = 0
 def getdata():
  #删除表的所有数据
  TianJuNews.objects.all().delete()
  #从天聚API获取数据
  def get
specificdata(datanews):
   try:
    conn.request('POST',f'/{datanews}/index',params,headers)
    tianapi = conn.getresponse()
    result = tianapi.read()
    data = result.decode('utf-8')
    dict
data = json.loads(data)
    return dict_data
   except Exception as e:
    print(e)

  def makeinstance(dict):
   for i in dict['result']['newslist']:
    my
modelinstance = TianJuNews(
     title=i['title'],
     source=i['source'],
     ctime=i['ctime'],
     url=i['url'],
    )
    model
instances.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 = []
  make
instance(dictdataguonei)
  makeinstance(dictdatacaijing)
  make
instance(dictdataworld)

  TianJuNews.objects.bulkcreate(modelinstances)

 if datasource == 'TsNews':
  news = TsNews.objects.all().filter(content
contains = tag)
  items = [new.content[:10] for new in news]
  return items
 elif data
source == '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条评论


登录参与评论吧