Python易忽略要点记录二

为天地立心,为生民立命,为往圣继绝学,为万世开太平。

类的基本注意点

私有变量

1.变量名如果以双下划线__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。

2.如果外部代码要获取相应的私有变量,可定义get_xxx方法,如果想修改,定义set_xxx方法。

3.在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,自己定义时不要使用这种命名方法。

4.看一个例子:

class Student(object):

    def __init__(self, name, score):
        self.__name = name
        self.__score = score

    def get_name(self):
        return self.__name

    def get_score(self):
        return self.__score

    def print_score(self):
        print('%s: %s' % (self.__name, self.__score))

bart = Student('Bart Simpson', 59)
print(bart.get_name())

# 非规范方法修改私有变量,不会提示出错。
bart.__name = 'New Name' # 设置__name变量!
print(bart.__name)

print(bart.get_name())

# 打印出
# Bart Simpson
# New Name
# Bart Simpson

表面上看,外部代码“成功”地设置了__name变量,但实际上这个__name变量和class内部的__name变量不是一个变量!内部的__name变量已经被Python解释器自动改成了_Student__name,而外部代码给bart新增了一个__name变量,通过最后一个打印可以看出。

5.注意:不建议这么做,因为不同版本的Python解释器可能会把__name改成不同的变量名。

python单下划线与双下划线

1._xxx:不能用于from module import *以单下划线开头的表示的是保护变量,只能允许其本身与子类进行访问。
2.__xxx:双下划线的表示的是私有类型的变量(private),只能是允许这个类本身进行访问了,连子类也不可以
3.__xxx__:定义的是特列方法,如__init____str__,__call__

4.python中不存在protected的概念,上面所说的保护变量,应该把其看作私有变量使用,之所以称为保护变量是为了便于与private类型做区分。

5.一个例子:

class A(object):
    def __init__(self, name):
        self._name = name

    def get_name(self):
        return self._name

# 这个就是格式化后再当做参数传入对应属性
a = A('%s/%s' % ('dsad','abc'))

print(a.get_name()) # dsad/abc
# 下面的可以访问,不过根据python的约定,应该将其视作private,
# 不要在外部使用它们,良好的编程习惯是不要在外部使用它。
print(a._name) # dsad/abc

继承和多态

1.继承最大的好处是子类获得了父类的全部功能。

2.当子类和父类都存在相同的run()方法时,子类的run()会覆盖了父类的run(),在代码运行的时候,总是会调用子类的run()。

3.在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类,可用isinstance(object, class)查看。

4.多态:对于一个变量,我们只需要知道它是Animal类型,无需确切地知道它的子类型,就可以放心地调用run()方法,而具体调用的run()方法是作用在Animal、Dog、Cat还是Tortoise对象上,由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:

  • 对扩展开放:允许新增Animal子类;
  • 对修改封闭:不需要修改依赖Animal类型的run_twice()等函数。

5.动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的。

获取对象信息

1.判断基本数据类型可以直接写int,str等,但如果要判断一个对象是否是函数,可以使用types模块中定义的常量:

>>> type(123)==type(456)
True
>>> type(123)==int
True
>>> type('abc')==type('123')
True
>>> type('abc')==str
True
>>> import types
>>> def fn():
...     pass
...
>>> type(fn)==types.FunctionType
True
>>> type(abs)==types.BuiltinFunctionType
True
>>> type(lambda x: x)==types.LambdaType
True
>>> type((x for x in range(10)))==types.GeneratorType
True

2.使用isinstance(),且推荐使用它。

>>> isinstance('a', str)
True
>>> isinstance(123, int)
True
# 并且还可以判断一个变量是否是某些类型中的一种,比如下面的代码就可以判断是否是list或者tuple:
>>> isinstance([1, 2, 3], (list, tuple))
True
>>> isinstance((1, 2, 3), (list, tuple))
True

3.使用dir()

  • 要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法:
