对pathlib进行扩展

pathlib 自 Python 3.4 以后成为了 Python 的标准库,该库非常的好用,大大简化了目录的管理。但也有一些不足,可以对其进行扩展,使其更加好用。

扩展的代码

# 项目:standard python lib
# 模块:path and file
# 作者:黄涛
# License:GPL
# Email:huangtao.sh@icloud.com
# 创建:2016-03-11 12:21
# 修改:2016-04-13 21:01
# 修改:2016-08-13 新增__iter__ 和 extractall功能
# 修订:2016-11-19 修改Path的实现方式 

import pathlib
import os
from codecs import BOM_UTF8,BOM_LE,BOM_BE
from .click import *

BOM_CODE={
    BOM_UTF8:'utf_8',
    BOM_LE:'utf_16_le',
    BOM_BE:'utf_16_be',
    }
    
DEFAULT_CODES='utf8','gbk','utf16','big5'

def is_installed(file_name):
    '''
    确认指定的文件是否已被安装。
    '''
    from sysconfig import get_path
    paths=[get_path(name) for name in ('platlib','scripts')]
    if os.name=='nt':
        file_name=file_name.lower()
        paths=[path.lower() for path in paths]
    return any([file_name.startswith(path) for path in paths])

def is_dev(cmd=None):
    import sys
    cmd=cmd or sys.argv[0]
    if('wsgi' in cmd):
        return False
    return 'test' in cmd or (not is_installed(cmd))

def decode(d):
    '''
    对指定的二进制,进行智能解码,适配适当的编码。返回解码后的字符串。
    '''
    for k in BOM_CODE:
        if k==d[:len(k)]:
            return d[len(k):].decode(BOM_CODE[k])
    for encoding in DEFAULT_CODES:
        try:
            return d.decode(encoding)
        except:
            pass
    raise Exception('解码失败')

_Parent= pathlib.WindowsPath if os.name=='nt' else pathlib.PosixPath

class Path(_Parent):
    __slots__=()
    def __new__(cls,path='.',*args,**kwargs):
        if isinstance(path,str):
            if path.startswith('~'):  # 支持用户目录开头
                path=os.path.expanduser(path)
            elif path.startswith('%'): # 支持环境变量转义
                path=os.path.expandvars(path)
        return super().__new__(cls,path,*args,**kwargs)
    
    def read(self,*args,**kwargs):
        '''以指定的参数读取文件'''
        with self.open(*args,**kwargs)as fn:
            return fn.read()

    def ensure(self,parents=True):
        '''确保目录存在,如果目录不存在则直接创建'''
        if not self.exists():
            self.mkdir(parents=parents)
            
    @property
    def text(self):
        '''读取文件,并返回字符串'''
        return decode(self.read('rb'))

    @text.setter
    def text(self,text):
        '''写入文本文件'''
        self.write(text=text)
        
    @property
    def lines(self):
        '''按行读取文件'''
        return self.text.splitlines()

    @lines.setter
    def lines(self,lines):
        '''按行写入文件'''
        self.write(*lines)
        
    def write(self,*lines,text=None,data=None,encoding='utf8',
              parents=False):
        '''写文件'''
        if parents:
            self.parent.ensure()
        if lines:
            text="\n".join(lines)
        if text:
            data=text.encode(encoding)
        if data:
            with self.open('wb')as fn:
                fn.write(data)

    def sheets(self,index=None):
        ''' 提供读取指定worksheet的功能,其中index可以为序号,
            也可以为表的名称。'''
        import xlrd
        book=xlrd.open_workbook(filename=str(self))
        if isinstance(index,int):
            sheet=book.sheet_by_index(index)
        elif isinstance(index,str):
            sheet=book.sheet_by_name(index)
        return sheet and sheet._cell_values
        
    def iter_sheets(self):
        '''如果指定的文件为excel文件,则可以迭代读取本文件的数据。
        返回:表的索引、表名、数据'''
        import xlrd 
        book=xlrd.open_workbook(filename=str(self))
        for index,sheet in enumerate(book.sheets()):
            yield index,sheet.name,sheet._cell_values

    @property
    def xmlroot(self):
        '''如果指定的文件为xml文件,则返回本文件的根元素'''
        import lxml.etree
        return lxml.etree.parse(str(self)).getroot()
        
    def __iter__(self):
        '''根据文件的不同,迭代返回不同的内容。支持如下文件:
        1、文本文件,按行返回文本
        2、Excel文件,返回表索引、表名及表中数据
        3、目录,则返回本目录下所有文件
        4、del文件,按行返回数据。
        5、csv文件,按行返回数据。
        '''
        if self.is_dir():
            return self.glob('*')
        suffix=self.lsuffix
        if suffix.startswith('.xls'):
            return self.iter_sheets()
        elif suffix=='.xml':
            return self.xmlroot.iterchildren()
        elif suffix=='.del':
            import re
            none_pattern=re.compile(",(?=,)")
            for line in self.lines:
                if line:
                    yield eval(none_pattern.sub(",None",line))
        elif suffix=='.csv':
            import csv
            with self.open() as fn:
                yield from csv.reader(fn)

    def extractall(self,path='.',members=None):
        '''如本文件为tar打包文件,则解压缩至指定目录'''
        import tarfile
        path=str(Path(path))
        tarfile.open(str(self),'r').extractall(path,members)
        
    @property
    def lsuffix(self):
        '''返回小写的扩展名'''
        return self.suffix.lower()
        
    @property
    def pname(self):
        '''返回不带扩展名的文件名'''
        return self.with_suffix("").name
    
    def rmtree(self):
        '''删除整个目录'''
        import shutil
        shutil.rmtree(str(self))

    def chdir(self):
        if self.is_dir():
            os.chdir(str(self))

