超级强化版的pandas:从数据框中编写Excel文件并加密

介绍一个ExcelHelper类,它允许您使用强密码或自定义密码加密Excel文件

Writing to Excel and Encrypting it (Image by the author)

介绍

本文将分享如何添加一个ExcelHelper类,在将数据框写入Excel后打开和加密Excel文件。我在以前的文章中的to_excelp函数中包括了这种加密功能。

超强pandas: 读取和写入Excel

增强.read_excel和.to_excel方法能够让您专注于数据探索

towardsdatascience.com

对于正在阅读本文的数据科学家和机器学习爱好者,您可能会发现它很有帮助,因为它可以加快导出数据帧和导出到Excel的工作。

动机

在最近的一个项目中,我需要分析数据并为几个人准备统计数据。由于数据包含敏感信息,因此需要对文件进行密码保护。这非常符合我的技能集,如果您读过我的先前文章,就会知道在pypo.py中,我已经有了一个to_excelp函数,它在使用df.to_excel()方法创建Excel文件后打开该文件。虽然这对我来说运行良好,但现在是一个好时机重新审视它的实现方式,并增加密码保护Excel文件的功能。

内容

  1. 打开和加密Excel文件
  2. 生成强密码
  3. 将所有内容组合在一起

完整代码请在此处查看。

第1部分-打开和加密Excel文件

在使用Python和pandas工作时,有很多原因需要打开Excel文件,例如在测试期间进行可视化检查或在发送给利益相关者之前进行格式化。如果有额外的需要加密Excel文件,则需要处理3种情况:仅打开,仅加密,打开和加密。如果既不打开也不加密Excel文件,则不需要做任何事情,因为使用df.to_excel()就足够了。

ExcelHelper是一个类,用于启动Excel(应用程序),然后基于提供的path打开工作簿。从程序上讲,这是一个两步骤过程。大多数人从未意识到这一点,因为当您双击Excel文件时,Excel应用程序和工作簿一起启动。

初始化ExcelHelper类

  • __init__(self, launch=True, encrypt=False, password=None, length=20, quit_=True)这是ExcelHelper的初始化调用。
  • 如果launch等于True,则在加密完成后显示工作簿。
  • 如果encrypt等于True,则调用self._encrypt()方法,稍后将对其进行解释。
  • password允许用户输入首选密码,否则它将自动建议具有length个字符的强密码,最大长度为255。

打开工作簿

  • _open_wb(self, path, visible=False)将给定的路径转换为绝对路径,然后打开它。将路径转换为绝对路径是必要的,否则由win32com.client分派的应用程序将无法找到该文件。(以前,我使用了一个try-except块来在当前工作目录前置路径,但这样做过于冗长,需要花费一些时间才能真正理解自己想要做什么。)
  • visible控制应用程序是否可见给用户。通常,仅在加密完成后才显示应用程序。因此,如果我们同时启动和加密,则应在调用self._encrypt()之后将visible=True设置为True。

加密Excel

  • _encrypt(self, visible=False) 加密Excel工作簿,然后通过在加密完成后设置 self.xl.Visible 属性来显示应用程序。
  • self.xl.DisplayAlerts 设置为 True 非常重要,否则启动的Excel文件将不会出现任何警报(例如,如果您按Ctrl + F并尝试查找一些乱码,将没有提示 😱;这对我来说发生了,我真的很困惑!)。

执行方法

  • execute(self, path, launch, encrypt, quit_) 处理上述3种情况。
  • quit_ 参数关闭Excel应用程序(尾随下划线是约定,表示 quit 是Python中的保留关键字)。 当初始化 ExcelHelper 时,如果 launch=False,则Excel应用程序在后台运行,Excel文件被打开。 如果用户现在双击Excel文件,将提示只能以只读模式打开。 对于非技术用户来说,关闭文件非常困难。 解决方法是打开任务管理器,选择Excel程序,然后结束任务。 因此,需要调用 .Quit() 来终止Excel应用程序。 我们本可以只关闭工作簿,但也许现在没有必要这样精细地处理。

第二部分——生成强密码

起初,我使用 from cryptography.fernet import Fernet; Fernet.generate_key() 生成随机密码。 尽管有几个用户对密码的长度和随机性感到惊喜,但我并不是很喜欢它,因为它有点太长了,而且不包含各种标点符号。 我在StackOverflow上搜索并找到了更好的方法。 (我总是对stackoverflow上如何轻松获得非常高质量的答案感到非常印象深刻。所有艰难的工作都已经由所有巨人完成了,我们所需要做的就是搜索,复制,粘贴,并进行小的调整(例如更改变量名)。)该函数非常简单,而且相当易于理解。

import secretsimport stringdef gen_password(self, length):    char = string.ascii_letters + string.digits + string.punctuation    return ''.join(secrets.choice(char) for _ in range(length))

正当一切都过于顺利时,测试我的代码时,我注意到有时密码无法用于打开文件! 我真的很困惑。 经过一些试错,我开始怀疑可能有些字符不适合用作密码,因为仅在密码包含2个反斜杠 \\ 时才会出现此现象。

以下是一些背景信息,以便您欣赏这种情况:我使用Powershell和Notepad ++,我的代码将密码打印到 stdout。 接下来,我在Powershell上突出显示打印的密码,然后在Excel提示我输入密码时将其粘贴。 所以问题是 \ 是一个转义字符,因此在我将其输入为密码时,第一个 \ 应该被忽略。 处理起来很麻烦,而对于密码的目的,我可以少用一个字符。 因此,我只是在 string.punctuation 中切掉了反斜杠。

  def _get_password(self, length):      string_punc = string.punctuation[:23] + string.punctuation[24:]      char = string.ascii_letters + string.digits + string_punc      return ''.join(secrets.choice(char) for _ in range(length))

第三部分——将所有内容组合在一起

由于如果不启动或加密Excel文件,则实例化 ExcelHelper 对象几乎没有任何增值,因此应从 if launch or encrypt: 开始。 接下来,仅将关键字参数从 to_excelp 传递到 ExcelHelper 并返回对象和 password

def to_excelp(df, *arg, launch=True, encrypt=False, password=None, **kw):
    '''将 DataFrame 写入 Excel 并打开文件'''
    filename, *arg = arg
    if not filename.endswith(('.xlsx','.xls','.xlsm')):
        filename += '.xlsx'
    if os.path.isfile(filename):
        name, ext = filename.rsplit('.')
        filename = f'{name}_{timestr()}.{ext}'
    # 默认 index=False
    index = kw.get('index', False)
    if not index:
        kw['index']=False
    df.to_excel(filename, *arg, **kw)
    if launch or encrypt:
        xl = ExcelHelper(filename, launch=launch, encrypt=encrypt, password=password)
        return xl, xl.password
    else:
        return filename

如果您通过调用此函数将数据帧写入多个不同的 Excel 文件,我建议您将结果存储在元组列表中。您随后可以迭代此元组列表以获取 Excel 文件的路径和密码。将对象存储可能在将来有用,特别是如果您打算向 ExcelHelper 添加更多功能。

l_xl_pw = []
for df in (df1, df2, df3, df4):
    xl, pw = df.to_excelp(launch=False, encrypt=True, password=None)
    l_xl_pw.append((xl, pw))
l_path_pass = [[xl.path, pw] for (xl, pw) in l_xl_pw]
df_path_pass = pd.DataFrame(l_path_pass, columns=['Path', 'Pw'])
# df_path_pass 也可以使用 .to_excelp() 写入 Excel,多么优雅! :D

ExcelHelper 也可以添加到您现有的其他脚本中。

def some_func():
    df = pd.read_excel('some_file.xlsx')
    # 一些数据操作...
    df.to_excel('some_file_modified.xlsx')
    
def some_func(launch=False, encrypt=True, password='5tr0ngP@ssw0rd'):
    df = pd.read_excel('some_file.xlsx')
    # 一些数据操作...
    df.to_excel('some_file_modified.xlsx')
    if launch or encrypt:
        xl = ExcelHelper('some_file_modified.xlsx', launch=launch, encrypt=encrypt, password=password)
        return xl, xl.password

结论

重新审视自己编写的旧代码就像是在回忆往事,揭示了我以前是多么的无知。虽然我为此感到非常尴尬,但我很高兴知道我已经有所进步。

“如果你不为自己的旧代码感到尴尬,那就说明你不是一名进步的程序员。” [匿名]

编写这些小型类和函数需要时间,但拥有它们有着巨大的好处,因为它自动化了工作中机械和不那么有趣的部分,并允许人们专注于重要的任务。(想象一下,每次都要考虑大小写字母、数字和标点符号的密码,并将它们存储在文件中。)