超简单 Python 头像戴圣诞帽教程

还记得疫情之初,我们用Python给头像戴口罩的文章吗?

当时只是简单的调用了第三方模块将口罩图片替换入原图,内容非常简短。

今天,我们将提供一个能够通过以下三种方式给头像戴上圣诞帽的Python教程:

  • 1.实时打开摄像头读取头像图佩戴圣诞帽
  • 2.从本地读取一幅头像图佩戴圣诞帽
  • 3.从文件夹中批量读取头像图佩戴圣诞帽

1.准备

开始之前,你要确保Python和pip已经成功安装在电脑上,如果没有,请访问这篇文章:超详细Python安装指南 进行安装。如果你用Python的目的是数据分析,可以直接安装Anaconda:Python数据分析与挖掘好帮手—Anaconda,它内置了Python和pip.

此外,推荐大家用VSCode编辑器,因为它可以在编辑器下方的终端运行命令安装依赖模块:Python 编程的最好搭档—VSCode 详细指南。

Windows环境下打开Cmd(开始—运行—CMD),苹果系统环境下请打开Terminal(command+空格输入Terminal),输入命令安装依赖:

pip install https://pypi.python.org/packages/da/06/bd3e241c4eb0a662914b3b4875fc52dd176a9db0d4a2c915ac2ad8800e9e/dlib-19.7.0-cp36-cp36m-win_amd64.whl#md5=b7330a5b2d46420343fbed5df69e6a3f
pip install opencv-python

有两个依赖,一个是 dlib模块,需要通过whl文件安装。一个是常用的opencv模块,直接pip安装即可。

文章源代码fork自:amusi/Merry_Christmas_Hat

本文全部源代码及图片可在此下载:
https://github.com/Ckend/Merry_Christmas_Hat

如果你访问不了Github,可在Python实用宝典公众号后台回复:圣诞帽 下载。

2.给头像戴上圣诞帽

为防大家没耐心看下去,我把佩戴圣诞帽的教程提前了。

下载源代码后,进入源代码的文件夹,一共有三种方式给你的头像佩戴圣诞帽。

1.打开摄像头读取头像图:

当摄像头打开后,会实时出现佩戴着圣诞帽的你。按q保存你最喜欢的图片即可。

def method_one(hat_img):
    """
    方式1: 打开摄像头读取头像图
    """
    cap = cv2.VideoCapture(0)
    if not cap.isOpened():
        print('摄像头打开失败!')
    else:
        print('摄像头打开成功!')
        print("请按下键盘上的'q',保存当前满意图像!")
        while cap.isOpened():
            _, img = cap.read()
            cv2.imshow("img", img)
            k = cv2.waitKey(33) & 0xFF
            if(k == ord('q')):
                cv2.imwrite("sefile.jpg", img)
                face_flag, output = add_hat(img, hat_img)
                if(-1 == face_flag):
                    break
                cv2.imshow("output", output)
                print("请按下键盘上的任意按键,退出当前程序!")
                cv2.waitKey(0)
                cv2.imwrite("output.jpg", output)
                break

# 读取帽子图,第二个参数-1表示读取为rgba通道,否则为rgb通道
hat_img = cv2.imread("hat.png", -1)

# 选择你需要的方式
method_one(hat_img)

cv2.destroyAllWindows()

这样,通过运行Merry_Chirstmas_Hat.py文件就能打开摄像头并实时显示佩戴圣诞帽的你:

python Merry_Chirstmas_Hat.py

2.读取一个图像佩戴圣诞帽

这是最常见的使用场景,你只需要的源代码文件夹下,放置你所需要佩戴圣诞帽的图像,命名为test.jpg(或其他,只需要改函数调用里你的指定文件名称)。

放置完成后运行Merry_Chirstmas_Hat.py文件即可佩戴圣诞帽。

def method_two(hat_img, filename):
    """
    方式2: 从本地读取一幅头像图
    """
    img = cv2.imread(filename)
    success, output = add_hat(img, hat_img)
    if not success:
        print("戴失败了!")
        return
    # 展示效果
    cv2.imshow("output", output)
    cv2.waitKey(0)
    cv2.imwrite("output.jpg", output)

# 读取帽子图,第二个参数-1表示读取为rgba通道,否则为rgb通道
hat_img = cv2.imread("hat.png", -1)

# 选择你需要的方式
method_two(hat_img, "test.jpg")

cv2.destroyAllWindows()

3. 从文件夹中读取多张头像图(批量处理)

在源代码的文件夹下,创建一个名为images的文件夹,在里面放置所有以.jpg结尾的图像,运行Merry_Chirstmas_Hat.py文件后你能看到所有这些图像佩戴圣诞帽的效果。

def method_three(hat_img):
    """
    方式3: 从文件夹中读取多张头像图(批量处理)
    """
    import glob as gb

    img_path = gb.glob("./images/*.jpg")

    for path in img_path:
        img = cv2.imread(path)

        # 添加帽子
        success, output = add_hat(img, hat_img)
        if not success:
            print("戴失败了!")
            continue

        # 展示效果
        cv2.imshow("output", output)
        cv2.waitKey(0)
        
# 读取帽子图,第二个参数-1表示读取为rgba通道,否则为rgb通道
hat_img = cv2.imread("hat.png", -1)

# 选择你需要的方式
method_three(hat_img)

cv2.destroyAllWindows()

我们的代码默认使用了第二种方式佩戴圣诞帽,你可以修改代码178行的method选择你所需要的方式。佩戴效果如下:

原图:

佩戴效果图:

3.原理分析

在上面的三种佩戴圣诞帽方法中,都调用了一个叫 add_hat 的函数。

顾名思义,这个函数里对原图像做了佩戴圣诞帽的逻辑,其步骤如下:

  • 1. 正脸识别(才好佩戴帽子到头部)。
  • 2. 遍历所有人脸,取5个关键点,并根据人脸大小调整帽子大小。
  • 3. 利用alpha通道的图像提取放帽子的区域(提取空心区域)。
  • 4. 将原帽子覆盖至第3步提取出来的空心区域上并放回原图。

下面分步骤阐述:

1.正脸识别

利用了dlib中已经训练好的人脸关键点检测模型对图像进行提取人脸的操作:

# dlib人脸关键点检测器(需要确保.py文件同级目录下有shape_predictor_5_face_landmarks.dat这个文件)
predictor_path = "shape_predictor_5_face_landmarks.dat"
predictor = dlib.shape_predictor(predictor_path)

# dlib正脸检测器
detector = dlib.get_frontal_face_detector()

# 正脸检测
dets = detector(img, 1)

检测到的人脸数据将会保存到dets变量中,因此dets的长度将大于0.

2.遍历人脸,取关键点并调整帽子大小

获取保存的人脸数据的各种参数,并根据这些参数对帽子进行比例转化:

