闭包
python中最强大的工具——装饰器,咱们小白必须学学咋用
正好咱们来写个入门级的装饰器——日志,用到上一个爬12306车票的小爬虫上
先简单介绍一下装饰器吧,简单地说:他们是修改其他函数的功能的函数。他们有助于让我们的代码更简短,也更Python范儿。其实装饰器也是闭包,之前有一次面试被问到闭包是什么,结果一脸懵,唉,只能怪自己当初也没准备好就去送菜。
回归正题,咱们先写一个闭包,闭包也就是函数中定义一个内函数,我们称包含内函数的函数为外函数,这个内函数调用了外函数的变量,并且外函数返回内函数。听起来可能晦涩难懂,那咱就跑个闭包看一看:
def A(x):
def B():
print(x)
return B
demo=A(7)
demo()
A就是外函数了,B是内函数,大家都知道一个函数运行结束后会清除所有变量来腾出内存,但这个闭包的外函数A在结束时会检测到内函数B调用了变量x,A便会在结束前把变量x的值绑定给内函数b。可以自己多写几个闭包试试,有助于后面写装饰器。
对于demo=A(7)后怎么可以直接demo(),小白会疑惑demo怎么变成函数了,在python中万物皆对象,函数也是对象,执行demo=A(7),函数A返回了B函数的名称,那么demo此时就等同于B,即demo()可以看作为B(),要是还不懂可以自己多搜索一下相关内容。
装饰器
现在开始写装饰器。上代码
def logit(func):
def with_logging(*args,**kwargs):
print(func.__name__+" was called")
return func(*args,**kwargs)
return with_logging
def addition(x):
return x+x
addition = logit(addition)
addition(2)
仔细一看就知道,logit()是一个日志装饰器的雏形,args和kwargs是万能参数,因为一个装饰器通常会装饰很多不同的函数或方法,而这些函数或方法会有不同数量的参数,所以直接使用万能参数,args会把参数转为tuple,kwargs会把带键值的参数转为dict,具体内容这里就不说了。运行结果如下
现在可以知道装饰器就是在被装饰的函数或方法被调用时之前运行,然后被调用的函数或方法正常运行。现在很容易可以联想到装饰器的两个用途,日志和授权认证。
刚刚调用装饰器的方式是addition = logit(addition),其实还可以直接通过在被装饰函数定义的上一行使用@装饰器来进行装饰,如下
def logit(func):
def with_logging(*args,**kwargs):
print(func.__name__+" was called")
return func(*args,**kwargs)
return with_logging
@logit
def addition(x):
return x+x
addition(2)
运行结果:
现在装饰器还有一个小问题,如下图所示
这并不是我们想要的!Ouput输出应该是"addition"。这里的函数被with_logging替代了。它重写了我们函数的名字和注释文档(docstring)。幸运的是Python提供给我们一个简单的函数来解决这个问题,那就是functools.wraps。我们修改上一个例子来使用functools.wraps:
现在好多了。我们直接把日志装饰器用到上一个12306车票小爬虫上吧
实际运用
如果不清楚这个小爬虫怎么写,可以查看我的上一篇文章(虽然写的很一般/(ㄒoㄒ)/~~)
下面是加入日志装饰器后的小爬虫,顺便优化了一下解析函数,不然解析函数会被调用很多次,然后日志里全是解析函数被调用的记录。。。。。
stations.py
import re
import requests
url = 'https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9161'
requests.packages.urllib3.disable_warnings()
response = requests.get(url, verify=False)
stations = re.findall(u'([\u4e00-\u9fa5]+)\|([A-Z]+)', response.text)
stations=dict(stations)
tickets.py
from stations import stations
from prettytable import PrettyTable
from selenium import webdriver
from functools import wraps
import datetime
import requests
import json
import re
import time
header = ["车次","出发站","到达站","出发时间","到达时间","历时","商务座","一等座","二等座","高级软卧","软卧","动卧","硬卧","软座","硬座","无座"]
class down_tickets(object):
def loggit(func):
@wraps(func)
def notes(*args):
now=datetime.datetime.now().strftime('%Y-%m-%d,%H:%M:%S')
sResult = now+" 方法 "+func.__name__+" 被调用\n"
with open("C:\\Users\\wzx12\\Desktop\\reptitle\\log.txt", "a", encoding='utf-8') as f:
f.write(str(sResult))
f.close()
return func(*args)
return notes
@loggit
def get_tickets(self,froms,tos,date):
froms = stations[froms]
tos = stations[tos]
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument('--headless')
browser = webdriver.Chrome(chrome_options=chrome_options)
print("请稍等,此处可能需要花费几秒钟.....")
browser.get('https://www.12306.cn/index/index.html')
time.sleep(3)
Cookie = browser.get_cookies()
strr = ''
for c in Cookie:
strr += c['name']
strr += '='
strr += c['value']
strr += ';'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36',
'Cookie':strr
}
print(headers)
browser.quit()
request_url = 'https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date={}&leftTicketDTO.from_station={}&leftTicketDTO.to_station={}&purpose_codes=ADULT'.format(date,froms,tos)
response = json.loads(requests.get(request_url,headers=headers).text)
result = response['data']['result']
new_list = []
for item in result:
if not '列车停运' in item:
new_list.append(item)
else:
pass
return new_list
@loggit
def get_info(self):
fw = input("请输入出发地>>>")
tw = input("请输入目的地>>>")
st = input("请输入出发时间;格式:(年-月-日)(默认为今日日期)>>>>")
if st == '':
st = datetime.date.today()
return fw,tw,st
else:
today = datetime.date.today()
date = str(today).split('-')
list = st.split('-')
if int(list[0]) < int(date[0]) or int(list[0]) > int(date[0]):
exit("输入的年份不在我的查询范围之内")
else:
if int(list[1]) < int(date[1]) or int(list[1]) > int(date[1])+1:
exit("你输入的月份不在我的查询范围之内")
else:
if int(list[2]) < int(date[2]):
exit("你输入的日期不在我的查询范围之内")
else:
if int(list[1]) < 10 and int(list[1][0]) != 0:
list[1] = '0' + list[1]
if int(list[2]) < 10 and int(list[2][0]) != 0:
list[2] = '0' + list[2]
return fw,tw,list[0] + '-' + list[1] + '-' + list[2]
@loggit
def decrypt(self,string,fu):
new_dict = {v: k for k, v in stations.items()}
for item in string:
item = ''.join(item)
reg = re.compile('.*?\|预订\|.*?\|(.*?)\|.*?\|.*?\|(.*?)\|(.*?)\|(.*?)\|(.*?)\|(.*?)\|.*?\|.*?\|.*?\|.*?\|.*?\|.*?\|.*?\|.*?\|.*?\|.*?\|(.*?)\|(.*?)\|(.*?)\|(.*?)\|(.*?)\|(.*?)\|(.*?)\|(.*?)\|(.*?)\|(.*?)\|(.*?)\|(.*?)\|.*?\|.*?\|.*?\|.*')
result = list(re.findall(reg,item)[0])
result[1] = new_dict[result[1]]
result[2] = new_dict[result[2]]
results = [result[0],result[1],result[2],result[3],result[4],result[5],result[-1],result[-2],result[-3],result[-12],result[-10],result[-6],result[-5],result[-8],result[-4],result[-7]]
fu.add_row(results)
return fu
if __name__ == '__main__':
pt = PrettyTable()
pt.field_names = header
t = down_tickets()
[fw,tw,st]=t.get_info()
trainlist=t.get_tickets(fw,tw,st)
pt = t.decrypt(trainlist,pt)
print(pt)
小白的文章,要是有啥缺陷请各位大佬多指点指点。