>>> dir('abc')
['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__',
'__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__',
'__getslice__', '__gt__', '__hash__', '__init__', '__le__', '__len__', 
'__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', 
'__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', 
'__sizeof__', '__str__', '__subclasshook__', '_formatter_field_name_split',
'_formatter_parser', 'capitalize', 'center', 'count', 'decode', 'encode', 
'endswith', 'expandtabs', 'find', 'format', 'index', 'isalnum', 'isalpha',
'isdigit', 'islower', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 
'lower', 'lstrip', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 
'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 
'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
  • 类似__xxx__的属性和方法在Python中都是有特殊用途的,比如__len__方法返回长度。在Python中,如果你调用len()函数试图获取一个对象的长度,实际上,在len()函数内部,它自动去调用该对象的__len__()方法,所以,下面的代码是等价的:
>>> len('ABC')
3
>>> 'ABC'.__len__()
3
  • 剩下的都是普通属性或方法,比如upper()返回小写的字符串:
>>> 'abc'.upper()
'ABC'
  • 仅仅把属性和方法列出来是不够的,配合getattr()、setattr()以及hasattr(),我们可以直接操作一个对象的状态:
>>> class MyObject(object):
...     def __init__(self):
...         self.x = 9
...     def power(self):
...         return self.x * self.x
...
>>> obj = MyObject()
>>> hasattr(obj, 'x') # 有属性'x'吗?
True
>>> obj.x
9
>>> hasattr(obj, 'y') # 有属性'y'吗?
False
>>> setattr(obj, 'y', 19) # 设置一个属性'y'
>>> hasattr(obj, 'y') # 有属性'y'吗?
True
>>> getattr(obj, 'y') # 获取属性'y'
19
>>> obj.y # 获取属性'y'
19
  • 如果试图获取不存在的属性,会抛出AttributeError的错误,可以传入一个default参数,如果属性不存在,就返回默认值:
>>> getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404
404
  • 通过内置的一系列函数,我们可以对任意一个Python对象进行剖析,拿到其内部的数据。要注意的是,只有在不知道对象信息的时候,我们才会去获取对象信息。如果可以直接写:
sum = obj.x + obj.y
# 就不要写成
sum = getattr(obj, 'x') + getattr(obj, 'y')

实例属性和类属性

1.由于Python是动态语言,根据类创建的实例可以任意绑定属性。

2.如果Student类本身需要绑定一个属性,可以直接在class中定义属性,这种属性是类属性,归Student类所有:

>>> class Student(object):
...     name = 'Student'
...
>>> s = Student() # 创建实例s
>>> print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性
Student
>>> print(Student.name) # 打印类的name属性
Student
>>> s.name = 'Michael' # 给实例绑定name属性
>>> print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性
Michael
>>> print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问
Student
>>> del s.name # 如果删除实例的name属性
>>> print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
Student

3.从上面的例子可以看出,在编写程序的时候,千万不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性(这里很容易出错,曾在这里困扰半天)

面向对象高级编程

使用__slots__

1.可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。

class Student(object):
    pass
# 添加属性
s = Student()
s.name = 'Bob' # 动态给实例绑定一个属性
print(s.name) # Bob

# 添加方法
def set_age(self, age): # 定义一个函数作为实例方法
    self.age = age
from types import MethodType
s.set_age = MethodType(set_age, s) # 给实例绑定一个方法
s.set_age(25) # 调用实例方法
print(s.age) # 测试结果 25

2.给一个实例绑定的方法,对另一个实例不起作用,为了给所有实例都绑定方法,可以给class绑定方法(这里没有讲给类绑定属性,因为方法和给实例绑定属性类似,只需把实例名改成相应的类名即可):

 def set_score(self, score):
    self.score = score
Student.set_score = set_score
# 但这种几乎不用,需要这样的时候直接在类中写即可。当然,如果如果是动态创建类,可能还是会用这种方法。

3.上面对实例的属性添加十分灵活,如果想限制,为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性

class Student(object):
    __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称

>>> s = Student() # 创建新的实例
>>> s.name = 'Michael' # 绑定属性'name'
>>> s.age = 25 # 绑定属性'age'
>>> s.score = 99 # 绑定属性'score'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'

由于'score'没有被放到__slots__中,所以不能绑定score属性,试图绑定score将得到AttributeError的错误。

使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的:

>>> class GraduateStudent(Student):
...     pass
...
>>> g = GraduateStudent()
>>> g.score = 9999

除非在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__

使用@property

1.对属性值做相应的限制,通过其可使相应的方法看作属性使用。

把一个getter方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,就拥有一个可控的属性操作:

class Student(object):

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be an integer!')
        if value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100!')
        self._score = value

s = Student()
s.score = 60 # OK,实际转化为s.set_score(60)
print(s.score) # OK,实际转化为s.get_score()
# s.score = 999 # 出错

2.若定义只读属性,只定义getter方法,不定义setter方法就是一个只读属性,详情

3.@property广泛应用在类的定义中,可以让调用者写出简短的代码,同时保证对参数进行必要的检查,这样,程序运行时就减少了出错的可能性。

多重继承

Python允许使用多重继承,因此,MixIn就是一种常见的设计。

枚举

1.enum 提供了Enum/IntEnum/unique 三个工具,可以通过继承Enum/IntEnum定义枚举类型。
其中IntEnum限定枚举成员必须为(或可以转化为)整数类型,而unique方法可以作为修饰器限定枚举成员的值不可重复。

2.实例

from enum import Enum, IntEnum, unique

# 例子1:
try:
    @unique
    class WEEKDAY(Enum):
        MON = 1
        TUS = 2
        WEN = 3
        THU = 4
        FRI = 1
except ValueError as e:
    print(e)  # 出错duplicate values found in <enum 'weekday'="">: FRI -> MON
print('********************************分割线**************************************')


# 例子2
class Color(Enum):
    R = 0
    G = 1
    B = 2

# 可以赋值给变量,但不可不可实例化,如Color.R = 3就是错的
red = Color(0)
green = Color(1)
blue = Color(2)

print(red, green, blue)

# 可以进行比较判断
print(red is Color.R)
print(red == Color.R)
print(red is blue)
print(green != Color.B)
print(red == 0)  # 不等于任何非本枚举类的值

# 由于枚举成员本身也是枚举类型,因此也可以通过枚举成员找到其它成员
print(red.B)
print(red.B.G.R)

# 通过属性访问
print(red.name, ':', red.value)

# 遍历访问
for color in Color:
    print(color.name, '=>', color.value)

# 如果要遍历Enum中带有重复的枚举成员,可能会用到__members__方法,注意,这里没有重复的
for name, member in Color.__members__.items():
    print(name, '=>', member, ',', member.value)

3.可参考:Python 中的枚举类型

元类(metaclass)

动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。

1.这个概念比较深,而且不太好理解,个人感觉未能很好地理解,下面是看到的很好的文章。

2.运用实例:元类的主要用途是创建API,一个典型的例子是Django ORM,下面是源码链接。

3.需要注意的是,Python2和Python3中的用法形式不同,这点可以认真看看原文和上面的源代码例子。


错误、调试和测试

错误处理

1.Python使用try...except...finally...进行错误处理。需要注意的是如果在except后又else语句,若没有错误则会执行else的语句,反之则不执行。

2.记录错误,Python内置的logging模块可以非常容易地记录错误信息。通过配置,logging可以把错误记录到日志文件里,方便事后排查。

3.抛出错误,捕获错误目的只是记录一下,便于后续追踪。但是,由于当前函数不知道应该怎么处理该错误,所以,最恰当的方式是继续往上抛,让顶层调用者去处理。Python中使用raise语把当前错误抛出。

调试

1.直接用print()查看结果调试,麻烦且效率不高。

2.使用断言(assert)如果断言失败,assert语句本身就会抛出AssertionError

# 使用断言
def foo(s):
    n = int(s)
    # assert的意思是,表达式n != 0应该是True,否则,抛出AssertionError
    assert n != 0, 'n is zero!'
    return 10 / n

foo('0') #运行结果 AssertionError: n is zero!

3.把print()替换为logging是第3种方式,和assert比,logging不会抛出错误,而且可以输出到文件。

4.第4种方法是使用pdb,启动Python的调试器pdb,让程序以单步方式运行,可以随时查看运行状态。

  • 命令行模式下运行某py文件python -m pdb xxx.py
  • 以参数-m pdb启动后,pdb定位到下一步要执行的代码,有->指示,可输入命令l来查看源码
  • 输入命令n可以单步执行代码
  • 任何时候都可以输入命令p 变量名来查看变量值,如查看变量s,则为p s
  • 输入命令q结束调试,退出程序。

5.方法太笨重了些,可以改进:使用pdb.set_trace()

  • 这个方法也是用pdb,但是不需要单步执行,只需要import pdb,然后,在可能出错的地方放一个pdb.set_trace(),就可以设置一个断点。
  • 运行代码,程序会自动在pdb.set_trace()暂停并进入pdb调试环境,可以用命令p查看变量,或者用命令c继续运行

6.使用IDE,效率较高,如Visual Studio Code,pycharm等。

单元测试

1.单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。

2.为了编写单元测试,需要引入Python自带的unittest模块,编写单元测试时,需要编写一个测试类,从unittest.TestCase继承。

3.以test开头的方法就是测试方法,不以test开头的方法不被认为是测试方法,测试的时候不会被执行,对每一类测试都需要编写一个test_xxx()方法。

4.unittest.TestCase提供了很多内置的条件判断,只需要调用这些方法就可以断言输出是否是所期望的。最常用的断言就是assertEqual()

self.assertEqual(abs(-1), 1) # 断言函数返回的结果与1相等

另一种重要的断言就是期待抛出指定类型的Error,如KeyError,AttributeError等。

5.运行

  • 方法一:加上两行代码,然后正常运行。
if __name__ == '__main__':
    unittest.main()
  • 方法二:在命令行通过参数-m unittest直接运行单元测试,如python -m unittest py文件名

6.可以在单元测试中编写两个特殊的setUp()tearDown()方法。这两个方法会分别在每调用一个测试方法的前后分别被执行。

文档测试

1.Python内置的“文档测试”doctest模块可以直接提取注释中的代码并执行测试。

2.doctest严格按照Python交互式命令行的输入和输出来判断测试结果是否正确。只有测试异常的时候,可以用...表示中间一大段烦人的输出。

3.doctest非常有用,不但可以用来测试,还可以直接作为示例代码。通过某些文档生成工具,就可以自动把包含doctest的注释提取出来。用户看文档的时候,同时也看到了doctest

4.一个例子,对函数fact(n)编写doctest并执行

def fact(n):
    '''
    Calculate 1*2*...*n

    >>> fact(1)
    1
    >>> fact(10) # 不用自己算,运行时出错会有结果,到时再改即可
    3628800
    >>> fact(-1) # doctest严格按照Python交互式命令行的输入和输出来判断测试结果是否正确。只有测试异常的时候,可以用...表示中间一大段烦人的输出。
    Traceback (most recent call last):
      ...
    ValueError
    '''
    if n < 1:
        raise ValueError()
    if n == 1:
        return 1
    return n * fact(n - 1)

if __name__ == '__main__':
    import doctest
    doctest.testmod()

5.什么输出也没有。这说明编写的doctest运行都是正确的。如果程序有问题,比如fact(10)下的3628800去掉,再运行就会报错。

IO编程

1.IO编程中,Stream(流)是一个很重要的概念,可以把流想象成一个水管,数据就是水管里的水,但是只能单向流动。

  • Input Stream就是数据从外面(磁盘、网络)流进内存
  • Output Stream就是数据从内存流到外面去。

对于浏览网页来说,浏览器和服务器之间至少需要建立两根水管,才可以既能发数据,又能收数据。

2.由于CPU和内存的速度远远高于外设的速度,所以,在IO编程中,就存在速度严重不匹配的问题,于是出现了两种模式:

  • 同步IO
  • 异步IO

3.操作IO的能力都是由操作系统提供的,每一种编程语言都会把操作系统提供的低级C接口封装起来方便使用,Python也不例外。

文件读写

1.Python中有open(),write(),read()等函数用于文件读写,但用完后得
close()

2.可以直接使用with open() as f自动调用close()方法简洁高效。

3.关于读文件内容常用的几个方法:

  • read()会一次性读取文件的全部内容,如果文件太大,会出错,所以,要保险起见,可以反复调用read(size)方法,每次最多读取size个字节的内容。
  • readline()可以每次读取一行内容
  • readlines()一次读取所有内容并按行返回list

4.file-like Object

open()函数返回的这种有个read()方法的对象,在Python中统称为file-like Object。除了file外,还可以是内存的字节流,网络流,自定义流等等。file-like Object不要求从特定类继承,只要写个read()方法就行。StringIO就是在内存中创建的file-like Object,常用作临时缓冲。

5.一些例子

# 写
# f = open("file.txt", 'a', encoding='utf-8', errors='ignore')
# f.write("I try to write something new to file\n")
# f.close()

# 推荐用法
# with open("file.txt", 'a', encoding='utf-8', errors='ignore') as f:
#     for i in range(6):
#         f.write("I try to write something new to file\n")


# 读
f = open("file.txt", 'r', encoding='utf-8', errors='ignore')  # 默认以只读的形式打开
print(f.readline())  # 因为\n是写入的,所以打印出来时加上每一行的回车,即两个回车
print("当前指针位置", f.tell()) # tell 方法获取当前文件读取指针的位置
print(f.readline().strip()) # 可以用strip()将行末尾的换行符除去
print("当前指针位置", f.tell())
print(f.read())
print("读取当前指针位置", f.tell())
# seek 方法,用于移动文件读写指针到指定位置,有两个参数,第一个offset: 偏移量,需要向前或向后的字节数,正为向后,负为向前;
# 第二个whence: 可选值,默认为0,表示文件开头,1表示相对于当前的位置,2表示文件末尾
# 注意,关于偏移量,只有在以'b'(二进制)的条件下才可用,不然只可以为0。
f.seek(0, 0)
print("读取当前指针位置", f.tell())
f.seek(0, 2)
print("读取当前指针位置", f.tell())
f.seek(0)
print("读取当前指针位置", f.tell())
print(f.closed)
print(f.mode)
f.close()
print(f.closed)
# print(f.truncate(10))

print('**********************************分割线***********************************')

# 推荐用法
with open('file.txt', mode='r', encoding='utf-8', errors='ignore') as f:
    f = f.readlines()
    print(f)

    for each in f:
        print(each, end='  ') # two space

6.所有模式的定义及含义可以参考Python的官方文档

StringIO和BytesIO

1.StringIO顾名思义就是在内存中读写str。

  • 要把str写入StringIO,需要先创建一个StringIO,然后,像文件一样写入即可:
from io import StringIO

f = StringIO()
f.write('Hello World')
s = f.readline()
print(s) # 这里读不出来,是因为文件读写指针已经到文件末尾了,可用getvalue()来读
print(f.getvalue())
# 或者先设置文件指针位置
f.seek(0)
s = f.readline()
print(s)
  • 也可以用一个str初始化StringIO,然后,像读文件一样读取
f = StringIO('abc')
print(f.read())

2.StringIO操作的只能是str,如果要操作二进制数据,就需要使用BytesIO。

  • BytesIO实现了在内存中读写bytes,创建一个BytesIO,然后写入一些bytes:
f = BytesIO()
f.write('中文'.encode('utf-8'))
print(f.getvalue())
  • 和StringIO类似,可以用一个bytes初始化BytesIO,然后,像读文件一样读取
f = BytesIO(b'abc')
print(f.read())

3.StringIO和BytesIO是在内存中操作str和bytes的方法,使得和读写文件具有一致的接口。

操作文件和目录

1.Python的os模块封装了操作系统的目录和文件操作,注意这些函数有的在os模块中,有的在os.path模块中。

2.记的话,有些难,用时看文档:操作系统接口模块

3.几个小例子:

# 列出当前目录下的所有目录:
[x for x in os.listdir('.') if os.path.isdir(x)]
# 列出当前目录下所有的.py文件:
[x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py']

序列化

1.把变量从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling,序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling。

2.Python提供了pickle模块来实现序列化,相关操作如下:

import pickle

d = dict(name='Bob', age=20, score=88)
# pickle.dumps()方法把任意对象序列化成一个bytes,然后,就可以把这个bytes写入文件。
print('序列化后的bytes为:', pickle.dumps(d))

# 或者使用pickle.dump()直接把对象序列化后写入一个file-like Object
f = open('dump.txt', 'wb')
pickle.dump(d, f)
f.close()

# 把对象从磁盘读到内存时,可以先把内容读到一个bytes,然后用pickle.loads()方法反序列化出对象。
# 也可以直接用pickle.load()方法从一个file-like Object中直接反序列化出对象。

f = open('dump.txt', 'rb')
d = pickle.load(f)
f.close()
print('反序列化后的内容为:', d)

# 输出为:
# 序列化后的bytes为: b'\x80\x03}q\x00(X\x04\x00\x00\x00nameq\x01X\x03\x00\x00\x00Bobq\x02X\x03\x00\x00\x00ageq\x03K\x14X\x05\x00\x00\x00scoreq\x04KXu.'
# 反序列化后的内容为: {'name': 'Bob', 'age': 20, 'score': 88}

3.JSON

  • Pickle的问题和所有其他编程语言特有的序列化问题一样,就是它只能用于Python,而JSON对象序列化是标准格式,可以被所有语言读取,也可以方便地存储到磁盘或者通过网络传输。
  • Python内置的json模块提供了非常完善的Python对象到JSON格式的转换。
  • 使用方法:和pickle使用方法类似,如下:
import json

d = dict(name='Bob', age=20, score=88)
# dumps()方法返回一个str,内容就是标准的JSON。
print('序列化后的bytes为:', json.dumps(d))

# 类似的,dump()方法可以直接把JSON写入一个file-like Object。
# 注意,这里是以'w'的形式写,因为dump()方法返回的是str
f = open('dumps.txt', 'w')
json.dump(d, f)
f.close()

# 要把JSON反序列化为Python对象,用loads()或者对应的load()方法,
# 前者把JSON的字符串反序列化,后者从file-like Object中读取字符串并反序列化:
f = open('dumps.txt', 'r')
d = json.load(f)
f.close()
print('反序列化后的内容为:', d)

# 输出为:
# 序列化后的bytes为: {"name": "Bob", "age": 20, "score": 88}
# 反序列化后的内容为: {'name': 'Bob', 'age': 20, 'score': 88}
  • 序列化对象:
import json

class Student(object):
    def __init__(self, name, age, score):
        self.name = name
        self.age = age
        self.score = score

s = Student('Bob', 20, 88)

# print(json.dumps(s))
# 可选参数default就是把任意一个对象变成一个可序列为JSON的对象,
# 只需要为Student专门写一个转换函数,再把函数传进去即可:
def student2dict(std):
    return {
        'name': std.name,
        'age': std.age,
        'score': std.score
    }

# 如果要把JSON反序列化为一个Student对象实例,loads()方法首先转换出一个dict对象,
# 然后,我们传入的object_hook函数负责把dict转换为Student实例:
def dict2student(d):
    return Student(d['name'], d['age'], d['score'])

# 这样即可序列化对象
print(json.dumps(s, default=student2dict))

# 或者直接这样,因为通常class的实例都有一个__dict__属性,它就是一个dict,用来存储实例变量。
# 也有少数例外,比如定义了__slots__的class。
print(json.dumps(s, default=lambda obj: obj.__dict__))
print(s.__dict__)

# 在反序列化时必须要有相应的方法
json_str = json.dumps(s, default=student2dict)
json_str1 = json.dumps(s, default=lambda obj: obj.__dict__)
print(json.loads(json_str, object_hook=dict2student))
print(json.loads(json_str1, object_hook=dict2student))

# 输出为:
# {"name": "Bob", "age": 20, "score": 88}
# {"name": "Bob", "age": 20, "score": 88}
# {'name': 'Bob', 'age': 20, 'score': 88}
# <__main__.Student object at 0x000001B58BC54630>
# <__main__.Student object at 0x000001B58BC546D8>
posted @ 2021-12-12 16:56  煊奕  阅读(11)  评论(0编辑  收藏  举报