# 如果检测到人脸
if len(dets) > 0:
    # 遍历所有人脸
    for d in dets:
        x, y, w, h = d.left(), d.top(), d.right()-d.left(), d.bottom()-d.top()
        
        # 关键点检测,5个关键点
        shape = predictor(img, d)
        
        # 选取左(0)右(2)眼眼角的点
        point1 = shape.part(0)
        point2 = shape.part(2)
        
        # 求两点中心
        eyes_center = ((point1.x+point2.x)//2, (point1.y+point2.y)//2)
        
        # 根据人脸大小调整帽子大小
        factor = 1.5    # 比例因子
        resized_hat_h = int(
            round(rgb_hat.shape[0]*w/rgb_hat.shape[1]*factor))
        resized_hat_w = int(
            round(rgb_hat.shape[1]*w/rgb_hat.shape[1]*factor))
        
        # 避免帽子高度超出图像画面
        if resized_hat_h > y:
            resized_hat_h = y-1
            
        # 根据人脸大小调整帽子大小
        resized_hat = cv2.resize(rgb_hat, (resized_hat_w, resized_hat_h))

3. 利用alpha通道的图像提取放帽子的区域(提取空心区域)。

通过alpha通道生成的黑白图像作为mask,将其填充到原图中。生成了bg.jpg.

# 用alpha通道作为mask(bitwise_not)
mask = cv2.resize(a, (resized_hat_w, resized_hat_h))
mask_inv = cv2.bitwise_not(mask)

# 帽子相对与人脸框上线的偏移量
dh = 0
dw = 0
# 原图ROI
bg_roi = img[y+dh-resized_hat_h:y+dh,
             (eyes_center[0]-resized_hat_w//3):(eyes_center[0]+resized_hat_w//3*2)]

# 原图ROI中提取放帽子的区域
bg_roi = bg_roi.astype(float)
mask_inv = cv2.merge((mask_inv, mask_inv, mask_inv))
alpha = mask_inv.astype(float)/255

# 相乘之前保证两者大小一致(可能会由于四舍五入原因不一致)
alpha = cv2.resize(alpha, (bg_roi.shape[1], bg_roi.shape[0]))
bg = cv2.multiply(alpha, bg_roi)
bg = bg.astype('uint8')
cv2.imwrite("bg.jpg", bg)

如果你打开bg.jpg,你就会发现这是一个佩带黑色模板的原图帽子区域:

4.将原帽子覆盖至第3步提取出来的空心区域上并放回原图。

接下来要做的,就是将帽子替换到第3部生成的空心区域上并放回原图:

# 提取帽子区域
hat = cv2.bitwise_and(resized_hat, resized_hat, mask=mask)
cv2.imwrite("hat.jpg", hat)

# 相加之前保证两者大小一致(可能会由于四舍五入原因不一致)
hat = cv2.resize(hat, (bg_roi.shape[1], bg_roi.shape[0]))

# 两个ROI区域相加
add_hat = cv2.add(bg, hat)

# 把添加好帽子的区域放回原图
img[y+dh-resized_hat_h:y+dh, (eyes_center[0]-resized_hat_w//3):(
    eyes_center[0]+resized_hat_w//3*2)] = add_hat
return 1, img

这样,便完成了整个佩戴圣诞帽的流程。文章完整源代码可在Python实用宝典公众号后台回复:圣诞帽 下载。

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

Python 利用股市找到那些存在问题的上市企业

当某个股票保持下跌的时候,你可以肯定该股票一定有什么地方不对,要么是它的市场,要么是它的公司。——利弗莫尔

因此结合自己最近的研究,提出了这个比较新颖的想法:利用股票市场找到已有或潜在食品安全问题的企业。

为什么会有这个想法呢?我们知道食品安全事故发生的时间点和媒体报道的时间点之间实际上是有一个间隔的,然而相关利益人士、内部人士却能提前知道事件的发生。

比如说,2012年11月19日,酒鬼酒被国家质检总局爆出塑化剂超标247%,但是在19号之前,酒鬼酒的收盘价却神奇地从2012年11月2日的55元回落到2012年11月16号的46元。然而国家质检总局的消息是在2012年11月19号才发布的。

而且这样的下跌,明显违背该股票当时上涨的趋势,在10月底时,它的股价已突破周K的压力线,却很不自然地下跌了?而且还违背了许多技术指标,如1号、14号、16号,KDJ和BOLL指标明显提示上涨,在股市这种情绪化的市场中,有人却不为所动,仍然售出大量股票导致其不正常下跌。

2012年11月19号消息公布时紧急停牌,复牌后有三个跌停,股价暴跌48%,但我相信某些人已经成功躲开了这场股灾。

找到已有或潜在食品安全问题的企业的重点在于两个方面:

1.该股票在该板块或者该股强走势的情况下却发生连续多日的下跌

2.不正常的跌停板

如果我们在消息公布/(不公布)前能提前捕捉到这个异常信息,我们就能提前捕捉到某个食品的安全问题,而且也能规避投资风险。当然,出现这种异常的可能性非常多,这种异常只能作为参考。

下面让我们尝试用Python来找到第一种趋势的企业,当然我最后不会公布结果,大家感兴趣可以自己试试:

首先利用tushare找到和食品安全相关的上市企业:

import tushare as ts
def food_codes():
    data = ts.get_industry_classified()
    print data[data.c_name.isin(['食品行业','农药化肥','酿酒行业'])]

得到结果:

均线是我们获得该股票趋势的基础,下面我们编写均线函数:

def get_ma(code,start='',end=datetime.date.today().strftime("%Y-%m-%d")):
    data = ts.get_k_data(code)
    data = data.sort_index(ascending=False)
    data['ma2'] = data['close'].rolling(2).mean().shift(-1)
    data['ma5'] = data['close'].rolling(5).mean().shift(-4)
    data['ma10'] = data['close'].rolling(10).mean().shift(-9)
    data['ma20'] = data['close'].rolling(20).mean().shift(-19)
    data['ma60'] = data['close'].rolling(60).mean().shift(-59)
    data['ma240'] = data['close'].rolling(240).mean().shift(-239)
    data['date'] = pd.to_datetime(data['date'])
    if start == '':
        return data
    start = pd.to_datetime(start)
    end = pd.to_datetime(end)
    if data['date'][len(data) - 1] < start:
        return 0
    while data.loc[data.date == start].empty:
        start = start + dateutil.relativedelta.relativedelta(days=1)
    while data.loc[data.date == end].empty:
        end = end - dateutil.relativedelta.relativedelta(days=1)
    return data.loc[(data.date >= start) & (data.date <= end)]

我们只需要确定两点:

1. 以20个交易日为窗口期,其内最高价和最低价的差距大于其最高价的15%。

2. 该股票处于涨势。

def analyzeOne(code):
    rng = pd.date_range('2018-1-1', datetime.date.today().strftime("%Y-%m-%d"), freq='D')
    # 获得日期
    flag = 0
    for i in range(20,len(rng)):
        data = get_ma(code,rng[i-20],rng[i])
        count = 0
        data = data.sort_index(ascending=True)
        # 升序
        max = data['close'][data['close'].argmax()]
        min = data['close'][data['close'].argmin()]
        for j in range(len(data)):
            if data.iloc[j]['ma5'] >= data.iloc[j]['ma20'] and data.iloc[j]['ma20'] >= data.iloc[j]['ma60'] and \
                    data.iloc[j]['ma60'] >= data.iloc[j]['ma240']:
                count = count + 1
                if count >= 5 and (max-min) - max*0.15 > 0:
                    print 'Code: ' + str(code) +', Problem:' + str(rng[i-20])+ ' ' + str(rng[i])
            else:
                count = 0

如果存在这样的趋势,就输出这个趋势的窗口时间段,当然这只是一个粗略的模型,结果中可能会有很多意外的情况,但是我觉得足够启发大家了。

想要应用于所有食品相关股票:

def find_down():
    for i in food_codes().code:
        analyzeOne(i)

通过这样的操作,我找到了两支类似的股票:

但这些股票都没有新闻报道发生了问题,最后也让我怀疑自己的模型的准确性,到底是市场正常波动,还是这个企业存在问题。

因此,这个模型存在许多的优化空间,如何通过股市最准确地找出那些出问题的企业,还有待进一步研究,本实验只是提出一个初步的模型和一些想法。

个人愚见,欢迎讨论。

我们的文章到此就结束啦,如果你喜欢今天的Python 实战教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应红字验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

点击下方阅读原文可获得更好的阅读体验

Python实用宝典 (pythondict.com)
不只是一个宝典
欢迎关注公众号:Python实用宝典

Python 2015-2020年美国警察致命枪击案EDA分析

2014年在密苏里州一名叫做弗格森(Ferguson)的警察杀害了迈克尔·布朗(Michael Brown)后,美国黑人开始了一场抗议警察暴力对待黑人的运动—Black Lives Matter(黑人的命也是命,简称BLM)。

2020年,在明尼阿波利斯警察Derek Chauvin杀害乔治·弗洛伊德(George Floyd)之后,BLM运动再次成为头条新闻,引起国际社会的进一步关注。

自2015年1月1日起,《华盛顿邮报》一直在整理一个数据库,其中记录了值班警员在美国发生的每起致命枪击事件。这个数据库里包含了死者的种族,年龄和性别,该人是否有武器,以及受害人是否正在遭受精神健康危机。

此外,还有四个其他数据集。有关贫困率,高中毕业率,家庭收入中位数和种族人口统计数据的美国人口普查数据。

下面就让我们来使用这些数据集来进行数据分析。

1.准备

开始之前,你要确保Python和pip已经成功安装在电脑上,如果没有,请访问这篇文章:超详细Python安装指南 进行安装。如果你用Python的目的是数据分析,可以直接安装Anaconda:Python数据分析与挖掘好帮手—Anaconda,它内置了Python和pip.

此外,推荐大家用VSCode编辑器,因为它可以在编辑器下方的终端运行命令安装依赖模块:Python 编程的最好搭档—VSCode 详细指南。

本文具备流程性,建议使用 VSCode 的 Jupiter Notebook 扩展,新建一个名为 test.ipynb 的文件,跟着教程一步步走下去。

Windows环境下打开Cmd(开始—运行—CMD),苹果系统环境下请打开Terminal(command+空格输入Terminal),准备开始输入命令安装依赖。

所需依赖:

pip install numpy
pip install pandas
pip install plotly
pip install seaborn

本文译自:https://www.kaggle.com/edoardo10/fatal-police-shooting-eda-plotly-seaborn/data,如需数据请在公众号后台回复:警察枪击EDA

2.代码与分析

首先,引入我们分析所需要使用的模块:

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
from datetime import datetime
import plotly.express as px
import plotly.graph_objects as go
import warnings
import plotly.offline as pyo
pyo.init_notebook_mode()
warnings.filterwarnings('ignore')
pd.set_option('display.max_columns', 500)
sns.set_style('white')
%matplotlib inline

打开需要分析的数据集:

df = pd.read_csv('./PoliceKillingsUS.csv', encoding='cp1252')
df.head()

时间特征

从这6年的月度数据来看,我们可以看到,在2015年上半年、2018年初和2020年第一季度,我们达到了每月超过100起致命事故的高峰。从月度来看,这种现象不具备明显的季节性。

df['date'] = df['date'].apply(lambda x: pd.to_datetime(x))
df['date'].groupby(df.date.dt.to_period('M')).count().plot(kind='line')

看看警察枪击案的事故是否具有周末特征:

count = df['date'].apply(lambda x: 'Weekday' if x.dayofweek < 5 else 'Weekend').value_counts(normalize=True)
f, ax = plt.subplots(1,1)
sns.barplot(x=count.index, y=count.values, ax=ax, palette='twilight')

显然,我们没有证据表明周末会发生更多的案件。

不过,如果细化到星期里的每一天,我们会发现周中发生案件的概率较高:

count = df['date'].apply(lambda x: x.dayofweek).value_counts(normalize=True).sort_index()
count.index = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun']
f, ax = plt.subplots(1,1)
sns.barplot(x=count.index, y=count.values, ax=ax, palette='twilight')
ax.set_title('Cases (%) for each day of the week');

接下来看看以下4个特征的分布:

signs_of_mental_illness:是否精神不稳定
threat_level:威胁等级
body_camera:警察是否带了随身摄像头
manner_of_death:死亡方式
count_1 = df['signs_of_mental_illness'].value_counts(normalize=True)
count_2 = df['threat_level'].value_counts(normalize=True)
count_3 = df['body_camera'].value_counts(normalize=True)
count_4 = df['manner_of_death'].value_counts(normalize=True)
fig, axes = plt.subplots(2, 2, figsize=(8, 8), sharey=True)
sns.barplot(x=count_1.index, y=count_1.values, palette="rocket", ax=axes[0,0])
axes[0,0].set_title('Signs of mental illness (%)')
sns.barplot(x=count_2.index, y=count_2.values, palette="viridis", ax=axes[0,1])
axes[0,1].set_title('Threat level (%)')
sns.barplot(x=count_3.index, y=count_3.values, palette="nipy_spectral", ax=axes[1,0])
axes[1,0].set_title('Body camera (%)')
sns.barplot(x=count_4.index, y=count_4.values, palette="gist_heat", ax=axes[1,1])
axes[1,1].set_title('Manner of death (%)');

我们可以看到,只有20%的案例受害者有精神不稳定的迹象;

只有10%的警察有随身摄像头;

70%的情况被宣布为危险状况;

死亡方式似乎不是一个有趣的变量,因为大多数案件都是“枪毙”;

美国的警察是否具有种族主义倾向?

count = df.race.value_counts(normalize=True)
count.index = ['White', 'Black', 'Hispanic', 'Asian', 'Native American', 'Other']

f, ax = plt.subplots(1,1, figsize=(8,6))
sns.barplot(y=count.index, x=count.values, palette='Reds_r')
ax.set_title('Total cases for each race (%)');

从上图我们知道,大部分致命的枪击事件中,涉及最多的是白人,其次是黑人和西班牙裔。

但这个图表并没有考虑人种比例。参考2019年美国的种族比例,我们可以看到,美国黑人受害者的比例更高:

数据来源:https://data.census.gov/cedsci/table?q=Hispanic%20or%20Latino&tid=ACSDP1Y2019.DP05&hidePreview=false

share_race_usa_2019 = pd.Series([60.0, 12.4, 0.9, 5.6, 18.4, 2.7], index=['White','Black','Native American','Asian','Hispanic','Other'])

count_races = count / share_race_usa_2019
count_races = count_races.sort_values(ascending=False)
f, ax = plt.subplots(1,1, figsize=(8,6))
sns.barplot(y=count_races.index, x=count_races.values, palette='Greens_r')
ax.set_title('Total cases for each race on total USA race percentage rate');

受害者的年龄

sns.set_style('whitegrid')
fig, axes = plt.subplots(1, 1, figsize=(10, 8))
axes.xaxis.set_ticks(np.arange(0,100,10))

sns.kdeplot(df[df.race == 'N'].age, ax=axes, shade=True, color='#7FFFD4')
sns.kdeplot(df[df.race == 'O'].age, ax=axes, shade=True, color='#40E0D0')
sns.kdeplot(df[df.race == 'B'].age, ax=axes, shade=True, color='#00CED1')
sns.kdeplot(df[df.race == 'H'].age, ax=axes, shade=True, color='#6495ED')
sns.kdeplot(df[df.race == 'A'].age, ax=axes, shade=True, color='#4682B4')
sns.kdeplot(df[df.race == 'W'].age, ax=axes, shade=True, color='#008B8B')


legend = axes.legend_
legend.set_title("Race")
for t, l in zip(legend.texts,("Native", "Other", 'Black', 'Hispanic', 'Asian', 'White')):
    t.set_text(l)

由这些叠加的密度图可以看出:

对于亚裔和白人来说,大多数案件的受害者年龄都在30岁左右。

对于其他和印第安人来说,在大多数案件中,受害者大约28岁。

对于西班牙裔和黑人来说,大多数案件的受害者年龄都在25岁左右。

所以我们可以说,西班牙裔和黑人的年轻人,是被警察开枪射击的高危群体。

受害者性别比例

按常理,这种暴力事件的受害者一般都为男性,看看是不是这样:

fig = px.pie(values = df.gender.value_counts(normalize=True).values, names=df.gender.value_counts(normalize=True).index, title='Total cases gender (%)')
fig.update(layout=dict(title=dict(x=0.5),autosize=False, width=400, height=400))
fig.show()

果然如此,超过95%的受害者都为男性。

简单的EDA分析就是这些,作者还分享了许多深层次的分析,不过并没有将数据分享出来,这里就不展示了。

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

不限速真香!Python 转移文件至对象存储(COS)上

对象存储(Cloud Object Storage,COS)是一种存储海量文件的分布式存储服务,具有高扩展性、低成本、可靠安全等优点。

对象存储有什么用呢?比如说Python实用宝典网站的服务器位于腾讯云香港,大陆访问速度会比较慢,这时候我可以将图片等静态资源转移到对象存储上,以提高用户的访问速度。

实际上,这两天我就对Python实用宝典做了这样的调整,搭配上懒加载,效果极佳,用户访问并渲染图片的过程所耗费的时间大大减少,大家可以访问Python实用宝典网体验一下:

https://pythondict.com

那么对于非站长的普通用户有什么用?相比于其他什么百度网盘之类的产品,COS最重要的优势是:

  • 上传下载不限速,数据中心多地任选
  • 能实现文件分享功能,同样不限速
  • 能自动创建文件历史备份,方便回溯办公文件
  • 支持各平台用客户端管理文件,电脑端甚至还可直接将网盘挂载为一个虚拟磁盘
  • 价格按用量计费(存储量、下行流量),免费额度用完后,你可能一个月大约也才需要花费 6 元

像腾讯云这样的大云服务商,每个月都给用户提供了50G的免费容量:

非常好,下面大家可以跟着我一起尝试使用一下,或许你会爱上这个产品。

0.创建存储桶

进入腾讯云的对象存储页面:

https://console.cloud.tencent.com/cos5/bucket#

选择创建存储桶,可以随意命名你的数据桶,地域可以选择离你最近的。不过请注意访问权限这一项,如果你希望你的朋友也能下载数据桶内的文件,一定要选择公有读私有写:

1.安装

接下来说说怎么用Python将静态资源转移到腾讯云COS上。

Coscmd是腾讯云官方基于Python推出的上传工具,使用起来非常方便。

使用 pip 安装 coscmd

pip install coscmd

如果直接安装失败了,可以尝试源码安装

git clone https://github.com/tencentyun/coscmd.git
cd coscmd
python setup.py install

可以使用 coscmd -v 查看版本号

# coscmd -v
coscmd 1.8.6.16

2.配置

安装完成之后我们还需要一些参数配置,配置密钥、存储桶地域等。我们迁移数据时仅仅只需要配置一些简单的参数即可

coscmd config -a SecretID -s SecretKey -b BucketName-APPID -r region

SecretID 和 SecretKey 可以在控制台获取,如果没有的话直接创建一个:

BucketName-APPID 是存储桶名称,region 为存储桶所在地域,例如:

coscmd config -a AChT4ThiXAbpBDEFGhT4ThiXAbp**** -s WE54wreefvds3462refgwewe**** -b examplebucket-1250000000 -r ap-beijing

3.迁移

下面以迁移本地附件到 COS 为例,演示一下 COSCMD 的使用

COSCMD 可以上传文件也可以直接上传文件夹,命令为:

#上传文件
coscmd upload  

#上传文件夹
coscmd upload -r  

这样我们就可以使用如下命令将媒体库的文件上传到 COS 中

# home/ 指将媒体库里的文件存放到COS的home文件夹中
coscmd upload -r 你的媒体库文件夹地址 存放到COS的具体位置如home/

#同步上传,跳过 md5 相同的文件
coscmd upload -rs 你的媒体库文件夹地址 home/

#忽略 .mp3 和 .gif 的后缀文件
coscmd upload -rs 你的媒体库文件夹地址 home/ --ignore *.mp3,*.gif

我们需要将媒体库的图片上传到 COS 中,存放的路径则因使用而异。

如果是网站的话,比如WordPress 的媒体库存储路径建议使用 wp-content/uploads/,如果你是为了个人使用,可以上传到任意位置,建议使用:home/.

上传完成后如上图所示,你可以在文件列表中找到你刚上传的这些媒体文件。

当然,COS也支持手动上传文件:

如果你按照我的步骤创建了一个公有读私有写的数据桶,那么上传的这些资源也支持分享文件下载地址给别人:

上图中显示的对象地址,就可以拿来分享给你任何朋友下载,不限速!不限速!不限速!怎么样,是不是超香?

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

Python 制作按键触发Windows通知的脚本

​对于键盘没有背光灯的同学而言,切换大小写或控制Num键开关的时候没有提示,经常需要试探性地输入一些字符来判断开关是否打开,体验非常糟糕。

因此,有人就想到自制脚本这一招,一旦触发大小写切换或Num键切换就进行windows通知提示:

https://github.com/skate1512/Toggle_Keys_Notification

今天我们来试试这个脚本,此外,我们还可以基于这个项目,扩展成任意一个按键被触发或切换都进行 windows 通知的脚本:

1.准备

开始之前,你要确保Python和pip已经成功安装在电脑上,如果没有,请访问这篇文章:超详细Python安装指南 进行安装。如果你用Python的目的是数据分析,可以直接安装Anaconda:Python数据分析与挖掘好帮手—Anaconda,它内置了Python和pip.

此外,推荐大家用VSCode编辑器,因为它可以在编辑器下方的终端运行命令安装依赖模块:Python 编程的最好搭档—VSCode 详细指南。

Windows环境下打开Cmd(开始—运行—CMD),苹果系统环境下请打开Terminal(command+空格输入Terminal),输入命令安装依赖:

pip install win10toast

除此之外,我们需要下载作者的代码,如果你能联通GitHub,请前往以下地址下载:
https://github.com/skate1512/Toggle_Keys_Notification

如果不能联通GitHub,或者网络速度比较慢,请在Python实用宝典公众号后台回复:按键触发通知 下载本文完整源代码。

2.源码使用与解析

2.1 源码使用

作者的项目可以在 Toggle_Keys_Notification 项目内,运行 notify.py 启动监听:

python notify.py

启动后点击一下大小写切换键,触发通知则说明代码正常运转:

2.1 源码分析

该项目通过win32gui和win32con实现了弹出toast进行通知的功能,最核心的_show_toast代码位于 toast.py 中,下面是这个函数的部分代码剖析:

注册和创建 window :

        message_map = {WM_DESTROY: self.on_destroy, }
        # 注册Window
        self.wc = WNDCLASS()
        self.hinst = self.wc.hInstance = GetModuleHandle(None)
        self.wc.lpszClassName = str("PythonTaskbar") # 定义该窗口结构的名称
        self.wc.lpfnWndProc = message_map
        try:
            self.classAtom = RegisterClass(self.wc)
        except:
            pass 
        # Window格式
        style = WS_OVERLAPPED | WS_SYSMENU
        # 创建Window
        self.hwnd = CreateWindow(self.classAtom, "Taskbar", style,
                                 0, 0, CW_USEDEFAULT,
                                 CW_USEDEFAULT,
                                 0, 0, self.hinst, None)
        UpdateWindow(self.hwnd)

所使用到的win32模块解析如下。

GetModuleHandle: 获取一个应用程序或动态链接库的模块句柄。
WM_DESTROY: 是关闭程序。
RegisterClass: 将定义好的Window属性保存保存下来。
WS_OVERLAPPED: 重叠式窗口,该式样窗口 带有一个标题栏和边框。
WS_SYSMENU: 具有 SYSTEM 菜单栏的样式
CW_USEDEFAULT: 采用系统默认位置

CreateWindow这个函数具有非常多的参数,甚至有一个百度百科来详细解析每一个参数的具体作用,大家感兴趣可以移步:
https://baike.baidu.com/item/CreateWindow/5076220

了解win32这些模块名称的意义后,理解上述代码的逻辑便很轻松了。

图标加载及任务栏图标显示配置:

        # 图标
        if icon_path is not None:
            # 获取图标地址
            icon_path = path.realpath(icon_path)
        else:
            icon_path = resource_filename(Requirement.parse("win10toast"), "win10toast/data/python.ico")
        # 加载格式
        icon_flags = LR_LOADFROMFILE | LR_DEFAULTSIZE
        try:
            hicon = LoadImage(self.hinst, icon_path, IMAGE_ICON, 0, 0, icon_flags)
        except Exception as e:
            logging.error("Some trouble with the icon ({}): {}"
                          .format(icon_path, e))
            hicon = LoadIcon(0, IDI_APPLICATION)

        # 任务栏图标
        flags = NIF_ICON | NIF_MESSAGE | NIF_TIP
        nid = (self.hwnd, 0, flags, WM_USER + 20, hicon, "Tooltip")
        Shell_NotifyIcon(NIM_ADD, nid)
        Shell_NotifyIcon(NIM_MODIFY, (self.hwnd, 0, NIF_INFO, WM_USER + 20, hicon, "Balloon Tooltip", msg, 200, title, NIIF_ICON_MASK))
        
        # 等待一会后销毁
        sleep(duration)
        DestroyWindow(self.hwnd)
        UnregisterClass(self.wc.lpszClassName, None)

这部分控制了通知弹出框的展示和销毁。如果你希望通知弹出框久一点再消失,可以适当修改传入的 duration 变量值。

DestroyWindow后,通知弹出框便消失了,整个 show_toast 的过程结束。

其实非常简单,从 CreateWindow 到 DestroyWindow 处理弹出框的各种属性,然后注销窗体,完成整个弹出流程。

3.扩展触发通知

为了扩展监听的按键,并能监听按键触发,需要先了解 notify.py 是如何检测到按键变化的。

获取按键状态:

keyboard = ctypes.WinDLL("User32.dll")
VK_NUMLOCK = 0x90
VK_CAPITAL = 0x14
def get_capslock_state():
    """Returns the current Caps Lock State(On/Off)"""
    return "Caps Lock On" if keyboard.GetKeyState(VK_CAPITAL) else "Caps Lock Off"


def get_numlock_state():
    """Returns The current Num Lock State(On/Off)"""
    return "Num Lock On" if keyboard.GetKeyState(VK_NUMLOCK) else "Num Lock Off"

可以看到,获取按键状态是通过 keyboard.GetKeyState(XXXX) 实现的。

而这个XXXX是对应的按键的十六进制,比如VK_NUMLOCK是Num键,对应的16进制代码是0x90,VK_CAPITAL是大小写按键,对应的十六进制代码是0x14.

变量名是可以用户自定义的,比如大小写键有些人习惯称之为VK_CAPITAL,也有些人喜欢称之为VK_CAPITAL,都可以,只要其最终对应的变量值为十六进制的0x14即可。

完整的按键16进制清单如下:

常数名称十六进制值十进制值对应按键
VK_LBUTTON011鼠标的左键
VK_RBUTTON022鼠标的右键
VK-CANCEL033Ctrl+Break(通常不需要处理)
VK_MBUTTON044鼠标的中键(三按键鼠标)
VK_BACK088Backspace键
VK_TAB099Tab键
VK_CLEAR0C12Clear键(Num Lock关闭时的数字键盘5)
VK_RETURN0D13Enter键
VK_SHIFT1016Shift键
VK_CONTROL1117Ctrl键
VK_MENU1218Alt键
VK_PAUSE1319Pause键
VK_CAPITAL1420Caps Lock键
VK_ESCAPE1B27Ese键
VK_SPACE2032Spacebar键
VK_PRIOR2133Page Up键
VK_NEXT2234Page Domw键
VK_END2335End键
VK_HOME2436Home键
VK_LEFT2537LEFT ARROW 键(←)
VK_UP2638UP ARROW键(↑)
VK_RIGHT2739RIGHT ARROW键(→)
VK_DOWN2840DOWN ARROW键(↓)
VK_Select2941Select键
VK_PRINT2A42Print键
VK_EXECUTE2B43EXECUTE键
VK_SNAPSHOT2C44Print Screen键(抓屏)
VK_Insert2D45Ins键(Num Lock关闭时的数字键盘0)
VK_Delete2E46Del键(Num Lock关闭时的数字键盘.)
VK_HELP2F47Help键
VK_030480键
VK_131491键
VK_232502键
VK_333513键
VK_434524键
VK_535535键
VK_636546键
VK_737557键
VK_838568键
VK_939579键
VK_A4165A键
VK_B4266B键
VK_C4367C键
VK_D4468D键
VK_E4569E键
VK_F4670F键
VK_G4771G键
VK_H4872H键
VK_I4973I键
VK_J4A74J键
VK_K4B75K键
VK_L4C76L键
VK_M4D77M键
VK_N4E78N键
VK_O4F79O键
VK_P5080P键
VK_Q5181Q键
VK_R5282R键
VK_S5383S键
VK_T5484T键
VK_U5585U键
VK_V5686V键
VK_W5787W键
VK_X5888X键
VK_Y5989Y键
VK_Z5A90Z键
VK_NUMPAD06096数字键0键
VK_NUMPAD16197数字键1键
VK_NUMPAD26298数字键2键
VK_NUMPAD36299数字键3键
VK_NUMPAD464100数字键4键
VK_NUMPAD565101数字键5键
VK_NUMPAD666102数字键6键
VK_NUMPAD767103数字键7键
VK_NUMPAD868104数字键8键
VK_NUMPAD969105数字键9键
VK_MULTIPLY6A106数字键盘上的*键
VK_ADD6B107数字键盘上的+键
VK_SEPARATOR6C108Separator键
VK_SUBTRACT6D109数字键盘上的-键
VK_DECIMAL6E110数字键盘上的.键
VK_DIVIDE6F111数字键盘上的/键
VK_F170112F1键
VK_F271113F2键
VK_F372114F3键
VK_F473115F4键
VK_F574116F5键
VK_F675117F6键
VK_F776118F7键
VK_F877119F8键
VK_F978120F9键
VK_F1079121F10键
VK_F117A122F11键
VK_F127B123F12键
VK_NUMLOCK90144Num Lock 键
VK_SCROLL91145Scroll Lock键

再来看看监听逻辑:

caps_curr = get_capslock_state()
num_curr = get_numlock_state()

while True:
    caps_change = get_capslock_state()
    num_change = get_numlock_state()

    if caps_curr != caps_change:
        if caps_change == "Caps Lock On":
            pop_up("Caps Lock On", "CapsLock_On.ico")
        else:
            pop_up("Caps Lock Off", "CapsLock_Off.ico")
        caps_curr = caps_change
        time.sleep(0.1)

    if num_curr != num_change:
        if num_change == "Num Lock On":
            pop_up("Num Lock On", "NumLock_On.ico")
        else:
            pop_up("Num Lock Off", "NumLock_Off.ico")
        num_curr = num_change
    time.sleep(0.2)

在刚开始运行监听脚本时,先获取到按键的状态,在循环体中,不断地获得当前按键状态,如果发生了状态变化,则触发pop_up函数,弹出刚刚我们提到的show_toast 函数:

def pop_up(body, icon):
    """Generates Pop-up notification when state changes"""
    notification = ToastNotifier()
    notification.show_toast("Lock Key State", body, icon_path="assets\\"+icon, duration=1.5)

整套监听并通知的机制还是非常简单的,如果我们想要自定义一些按键,你只需要在开头添加对应的按键的十六进制编码。

比如我们想监听 ESC 按键被按下:VK_ESCAPE=0x1B,使用 keyboard 模块添加一个钩子函数,监听按键:

import keyboard as kb
def hook_esc(button):
    """Alert if ESC button is pressed"""
    esc_button = kb.KeyboardEvent('down', VK_ESCAPE, 'ESC')
    if button.event_type == 'down' and esc_button.name == button.name:
        pop_up("ESC Pressed", "CapsLock_On.ico")
        # 敲击后回填为None
        button.event_type = None

然后再在循环体内添加判断逻辑:

kb.hook(hook_esc)

效果如下:

当然,图标和标题还可以进一步优化:

def pop_up(body, icon, toast_title="Lock Key State"):
    """Generates Pop-up notification when state changes"""
    notification = ToastNotifier()
    notification.show_toast(toast_title, body, icon_path="assets\\"+icon, duration=1.5)

比如将Lock Key State这个标题用 toast_title 变量替代,默认为Lock Key State。这样在调用pop_up函数的时候就能自定义标题了,效果如下:

总而言之,能扩展的东西非常多,这只是一个学习的例子,如果大家感兴趣的话可以在 Python实用宝典 公众号后台回复 按键触发通知 下载完整源代码进行改造。

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

Python 预测银行信用卡客户是否流失

1.题目背景

越来越多的客户不再使用信用卡服务,银行的经理对此感到不安。如果有人能为他们预测哪些客户即将流失,他们将不胜感激,因为这样他们可以主动向客户提供更好的服务,并挽回这些即将流失的客户。

2.数据集

该数据集由10,000个客户组成,其中包含了他们的年龄,工资,婚姻状况,信用卡限额,信用卡类别等。

不过,这里面只有16%的客户是流失的,因此拿来预测客户是否会流失有点难度。

在Python实用宝典后台回复 预测客户流失 下载这份数据和源代码。

译自kaggle并对原文进行了修改和补充,感谢原作者:

https://www.kaggle.com/thomaskonstantin/bank-churn-data-exploration-and-churn-prediction/

3.代码与分析

开始之前,你要确保Python和pip已经成功安装在电脑上,如果没有,请访问这篇文章:超详细Python安装指南 进行安装。如果你用Python的目的是数据分析,可以直接安装Anaconda:Python数据分析与挖掘好帮手—Anaconda,它内置了Python和pip.

此外,推荐大家用VSCode编辑器,因为它可以在编辑器下方的终端运行命令安装依赖模块:Python 编程的最好搭档—VSCode 详细指南。

本文具备流程性,建议使用 VSCode 的 Jupiter Notebook 扩展,新建一个名为 test.ipynb 的文件,跟着教程一步步走下去。

Windows环境下打开Cmd(开始—运行—CMD),苹果系统环境下请打开Terminal(command+空格输入Terminal),准备开始输入命令安装依赖。

所需依赖:

pip install numpy
pip install pandas
pip install plotly
pip install scikit-learn
pip install scikit-plot

# 这个需要conda
conda install -c conda-forge imbalanced-learn

3.1 导入需要的模块

import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as ex
import plotly.graph_objs as go
import plotly.figure_factory as ff
from plotly.subplots import make_subplots
import plotly.offline as pyo
pyo.init_notebook_mode()
sns.set_style('darkgrid')
from sklearn.decomposition import PCA
from sklearn.model_selection import train_test_split,cross_val_score
from sklearn.ensemble import RandomForestClassifier,AdaBoostClassifier
from sklearn.svm import SVC
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import f1_score as f1
from sklearn.metrics import confusion_matrix
import scikitplot as skplt

plt.rc('figure',figsize=(18,9))
%pip install imbalanced-learn
from imblearn.over_sampling import SMOTE

遇到任何 No module named “XXX” 都可以尝试pip install一下。

如果pip install没解决,可以谷歌/百度一下,看看别人是怎么解决的。

3.2 加载数据

c_data = pd.read_csv('./BankChurners.csv')
c_data = c_data[c_data.columns[:-2]]
c_data.head(3)

这里去掉了最后两列的朴素贝叶斯分类结果。

显示前三行数据, 可以看到所有的字段:

3.3 探索性数据分析

下面看看这20+列数据中,哪一些是对我们有用的。

首先,我想知道数据集中的客户年龄分布:

fig = make_subplots(rows=2, cols=1)

tr1=go.Box(x=c_data['Customer_Age'],name='Age Box Plot',boxmean=True)
tr2=go.Histogram(x=c_data['Customer_Age'],name='Age Histogram')

fig.add_trace(tr1,row=1,col=1)
fig.add_trace(tr2,row=2,col=1)

fig.update_layout(height=700, width=1200, title_text="Distribution of Customer Ages")
fig.show()

可以看到,客户的年龄分布大致遵循正态分布,因此使用可以在正态假设下进一步使用年龄特征。

同样滴,我想知道性别分布如何:

ex.pie(c_data,names='Gender',title='Propotion Of Customer Genders')

可见,在我们的数据集中,女性的样本比男性更多,但是差异的百分比不是那么显著,所以我们可以说性别是均匀分布的。

每个客户的家庭人数的分布怎么样?

fig = make_subplots(rows=2, cols=1)

tr1=go.Box(x=c_data['Dependent_count'],name='Dependent count Box Plot',boxmean=True)
tr2=go.Histogram(x=c_data['Dependent_count'],name='Dependent count Histogram')

fig.add_trace(tr1,row=1,col=1)
fig.add_trace(tr2,row=2,col=1)

fig.update_layout(height=700, width=1200, title_text="Distribution of Dependent counts (close family size)")
fig.show()

可见,它也是大致符合正态分布的,偏右一点。

客户的受教育水平如何?

ex.pie(c_data,names='Education_Level',title='Propotion Of Education Levels')

假设大多数教育程度不明(Unknown)的顾客都没有接受过任何教育。我们可以指出,超过70%的顾客都受过正规教育,其中约35%的人受教育程度达到硕士以上水平,45%的人达到本科以上水准。

他们的婚姻状态如何?

ex.pie(c_data,names='Marital_Status',title='Propotion Of Different Marriage Statuses')

看来,这家银行几乎一半的客户都是已婚人士,有趣的是,另一半客户几乎都是单身人士,另外只有7%的客户离婚了。

看看收入分布和卡片类型的分布:

ex.pie(c_data,names='Income_Category',title='Propotion Of Different Income Levels')
ex.pie(c_data,names='Card_Category',title='Propotion Of Different Card Categories')

可见大部分人的年收入处于60K美元以下。

在持有的卡片的类型上,蓝卡占了绝大多数。

每月账单数量有没有特征?

fig = make_subplots(rows=2, cols=1)

tr1=go.Box(x=c_data['Months_on_book'],name='Months on book Box Plot',boxmean=True)
tr2=go.Histogram(x=c_data['Months_on_book'],name='Months on book Histogram')

fig.add_trace(tr1,row=1,col=1)
fig.add_trace(tr2,row=2,col=1)

fig.update_layout(height=700, width=1200, title_text="Distribution of months the customer is part of the bank")
fig.show()

可以看到中间的峰值特别高,显然这个指标不是正态分布的。

每位客户持有的银行业务数量有没有特征呢?

fig = make_subplots(rows=2, cols=1)

tr1=go.Box(x=c_data['Total_Relationship_Count'],name='Total no. of products Box Plot',boxmean=True)
tr2=go.Histogram(x=c_data['Total_Relationship_Count'],name='Total no. of products Histogram')

fig.add_trace(tr1,row=1,col=1)
fig.add_trace(tr2,row=2,col=1)

fig.update_layout(height=700, width=1200, title_text="Distribution of Total no. of products held by the customer")
fig.show()

基本上都是均匀分布的,显然这个指标对于我们而言也没太大意义。

用户不活跃月份数量有没有好用的特征?

fig = make_subplots(rows=2, cols=1)

tr1=go.Box(x=c_data['Months_Inactive_12_mon'],name='number of months inactive Box Plot',boxmean=True)
tr2=go.Histogram(x=c_data['Months_Inactive_12_mon'],name='number of months inactive Histogram')

fig.add_trace(tr1,row=1,col=1)
fig.add_trace(tr2,row=2,col=1)

fig.update_layout(height=700, width=1200, title_text="Distribution of the number of months inactive in the last 12 months")
fig.show()

这个似乎有点用处,会不会越不活跃的用户越容易流失呢?

信用卡额度的分布如何?

fig = make_subplots(rows=2, cols=1)

tr1=go.Box(x=c_data['Credit_Limit'],name='Credit_Limit Box Plot',boxmean=True)
tr2=go.Histogram(x=c_data['Credit_Limit'],name='Credit_Limit Histogram')

fig.add_trace(tr1,row=1,col=1)
fig.add_trace(tr2,row=2,col=1)

fig.update_layout(height=700, width=1200, title_text="Distribution of the Credit Limit")
fig.show()

大部分人的额度都在0到10k之间,这比较正常,暂时看不出和流失有什么关系。

客户总交易额的分布怎么样?

fig = make_subplots(rows=2, cols=1)

tr1=go.Box(x=c_data['Total_Trans_Amt'],name='Total_Trans_Amt Box Plot',boxmean=True)
tr2=go.Histogram(x=c_data['Total_Trans_Amt'],name='Total_Trans_Amt Histogram')

fig.add_trace(tr1,row=1,col=1)
fig.add_trace(tr2,row=2,col=1)

fig.update_layout(height=700, width=1200, title_text="Distribution of the Total Transaction Amount (Last 12 months)")
fig.show()

这个有点意思,总交易额的分布体现出“多组”分布,如果我们根据这个指标将客户聚类为不同的组别,看他们之间的相似性,并作出不同的画线,也许对我们最终的流失分析有一定的意义。

接下来,最重要的流失用户分布

ex.pie(c_data,names='Attrition_Flag',title='Proportion of churn vs not churn customers')

我们可以看到,只有16%的数据样本代表流失客户,在接下来的步骤中,我将使用SMOTE对流失样本进行采样,使其与常规客户的样本大小匹配,以便给后面选择的模型一个更好的机会来捕捉小细节。

3.4 数据预处理

使用SMOTE模型前,需要根据不同的特征对数据进行One Hot编码:

c_data.Attrition_Flag = c_data.Attrition_Flag.replace({'Attrited Customer':1,'Existing Customer':0})
c_data.Gender = c_data.Gender.replace({'F':1,'M':0})
c_data = pd.concat([c_data,pd.get_dummies(c_data['Education_Level']).drop(columns=['Unknown'])],axis=1)
c_data = pd.concat([c_data,pd.get_dummies(c_data['Income_Category']).drop(columns=['Unknown'])],axis=1)
c_data = pd.concat([c_data,pd.get_dummies(c_data['Marital_Status']).drop(columns=['Unknown'])],axis=1)
c_data = pd.concat([c_data,pd.get_dummies(c_data['Card_Category']).drop(columns=['Platinum'])],axis=1)
c_data.drop(columns = ['Education_Level','Income_Category','Marital_Status','Card_Category','CLIENTNUM'],inplace=True)

显示热力图:

sns.heatmap(c_data.corr('pearson'),annot=True)

3.5 SMOTE模型采样

SMOTE模型经常用于解决数据不平衡的问题,它通过添加生成的少数类样本改变不平衡数据集的数据分布,是改善不平衡数据分类模型性能的流行方法之一。

oversample = SMOTE()
X, y = oversample.fit_resample(c_data[c_data.columns[1:]], c_data[c_data.columns[0]])
usampled_df = X.assign(Churn = y)
ohe_data =usampled_df[usampled_df.columns[15:-1]].copy()
usampled_df = usampled_df.drop(columns=usampled_df.columns[15:-1])
sns.heatmap(usampled_df.corr('pearson'),annot=True)

3.6 主成分分析

我们将使用主成分分析来降低单次编码分类变量的维数,从而降低方差。同时使用几个主成分而不是几十个单次编码特征将帮助我构建一个更好的模型。

N_COMPONENTS = 4

pca_model = PCA(n_components = N_COMPONENTS )

pc_matrix = pca_model.fit_transform(ohe_data)

evr = pca_model.explained_variance_ratio_
cumsum_evr = np.cumsum(evr)

ax = sns.lineplot(x=np.arange(0,len(cumsum_evr)),y=cumsum_evr,label='Explained Variance Ratio')
ax.set_title('Explained Variance Ratio Using {} Components'.format(N_COMPONENTS))
ax = sns.lineplot(x=np.arange(0,len(cumsum_evr)),y=evr,label='Explained Variance Of Component X')
ax.set_xticks([i for i in range(0,len(cumsum_evr))])
ax.set_xlabel('Component number #')
ax.set_ylabel('Explained Variance')
plt.show()
usampled_df_with_pcs = pd.concat([usampled_df,pd.DataFrame(pc_matrix,columns=['PC-{}'.format(i) for i in range(0,N_COMPONENTS)])],axis=1)
usampled_df_with_pcs

特征变得越来越明显:

sns.heatmap(usampled_df_with_pcs.corr('pearson'),annot=True)

4.模型选择及测试

选择出以下特征划分训练集并进行训练:

X_features = ['Total_Trans_Ct','PC-3','PC-1','PC-0','PC-2','Total_Ct_Chng_Q4_Q1','Total_Relationship_Count']

X = usampled_df_with_pcs[X_features]
y = usampled_df_with_pcs['Churn']

train_x,test_x,train_y,test_y = train_test_split(X,y,random_state=42)

4.1 交叉验证

分别看看随机森林、AdaBoost和SVM模型三种模型的表现如何:

rf_pipe = Pipeline(steps =[ ('scale',StandardScaler()), ("RF",RandomForestClassifier(random_state=42)) ])
ada_pipe = Pipeline(steps =[ ('scale',StandardScaler()), ("RF",AdaBoostClassifier(random_state=42,learning_rate=0.7)) ])
svm_pipe = Pipeline(steps =[ ('scale',StandardScaler()), ("RF",SVC(random_state=42,kernel='rbf')) ])


f1_cross_val_scores = cross_val_score(rf_pipe,train_x,train_y,cv=5,scoring='f1')
ada_f1_cross_val_scores=cross_val_score(ada_pipe,train_x,train_y,cv=5,scoring='f1')
svm_f1_cross_val_scores=cross_val_score(svm_pipe,train_x,train_y,cv=5,scoring='f1')
plt.subplot(3,1,1)
ax = sns.lineplot(x=range(0,len(f1_cross_val_scores)),y=f1_cross_val_scores)
ax.set_title('Random Forest Cross Val Scores')
ax.set_xticks([i for i in range(0,len(f1_cross_val_scores))])
ax.set_xlabel('Fold Number')
ax.set_ylabel('F1 Score')
plt.show()
plt.subplot(3,1,2)
ax = sns.lineplot(x=range(0,len(ada_f1_cross_val_scores)),y=ada_f1_cross_val_scores)
ax.set_title('Adaboost Cross Val Scores')
ax.set_xticks([i for i in range(0,len(ada_f1_cross_val_scores))])
ax.set_xlabel('Fold Number')
ax.set_ylabel('F1 Score')
plt.show()
plt.subplot(3,1,3)
ax = sns.lineplot(x=range(0,len(svm_f1_cross_val_scores)),y=svm_f1_cross_val_scores)
ax.set_title('SVM Cross Val Scores')
ax.set_xticks([i for i in range(0,len(svm_f1_cross_val_scores))])
ax.set_xlabel('Fold Number')
ax.set_ylabel('F1 Score')
plt.show()

看看三种模型都有什么不同的表现:

看得出来随机森林 F1分数是最高的。

4.2 模型预测

对测试集进行预测,看看三种模型的效果:

rf_pipe.fit(train_x,train_y)
rf_prediction = rf_pipe.predict(test_x)

ada_pipe.fit(train_x,train_y)
ada_prediction = ada_pipe.predict(test_x)

svm_pipe.fit(train_x,train_y)
svm_prediction = svm_pipe.predict(test_x)

print('F1 Score of Random Forest Model On Test Set - {}'.format(f1(rf_prediction,test_y)))
print('F1 Score of AdaBoost Model On Test Set - {}'.format(f1(ada_prediction,test_y)))
print('F1 Score of SVM Model On Test Set - {}'.format(f1(svm_prediction,test_y)))

4.3 对原始数据(采样前)进行模型预测

​接下来对原始数据进行模型预测:

ohe_data =c_data[c_data.columns[16:]].copy()
pc_matrix = pca_model.fit_transform(ohe_data)
original_df_with_pcs = pd.concat([c_data,pd.DataFrame(pc_matrix,columns=['PC-{}'.format(i) for i in range(0,N_COMPONENTS)])],axis=1)

unsampled_data_prediction_RF = rf_pipe.predict(original_df_with_pcs[X_features])
unsampled_data_prediction_ADA = ada_pipe.predict(original_df_with_pcs[X_features])
unsampled_data_prediction_SVM = svm_pipe.predict(original_df_with_pcs[X_features])

效果如下:

F1最高的随机森林模型有0.63分,偏低,这也比较正常,毕竟在这种分布不均的数据集中,查全率是很难做到很高的。

4.4 结果

让我们看看最终在原数据上使用随机森林模型的运行结果:

ax = sns.heatmap(confusion_matrix(unsampled_data_prediction_RF,original_df_with_pcs['Attrition_Flag']),annot=True,cmap='coolwarm',fmt='d')
ax.set_title('Prediction On Original Data With Random Forest Model Confusion Matrix')
ax.set_xticklabels(['Not Churn','Churn'],fontsize=18)
ax.set_yticklabels(['Predicted Not Churn','Predicted Churn'],fontsize=18)

plt.show()

可见,没有流失的客户命中了7709人,未命中791人。

流失客户命中了1130人,未命中497人。

整体而言,是一个比较优秀的模型了。

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

pathlib vs os 这两大Python模块谁更好?优势对比

作者:somenzz

来源:Python七号

前段时间,在使用新版本的 Django 时,我发现了 settings.py 的第一行代码从

import os
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

变成了

from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent

于是我就好奇,os 和 pathlib 同样是标准库,为什么 pathlib 得到了 Django 的青睐?学习了一番 pathlib 之后,发现这是一个非常高效便捷的工具,用它来处理文件系统路径相关的操作最合适不过,集成了很多快捷的功能,提升你的编程效率,那是妥妥的。

接下来让一起看一下,为什么 pathlib 更值得我们使用。

pathlib vs os

话不多说,先看下使用对比:比如说

  1. 打印当前的路径:

使用 os:

In [13]: import os

In [14]: os.getcwd()
Out[14]: '/Users/aaron'

使用 pathlib:

In [15]: from pathlib import Path

In [16]: Path.cwd()
Out[16]: PosixPath('/Users/aaron')
In [17]: print(Path.cwd())
/Users/aaron

使用 print 打印的结果是一样的,但 os.getcwd() 返回的是字符串,而 Path.cwd() 返回的是 PosixPath 类,你还可以对此路径进行后续的操作,会很方便。

  1. 判断路径是否存在:

使用 os:

In [18]: os.path.exists("/Users/aaron/tmp")
Out[18]: True

使用 pathlib:

In [21]: tmp = Path("/Users/aaron/tmp")

In [22]: tmp.exists()
Out[22]: True

可以看出 pathlib 更易读,更面向对象。

  1. 显示文件夹的内容
In [38]: os.listdir("/Users/aaron/tmp")
Out[38]: ['.DS_Store''.hypothesis''b.txt''a.txt''c.py''.ipynb_checkpoints']

In [39]: tmp.iterdir()
Out[39]: <generator object Path.iterdir at 0x7fa3f20d95f0>

In [40]: list(tmp.iterdir())
Out[40]:
[PosixPath('/Users/aaron/tmp/.DS_Store'),
 PosixPath('/Users/aaron/tmp/.hypothesis'),
 PosixPath('/Users/aaron/tmp/b.txt'),
 PosixPath('/Users/aaron/tmp/a.txt'),
 PosixPath('/Users/aaron/tmp/c.py'),
 PosixPath('/Users/aaron/tmp/.ipynb_checkpoints')]

可以看出 Path().iterdir 返回的是一个生成器,这在目录内文件特别多的时候可以大大节省内存,提升效率。

  1. 通配符支持

os 不支持含有通配符的路径,但 pathlib 可以:

In [45]: list(Path("/Users/aaron/tmp").glob("*.txt"))
Out[45]: [PosixPath('/Users/aaron/tmp/b.txt'), PosixPath('/Users/aaron/tmp/a.txt')]
  1. 便捷的读写文件操作

这是 pathlib 特有的:

f = Path('test_dir/test.txt'))
f.write_text('This is a sentence.')
f.read_text()

也可以使用 with 语句:

>>> p = Path('setup.py')
>>> with p.open() as f: f.readline()
...
'#!/usr/bin/env python3\n'
  1. 获取文件的元数据
In [56]: p = Path("/Users/aaron/tmp/c.py")

In [57]: p.stat()
Out[57]: os.stat_result(st_mode=33188, st_ino=35768389, st_dev=16777221, st_nlink=1, st_uid=501, st_gid=20, st_size=20, st_atime=1620633580, st_mtime=1620633578, st_ctime=1620633578)

In [58]: p.parts
Out[58]: ('/''Users''aaron''tmp''c.py')

In [59]: p.parent
Out[59]: PosixPath('/Users/aaron/tmp')

In [60]: p.resolve()
Out[60]: PosixPath('/Users/aaron/tmp/c.py')

In [61]: p.exists()
Out[61]: True

In [62]: p.is_dir()
Out[62]: False

In [63]: p.is_file()
Out[63]: True

In [64]: p.owner()
Out[64]: 'aaron'

In [65]: p.group()
Out[65]: 'staff'

In [66]: p.name
Out[66]: 'c.py'

In [67]: p.suffix
Out[67]: '.py'

In [68]: p.suffixes
Out[68]: ['.py']

In [69]: p.stem
Out[69]: 'c'

  1. 路径的连接 join

相比 os.path.join,使用一个 / 是不是更为直观和便捷?

>>> p = PurePosixPath('foo')
>>> p / 'bar'
PurePosixPath('foo/bar')
>>> p / PurePosixPath('bar')
PurePosixPath('foo/bar')
>>> 'bar' / p
PurePosixPath('bar/foo')

当然,也可以使用 joinpath 方法

>>> PurePosixPath('/etc').joinpath('passwd')
PurePosixPath('/etc/passwd')
>>> PurePosixPath('/etc').joinpath(PurePosixPath('passwd'))
PurePosixPath('/etc/passwd')
>>> PurePosixPath('/etc').joinpath('init.d''apache2')
PurePosixPath('/etc/init.d/apache2')
>>> PureWindowsPath('c:').joinpath('/Program Files')
PureWindowsPath('c:/Program Files')
  1. 路径匹配
>>> PurePath('a/b.py').match('*.py')
True
>>> PurePath('/a/b/c.py').match('b/*.py')
True
>>> PurePath('/a/b/c.py').match('a/*.py')
False

pathlib 出现的背景和要解决的问题

pathlib 目的是提供一个简单的类层次结构来处理文件系统的路径,同时提供路径相关的常见操作。那为什么不使用 os 模块或者 os.path 来实现呢?

许多人更喜欢使用 datetime 模块提供的高级对象来处理日期和时间,而不是使用数字时间戳和 time 模块 API。同样的原因,假如使用专用类表示文件系统路径,也会更受欢迎。

换句话说,os.path 是面向过程风格的,而 pathlib 是面向对象风格的。Python 也在一直在慢慢地从复制 C 语言的 API 转变为围绕各种常见功能提供更好,更有用的抽象。

其他方面,使用专用的类处理特定的需求也是很有必要的,例如 Windows 路径不区分大小写。

在这样的背景下,pathlib 在 Python 3.4 版本加入标准库。

pathlib 的优势和劣势分别是什么

pathlib 的优势在于考虑了 Windows 路径的特殊性,同时提供了带 I/O 操作的和不带 I/O 操作的类,使用场景更加明确,API 调用更加易懂。

先看下 pathlib 对类的划分:

图中的箭头表示继承自,比如 Path 继承自 PurePath,PurePath 表示纯路径类,只提供路径常见的操作,但不包括实际 I/O 操作,相对安全;Path 包含 PurePath 的全部功能,包括 I/O 操作。

PurePath 有两个子类,一个是 PureWindowsPath,表示 Windows 下的路径,不区分大小写,另一个是 PurePosixPath,表示其他系统的路径。有了 PureWindowsPath,你可以这样对路径进行比较:

from pathlib import PureWindowsPath
>>> PureWindowsPath('a') == PureWindowsPath('A')
True

PurePath 可以在任何操作系统上实例化,也就是说与平台无关,你可以在 unix 系统上使用 PureWindowsPath,也可以在 Windows 系统上使用 PurePosixPath,他们还可以相互比较。

>>> from pathlib import PurePosixPath, PureWindowsPath, PosixPath  
>>> PurePosixPath('a') == PurePosixPath('b')
False
>>> PurePosixPath('a') < PurePosixPath('b')
True
>>> PurePosixPath('a') == PosixPath('a')
True
>>> PurePosixPath('a') == PureWindowsPath('a')
False

可以看出,同一个类可以相互比较,不同的类比较的结果是 False。

相反,包含 I/O 操作的类 PosixPath 及 WindowsPath 只能在对应的平台实例化:

In [8]: from pathlib import PosixPath,WindowsPath

In [9]: PosixPath('a')
Out[9]: PosixPath('a')

In [10]: WindowsPath('a')
---------------------------------------------------------------------------
NotImplementedError                       Traceback (most recent call last)
<ipython-input-10-cc7a0d86d4ed> in <module>
----> 1 WindowsPath('a')

/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/pathlib.py in __new__(cls, *args, **kwargs)
   1038         self = cls._from_parts(args, init=False)
   1039         if not self._flavour.is_supported:
-> 1040             raise NotImplementedError("cannot instantiate %r on your system"
   1041                                       % (cls.__name__,))
   1042         self._init()

NotImplementedError: cannot instantiate 'WindowsPath' on your system

In [11]:

要说劣势,如果有的话,那就是在选择类时会比较困惑,到底用哪一个呢?其实如果你不太确定的话,用 Path 就可以了,这也是它的名称最短的原因,因为更加常用,短点的名称编写的更快。

适用的场景

如果要处理文件系统相关的操作,选 pathlib 就对了。

一些关键点

获取家目录:

In [70]: from pathlib import Path

In [71]: Path.home()
Out[71]: PosixPath('/Users/aaron')

父目录的层级获取:

>>> p = PureWindowsPath('c:/foo/bar/setup.py')
>>> p.parents[0]
PureWindowsPath('c:/foo/bar')
>>> p.parents[1]
PureWindowsPath('c:/foo')
>>> p.parents[2]
PureWindowsPath('c:/')

获取多个文件后缀:

>>> PurePosixPath('my/library.tar.gar').suffixes
['.tar''.gar']
>>> PurePosixPath('my/library.tar.gz').suffixes
['.tar''.gz']
>>> PurePosixPath('my/library').suffixes
[]


Windows 风格转 Posix:

>>> p = PureWindowsPath('c:\\windows')
>>> str(p)
'c:\\windows'
>>> p.as_posix()
'c:/windows'

获取文件的 uri:

>>> p = PurePosixPath('/etc/passwd')
>>> p.as_uri()
'file:///etc/passwd'
>>> p = PureWindowsPath('c:/Windows')
>>> p.as_uri()
'file:///c:/Windows'

判断是否绝对路径:

>>> PurePosixPath('/a/b').is_absolute()
True
>>> PurePosixPath('a/b').is_absolute()
False

>>> PureWindowsPath('c:/a/b').is_absolute()
True
>>> PureWindowsPath('/a/b').is_absolute()
False
>>> PureWindowsPath('c:').is_absolute()
False
>>> PureWindowsPath('//some/share').is_absolute()
True

文件名若有变化:

>>> p = PureWindowsPath('c:/Downloads/pathlib.tar.gz')
>>> p.with_name('setup.py')
PureWindowsPath('c:/Downloads/setup.py')

是不是非常方便?

技术的底层原理和关键实现

pathlib 并不是基于 str 的实现,而是基于 object 设计的,这样就严格地区分了 Path 对象和字符串对象,同时也用到了一点 os 的功能,比如 os.name,os.getcwd 等,这一点大家可以看 pathlib 的源码了解更多。

最后的话

本文分享了 pathlib 的用法,后面要处理路径相关的操作时,你应该第一时间想到 pathlib,不会用没有关系,搜索引擎所搜索 pathlib 就可以看到具体的使用方法。

虽然 pathlib 比 os 库更高级,更方便并且提供了很多便捷的功能,但是我们仍然需要知道如何使用 os 库,因为 os 库是 Python 中功能最强大且最基本的库之一,但是,在需要一些文件系统操作时,强烈建议使用 pathlib。

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

Python 你可能从未听说过的5种隐藏技巧

1. …

没错,你没看错,就是 …

在Python中 … 代表着一个名为 Ellipsis 的对象。根据官方说明,它是一个特殊值,通常可以作为空函数的占位符,或是用于Numpy中的切片操作

如:

def my_awesome_function():
    ...

等同于:

def my_awesome_function():
    Ellipsis

当然,你也可以使用pass或者字符串作为占位符:

def my_awesome_function():
    pass
def my_awesome_function():
    "An empty, but also awesome function"

他们最终的效果都是相同的。

接下来讲讲 … 对象是如何在Numpy中体现出作用的,创建一个 3x3x3 的矩阵数组,然后获取所有最内层矩阵的第二列:

>>> import numpy as np
>>> array = np.arange(27).reshape(3, 3, 3)
>>> array
array([[[ 0,  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]]])

为了获取最层矩阵的第二列,传统方法可能是这样的:

>>> array[:, :, 1] 
array([[ 1,  4,  7],
       [10, 13, 16],
       [19, 22, 25]])

如果你会用 … 对象,则是这样的:

>>> array[..., 1] 
array([[ 1,  4,  7],
       [10, 13, 16],
       [19, 22, 25]])

不过请注意, … 对象仅可用于Numpy,不适用于Python内置数组。

2.解压迭代对象

解压迭代对象是一个非常方便的特性:

>>> a, *b, c = range(1, 11)
>>> a
1
>>> c
10
>>> b
[2, 3, 4, 5, 6, 7, 8, 9]

或者是:

>>> a, b, c = range(3)
>>> a
0
>>> b
1
>>> c
2

同理,与其写这样的代码:

>>> lst = [1]
>>> a = lst[0]
>>> a
1
>>> (a, ) = lst
>>> a
1

你不如跟解压迭代对象一样,进行更优雅的赋值操作:

>>> lst = [1]
>>> [a] = lst
>>> a
1

虽然这看起来有点蠢,但就我个人来看,比前一种写法更优雅一些。

3.展开的艺术

数组展开有各种千奇百怪的姿势,比如说:

>>> l = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> flattened = [elem for sublist in l for elem in sublist]
>>> flattened
[1, 2, 3, 4, 5, 6, 7, 8, 9]

如果你对reduce和lambda有一定了解,建议使用更优雅的方式:

>>> from functools import reduce
>>> reduce(lambda x,y: x+y, l)
[1, 2, 3, 4, 5, 6, 7, 8, 9]

reduce和lambda组合起来,就能针对 l 数组内的每个子数组做拼接操作。

当然,还有更神奇的方式:

>>> sum(l, [])
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> # 其实相当于 [] + [1, 2, 3] + [4, 5, 6] + [7, 8, 9]

没错,这样对二维数组做sum操作,就能使二维数组内的每个元素做“加”法拼接起来。同样的道理,如果你对三位数组做sum操作,就能使其变为二维数组,再对二维数组做sum操作,就能展开为一维数组。

不过,虽然这个技巧很出色,并不推荐使用,因为可读性太差了。

4.下划线 _ 变量

每当你在Python解释器,IPython或Django Console中运行表达式时,Python都会将输出的值绑定到 _ 变量中:

>>> nums = [1, 3, 7]
>>> sum(nums)
11
>>> _
11
>>>

由于它是一个变量,你可以随时覆盖它,或像普通变量一样操作它:

>>> 9 + _
20
>>> a = _
>>> a
20

5.多种用途的else

很多人都不知道,else 可以被用于许多地方,除了典型的 if else, 我们还可以在循环和异常处理里用到它。

循环

如果需要判断循环里是否处理了某个逻辑,通常情况下会这么做:

found = False
a = 0

while a < 10:
    if a == 12:
        found = True
    a += 1
if not found:
    print("a was never found")

如果引入else,我们可以少用一个变量:

a = 0

while a < 10:
    if a == 12:
        break
    a += 1
else:
    print("a was never found")

异常处理

我们可以在 try … except … 中使用 else 编写未捕获到异常时的逻辑:

In [13]: try:
    ...:     {}['lala']
    ...: except KeyError:
    ...:     print("Key is missing")
    ...: else:
    ...:     print("Else here")
    ...: 
Key is missing

这样,如果程序没有异常,则会走else分支:

In [14]: try:
    ...:     {'lala': 'bla'}['lala']
    ...: except KeyError:
    ...:     print("Key is missing")
    ...: else:
    ...:     print("Else here")
    ...: 
Else here

如果你经常做异常处理,你就会知道这个技巧相当方便。

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

Python 简化函数调用的3种技巧

假设有一个函数,这个函数需要接收4个参数,并返回这4个参数的和:

def sum_four(a, b, c, d):
    return a + b + c + d

如果需要固定最后前三个参数,仅改变最后一个参数的值,这时候可能需要这么调用:

>>> a, b, c = 1, 2, 3

>>> sum_four(a=a, b=b, c=c, d=1)
7

>>> sum_four(a=a, b=b, c=c, d=2)
8

>>> sum_four(a=a, b=b, c=c, d=3)
9

>>> sum_four(a=a, b=b, c=c, d=4)
10

这样写实在是太丑了,如果用 Map 函数,是否能简化代码?

答案是肯定的,但是Map函数只能接受单一元素,如果你强行使用的话,它会报这样的错:

>>> list(map(sum_four, [(1, 2, 3, 4)]))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: sum_four() missing 3 required positional arguments: 'b', 'c', and 'd'

怎么解决?

方案1: itertools.starmap

我们可以使用 itertools 的函数 starmap 替换Map.

它与Map不同,允许接受一个元组作为传入sum_four的参数。

>>> import itertools
>>> list(itertools.starmap(sum_four, [(1, 2, 3, 4)]))
[10]

非常棒,这样的话,上述问题就可以使用starmap函数解决:

>>> import itertools

>>> ds = [1, 2, 3, 4]

>>> items = ((a, b, c, d) for d in ds)

>>> list(items)
 [(1, 2, 3, 1), (1, 2, 3, 2), (1, 2, 3, 3), (1, 2, 3, 4)]

>>> list(itertools.starmap(sum_four, items))
 [7, 8, 9, 10]

请注意 items 是一个生成器,这是为了避免 items 过大导致内存消耗量过大。平时开发的时候注意这些细节,能够使你和普通的开发者拉开差距。

方案2: functools.partial

第二种解决方案是使用 partial 函数固定前三个参数。

根据文档,partial 将“冻结”函数的参数的某些部分,从而生成简化版的函数。

因此上述问题的解决方案就是:

>>> import functools
>>> partial_sum_four = functools.partial(sum_four, a, b, c)
>>> partial_sum_four(3)
9
>>> # 这样就可以使用map函数了:
>>> list(map(partial_sum_four, ds))
[7, 8, 9, 10]

方案3: itertools.repeat()

事实上,Map 函数是允许传递可迭代参数的,但是有一个有趣的特点,他会用每个可迭代对象里的项作为传入函数的不同参数。这样说可能太过于抽象了,来看看实际的例子:

>>> list(map(sum_four, [1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3], [1,2,3,4]))
 [7, 8, 9, 10]

明白了吧,每次都使用了不同数组中对应下标的项传入函数进行计算。

这样,我们可以使用这个特点进行优化。

itertools.repeat() 函数能够根据参数产生一个迭代器,该迭代器一次又一次返回对象。不指定times参数,它将无限期运行。

而 Map 函数会在最短的可迭代对象被迭代完后,就会自动停止运行。

结合这两个特点,上述问题的解决方案就出来了:

>>> import itertools
>>> list(map(sum_four, itertools.repeat(a), itertools.repeat(b), itertools.repeat(c), ds))
 [7, 8, 9, 10]

这招还是非常巧妙的。缺点是能读懂的人不多。不过没关系,计算机世界中某些东西知道就好,你并不一定需要去使用它。

比如本文中的这几种解决方案,日常生活工作中一般用不到,所以你不需要死记硬背,但你需要知道【有这样的问题】和【有这些解决方案】,万一遇到了相似的场景,你就可以回忆起这篇文章并快速找到解决的方法。

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

接近完美的监控系统—普罗米修斯

普罗米修斯(Prometheus)是一个SoundCloud公司开源的监控和告警系统。当年,由于SoundCloud公司生产了太多的服务,传统的监控已经无法满足监控需求,于是他们在2012决定着手开发新的监控系统,即普罗米修斯。

普罗米修斯(下称普罗)的作者 Matt T.Proud 在2012年加入该公司,他从google的监控系统Borgmon中获得灵感,与另一名工程师Julius Volz合作开发了开源的普罗,后来其他开发人员陆续加入到该项目,最终于2015正式发布。

普罗基于Go语言开发,其架构图如下:

其中:

  • Prometheus Server: 用数据的采集和存储,PromQL查询,报警配置。
  • Push gateway: 用于批量,短期的监控数据的汇报总节点。
  • Exporters: 各种汇报数据的exporter,例如汇报机器数据的node_exporter,汇报MondogDB信息的 MongoDB_exporter 等等。
  • Alertmanager: 用于高级通知管理。

1.怎么采集监控数据?

要采集目标(主机或服务)的监控数据,首先就要在被采集目标地方安装采集组件,这种采集组件被称为Exporter。prometheus.io官网上有很多这种exporter:exporter列表,比如:

Consul exporter (official)
Memcached exporter
 (official)
MySQL server exporter
 (official)
Node/system metrics exporter
 (official)
HAProxy exporter
 (official)
RabbitMQ exporter

Grok exporter

InfluxDB exporter
 (official)

等等…

这些exporter能为我们采集目标的监控数据,然后传输给普罗米修斯。这时候,exporter会暴露一个http接口,普罗米修斯通过HTTP协议使用Pull的方式周期性拉取相应的数据。

不过,普罗也提供了Push模式来进行数据传输,通过增加Push Gateway这个中间商实现,你可以将数据推送到Push Gateway,普罗再通过Pull的方式从Push Gateway获取数据。

这就是为什么你从架构图里能看到两个 Pull metrics 的原因,一个是采集器直接被Server拉取数据(pull);另一个是采集器主动Push数据到Push Gateway,Server再对Push Gateway主动拉取数据(pull)。

采集数据的主要流程如下:

1. Prometheus server 定期从静态配置的主机或服务发现的 targets 拉取数据(zookeeper,consul,DNS SRV Lookup等方式)

2. 当新拉取的数据大于配置内存缓存区的时候,Prometheus会将数据持久化到磁盘,也可以远程持久化到云端。

3. Prometheus通过PromQL、API、Console和其他可视化组件如Grafana、Promdash展示数据。

4. Prometheus 可以配置rules,然后定时查询数据,当条件触发的时候,会将告警推送到配置的Alertmanager。

5. Alertmanager收到告警的时候,会根据配置,聚合,去重,降噪,最后发出警告。

2.采集的数据结构与指标类型

2.1 数据结构

了解普罗米修斯的数据结构对于了解整个普罗生态非常重要。普罗采用键值对作为其基本的数据结构:

Key是指标名字,Value是该指标的值,此外Metadata(元信息)也非常重要,也可称之为labels(标签信息)。这些标签信息指定了当前这个值属于哪个云区域下的哪台机器,如果没有labels,数据有可能会被丢失。

2.2 指标类型

普罗米修斯的监控指标有4种基本类型:

1.Counter(计数器):

计数器是我们最简单的指标类型。比如你想统计某个网站的HTTP错误总数,这时候就用计数器。

计数器的值只能增加或重置为0,因此特别适合计算某个时段上某个时间的发生次数,即指标随时间演变发生的变化。

2.Gauges

Gauges可以用于处理随时间增加或减少的指标,比如内存变化、温度变化。

这可能是最常见的指标类型,不过它也有一定缺点:如果系统每5秒发送一次指标,普罗服务每15秒抓取一次数据,那么这期间可能会丢失一些指标,如果你基于这些数据做汇总分析计算,则结果的准确性会有所下滑。

3.Histogram(直方图)

直方图是一种更复杂的度量标准类型。它为我们的指标提供了额外信息,例如观察值的总和及其数量,常用于跟踪事件发生的规模。

比如,为了监控性能指标,我们希望在有20%的服务器请求响应时间超过300毫秒时发送告警。对于涉及比例的指标就可以考虑使用直方图。

4.Summary(摘要)

摘要更高级一些,是对直方图的扩展。除了提供观察的总和和计数之外,它们还提供滑动窗口上的分位数度量。分位数是将概率密度划分为相等概率范围的方法。

对比直方图:

直方图随时间汇总值,给出总和和计数函数,使得易于查看给定指标的变化趋势。

而摘要则给出了滑动窗口上的分位数(即随时间不断变化)。

3.实例概念

随着分布式架构的不断发展和云解决方案的普及,现在的架构已经变得越来越复杂了。

分布式的服务器复制和分发成了日常架构的必备组件。我们举一个经典的Web架构,该架构由3个后端Web服务器组成。在该例子中,我们要监视Web服务器返回的HTTP错误的数量。

使用普罗米修斯语言,单个Web服务器单元称为实例(主机实例)。该任务是计算所有实例的HTTP错误数量。

事实上,这甚至可以说是最简单的架构了,再复杂一点,实例不仅能是主机实例,还能是服务实例,因此你需要增加一个instance_type的标签标记主机或服务。

再再复杂一点,同样的IP,可能存在于不同云区域下,这属于不同的机器,因此还需要一个cloud标签,最终该数据结构可能会变为:

cpu_usage {job=”1″, instance=”128.0.0.1″, cloud=”0″, instance_type=”0″}

4.数据可视化

如果使用过基于InfluxDB的数据库,你可能会熟悉InfluxQL。普罗米修斯也内置了自己的SQL查询语言用于查询和检索数据,这个内置的语言就是PromQL。

我们前面说过,普罗米修斯的数据是用键值对表示的。PromQL也用相同的语法查询和返回结果集。

PromQL会处理两种向量:

即时向量:表示当前时间,某个指标的数据向量。

时间范围向量:表示过去某时间范围内,某个指标的数据向量。

如针对8核CPU的使用率:

知道怎么提取数据后,可视化数据就简单了。

Grafana是一个大型可视化系统,功能强大,可以创建自己的自定义面板,支持多种数据来源,当然也支持普罗米修斯。

通过配置数据源,Grafana会使用相应的SQL拉取并绘制图表,能直接看到普罗米修斯的各个指标数据图表:

更方便的是,Grafana有很多仪表盘模板供你使用,只要import模板进行简单的配置,就能得到以下效果:

5.应用前景

普罗米修斯非常强大,可以应用到各行各业。

5.1 DevOps

为了观察整个服务体系是否在正常运转,运维非常需要监控系统。在实例的创建速度和销毁速度一样快的容器世界中,灵活配置各类容器的监控项并迅速安装启动监控是非常重要的。

5.2 金融行业

金融服务巨头Northern Trust于2017年6月选择普罗米修斯,不是为了进行应用程序的监视,而是为了更好地了解其某些硬件的运作情况。Northern Trust使用普罗米修斯监控其平台上的750多种微服务。

5.3 汽车行业

Life360是一款用于定位、行车安全和家庭成员之间共享信息的移动应用程序,他们需要给用户提供稳定的定位服务,而原有的监控方案都非常局限,无法监视到所有组件的工作状态。

因此该公司使用普罗米修斯来监视其MySQL多主群集和一个12节点的Cassandra环,该环可容纳约4TB的数据。普罗米修斯在初步测试中表现良好。

在普罗米修斯的有限部署之后,Life360报告了监控方面的巨大进步,并设想在其数据中心基础架构的其他部分中使用它。 

总而言之,普罗米修斯这样的分布式监控系统,在未来的世界中用处可能会越来越大,它或许将会成为监控领域寡头式的存在,希望我们能熟悉这个工具,并在以后的架构和实践中使用它解决系统和应用监控的问题。

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

有趣好用的Python教程

退出移动版
微信支付
请使用 微信 扫码支付