一、任务
在某一次应用中,需要将文本文件的编码修改为UTF-8。当我读入文本文件内容再用UTF-8编码方式写入源文件的时候,出错了。原因是文本文件是只读文件。
所以必须将文本文件去掉只读属性才能写入。这个比较简单,用os.chmod(fn, stat.S_IWRITE)即可。
有些文件具有隐藏属性,去掉隐藏属性的话,这个就不能用os.chmod了。在Windows系统中,Python需要借助于win32api才能搞定修改隐藏属性的事情。除了只读属性和隐藏属性外,我们能改变的还有系统属性和档案属性。
本文给出用win32api修改文件属性的方法。
二、环境
Win7中文旗舰版64位 + Python 3.64 64位
三、用os.chmod去掉只读属性
在硬盘上新建一个文本文件,采取系统默认的文件编码,写入“你好”二字,保存,鼠标右键添加只读属性。以下是IDLE中的操作实例。
>>> fn = r"d:\ftp\a.txt"
>>> t = open(fn).read()
>>> t
'你好'
>>> import os # 查看文件字节数
>>> os.path.getsize(fn)
4
>>> open(fn, "wt", encoding = "utf-8").write(t)
Traceback (most recent call last):
File "<pyshell#21>", line 1, in <module>
open(fn, "wt", encoding = "utf-8").write(t)
PermissionError: [Errno 13] Permission denied: 'd:\\ftp\\a.txt'
>>> import stat
>>> os.chmod(fn, stat.S_IWRITE)
>>> open(fn, "wt", encoding = "utf-8").write(t)
2
>>> t2 = open(fn).read() # 用Python读取文本文件的默认编码是"gbk"
>>> t2
'浣犲ソ'
>>> t3 = open(fn, encoding = "utf-8").read()
>>> t3
'你好'
>>> os.path.getsize(fn)
6
>>>
四、用win32api改变文件属性的原理与方法
本人没有在Python的帮助文档中找到对win32api的详细介绍,幸亏在学习C语言和C++的时候了解过一点更改文件属性的方法。
先来介绍一下Windows系统中的文件属性。在Windows中,文件总共有15种属性,按照C/C++的宏定义常量从小到大的顺序,列成下表:

说明:
1、"只读"、“隐藏”、“系统”、“存档”为文件的四种基本属性。
2、在Windows下,鼠标点右键新建的文件具有“存档”的属性。
3、文件去掉四种基本属性后,将自动具有为“正常”属性。
4、很多病毒文件同时具有“隐藏”和“系统”属性,在Windows系统中往往很难被人发现。
5、“压缩”、“索引”和“加密”只存在于NTFS分区中,且“压缩”和“加密”不能共存。
6、文件的不同属性对应的数值都是2的整数次幂,1、2、4、16都有,唯独没有8,具体什么原因也不清楚,居然没查到相关资料。
7、默认情况下,所有文件都有索引属性,注意数值为8192的这个属性是“NOT_CONTENT_INDEXED”,所以一般的文件的属性没有8192这个整数对应的二进制位。
我们通常需要获取如下五种属性,一般能改变的是除了目录属性之外的另外四种基本属性:

注意,在Python中,因为改变Windows系统中的文件属性需要用win32api,所以这些属性对应的数值在Python中是可以使用的,但Python不认识C/C++中定义的那些常量符号,而在Python中定义常量又是一件非常麻烦的事情,我们可以定义几个自己需要的变量。
添加或者去除某种属性,需要用到二进制运算的或运算和与运算这两种运算。下面定义我们需要的几个变量(假定我们需要修改的仅仅是四个基本属性,目录属性只能获取到不能修改):
ATTR_READ_ONLY = 1
ATTR_HIDDEN = 2
ATTR_SYSTEM = 4
ATTR_DIR = 16
ATTR_ARCHIVE = 32
attrdict = {ATTR_READ_ONLY: "只读",
ATTR_HIDDEN : "隐藏",
ATTR_SYSTEM : "系统",
ATTR_DIR : "目录",
ATTR_ARCHIVE : "存档"}
获取文件的属性比较简单:
import win32api
attr = win32api.GetFileAttributes(fn)
是Python的内置模块,无需安装,可直接用import来加载使用。函数win32api.GetFileAttributes(fn)的返回值是一个正整数,如果该数的某个二进制位为1,说明文件具有该二进制位对应的权重数值相应的属性。
一般情况下,我们获取的这个整数对我们来说很陌生,我们很难把它转化为二进制再逐位比较是不是具有某个属性。这时候,我们可以自己写个函数来用字符串直接告诉我们,这个文件具有什么属性。
这里刚好用到上次推文发的将一个正整数分解成2的不同次幂的和这个函数。
# 获取文件的属性,文件不存在返回-1
def my_get_file_attr(fn):
try:
r = win32api.GetFileAttributes(fn)
except:
r = -1
return r
# 将一个正整数分解成2的不同次幂的和,返回一个列表
def divid_n_into_different_powers_of_2_list(n):
if n <= 0:
return []
else:
return [2**pos for (pos, value)
in enumerate(list(bin(n)[-1:1:-1]))
if value != '0']
# 获取文件的属性的文字描述,文件不存在时返回空字符串
def my_get_file_attr_str(fn):
attr = my_get_file_attr(fn)
if attr < 0:
s = ""
else:
attrlist = divid_n_into_different_powers_of_2_list(attr)
sattr = [attrdict[item] for item in attrlist]
s = " ".join(sattr)
return s
单纯设置文件的属性也比较简单(比如设置只读属性):
win32api.GetFileAttributes(fn, 1)
这么简单的操作有一个后遗症,它在设置只读属性的同时,把所有其他的属性也都弄丢了。比如本来具有隐藏属性,添加了只读属性的同时,也去掉了隐藏属性,这是我们所不愿意看到的。我们希望,设置一个属性的时候,最好不要影响其他的属性。以只读属性为例说明一下具体操作。
添加只读属性比较容易,只需要将只读属性对应的二进制位设置为1即可,这个显然用二进制或运算比较方便:
attr = win32api.GetFileAttributes(fn)
attr = attr | ATTR_READ_ONLY
win32api.SetFileAttributes(fn, attr)
去掉只读属性需要将只读属性对应的二进制位变为0,同时不能影响文件的其他属性,这个可以用二进制与运算来实现:
attr = win32api.GetFileAttributes(fn)
attr = attr & ~ATTR_READ_ONLY
win32api.SetFileAttributes(fn, attr)
五、代码
这里给出全部代码。




六、结果
刚创建的文件的属性:存档
加上只读、系统和隐藏属性后:只读 隐藏 系统 存档
去掉只读、隐藏属性后:系统 存档