@command(description='Windows 格式文件转换为 Unix 文件格式')
@arg('files',nargs='*',help='待转换的文件',metavar='file')
def convert(files):
    for file in files:
        Path(file).lines=Path(file).lines
        print('转换文件"%s"成功'%(file))

新增功能介绍

支持家目录及目录扩展

支持自动对目录进行扩展,如果目录中包含 "~""%appdata%"等内容时,系统会自动进行扩展。其好处是显而意见的,如果不同操作系统的文件都放在相同的目录,那么我们不就用去管操作系统的差异。其使用代码如下:

import os

print(Path('~/abc')
if os.name!='posix':
    print(Path('%appdata%/abc')

增加 read 方法

用于读取指定的文件,其参数为open所需要的参数。代码示例如下:

fn=Path('~/abc.txt')
s=fn.read()
print(s)

增加 ensure 方法

对指定的目录的进行检查,如指定的目录不存在,则自动创建。代码示例如下:

Path('~/abc').ensure()

增加 text 属性

该属性用于读写文本文件的内容。示例代码如下:

Path('~/a.txt').text='This is a test.\nHello world.\n'

print(Path('~/a.txt').text)

** 注:文本文件在读取时,其编码可以由系统自动进行只别;写入时编码为 UTF8 。**

增加 lines 属性

该属性以行的方式读取或写入文本。代码如下:

lines=['This is a test.',
           'Hello world']
Path('~/a.txt').lines=lines
print(*(Path('~/a.txt').lines),sep='\n')

增加 write 方法

写入文件,可以按行写入,按文本写入、或直接写数据。示例代码如下:

lines=['This is a test.',
           'Hello world']
Path('~/a.txt').wreite(*lines)
print(*(Path('~/a.txt').lines),sep='\n')

增加 sheets 方法

如果指定的文件为 Excel 文件,根据工作表的名字或索引读取该工作表的数据。示例代码如下:

data=Path('~/abc.xlsx').sheets(0)
data=Path('~/abc.xls').sheets('Sheet1')

增加 iter_sheets 方法

for idx,name,data in Path('~/abc.xls').iter_sheets():
    print(name,idx)

增加 xmlroot 属性

如指定文件为 xml 文件,则返回该文件的根元素。

for i in Path('a.xml').xlmroot:
    print(i.tag)

增加 __iter__ 方法

根据指定目录或文件的不同返回不同的内容:

  1. 目录,则返回当前目录下所有的文或目录。示例代码如下:
for d in Path('.'):
    print(d)
  1. Excel文件,返回索引、表名及表中的数据。示例代码如下:
for idx,name,data in Path('~/abc.xlsx'):
    print(idx,name)
  1. txt文本文件。则按行返回数据。示例代码如下:
for line in Path('a.txt'):
    print(line)

增加 lsuffix 只读属性

返回指定文件的小写扩展名,便于判断文件的类型。示例代码如下:

if Path(filename).lsuffix in ('.xls','.xlsx','.xlsm'):
    print('Excel 文件')

增加 pname 只读属性

返回指定文件的文件名(不含扩展名)。示例代码如下:

print(Path('~/abc/def.txt').pname)

增加 chdir 方法

将指定的目录作为当前工作目录。示例代码如下:

Path('~/SendTo').chdir()

增加 rmtree 方法

将指定的目录全部删除。示例代码如下:

Path('~/test').rmtree()
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,591评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,732评论 6 342
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,413评论 25 707
  • 今天天刚亮就被朋友叫去陪他去医院看病,女儿和老公在家,等我回家的时候已经2:00了 老公煮了汤圆和女儿两个正在吃,...
    虹毅阅读 193评论 0 4
  • 在曹妃甸一年半的日子里,让我有所感的就是:“没工作之前每天穿不同而衣服面对相同的人,而现在变成每天穿相同的衣服面对...
    蜗汼阅读 581评论 0 2