一、Python 中的文件操作
1.1 文件操作需要了解的前置知识
1.1.1 为什么需要用到文件操作
- 如果单纯的将程序需要用到的内容直接的保存在内存中,那么程序每一次执行之后,所保存的数据就会消失,这个时候就需要使用某种方式长期的存储数据,例如保存在文件中,或者保存在数据库中。
1.1.2 对路径的划分
- 相对路径:相对于当前文件所在目录的一个路径。通常使用 . 表示当前目录,.. 表示上级目录,通过
../../ abc.txt
的指令我们可以找到上上级目录下的 abc.txt 文件 - 绝对路径:从文件的根目录开始计算的路径。在 windows 下,绝对路径通常是以盘符开头的,在 linux/unix 下绝对路径通常是以 / 开头的,例如
C:UsersminDesktopabc.txt
就是一个绝对路径。 - 如果说一个文件它的位置和当前的项目有关,就可以写作相对路径,如果这个文件和系统有关,就可以写成绝对路径,例如网页设计中的资源我们通常会写作相对路径,用于防止目录的泄露。
1.1.3 二进制文件和文本文件
- 二进制文件: 指的是拥有特定格式,需要使用对应的算法进行解析的文件,例如可执行文件(.exe)、音频文件(.mp3 .aac)、视频文件(.mp4)、图片文件(.jpg)。
- 文本文件:其中保存的直接就是字符串,只需要通过基本的文本查看工具就可以打开,不需要专门解析。
1. 2 文件的操作函数
1.2.1 文件的基本操作函数
看看https://zhuanlan.zhihu.com/p/112630994
-
open: 用于打开文件,将文件和文件对象建立关联。其中第一个参数是文件的路径,第二个参数是文件的打开方式,还有一个参数 encoding 用于说明文件的编码类型。
# 通过 open 函数可以将文件对象和一个文件建立关联 # 文件的打开方式通常有 r(读) w(写) a(追加) b(在操作二进制文件的时候使用) # 函数的返回值是一个文件对象,通过文件对象可以执行相应的文件操作 file = open(r'data.txt', 'w+')
# 通过 open 函数可以将文件对象和一个文件建立关联 # 文件的打开方式通常有 r(读) w(写) a(追加) b(在操作二进制文件的时候使用) # 函数的返回值是一个文件对象,通过文件对象可以执行相应的文件操作 try: # 使用这个写法会自动的关闭文件,不需要编写 file.close() with open(r'data.txt', 'w+') as file: file.write('hello world') except Exception as e: print(e)
-
file.close(): 用于关闭一个文件,否则可能产生问题
# 通常在文件操作完毕之后,需要关闭文件,没有正确的关闭文件可能会导致两个问 # 题,(1) 文件被占用,在当前项目中无法再次打开文件。(2) 文件的修改无法被应用 file.close()
-
file.write() file.writelines(): 向文件内写入数据
# 通过 write 一次只能写入一个字符串 file.write('hello world') # 通过 writelines 一次可以写入多个字符串 file.writelines(['hello', ' world'])
-
file.read() file.readlline() file.readlines(): 从文件内读取数据
# 读取所有内容,可以传入一个参数,表示想要读取的字符数 print(file.read()) # 读取一行内容,可以传入一个参数,表示想要读取的字符数 print(file.readline()) # 读取文本内的所有内容,将每一行作为一个元素保存到列表中 print(file.readlines())
1.2.2 文件指针操作函数
-
文件指针是什么? 文件指针用于标识当前文件的读写位置,通过移动文件指针可以改变读写的起始位置
-
seek: 设置文件的读写位置。
# 通过 seek 将文件指针移动到倒数第 4 个位置 # 0: 文件开头开始算起,1:当前位置,2: 文件末尾。 file.seek(-4, 2) print(file.read())
-
tell: 获取文件指针当前所在的位置。
# tell 函数用于获取当前文件指针的位置,可以计算文件大小 file.seek(0, 2) print(file.tell())
1.2.3 通过 os 模块操作文件
- 需要使用 import os 导入 os 模块,使用其中提供的文件操作函数来操作文件
- python 的内置文件操作使用的是面向对象的编程方式file.read(),而 os 模块使用面向过程的方式 read(file)
file = os.open('data.txt', os.O_RDONLY)
print(os.read(file, 2))
os.lseek(file, 5, os.SEEK_SET)
print(os.read(file, 2))
os.close(file)
二、面向对象编程
2.1 面向对象和面向过程
2.1.1 面向过程
- 思想:程序设计的着眼点在于处理问题的一系列过程(函数)。
- 特点:数据和处理数据的函数 分离的。
2.1.2 面向对象
-
思想:程序设计的着眼点在于解决这个问题所需要的对象有哪些,需要完成什么功能。
-
特点:封装特性,将数据和处理数据的函数封装到了一起。
-
三大特性:封装 继承 多态
2.2 类的编写
2.2.1 简单的类
- 通过 class 定义出一个类,一个类其实就是一个自定义的类型,其中描述了某个类别的对象可以实现的功能(方法)和一些描述信息(属性),通过 对象 = 类名() 的方式可以创建出一个对象。
# 简单类的定义方式,使用关键字 class
class ClassName(object):
# 类成员函数,在 python 中被称为方法
def class_func1(self):
print('def class_func(self);')
# 类成员函数,第一个参数必须是 self,后续可以含有其它的参数
def class_func2(self, value = 100):
print('def class_func(self, %d);' % value)
# 通过对象名 = 类名(参数) 的方式可以创建一个对象
object1 = ClassName()
object1.class_func1()
object1.class_func2()
2.2.2 构造函数和析构函数
-
构造函数:python 中的每一个类中都拥有一个构造函数叫做
__init__
,这个函数会被用于执行初始化操作,在一个对象被创建时,自动的调用一次。 -
析构函数: python 中的每一个类中都拥有一个析构函数叫做
__del__
,这个函数会被用于执行清理操作,在对象被删除的时候,自动的调用一次# 一个包含构造函数和析构函数的类 class ClassName2(object): # 当前类的构造函数,函数可以拥有参数,在创建对象是传入 def __init__(self, name, age): # 在构造函数内执行基本的初始化操作 print("def __init__(self, %s, %d)" % (name, age)) # 析构函数用于释放资源,但是由于 python 存在垃圾回收机制 # 所以析构函数使用的比较少,我们可以在其中关闭文件等资源 def __del__(self): # 输出表示析构函数被调用了 print("def __del__(self)") # 创建对象时需要传入参数,和构造函数的参数对应,因为创建对象时,会自动调用__init__()函数,当然也会执行里面的语句,在这里是一个print()函数打印相应的内容 object2 = ClassName2('xiaoming', 18) >>>def __init__(self, xiaoming, 18) def __del__(self) object3 = ClassName2('xiaohong', 80) >>>def __init__(self, xiaohong, 80) def __del__(self)
关于析构函数何时被调用:
析构方法__del__是对象在被垃圾回收的时候起作用的一个方法,它的执行一般也就意味着对象不能够继续引用, 回收内存.
内存回收的方式有两种:
- 1、当对象在某个作用域中调用完毕,在跳出其作用域的同时析构函数会被调用一次,这样可以用来释放内存空间:
#!/usr/bin/env python
#-*- coding: utf-8 -*-
class Foo:
#初始化方法
#创建完对象后会自动被调用
def __init__(self):
print('构造方法被调用')
#析构方法
#当对象被删除时,会自动被调用,然后释放内存
def __del__(self):
print('脚本运行结束, 释放内存')
#创建对象
f = Foo()
print('这应该是脚本最后一句.')
# 这个地方整个文件的作用域就结束了,所以对象f结束其生命周期(因为程序运行停止而结束),系统自动执行析构函数来做清理善后的工作,所以打印"脚本运行结束, 释放内存"成了最后执行的语句.
运行结果:
构造方法被调用
这应该是脚本最后一句.
脚本运行结束, 释放内存
Process finished with exit code 0
- 2、使用del 删除对象时,会调用他本身的析构函数, 相当于手动释放内存
class Foo:
#初始化方法
#创建完对象后会自动被调用
def __init__(self):
print('构造方法被调用')
#析构方法
#当对象被删除时,会自动被调用,然后释放内存
def __del__(self):
print('脚本运行结束, 释放内存')
#创建对象
f = Foo()
#删除对象
del f # 删除就立马调用析构函数,(因为del删除对象而结束,对象结束,系统就会调用析构函数)
print('这应该是脚本最后一句.')
运行结果:
构造方法被调用
脚本运行结束, 释放内存
这应该是脚本最后一句.
Process finished with exit code 0
2.2.3 实例属性和类属性
-
实例属性: 具体的某一个对象拥有的属性
-
通过 self 和 实例名称都可以添加实例属性,添加的属性是归某一个具体的实例所有的。构造函数中的一开始初始化的实例属性会被所有的实例拥有(那是因为所有的实例都会调用构造函数)
# 一个含有实例属性的类 class ClassName3(object): # 构造函数,用于初始化实例属性 def __init__(self, name, age): # 1. 为当前的实例(对象)创建两个个实例属性并进行初始化 self.age = age self.name = name # 一个方法,调用之后可以添加实例属性 def add_value(self, v): # 2. 通过 self 可以在方法内为调用当前方法的实例添加实例属性,在类外面添加属性:实例名称.属性 = value 进行赋值添加 self.value = v # 用于输出当前类内的所有属性 def print_member(self): # __dict__可以输出当前实例内的属性组成的键值对 print(self.__dict__) object4 = ClassName3('xiaoming', 80) object4.print_member() >>>{'age': 80, 'name': 'xiaoming'} object5 = ClassName3('xiaohong', 18) object5.add_value(30) object5.print_member() >>>{'age': 18, 'name': 'xiaohong', 'value': 30} # 3. 通过实例名称直接的点出属性进行赋值添加 object4.value2 = 300 object4.print_member() >>>{'age': 80, 'name': 'xiaoming', 'value2': 300}
-
类属性:归整个类所有,被所有的对象共有的属性
-
通过类名和在类内直接定义可以添加类属性,添加的属性是归所有的实例拥有的
# 一个含有类属性的类 class ClassName4(object): # 1. 在类内的一级缩进内直接定义的属性就是类属性 class_value = 10 # 构造函数,用于访问类属性 def __init__(self): # 在构造函数中可以使用下面的两种形式访问类属性 print(self.class_value) print(ClassName4.class_value) # 一个方法,调用之后可以添加类属性 def add_value(self, v): # 2. 通过 类名 可以在方法内为整个类添加一个类属性 3. 在类外也可以通过类名直接的添加属性 ClassName4.value = v # 用于输出当前类内的所有属性 def print_member(self): # 实例.__dict__ 保存的是实例内的所有属性 # 类名.__dict__ 保存的是类内的所有属性 print('实例', self.__dict__) # 因为没有实例属性,所以结果是{} print('类', ClassName4.__dict__) object5 = ClassName4() object5.print_member() >>> 10 10 实例 {} 类 {'__module__': '__main__', 'class_value': 10, '__init__': <function ClassName4.__init__ at 0x0000027B3F2414C0>, 'add_value': <function ClassName4.add_value at 0x0000027B3F241550>, 'print_member': <function ClassName4.print_member at 0x0000027B3F2415E0>, '__dict__': <attribute '__dict__' of 'ClassName4' objects>, '__weakref__': <attribute '__weakref__' of 'ClassName4' objects>, '__doc__': None} # 3. 在类外可以通过类名直接的添加属性 ClassName4.value2 = 1000 print(object5.value2) # 在类外没有这种东西:self.value3 = 2000, self只能在类里面使用 >>>1000 object5.print_member() >>> 实例 {} 类 {'__module__': '__main__', 'class_value': 10, '__init__': <function ClassName4.__init__ at 0x000001EA731D14C0>, 'add_value': <function ClassName4.add_value at 0x000001EA731D1550>, 'print_member': <function ClassName4.print_member at 0x000001EA731D15E0>, '__dict__': <attribute '__dict__' of 'ClassName4' objects>, '__weakref__': <attribute '__weakref__' of 'ClassName4' objects>, '__doc__': None, 'value2': 1000}
-
类属性的访问方式
# 类属性的访问方式(接上面一段代码的内容) object6 = ClassName4() >>> 10 10 object6.print_member() >>> 实例 {} 类 {'__module__': '__main__', 'class_value': 10, '__init__': <function ClassName4.__init__ at 0x00000259999D14C0>, 'add_value': <function ClassName4.add_value at 0x00000259999D1550>, 'print_member': <function ClassName4.print_member at 0x00000259999D15E0>, '__dict__': <attribute '__dict__' of 'ClassName4' objects>, '__weakref__': <attribute '__weakref__' of 'ClassName4' objects>, '__doc__': None} # 可以使用 类名 或 实例名 直接点出属性进行访问类 print(object6.class_value) >>>10 print(ClassName4.class_value) >>10 # 不能通过实例.类属性的方式修改属性,这样的操作会创建出一个和类属性同名的实例属性,不管是修改还是访问类属性都推荐使用类名进行操作 object6.class_value = 1515 object6.print_member() >>> 实例 {'class_value': 1515}# 果然创建出一个和类属性同名的实例属性 类 {'__module__': '__main__', 'class_value': 10, '__init__': <function ClassName4.__init__ at 0x00000261AC2314C0>, 'add_value': <function ClassName4.add_value at 0x00000261AC231550>, 'print_member': <function ClassName4.print_member at 0x00000261AC2315E0>, '__dict__': <attribute '__dict__' of 'ClassName4' objects>, '__weakref__': <attribute '__weakref__' of 'ClassName4' objects>, '__doc__': None}
2.3 self 的作用
- self 用于标识当前函数是被谁调用的,self实际上就是对象的地址 id(object) ,可以进行访问
# self 的作用
class ClassName5(object):
# 构造函数,用于初始化实例属性
def __init__(self, value):
self.value = value
# 访问实例属性的方法
def visit_value(self):
print(hex(id(self)), self.value)
# 对于每一个实例(对象)来说,函数使用的都是相同的,但是实例属性归属于每一个实例自己,函数是通过self区分当前需要用到的数据到底是属于谁的
object7 = ClassName5(100)
object8 = ClassName5(200)
object7.visit_value()
>>>0x1bb49968400 100
object8.visit_value()
>>>0x1bb49a52760 200
# 上面的函数调用可以看作 ClassName5(id(object8), 参数)
print(hex(id(object7)), type(object7))
>>>0x229bead8400 <class '__main__.ClassName5'>
print(hex(id(object8)), type(object8))
>>>0x229bebc1760 <class '__main__.ClassName5'>
1、self是什么
在python的类中self代表实例本身,具体来说,是该实例的内存地址。
在调用实例的方法时,Python解释器会自己把实例!!变量!!传给类的函数中的self。
以上述代码I为例,代码I定义了一个类Test,在这个类中,self为参数变量,在类Test实例化得到实例ins时,python解释器自动调用__init__,执行Test.init(ins, 123),该self可接收实例ins的内存地址,从而self代表了实例本身。类似的,如果实例化ins后,执行ins.fun1( ),python解释器会将ins.fun1( )解释成Test.fun1(ins)。可见,self这个变量是无需用户手动传送值的,解释器会自动帮我们给其传递实例。
需要注意的是,self不是关键字,换言之,可以用其它的合法变量名替换self,但是,规范和标准建议我们一致使用self。
2、self的使用场景
在类中,self的使用有下面3个场景:
1)self为类中的函数的第一个参数,例如在类中,def fun1(self, …)。
上文说过,“在调用实例的方法时,Python解释器会自己把实例变量传给类的函数中的self”,如果类的函数的第一个参数不是代表实例的self,则调用实例的方法时,该方法没有参数接收解释器自动传入的实例变量,从而程序会产生异常。
事实上,“和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数。除此之外,类的方法和普通函数没有什么区别,所以,你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数”(廖雪峰老师说的)。
2)在类中,引用实例的属性,示例:self.变量名(如self.val0)。
引用实例的属性的目的是为实例绑定属性、写入或读取实例的属性。
例如,在代码I中,在类的函数__ init __中,“self.val1 = val1”将属性val0绑定到了实例self(类实例化成ins后,self就代表实例ins了)上,并且将变量val1的值赋给了实例的属性val0。在函数fun1中,print(self.val0),读取了实例self的值val0,并打印出来,当然,在函数中修改属性val0的值也是可以的。
3)在类中,调用实例的方法,例如,self.fun1();获取实例的方法的地址,例如self.fun1。
类是抽象的模板,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。既然,self代表实例,则可以“self.函数名”的方式表示实例的方法地址,以“self.函数名()”的方式,调用实例的方法。在类的定义中,以及实例化后对实例方法的调用,都可以这样做。
3、python的几种变量——按作用域分
a、全局变量:在模块内、在所有函数外面、在class外面,这就是全局变量。
b、局部变量:在函数内、在class的方法内(未加self修饰的) ,这就是局部变量
c、静态变量(也可以说,类属性):在class内的,但不在class的方法内的,这就是静态变量
d、实例变量(也可以说,实例属性):在class的方法内的,用self修饰的变量,这就是实例变量
4、self和变量的关系
综合上述的1、2和3点,可以得到在类中,self和变量的关系了,一言以蔽之,被self修饰的变量是实例变量,不被self修饰的变量不是实例变量。
实例变量有什么作用,或者说,什么时候应该使用self修饰变量比较好?我的总结如下:
当我们想将某个变量绑定给实例时,就在类中,使用self修饰该变量。一般来说,类实例化为不同实例后,为了不同实例的某一变量互不干扰,就将该变量绑定给实例。
2.4 访问属性控制
2.4.1 单下划线:文件保护变量
# 当一个模块内的变量以单下划线开头,则表示这个变量不希望被其它文件访问,以 from m import * 的方式就访问不到这个变量,其它的变量可以照常访问
from module import *
# print(_protected_value)
print(normal_value)
# 通过 import 和 from m import v 的方式都可以照常访问
import module
print(module._protected_value)
from module import _protected_value
print(_protected_value)
2.4.2 双下划线:私有成员
# 通过代码来了解python 中的保护属性
class ClassName6(object):
# 以双下划线开头的是私有属性,不能被外界直接访问
__value = 12341515
# 私有属性实际就是被解释器改头换面换了名字,具体的格式如下: _ + 类名 + 双下划线开头的属性名,写成这样任然可以访问
# 改变类的私有变量的值有2种方法:
# 间接:为这个私有变量提供一个操作的方法,如:def get_score(self, score)
# 直接:实例名._类名__私有变量名 = 值, 如:f._Student__score = 10
print(ClassName6._ClassName6__value)
>>>12341515
# 在 python 中,单下划线开头的属性约定俗称通常是不需要
# 被外界访问的,在写代码或使用第三方库的时候,不要修改
# 和而访问其它模块中的属性
2.3魔术方法
__complex__(self) 定义当被 complex() 调用时的行为(需要返回恰当的值)
__int__(self) 定义当被 int() 调用时的行为(需要返回恰当的值)
__float__(self) 定义当被 float() 调用时的行为(需要返回恰当的值)
__round__(self[, n]) 定义当被 round() 调用时的行为(需要返回恰当的值)
__index__(self) 1. 当对象是被应用在切片表达式中时,实现整形强制转换
2. 如果你定义了一个可能在切片时用到的定制的数值型,你应该定义 __index__
3. 如果 __index__ 被定义,则 __int__ 也需要被定义,且返回相同的值
上下文管理(with 语句)
__enter__(self) 1. 定义当使用 with 语句时的初始化行为
2. __enter__ 的返回值被 with 语句的目标或者 as 后的名字绑定
__exit__(self, exc_type, exc_value, traceback) 1. 定义当一个代码块被执行或者终止后上下文管理器应该做什么
2. 一般被用来处理异常,清除工作或者做一些代码块执行完毕之后的日常工作
容器类型
__len__(self) 定义当被 len() 调用时的行为(返回容器中元素的个数)
__getitem__(self, key) 定义获取容器中指定元素的行为,相当于 self[key]
__setitem__(self, key, value) 定义设置容器中指定元素的行为,相当于 self[key] = value
__delitem__(self, key) 定义删除容器中指定元素的行为,相当于 del self[key]
__iter__(self) 定义当迭代容器中的元素的行为
__reversed__(self) 定义当被 reversed() 调用时的行为
__contains__(self, item) 定义当使用成员测试运算符(in 或 not in)时的行为