1. 元类的应用
使用元类给对象添加一个固有属性author:
对类名进行限定,要求类名必须是大写字母开头:
class MetaC(type):
def __init__(cls, name, bases, attrs):
if not name.istitle():
raise TypeError("类名必须是大写字母开头~")
return type.__init__(cls, name, bases, attrs)
限制类实例化时只能传递关键字参数:
class MetaC(type):
def __call__(cls, *args, **kwargs):
if args:
raise TypeError("仅支持关键字参数~")
return type.__init__(cls, name, bases, attrs)
禁止类进行实例化:
class NoInstance(type):
def __call__(cls, *args, **kwargs):
raise TypeError("该类不允许被实例化对象!")
不允许该类创建对象,那么可以使用静态方法或者类方法来使用该类:
只允许类实例化一个对象:
class SimpleInstance(type):
def __init__(cls, *args, **kwargs):
cls.__instance = None
return type.__init(cls, *args, **kwargs)
def __call__(cls, *args, **kwargs):
if cls.__instance is None:
cls.__instance = type.__call__(cls, *args, **kwargs)
return cls.__instance
可以看到这个唯一的对象保存在类的_SimpleInstance__instance的变量里。
2. 抽象基类
抽象基类不能被实例化,只能被继承,其子类必须实现其定义的抽象方法。
from abc import ABCMeta, abstractmethod
class Fruit(metaclass=ABCMeta):
def __init__(self, name):
self.name = name
@abstractmethod
def good_for_health(self):
pass
要想定义一个抽象基类,只需要指定其metaclass为ABCMeta即可,要定义一个抽象方法,使用abstractmethod装饰器即可。
class Banana(Fruit):
def good_for_health:
print("~")
3. 模块和包
一个.py文件就是一个模块,导入模块的三种方式(假设模块名为hello):
import hello
# 此时调用模块函数,使用hello.say_hi()、hello.say_hello()
from hello import say_hi, say_hello
# 此时可能会有命名冲突的问题,即两个模块有同名函数/对象,那么后导入的会覆盖先导入的
import hello as h
# 通过as命名别名可以解决命名冲突的问题,此时调用函数使用h.say_hi()、h.say_hello()
模块在导入过程中,会从头到尾执行一遍模块的所有代码,因此如果不使用if __name__ == "__main__":将一些测试语句包裹起来,那么在导入模块的时候会执行一些没必要的代码。 因为当一个.py文件/一个模块被当做脚本(而不是导入)被独立执行的时候,它的__name__属性就会被赋值为“__main__”。如果一个.py文件/一个模块被导入的时候,它的__name__属性就是模块的名称。
python的包是多个模块的集合,是一个文件夹,里面有必须的一个__init__.py文件,标识这个文件夹是一个python包。导入模块的方式变为:import 包名.模块名。
可以对__init__.py文件进行修改,用于对包进行初始化的操作:
print(f"__init__.py 被调用,此时 __name__ 的值是 {__name__}")
此时打印的是包名。导入包的模块,模块中的__name__的值是包名.模块名。
可以在__init__.py文件里定义全局变量,但是包里的模块不能直接访问这个全局变量(可以编写函数访问全局变量,但是运行该模块会报错),要通过包外的其他模块引用该模块,才可以进行访问。
例如在__init__.py文件中添加两个全局变量:
包内的模块尝试访问x:
这是因为包内模块是看不到这个包的。在包外模块访问包的全局变量是ok的
在__init__.py里还可以导入包里的某个模块,这样其他模块在导入这个包的时候就会自动导入模块:
导入模块时,使用from 模块名 import *会造成命名空间的污染,为此,python提供了__all__属性来解决这一问题,通过__all__属性来指定特定的模块内的变量和函数才能被导入:
__all__ = ["say_hello", "x"]
x = 250
s = "FishC"
def say_hello():
print("Hello FishC")
def say_hi():
print("Hi FishC")
此时,s和say_hi不能被访问到
但是如果不是使用from hello import *,而是使用import hello as h,则可以正常访问:
__all__属性也可以应用在包的__init__.py文件中,现在有一个FC包,里面有三个模块:fc1.py,fc2.py,fc3.py,以及一个空的__init__.py文件,在另一个文件中导入该包:
使用dir()函数可以获取当前作用域的变量和方法,结果如下:
可以看到,并未得到fc1,fc2,fc3三个模块,但是如果在__init__.py里使用__all__属性:
然后在另一个模块中就可以使用fc1和fc2这两个模块了:
总结:对于模块来说,如果没有使用__all__属性,使用from 模块名 import *的方式导入,将导入模块的所有内容;对于包来说,如果__init__.py没有使用__all__属性,使用from 包名 import *的方式导入,将不会导入包里的任何模块。
4. 文件操作
打开一个文件:
f = open('FishC.txt', 'w')
此时会创建一个文件FishC.txt,并且可以对这个文件进行写入:
f.write("I love Python")
f.writelines(["I love FishC\n", "I love my wife."])
f.close()
write方法是写入一个字符串,不会换行。writelines写入的是可迭代对象,注意,该方法也不会换行。文件如果操作完毕,需要调用close方法进行关闭。
使用r+的模式打开文件,既可以读,又可以写:
f = open("FishC.txt", "r+")
f.readable() #返回True
f.writable() #返回True
for item in f:
print(f)
此时,如果使用f.read()方法再去读取该文件,会得到空字符串,因为此时f的文件指针已经指到文件末尾了。使用f.tell()可以查看文件指针的位置:
f.tell() # 44
f.seek(0) # 将文件指针移到指定位置,此时移到文件开头
f.readline() # 读取一行
f.read() # 读取到文件结束的位置(EOF)
使用flush方法可以在不关闭文件的情况下,将内容写到文件(计算机磁盘)中。
使用truncate方法将文件截取到指定位置,即之后的内容(包括指定位置)全部丢弃:
f.truncate(29)
如果打开文件的模式是'w',并且该文件存在,那么文件原来内容会被全部清空。
open函数总是需要调用close函数,但是文件操作过程中发生异常,那么可能会导致文件对象没有close。针对这个问题,可以使用with语句(上下文管理器)来解决:
with open("FishC.txt", "w") as f:
f.write("I love FishC.")
使用pickle模块的dump方法可以将python对象(包括字符串、列表、字典等)序列化,即转化为二进制格式:
import pickle
x, y, z = 1, 2, 3
s = "FishC"
l = ["小甲鱼", 520, 3.14]
d = {"one": 1, "two": 2}
with open("data.pkl", "wb") as f:
pickle.dump(x, f)
pickle.dump(y, f)
pickle.dump(z, f)
pickle.dump(s, f)
pickle.dump(l, f)
pickle.dump(d, f)
使用pickle模块的load方法可以将pickle文件反序列化为Python对象,注意读取顺序为序列化时写入的顺序(即先写入的先读出来,后写入的后读出来):
import pickle
with open("data.pkl", "rb") as f:
x = pickle.load(f)
y = pickle.load(f)
z = pickle.load(f)
s = pickle.load(f)
l = pickle.load(f)
d = pickle.load(f)
print(x, y, z, s, l, d, sep="\n")
上述对象有点多,使用元组的方式在写入和读取时打包解包可以简化代码:
import pickle
x, y, z = 1, 2, 3
s = "FishC"
l = ["小甲鱼", 520, 3.14]
d = {"one": 1, "two": 2}
with open("data.pkl", "wb") as f:
pickle.dump((x, y, z, s, l, d), f)
with open("data.pkl", "rb") as f:
x, y, z, s, l, d = pickle.load(f)
print(x, y, z, s, l, d, sep="\n")
5. 使用pathlib操作文件路径
pathlib是Python3.4之后才有的包,用于替代os.path。从pathlib中导入Path:
from pathlib import Path
使用cwd方法获取当前路径:
Path是一个类,传入字符串就可以获得一个Path对象:
将Path对象用反斜杠和字符串进行拼接,就可获得一个新的Path对象:
判断Path对象是否为一个文件/文件夹:
判断路径/文件是否存在:
通过Path对象的name属性可以获取文件/文件夹的名字,即最后一个反斜杠后面的内容:
通过Path对象的stem属性可以获取文件的文件名(不带后缀):
通过Path对象的suffix属性可以获取文件的后缀:
通过Path对象的parent属性可以获取文件/文件夹的上一级目录:
通过Path对象的parent属性可以获取文件/文件夹的各级目录,以元组的形式存储:
通过Path对象的stat()方法可以获取文件/文件夹的信息,比如stat方法返回的对象中有个属性st_size表示大小(单位为字节):
通过Path对象的resolve()方法可以将相对路径转换为绝对路径:
通过Path对象的iterdir()方法获取当前目录下的所有子文件和子文件夹:
通过Path对象的mkdir()方法创建目录:
如果要创建的目录已存在,调用mkdir方法会报错。如果想要忽略报错,关键字参数exist_ok设为True即可:
如果创建的目录的父目录不存在,也会报错:
如果想要将不存在的父目录也创建,parent参数设置为True即可:
通过Path对象的open()方法打开文件,其中可以指定操作模式,比如'w',open方法返回一个文件对象,可以对其进行读写操作:
通过Path对象的rename()方法进行重命名:
注意此时只传了一个新文件名字符串,没有传入路径,文件将会被移动到当前工作目录(cwd()方法可以查看当前工作目录)。
通过Path对象的rmdir()方法和unlink()方法删除文件夹和文件:
如果目录不为空,调用rmdir()方法会报错
通过Path对象的glob()方法来查找文件:
6. 将程序发布到PyPI上
将程序发布到PyPI上,将可以让世界上所有的程序员通过pip下载你的代码。具体步骤请参考:模块和包(下)_哔哩哔哩_bilibili