Python使用技巧
Python:正则匹配网址中的数字
第一种利用re.match
url="https://baike.baidu.com/item/%E6%9D%8E%E7%99%BD/1043?fr=kg_hanyu"
baike_id=re.match("https://baike.baidu.com/item/(.*)/(\d+)",href).group(2)
print(baike_id)
第二种利用re.search
url="https://baike.baidu.com/item/%E6%9D%8E%E7%99%BD/1043?fr=kg_hanyu"
baike_id=re.search("https://baike.baidu.com/item/(.*)/(\d+)",href).group(2)
print(baike_id)
python3 生成指定长度的(存数字)唯一标识码/短uuid
python3 生成指定长度的唯一标识码,可以使用第三方模块:shortuuid
安装:
pip install shortuuid
github地址:https://github.com/skorokithakis/shortuuid
使用:
生成一个短的uuid
import shortuuid
shortuuid.uuid()
'vytxeTZskVKR7C7WgdSP3d'
生成指定长度id
import shortuuid
shortuuid.ShortUUID().random(length=12)
'7Pk8UpQi2Lf2'
生成指定长度纯数字id
import shortuuid
su = shortuuid.ShortUUID(alphabet="0123456789")
nid=su.random(length=12)
print(nid)
'470147326239'
python3 通过shell获取本机ip地址
python3 通过shell获取本机ip地址
import socket, subprocess
def getIpAndHostname():
hostname = socket.gethostname()
shell_cmd = "ifconfig | awk '/inet addr/{print substr($2,6)}'"
proc = subprocess.Popen([shell_cmd], stdout=subprocess.PIPE, shell=True)
(out, err) = proc.communicate()
ip_list = out.split(b'\n')
ip = ip_list[0]
for _ip in ip_list:
try:
if _ip != "127.0.0.1" and _ip.split(".")[3] != "1":
ip = _ip
except:
pass
return ip, hostname
ip_addr, hostname = getIpAndHostname()
更多方法获取本机ip地址,请参考:
python3 获取本机ip地址
第一种方法:
使用socket.gethostname()方法即可获取本机IP地址,但有时候获取不到(比如没有正确设置主机名称)
gethostname():gethostname函数检索本地计算机的标准主机名。
gethostbyname():gethostbyname函数从主机数据库中检索与主机名相对应的主机信息。
import socket
host_name=socket.gethostname()
host=socket.gethostbyname(host_name)
print(host)
'192.168.204.1'
注意:
该方法不适用于ubuntu,因为只返回127.0.0.1
第二种方法:
通过 UDP 获取本机 IP,没有任何的依赖,也不需要机器上的网络设备信息,利用 UDP 协议来实现的,生成一个UDP包,把自己的 IP 放如到 UDP 协议头中,然后从UDP包中获取本机的IP。
这个方法并不会真实的向外部发包,所以用抓包工具是看不到的。但会申请一个 UDP 的端口,所以如果经常调用也会比较耗时的,如果将查询到的IP给缓存起来,性能可以获得很大提升。
import socket
def get_ip():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
# doesn't even have to be reachable
s.connect(('10.255.255.255', 1))
IP = s.getsockname()[0]
except Exception:
IP = '127.0.0.1'
finally:
s.close()
return IP
适用于Linux,Windows和OSX
第三种:
使用第三方模块:netifaces
安装:
pip install netifaces
使用
from netifaces import interfaces, ifaddresses, AF_INET
for ifaceName in interfaces():
addresses = [i['addr'] for i in ifaddresses(ifaceName).setdefault(AF_INET, [{'addr':'No IP addr'}] )]
print '%s: %s' % (ifaceName, ', '.join(addresses))
第四种:
使用shell来获取设备的ip
import socket, subprocess
def getIpAndHostname():
hostname = socket.gethostname()
shell_cmd = "ifconfig | awk '/inet addr/{print substr($2,6)}'"
proc = subprocess.Popen([shell_cmd], stdout=subprocess.PIPE, shell=True)
(out, err) = proc.communicate()
ip_list = out.split(b'\n')
ip = ip_list[0]
for _ip in ip_list:
try:
if _ip != "127.0.0.1" and _ip.split(".")[3] != "1":
ip = _ip
except:
pass
return ip, hostname
ip_addr, hostname = getIpAndHostname()
第五种(python3.4版以上才可以使用):
利用协程特性
import asyncio
async def get_local_ip():
loop = asyncio.get_event_loop()
transport, protocol = await loop.create_datagram_endpoint(
asyncio.DatagramProtocol,
remote_addr=('8.8.8.8', 80))
result = transport.get_extra_info('sockname')[0]
transport.close()
return result
if __name__ == '__main__':
print(asyncio.run(get_local_ip()))
Python 列表/数组(list)长度不足自动补零(补齐元素)
Python 列表/数组(list)长度不足自动补零(补齐元素)
背景:
python3.8
一个三元组列表,需要拆成三个独立列表,然后每个列表长度必须满足5位
如:
li = [(0, 15, 33), (12, 15, 33), (17, 15, 33)]
希望等到结果是:
hour=[0,12,17,0,0]
minute=[15,15,15,0,0]
mode=[33,33,33,0,0]
解决思路
-
利用zip进行三元组分解
-
利用list特性
代码
li = [(0, 15, 33), (12, 15, 33), (17, 15, 33)]
final_li = list(zip(*li))
hour = list(final_li[0])
minute = list(final_li[1])
mode = list(final_li[2])
hour = hour + [0] * (5- len(hour))
minute = minute + [0] * (5 - len(minute))
mode = mode + [0] * (5- len(mode))
python3 把文件从文件夹A移动到文件夹B中
python3 把文件从文件夹A移动到文件夹B中
a_path=''
b_path=''
# 获取a_path下的文件
for (path_name, dirs, files) in os.walk(a_path):
if files: # 文件,则添加进列表
for i,file_name in enumerate(files):
print(i,file_name)
path=os.path.join(a_path,file_name)
# 如果文件在目标文件夹中,则删除该文件夹
if os.path.exists(path):
os.remove(path)
continue
shutil.move(a_path,b_path)
python3 使用正则提取书名
python3 使用正则提取书名
import re
val="《白氏长庆集》《长恨歌》《琵琶行》《卖炭翁》"
res=re.findall("《(.*?)》",val)
li=[]
for i in res:
li.append(i)
print(li)
# 输出
['白氏长庆集', '长恨歌', '琵琶行', '卖炭翁']
python3 正则提取年月日、年月、年思路
python3 正则提取年月日、年月、年思路
使用到知识点:
-
[] 用来表示一组字符串
-
() 对正则表达式分组并记住匹配的文本
-
[0-9] 匹配任何数字
-
\d 匹配一个数字字符,等价于[0-9]
-
? 匹配0或者2个 非贪婪模式
-
| 或
提取的文本:
1961年5月27日
1961年05月01日
1961年03月05日
1961年11月10日
3000年01月30日
1111年8月27日
1345年09月27日
1574年12月27日
1961年10月12日
61年
1年2月
888年12月
长安元年(701年)
思路:
# 匹配数字年
year_pattern="(\d{1,4}年)"
# 匹配月 月比较特殊 可能会出现01月 1月这两种情况 还有10月,12月
month_pattern="(([0?][1-9])月)|(([1?][0-2])月)|([1-9]月)"
# 匹配日 保证匹配的数字是1-31,暂未考虑单双月和2月 闰年
day_pattern="([0?][1-9]日)|([1?][0-9]日)|([2?][1-9]日)|([3][0-1]日)"
date_pattern="(\d{1,4}年)((([0?][1-9])月)|(([1?][0-2])月)|([1-9]月)?)(([0?][1-9]日)|([1?][0-9]日)|([2?][1-9]日)|([3][0-1]日)?)"
import re
def get_date(val):
res=re.search(date_pattern,val)
if res:
return res.group()
return None
日期正则表达式构建规则分析
写复杂正则的一个常用方法,就是先把不相关的需求拆分开,分别写出对应的正则,然后组合,检查一下相互的关联关系以及影响,基本上就可以得出对应的正则。
按闰年的定义可知,日期可以有几种分类方法。
根据天数是否与年份有关划分为两类
与年份无关的一类中,根据每月天数的不同,又可细分为两类
-
1、3、5、7、8、10、12月为1-31日
-
4、6、9、11月为1-30日
与年份有关的一类中
-
平年2月为1-28日
-
闰年2月为1-29日
根据包含日期不同可划分为四类
-
所有年份的所有月份都包含1-28日
-
所有年份除2月外都包含29和30日
-
所有年份1、3、5、7、8、10、12月都包含31日
-
闰年2月包含29日
正则实现
针对每一个规则写出对应的正则,以下暂按xx年xx月xx日格式进行实现。
先考虑与年份无关的前三条规则,年份可统一写作
((?!0000)[0-9]{1,4}年)
下面仅考虑月和日的正则
包括平年在内的所有年份的月份都包含1-28日
(0?[1-9]|1[0-2])月(0?[1-9]|1[0-9]|2[0-8])日
包括平年在内的所有年份除2月外都包含29和30日
(0?[13-9]|1[0-2])月(29|30)日
包括平年在内的所有年份1、3、5、7、8、10、12月都包含31日
(0?[13578]月|1[02]月)(31日)
python3 中文繁体转换简体,简体转换为繁体,汉字转换拼音
SnowNLP是一个python写的类库,可以方便的处理中文文本内容,是受到了TextBlob的启发而写的,由于现在大部分的自然语言处理库基本都是针对英文的,于是写了一个方便处理中文的类库,并且和TextBlob不同的是,这里没有用NLTK,所有的算法都是自己实现的,并且自带了一些训练好的字典。注意本程序都是处理的unicode编码,所以使用时请自行decode成unicode。
github
https://github.com/isnowfy/snownlp
汉字转换拼音样例代码:
from snownlp import SnowNLP
s = SnowNLP(u'这个东西真心很赞')
s.pinyin # [u'zhe', u'ge', u'dong', u'xi',
# u'zhen', u'xin', u'hen', u'zan']
繁体转换简体样例代码:
from snownlp import SnowNLP
s = SnowNLP(u'「繁體字」「繁體中文」的叫法在臺灣亦很常見。')
s.han
# u'「繁体字」「繁体中文」的叫法在台湾亦很常见。'
python小技巧:读取大文件 一行一行读取
python小技巧:读取大文件 一行一行读取
正确Pythonic读取文件方法如下:
with open("x.txt") as f:
for line in f:
do something with data
原因:with语句用于处理文件的打开和关闭,包括是否在内部块中引发异常。 f中的for行将文件对象f视为可迭代对象,它自动使用缓冲的I/O和内存管理,因此您不必担心大文件。
这是python最好一种读取大文件方法。
python基础线程-管理并发线程
线程模块建立在线程的底层特性之上,使线程的工作变得更简单、更像python。使用线程允许程序在同一进程空间中并发运行多个操作。
线程对象
使用线程最简单的方法是用目标函数实例化它,然后调用start()让它开始工作。
import threading
def worker():
"""线程worker函数"""
print('Worker')
return
threads = []
for i in range(5):
t = threading.Thread(target=worker)
threads.append(t)
t.start()
结果:输出为五行,每行上都有“Worker”
$ python3 threading_simple.py
Worker
Worker
Worker
Worker
Worker
能够生成一个线程并传递参数来告诉它要做什么工作是很有用的。这个例子传递一个数字,然后线程打印这个数字。
import threading
def worker(num):
"""线程worker函数"""
print('Worker: {num}' )
return
threads = []
for i in range(5):
t = threading.Thread(target=worker, args=(i,))
threads.append(t)
t.start()
现在每个线程打印的消息包含一个数字:
$ python3 -u threading_simpleargs.py
Worker: 0
Worker: 1
Worker: 2
Worker: 3
Worker: 4
确定当前线程
使用参数来标识或命名线程很麻烦,而且没有必要。每个Thread实例都有一个具有默认值的名称,该名字可以在创建线程时更改。命名线程在具有多个服务线程来处理不同操作的服务器进程中很有用。
import threading
import time
def worker():
print(threading.currentThread().getName(), '开始运行')
time.sleep(2)
print(threading.currentThread().getName(), '结束运行')
def my_service():
print(threading.currentThread().getName(), '开始运行')
time.sleep(3)
print(threading.currentThread().getName(), '结束运行')
t = threading.Thread(name='my_service', target=my_service)
w = threading.Thread(name='worker', target=worker)
w2 = threading.Thread(target=worker) # 使用默认名字
w.start()
w2.start()
t.start()
调试输出包括每行上当前线程的名称。“线程名称”列中带有“Thread-1”的行对应于未命名的线程w2。
$ python -u threading_names.py
worker Thread-1 开始运行
my_service 开始运行
开始运行
Thread-1worker 结束运行
结束运行
my_service 结束运行
大多数程序不使用打印进行调试。logging支持使用格式化程序代码%(threadName)在每个日志消息中嵌入线程名称。在日志消息中包含线程名称可以更容易地将这些消息追溯到其源。
import logging
import threading
import time
logging.basicConfig(level=logging.DEBUG,
format='[%(levelname)s] (%(threadName)-10s) %(message)s',
)
def worker():
logging.debug('开始运行')
time.sleep(2)
logging.debug('结束运行')
def my_service():
logging.debug('开始运行')
time.sleep(3)
logging.debug('结束运行')
t = threading.Thread(name='my_service', target=my_service)
w = threading.Thread(name='worker', target=worker)
w2 = threading.Thread(target=worker) # 使用默认名字
w.start()
w2.start()
t.start()
logging是线程安全的,因此来自不同线程的消息在输出中保持不同。
$ python threading_names_log.py
[DEBUG] (worker ) 开始运行
[DEBUG] (Thread-1 ) 开始运行
[DEBUG] (my_service) 开始运行
[DEBUG] (worker ) 结束运行
[DEBUG] (Thread-1 ) 结束运行
[DEBUG] (my_service) 结束运行
守护程序与非守护程序线程
到目前为止,示例程序已隐式地等待退出,直到所有线程都完成了它们的工作。有时程序生成一个线程作为守护进程运行,而该线程在运行时不会阻止主程序退出。使用守护进程线程对于那些可能无法轻松中断线程或让线程在其工作过程中死亡不会丢失或损坏数据的服务(例如,为服务监视工具生成“心跳”的线程)非常有用。要将线程标记为守护程序,请使用布尔参数调用其setDaemon()方法。默认情况下,线程不是守护程序,因此传递True将打开守护程序模式。
import threading
import time
import logging
logging.basicConfig(level=logging.DEBUG,
format='(%(threadName)-10s) %(message)s',
)
def daemon():
logging.debug('开始运行')
time.sleep(2)
logging.debug('结束运行')
d = threading.Thread(name='daemon', target=daemon)
d.setDaemon(True)
def non_daemon():
logging.debug('开始运行')
logging.debug('结束运行')
t = threading.Thread(name='non-daemon', target=non_daemon)
d.start()
t.start()
请注意,输出不包括来自守护进程线程的“结束运行”消息,因为所有非守护进程线程(包括主线程)都在守护进程线程从其两秒钟的睡眠中唤醒之前退出。
$ python threading_daemon.py
(daemon ) 开始运行
(non-daemon) 开始运行
(non-daemon) 结束运行
要等到守护进程线程完成其工作,请使用join()方法。
import threading
import time
import logging
logging.basicConfig(level=logging.DEBUG,
format='(%(threadName)-10s) %(message)s',
)
def daemon():
logging.debug('开始运行')
time.sleep(2)
logging.debug('结束运行')
d = threading.Thread(name='daemon', target=daemon)
d.setDaemon(True)
def non_daemon():
logging.debug('开始运行')
logging.debug('结束运行')
t = threading.Thread(name='non-daemon', target=non_daemon)
d.start()
t.start()
d.join()
t.join()
等待守护线程使用join()退出意味着它有机会生成“结束运行”消息。
$ python threading_daemon_join.py
(daemon ) 开始运行
(non-daemon) 开始运行
(non-daemon) 结束运行
(daemon ) 结束运行
默认情况下,join()无限期阻塞。也可以传递一个超时参数(一个浮点数,表示等待线程变为非活动状态的秒数)。如果线程没有在超时时间内完成,join()仍然返回。
import threading
import time
import logging
logging.basicConfig(level=logging.DEBUG,
format='(%(threadName)-10s) %(message)s',
)
def daemon():
logging.debug('开始运行')
time.sleep(2)
logging.debug('结束运行')
d = threading.Thread(name='daemon', target=daemon)
d.setDaemon(True)
def non_daemon():
logging.debug('开始运行')
logging.debug('结束运行')
t = threading.Thread(name='non-daemon', target=non_daemon)
d.start()
t.start()
d.join(1)
# python2写法
print ('d.isAlive()', d.isAlive())
# python3写法
print ('d.is_alive()', d.is_alive())
t.join()
由于传递的超时值小于守护进程线程的睡眠时间,因此join()返回后,线程仍然是“活动的”。
$ python threading_daemon_join_timeout.py
(daemon ) 开始运行
(non-daemon) 开始运行
(non-daemon) 结束运行
d.isAlive() True
d.is_alive() True
枚举所有线程
没有必要保留所有守护进程线程的显式句柄,以确保它们在退出主进程之前已完成。enumerate()返回活动线程实例的列表。该列表包含当前线程,并且由于不允许加入当前线程(这会导致死锁情况),因此必须跳过它。
import random
import threading
import time
import logging
logging.basicConfig(level=logging.DEBUG,
format='(%(threadName)-10s) %(message)s',
)
def worker():
"""线程worker函数"""
t = threading.currentThread()
pause = random.randint(1,5)
logging.debug('sleeping %s', pause)
time.sleep(pause)
logging.debug('ending')
return
for i in range(3):
t = threading.Thread(target=worker)
t.setDaemon(True)
t.start()
main_thread = threading.currentThread()
for t in threading.enumerate():
if t is main_thread:
continue
logging.debug(f'joining { t.getName()}')
t.join()
由于worker睡眠的时间是随机的,所以这个程序的输出可能会有所不同。应该是这样的:
$ python threading_enumerate.py
(Thread-1 ) sleeping 3
(Thread-2 ) sleeping 2
(Thread-3 ) sleeping 5
(MainThread) joining Thread-1
(Thread-2 ) ending
(Thread-1 ) ending
(MainThread) joining Thread-3
(Thread-3 ) ending
(MainThread) joining Thread-
子类化线程
在启动时,线程进行一些基本的初始化,然后调用其run()方法,该方法调用传递给构造函数的目标函数。若要创建Thread的子类,请重写run()以执行任何必要的操作。
import threading
import logging
logging.basicConfig(level=logging.DEBUG,
format='(%(threadName)-10s) %(message)s',
)
class MyThread(threading.Thread):
def run(self):
logging.debug('running')
return
for i in range(5):
t = MyThread()
t.start()
忽略run()的返回值。
$ python threading_subclass.py
(Thread-1 ) running
(Thread-2 ) running
(Thread-3 ) running
(Thread-4 ) running
(Thread-5 ) running
因为传递给Thread构造函数的args和kwargs值保存在私有变量中,因此不容易从子类访问它们。要将参数传递给自定义线程类型,请重新定义构造函数以将值保存在实例属性中,该属性可以在子类中看到。
import threading
import logging
logging.basicConfig(level=logging.DEBUG,
format='(%(threadName)-10s) %(message)s',
)
class MyThreadWithArgs(threading.Thread):
def __init__(self, group=None, target=None, name=None,
args=(), kwargs=None, verbose=None):
threading.Thread.__init__(self, group=group, target=target, name=name,
verbose=verbose)
self.args = args
self.kwargs = kwargs
return
def run(self):
logging.debug('running with %s and %s', self.args, self.kwargs)
return
for i in range(5):
t = MyThreadWithArgs(args=(i,), kwargs={'a':'A', 'b':'B'})
t.start()
MyThreadWithArgs与Thread使用相同的API,但是与其他任何类一样,另一个类可以轻松地更改构造函数方法以采用与线程目的更直接相关的更多或不同的参数。
$ python threading_subclass_args.py
(Thread-1 ) running with (0,) and {'a': 'A', 'b': 'B'}
(Thread-2 ) running with (1,) and {'a': 'A', 'b': 'B'}
(Thread-3 ) running with (2,) and {'a': 'A', 'b': 'B'}
(Thread-4 ) running with (3,) and {'a': 'A', 'b': 'B'}
(Thread-5 ) running with (4,) and {'a': 'A', 'b': 'B'}
计时器线程
Timer提供了一个将Thread子类化的原因的示例,它也包含在线程中。计时器在延迟之后开始工作,并且可以在该延迟时间段内的任何时间取消。
import threading
import time
import logging
logging.basicConfig(level=logging.DEBUG,
format='(%(threadName)-10s) %(message)s',
)
def delayed():
logging.debug('worker running')
return
t1 = threading.Timer(3, delayed)
t1.setName('t1')
t2 = threading.Timer(3, delayed)
t2.setName('t2')
logging.debug('starting timers')
t1.start()
t2.start()
logging.debug('waiting before canceling %s', t2.getName())
time.sleep(2)
logging.debug('canceling %s', t2.getName())
t2.cancel()
logging.debug('done')
请注意,第二个计时器从不运行,而第一个计时器似乎在主程序的其余部分完成后运行。因为它不是守护进程线程,所以当主线程完成时,它是隐式连接的。
$ python threading_timer.py
(MainThread) starting timers
(MainThread) waiting before canceling t2
(MainThread) canceling t2
(MainThread) done
(t1 ) worker running
线程间信令
虽然使用多个线程的目的是分离出单独的操作以并发运行,但有时能够在两个或多个线程中同步这些操作是很重要的。线程之间通信的一个简单方法是使用事件对象。事件管理内部标志,调用者可以set()或clear()。其他线程可以wait()设置set(),有效地阻止进程,直到允许继续。
import logging
import threading
import time
logging.basicConfig(level=logging.DEBUG,
format='(%(threadName)-10s) %(message)s',
)
def wait_for_event(e):
"""Wait for the event to be set before doing anything"""
logging.debug('wait_for_event starting')
event_is_set = e.wait()
logging.debug('event set: %s', event_is_set)
def wait_for_event_timeout(e, t):
"""Wait t seconds and then timeout"""
while not e.isSet():
logging.debug('wait_for_event_timeout starting')
event_is_set = e.wait(t)
logging.debug('event set: %s', event_is_set)
if event_is_set:
logging.debug('processing event')
else:
logging.debug('doing other work')
e = threading.Event()
t1 = threading.Thread(name='block',
target=wait_for_event,
args=(e,))
t1.start()
t2 = threading.Thread(name='non-block',
target=wait_for_event_timeout,
args=(e, 2))
t2.start()
logging.debug('Waiting before calling Event.set()')
time.sleep(3)
e.set()
logging.debug('Event is set')
wait()方法在等待表示时间的参数之前占用了秒数。它返回一个布尔值,指示是否设置了事件,因此调用者知道wait()返回的原因。isSet()方法可以单独用于事件,而不必担心阻塞。
在本例中,wait_for_event_timeout()检查事件状态,不会无限期阻塞。wait_for_event()会阻塞对wait()的调用,直到事件状态更改后才会返回。
$ python threading_event.py
(block ) wait_for_event starting
(non-block ) wait_for_event_timeout starting
(MainThread) Waiting before calling Event.set()
(non-block ) event set: False
(non-block ) doing other work
(non-block ) wait_for_event_timeout starting
(MainThread) Event is set
(block ) event set: True
(non-block ) event set: True
(non-block ) processing event
参考:https://github.com/ant-design/ant-design
python 检查路径是否存在(合法) 如果不存在在创建文件
python 检查路径是否存在(合法) 如果不存在在创建文件
调用下面定义的is_path_exists_createable()函数。
两个问题的故事
“如何测试路径名的有效性,对于有效的路径名,如何测试这些路径的存在性或可写性?”显然是两个独立的问题。这两个问题都没有得到令人满意的答案。
最简单方法:
try:
open(filename,'r')
return True
except IOError:
try:
open(filename, 'w')
return True
except IOError:
return False
但有一个显著的缺点:
-
不必要地打开(…然后无法可靠地关闭)文件句柄。
-
不必要地写入(…然后无法可靠地关闭或删除)0字节文件。
-
忽略操作系统特定的错误,区分不可忽略的无效路径名和可忽略的文件系统问题。不出所料,这在Windows下非常关键。(见下文。)
-
忽略外部进程同时(重新)移动要测试的路径名的父目录而导致的争用条件。(见下文。)
-
忽略由于路径名驻留在过时、缓慢或暂时无法访问的文件系统上而导致的连接超时。这可能会使面向公众的服务暴露于潜在的DoS驱动的攻击。(见下文。)
我们会搞定的。
问题1:什么是路径名有效性?
我们也许应该定义一下我们所说的“路径名有效性”是什么意思,确切地说,有效性怎么定义?
“路径名有效性”是指路径名相对于当前系统的根文件系统的语法正确性,而不管该路径或其父目录是否实际存在。如果路径名符合根文件系统的所有语法要求,那么它在这个定义下语法上是正确的。
“根文件系统”是指:
-
在与POSIX兼容的系统上,文件系统已安装到根目录(/)。
-
在Windows上,文件系统挂载到%HOMEDRIVE%,%HOMEDRIVE%包含当前Windows安装(通常但不一定是C :),后缀为冒号。
反过来,“语法正确性”的含义取决于根文件系统的类型。 对于ext4(以及大多数但并非所有POSIX兼容的)文件系统,路径名称在且仅当该路径名称在语法上正确:
-
不包含空字节(即Python中的\ x00)。 这是所有POSIX兼容文件系统的硬性要求。
-
包含不超过255个字节的路径成分(例如,Python中的“ a” * 256)。 路径部分是路径名中最长的子字符串,不包含/字符(例如,路径名/bergtatt/ind/i/fjeldkamrene中的bergtatt,ind,i和fjeldkamrene)。
句法正确性。 根文件系统。 而已。
import os
import sys
import errno
# Sadly, Python fails to provide the following magic number for us.
ERROR_INVALID_NAME = 123
'''
Windows特定的错误代码,指示路径名无效
See Also
----------
https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
Official listing of all such codes.
'''
def is_pathname_valid(pathname: str) -> bool:
'''
`True` 如果传递的路径名是当前操作系统的有效路径名;
`False` .
'''
# 如果此路径名不是字符串或为空,则此路径名无效
try:
if not isinstance(pathname, str) or not pathname:
return False
# 删除此路径名的Windows特定驱动器说明符(例如,`C:\`)
# 如果有的话。由于Windows禁止路径组件包含`:`字符,
# 如果未能删除此后缀前缀“:”,则所有有效的绝对路径名无效。
_, pathname = os.path.splitdrive(pathname)
# 目录保证存在。如果当前操作系统是Windows,
# 则这是安装Windows的驱动器(例如,“%HOMEDRIVE%”环境变量);
# 否则是典型的根目录。
root_dirname = os.environ.get('HOMEDRIVE', 'C:') \
if sys.platform == 'win32' else os.path.sep
assert os.path.isdir(root_dirname) # ...Murphy and her ironclad Law
# 如果需要,请将路径分隔符附加到此目录.
root_dirname = root_dirname.rstrip(os.path.sep) + os.path.sep
# 测试从该路径名拆分的每个路径组件是否有效,忽略不存在且不可读的路径组件。
for pathname_part in pathname.split(os.path.sep):
try:
os.lstat(root_dirname + pathname_part)
# 如果引发特定于操作系统的异常,则其错误代码将指示此路径名是否有效。
# 除非是这种情况,否则此异常意味着可忽略的内核或文件系统投诉(例如,路径找不到或无法访问)。
#
# 只有以下例外情况表示路径名无效:
#
# * Windows特定的“WindowsError”类的实例,
# 该类定义的“winerror”属性的值为“ERROR_INVALID_NAME”。
# 在Windows下,“winerror”比一般的“errno”属性更细粒度,因此更有用。
# 当传递的路径名太长时,例如,“errno”是“enoint”(即没有这样的文件或目录)
# 而不是“ENAMETOOLONG”(即文件名太长)。
# * 定义泛型“errno”属性的跨平台“OSError”类的实例,其值为::
# * 在大多数与POSIX兼容的操作系统下,“ENAMETOOLONG”。.
# * 在某些边缘情况下,OSE(例如SunOS,*BSD),“ERANGE”.
except OSError as exc:
if hasattr(exc, 'winerror'):
if exc.winerror == ERROR_INVALID_NAME:
return False
elif exc.errno in {errno.ENAMETOOLONG, errno.ERANGE}:
return False
# If a "TypeError" exception was raised, it almost certainly has the
# error message "embedded NUL character" indicating an invalid pathname.
except TypeError as exc:
return False
# 如果引发了“TypeError”异常,那么几乎可以肯定的是,
# 它会显示错误消息“embedded NUL character”指示路径名无效。
else:
return True
# 如果引发了任何其他异常,则这是一个不相关的致命问题(例如,bug)。允许此异常展开调用堆栈。
参考: