一、面向对象
-
面向对象是一种抽象化的编程思想,面向对象作用就是简化代码。面向对象就是将编程当成一种事物,对于外界事物可直接使用而不用管它内部的具体实现步骤,而编程就是设置这个事物可以做什么。
-
面向对象编程中两个重要组成部分:类,对象。
1.组成
1)类
- 变量:类中叫属性
- 函数:类中叫方法
- 对象:类通过实例化产生对象
- 实例:对象是类的实例
python中一切皆为对象,python类本身也是一种对象,我们可以称其为类对象。对象=属性+方法,对象是类的实例。类是对一系列具有相同特征和行为的事物的统称,是一个抽象化概念,不是真实存在的事物。类主要是定义对象的结构,以类为模板创建对象。类不但包含方法定义,还包含所有实例共享的数据。
确定需要哪些类以及这些类应包含哪些方法时,尝试像下面这样做。
- 将有关问题的描述(程序需要做什么)记录下来,并给所有的名词、动词和形容词加上标记。
- 在名词中找出可能的类。
- 在动词中找出可能的方法。
- 在形容词中找出可能的属性。
- 将找出的方法和属性分配给各个类。
2)对象
类是一系列对象的集合,先有类,再有对象。类是抽象的,对象是具体的。类和类之间行为不同,对象与对象之间数据不同。
类 | 对象 |
---|---|
奔驰车 | 我家的这一辆奔驰车 |
狗 | 大黄狗、你家的大黄狗 |
苹果 | 我嘴里吃了一口的这个苹果 |
callable()
callable函数是Python内置函数之一,用于判断一个对象是否可调用。如果对象可以被调用,返回True;否则返回False。可调用对象包括函数、方法、类以及实现了__call__
方法的对象。
callable(object)
# object为要判断的对象。
type()
返回对象类型
print(type(23))
# <class 'int'>
2.实现
1)定义类
注意:类名要满足标识符规则,同时遵循大驼峰命名规则。
# 定义函数
def 函数名():
函数体
# 定义类
class 类名():
代码
举例:
class Washer: # Washer写成Washer()会显得冗余
def wash(self): # 实例方法
print('洗衣服=====')
2)创建对象
# 语法:
对象名 = 类名()
举例:
# 创建类
class Washer:
def wash(self): # 实例方法
print('洗衣服=====')
# 创建对象
haier = Washer()
# print(haier)
# 验证功能
# 使用washer功能 在类中叫实例方法/对象方法 ---- 对象名.实例方法名()
haier.wash()
3)self
self是调用改实例方法的对象。
class Washer:
def wash(self):
print('洗衣服')
print(self) # self指的是调用该方法的对象
haier = Washer() # self = haier
print(haier)
haier.wash()
# <__main__.Washer object at 0x0000025093E1B5B0>
# 洗衣服
# <__main__.Washer object at 0x0000025093E1B5B0>
haier1 = Washer()
print(haier1)
haier1.wash()
# <__main__.Washer object at 0x0000025093E1A0E0>
# 洗衣服
# <__main__.Washer object at 0x0000025093E1A0E0>
3.添加对象属性
对象属性可以在类里面添加,也可以在类外面添加和获取。
1)类外添加
# 语法:
对象名.属性名 = 值
举例:
class Washer:
def __init__(self):
self.width = None
self.height = None
def wash(self):
print('洗衣服')
haier = Washer()
haier.wash()
# 添加属性
haier.height = 1000 # 高
haier.width = 500 # 宽
# 获取属性
print("洗衣机的高度是:", haier.height)
print("洗衣机的宽度是:", haier.width)
# 洗衣服
# 洗衣机的高度是: 1000
# 洗衣机的宽度是: 500
2)类内添加
class Washer:
def __init__(self):
self.width = None
self.height = None
def wash(self):
print('洗衣服')
def print_info(self):
"""获取属性"""
print("洗衣机的高度是:", self.height)
print("洗衣机的宽度是:", self.width)
haier2 = Washer()
haier2.wash() # 洗衣服
# 添加属性
haier2.height = 1000 # 高
haier2.width = 500 # 宽
haier2.print_info()
# 洗衣机的高度是: 1000
# 洗衣机的宽度是: 500
# 获取属性
print("洗衣机的高度是:", haier2.height)
print("洗衣机的宽度是:", haier2.width)
# 洗衣机的高度是: 1000
# 洗衣机的宽度是: 500
4.类的命名空间
在class语句中定义的代码都是在一个特殊的命名空间(类的命名空间)内执行的,而类的所有成员都可访问这个命名空间。在类定义中,并非只能包含def语句。
class MemberCounter:
members = 0
def init(self):
MemberCounter.members += 1
m1 = MemberCounter()
m1.init()
print(MemberCounter.members)
# 1
m2 = MemberCounter()
m2.init()
print(MemberCounter.members)
# 2
# 每个实例都可访问这个类作用域内的变量,就像方法一样。
print(m1.members)
# 2
print(m2.members)
# 2
# 在实例中赋值
m1.members = 'three'
print(m1.members)
# three
print(m2.members)
# 2
二、对象魔法
- 封装
- 将属性和方法书写到类里面的操作即是封装
- 封装可以为我们的属性和方法添加私有权限
- 继承
- 子类默认继承父类的所有属性和方法
- 子类可以重写父类同名属性和方法
- 多态
- 传入不同的对象,产生不同的效果
1.继承
Python面向对象中的继承就是指的是多个类之间一个从属关系,即子类默认继承父类的所有方法和属性。慎用继承,尤其是多重继承。继承有时很有用,但在有些情况下可能带来不必要的复杂性。要正确地使用多重继承很难,要排除其中的bug更难。
继承的特点:子类默认拥有父类的所有属性和方法,除了私有属性和私有方法。
-
经典类
不由任意内置类型派生出的类,称之为经典类
class 类名: 代码
-
新式类
class 类名(object): 代码 # 在Python中,所有类默认继承自object类,object类是顶级类或者说是基类;其他子类叫派生类。
# 定义父类
class A(object): # "(object)"通常省略
def __init__(self):
self.age = 1
def print_info(self):
print(self.age)
# 定义子类
class B(A):
pass
res = B()
res.print_info() # 1
print(res.age) # 1
1)超类
超类是指 2层以上的继承关系,假如C类继承B类,B类由继承A类,那么A类就是C类的超类
指定超类:子类可以扩展超类的定义,将其他类名写在class语句后的圆括号内可以指定超类,此时圆括号内的类即为超类。
# 在指定列表中过滤掉self.blocked中的元素
class Filter:
def init(self):
self.blocked = []
def filter(self, sequence):
return [x for x in sequence if x not in self.blocked]
# 指定列表的值
class SPAMFilter(Filter): # SPAMFilter是Filter的子类
def init(self): # 重写超类Filter的方法init
self.blocked = ['SPAM']
2)方法
self
-
在类中self代表实例本身,既该实例内存地址。调用实例的方法时把实例变量传给类的函数中的self。
-
self不是关键字,可用其它合法变量名替换self,但规范和标准建议一致使用self。
上述代码定义一个类A,self为参数变量,在类A实例化得到实例res时自动调用
__init__
,执行res.print_info(),该self可接收实例res的内存地址,从而self代表了实例本身。所以self变量无需手动传值。
self的使用场景:
- self为类中的函数的第一个参数
如果类的函数的第一个参数不是代表实例的self,则调用实例的方法时,该方法没有参数接收解释器自动传入的实例变量,从而程序会产生异常。和普通的函数相比在类中定义的函数第一个参数永远是实例变量self,且调用时不用传递该参数。之外类的方法和普通函数没有什么区别,所以仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。self通常代指子类的实例对象。
- 在类中引用实例的属性
self.变量名(如self.val())。引用实例的属性的目的是为实例绑定属性、写入或读取实例的属性。例如,在上述代码中,在类的函数__init__
中,“self.age = 1”将属性age绑定到了实例self(类实例化成res后,self就代表实例res了)上,并且将变量age的值赋给了实例的属性res。
- 在类中调用实例的方法
类是抽象的模板,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。既然,self代表实例,则可以“self.函数名”的方式表示实例的方法地址,以“self.函数名()”的方式,调用实例的方法。在类的定义中,以及实例化后对实例方法的调用,都可以这样做。
issubclass()
确定一个类是否是另一个类的子类,可使用内置方法issubclass。
print(issubclass(SPAMFilter, Filter))
# True
print(issubclass(Filter, SPAMFilter))
# False
__bases__
了解一个类的基类可访问其特殊属性__bases__
。
print(SPAMFilter.__bases__)
# (<class '__main__.Filter'>,)
print(Filter.__bases__)
# (<class 'object'>,)
isinstance()
确定对象是否是特定类的实例,可使用isinstance,返回True表示含有但不一定完全相同,既无法验证是否有相同的作用。
s = SPAMFilter()
print(isinstance(s, SPAMFilter))
# True
print(isinstance(s, Filter))
# True
print(isinstance(s, str))
# False
3)继承方式
单继承
# 定义父类
class Master:
def __init__(self):
self.name = "师傅"
self.skill = "师傅的第一个技能"
def teach(self):
print(f"{self.name}展示了{self.skill}。")
# 定义子类
class Prentice(Master):
pass
son = Prentice() # 用子创建对象,调用父类的属性和方法
print(son.skill) # 子类对象调用父类的属性
son.teach() # 子类对象调用父类方法
# 师傅的第一个技能
# 师傅展示了师傅的第一个技能。
print(son.teach()) # print()为函数需要返回值。son.teach()无返回值,所以执行完son.teach()后打印None
# 师傅展示了师傅的第一个技能。
# None
- 总结:
- 子类在继承的时候,在定义类的时候,小括号中写的就是父类的名字
- 父类的方法,属性都会被子类继承
多继承
所谓多继承就是指一个类同时继承多个类。
# 定义父类
class Master:
def __init__(self):
self.name = "师傅"
self.skill = "师傅的技能"
def teach(self):
print(f"{self.name}展示了{self.skill}。")
# 定义另一个父类类
class MasterBrother:
def __init__(self):
self.name = '师傅的兄弟'
self.skill = '师傅兄弟的技能'
def teach(self):
print(f"{self.name}展示了{self.skill}。")
# 定义子类
class Prentice(Master, MasterBrother):
pass
son = Prentice() # 用子类创建对象,调用父类的属性和方法
print(son.skill) # 子类对象调用父类的属性
son.teach() # 子类对象调用父类方法
print(son.name)
# 师傅的技能
# 师傅展示了师傅的技能。
# 师傅
说明:
- 多继承可以继承多个父类,也继承了所有父类的属性和方法
- 注意:如果多个父类中有同名的属性和方法,则默认使用第一个父类的属性和方法(根据类中魔法属性mro的顺序来查找的)
- 多个父类中,不同名的属性和方法,不会有任何影响。
多层继承
既父类继承给子类,子类再继承给下一个子类。
class Master:
def __init__(self):
self.name = "师傅"
self.skill = "师傅的技能"
def teach(self):
print(f"{self.name}展示了{self.skill}。")
class MasterBrother:
def __init__(self):
self.name = '师傅的兄弟'
self.skill = '师傅兄弟的技能'
def teach(self):
print(f"{self.name}展示了{self.skill}。")
class Prentice(Master, MasterBrother):
def __init__(self):
self.name = '徒弟'
self.skill = '徒弟的技能'
def teach(self):
print(f"{self.name}展示了{self.skill}。")
def master_skill(self):
Master.__init__(self)
Master.teach(self) # 一旦出现"类名.__init__(self)",则同名方法优先使用此类
MasterBrother.teach(self)
def master_brother_skill(self):
MasterBrother.__init__(self)
MasterBrother.teach(self)
Master.teach(self)
class PrenticeSon(Prentice):
pass
son = PrenticeSon()
son.master_skill()
# 师傅展示了师傅的技能。
# 师傅展示了师傅的技能。
son.teach()
# 师傅展示了师傅的技能。
print(son.name)
# 师傅
print()
son.master_brother_skill()
# 师傅的兄弟展示了师傅兄弟的技能。
# 师傅的兄弟展示了师傅兄弟的技能。
son.teach()
# 师傅的兄弟展示了师傅兄弟的技能。
print(son.name)
# 师傅的兄弟
print()
son.__init__()
son.teach()
# 徒弟展示了徒弟的技能。
print(son.name)
# 徒弟
多重继承
class Calculator:
def __init__(self):
self.value = None
def calculate(self, expression):
self.value = eval(expression)
class Talker:
def __init__(self):
self.value = None
def talk(self):
print('Hi, my value is', self.value)
# 子类TalkingCalculator所有行为都从超类继承。
class TalkingCalculator(Calculator, Talker):
pass
tc = TalkingCalculator()
tc.calculate('1 + 2 * 3')
tc.talk()
# Hi, my value is 7
应避免使用多重继,在有些情况下可能会带来意外的“并发症”。使用多重继承务必注意:如果多个超类以不同的方式实现了同一个方法(即有多个同名方法 ),必须在class语句中小心排列这些超类,因为位于前面的类的方法将覆盖位于后面的类的方法。
如果Calculator类包含方法talk,那么这个方法将覆盖Talker类的方法talk(导致它不可访问 )。如果像下面这样反转超类的排列顺序:
class TalkingCalculator(Talker, Calculator):
pass
将导致Talker的方法talk是可以访问的。多个超类的超类相同时,查找特定方法或属性时访问超类的顺序称为方法解析顺序(MRO)。
# 在指定列表中过滤掉self.blocked中的元素
class Filter:
def init(self):
self.blocked = []
def filter(self, sequence):
return [x for x in sequence if x not in self.blocked]
# 指定列表的值
class SPAMFilter(Filter): # SPAMFilter是Filter的子类
def init(self): # 重写超类Filter的方法init
self.blocked = ['SPAM']
4)重写
class A:
def hello(self):
print("Hello,I'm A.")
class B(A):
pass
# 如果找不到该方法(或属性),将在其超类A中查找
a = A()
b = B()
a.hello()
# Hello,I'm A.
b.hello()
# Hello,I'm A.
如果子类和父类具有同名方法和属性则认为子类重写父类同名方法和属性,但默认使用子类的同名方法和属性:
class A:
def hello(self):
print("Hello,I'm A.")
class B(A):
def hello(self):
print("Hello,I'm B.")
# 这样修改定义后,b.hello()的结果将不同。
b = B()
b.hello()
# Hello,I'm B.
5)调用
class Bird:
def __init__(self):
self.hungry = True
# 进食
def eat(self):
if self.hungry:
print('Aaaah...')
self.hungry = False
else:
print('No,thanks!')
# 鸟进食后就不再饥饿
b = Bird()
b.eat()
# Aaaah...
b.eat()
# Squawk!
# 新增鸣叫功能
class SongBird(Bird):
# 缺少对超类 __init__ 的调用
def __init__(self):
self.sound = 'Squawk!'
def sing(self):
print(self.sound)
sb = SongBird()
sb.sing()
# Squawk!
sb.eat()
# Traceback (most recent call last):
# File "text2.py", line 34, in <module>
# sb.eat()
# File "text2.py", line 7, in eat
# if self.hungry:
# ^^^^^^^^^^^
# AttributeError: 'SongBird' object has no attribute 'hungry'
异常指出SongBird没有属性hungry。因为在SongBird中重写了构造函数,但新的构造函数没有包含任何初始化属性hungry的代码。要消除这种错误,SongBird的构造函数必须调用其超类(Bird)的构造函数,以确保基本的初始化得以执行。为此有两种方法:调用未关联的超类构造函数,以及使用函数super。
未关联调用
将未关联方法的self参数设置为当前实例,会使用超类的构造函数来初始化子类对象。
class SongBird(Bird):
def __init__(self):
Bird.__init__(self)
self.sound = 'Squawk!'
def sing(self):
print(self.sound)
sb = SongBird()
sb.sing()
# Squawk!
sb.eat()
# Aaaah...
super()
super()用于调用父类(超类)。调用super()会将当前类和当前实例作为参数。对其返回的对象调用方法时,调用的将是超类(而不是当前类)的方法。另外可调用方法__init__
。通常不提供任何参数。
# 语法:
super(type[, object-or-type])
# type – 类。
# object-or-type – 类,一般是 self
# 可以使用直接使用 super().xxx 代替 super(Class, self).xxx :
可通过super() 来调用父类的__init__
构造方法:
class SongBird(Bird):
def __init__(self):
super().__init__()
self.sound = 'Squawk!'
def sing(self):
print(self.sound)
sb = SongBird()
sb.sing()
# Squawk!
sb.eat()
# Aaaah...
sb.eat()
# No,thanks!
class C:
def __init__(self):
print('我是C的__init__构造方法')
class A:
def __init__(self):
print('我是A的__init__构造方法')
class B(A):
def __init__(self):
super(B, self).__init__() # super(B, self).__init__()
super(A, self).__init__()
print('我是B的__init__构造方法')
D = B()
# 我是A的__init__构造方法
# 我是B的__init__构造方法
super().__init__()
指调用B类的父类A类,优点在于更改A类时不用更改B类。适用于单继承的情况。对于多继承则默认继承于第一个父类。多层继承时仅调用含super().__init__()
类的上一个父类。super(B, self).__init__()
指调用B类的父类A类,在同一个类中与super().__init__()
用法相同super(A, self).__init__()
指调用A类的父类C类,在同一个类中可以调用括号中类的父类。
在多继承中,会涉及到一个__mro__
(继承父类方法时的顺序表) 的调用排序问题。即严格按照该顺序执行super方法
class A:
def __init__(self):
self.n = 2
def add(self, m):
print('self is {0} @A.add'.format(self))
self.n += m
class B(A):
def __init__(self):
self.n = 3
def add(self, m):
print('self is {0} @B.add'.format(self))
super().add(m)
self.n += 3
class C(A):
def __init__(self):
self.n = 4
def add(self, m):
print('self is {0} @C.add'.format(self))
super().add(m)
self.n += 4
class D(B, C):
def __init__(self):
self.n = 5
def add(self, m):
print('self is {0} @D.add'.format(self))
super().add(m)
self.n += 5
d = D()
d.add(2)
print(d.n)
# self is <__main__.D object at 0x0000014E1D2E4450> @D.add
# self is <__main__.D object at 0x0000014E1D2E4450> @B.add
# self is <__main__.D object at 0x0000014E1D2E4450> @C.add
# self is <__main__.D object at 0x0000014E1D2E4450> @A.add
# 19
同样,不管往上调用几次,调用父类方法中 self 并不是父类的实例而是子类的实例,在上例中都是D的实例化对象
D.mro() == [D,B, C, A, object] ,多继承的执行顺序会严格按照mro的顺序执行。整体的调用流程图如下:
d = D()
d.n == 5
d.add(2)
class D(B, C): class B(A): class C(A): class A:
def add(self, m): def add(self, m): def add(self, m): def add(self, m):
super().add(m) 1.---> super().add(m) 2.---> super().add(m) 3.---> self.n += m
self.n += 5 <------6. self.n += 3 <----5. self.n += 4 <----4. <--|
(14+5=19) (11+3=14) (7+4=11) (5+2=7)
super().__init__
相对于类名.init,在单继承上用法基本无差别- 但在多继承上有区别,super方法能保证每个父类的方法只会执行一次,而使用类名的方法会导致方法被执行多次。
- 多继承时,使用super方法,对父类的传参数,应该是由于python中super的算法导致的原因,必须把参数全部传递,否则会报错
- 单继承时,使用super方法,则不能全部传递,只能传父类方法所需的参数,否则会报错
子类调用父类同名方法和属性:
class Master:
def __init__(self):
self.name = "师傅"
self.skill = "师傅的技能"
def teach(self):
print(f"{self.name}展示了{self.skill}。")
class MasterBrother:
def __init__(self):
self.name = '师傅的兄弟'
self.skill = '师傅兄弟的技能'
def teach(self):
print(f"{self.name}展示了{self.skill}。")
class Prentice(Master, MasterBrother):
def __init__(self):
self.name = '徒弟'
self.skill = '徒弟的技能'
def teach(self):
print(f"{self.name}展示了{self.skill}。")
def master_skill(self):
Master.__init__(self)
Master.teach(self)
def master_brother_skill(self):
MasterBrother.__init__(self)
MasterBrother.teach(self)
son = Prentice()
son.master_skill() # 调用第一个父类
son.teach()
print(son.name)
print()
son.master_brother_skill() # 调用第二个父类
son.teach()
print(son.name)
print()
son.__init__() # 调用子类
son.teach()
print(son.name)
# 师傅展示了师傅的技能。
# 师傅展示了师傅的技能。
# 师傅
# 师傅的兄弟展示了师傅兄弟的技能。
# 师傅的兄弟展示了师傅兄弟的技能。
# 师傅的兄弟
# 徒弟展示了徒弟的技能。
# 徒弟
无论何时何地,self都表示的是子类的对象,在调用父类方法时,通过传递self参数,来控制方法和属性的访问和修改。
Maid().battle() # 这种方法不推荐使用,因为相当于重新创建了一个新的父类对象,占用不必要的内存。
a = Maid()
a.battle()
2.封装
1)定义
定义私有属性和方法。在Python中,可以为实例方法和属性设置私有权限,即设置某个实例属性和实例方法不继承给子类。设置私有属性和私有方法:在属性和方法前面加上两个下划线。
class Master:
def __init__(self):
self.name = "师傅"
self.__skill = "师傅的技能" # self.skill = "师傅的技能"
class Prentice(Master):
def __init__(self):
super().__init__()
def teach(self):
super().__init__()
print(f"{self.name}展示了{self.__skill}。") # print(f"{self.name}展示了{self.skill}。")
son = Prentice()
son.teach()
# 结果保错
总结:对象不能访问私有属性和私有方法,子类无法继承父类的私有属性和私有方法。
# 以两个下划线打头幕后的处理手法并不标准:在类定义中,对所有以两个下划线打头的名称都进行转换,即在开头加上一个下划线和类名。
print(Secretive._Secretive__inaccessible)
# <unbound method Secretive. inaccessible>
# 知道幕后处理手法就能从类外访问私有方法,然而不应这样做。
print(s._Secretive__inaccessible())
# 总之无法禁止别人访问对象的私有方法和属性,但这种名称修改方式让他们不要这样做。如果不希望名称被修改,又想发出不要从外部修改属性或方法的信号,可用一个下划线打头。这虽然只是一种约定,但也有些作用。如from module import *不会导入以一个下划线打头的名称”。
2)获取修改
获取修改私有属性值。Python中一般定义方法名为get_xxxx用来表示获取私有属性,定义set_xxxx 用来表示修改私有属性值,这是约定俗称的命名方法,不是强制要求。
class Master:
def __init__(self):
self.__name = "师傅"
self.__skill = "师傅的技能"
def get_name(self):
return self.__name
def set_name(self, name):
self.__name = name
def get_skill(self):
return self.__skill
def set_skill(self, skill):
self.__skill = skill
class Prentice(Master):
def start(self):
super().__init__()
print(super().get_name())
print(super().get_skill())
super().set_name("乔峰")
super().set_skill("乔峰的第二个技能")
print(super().get_name())
print(super().get_skill())
son = Prentice()
son.start()
# 师傅
# 师傅的技能
# 乔峰
# 乔峰的第二个技能
3.多态
多态指的是一类事物有多种形态(一个抽象类有多个子类,因而多态的概念依赖于继承)
- 定义:多态是一种使用对象的方式,子类重写父类方法,调用不同的子类对象的同一父类方法时,产生不同的对象。
- 实现步骤:
- 定义父类,并提供公共方法
- 定义子类,并重写父类方法
- 传递子类对象给调用者,可以看到不同的子类的执行结果
class Hero:
def __init__(self):
self.__name = "英雄"
self.__skill = "英雄的技能"
def get_name(self):
return self.__name
def set_name(self, name):
self.__name = name
def get_skill(self):
return self.__skill
def set_skill(self, skill):
self.__skill = skill
class BaiLiShouYue(Hero):
def start(self):
super().__init__()
super().set_name("百里守约")
super().set_skill("狂风之息")
print(f"{super().get_name()}施展{super().get_skill()}")
class TheMonkeyKing(Hero):
def start(self):
super().__init__()
super().set_name("孙悟空")
super().set_skill("护身咒法")
print(f"{super().get_name()}施展{super().get_skill()}")
role1 = BaiLiShouYue()
role1.start()
# 百里守约施展狂风之息
role2 = TheMonkeyKing()
role2.start()
# 孙悟空施展护身咒法
1)接口
接口与多态有关,处理多态对象时只考虑接口(协议)——对外暴露的方法和属性。在Python中,不显式地指定对象必须包含哪些方法才能用作参数。通常要求对象遵循特定的接口(即实现特定的方法),
class Calculator:
def __init__(self):
self.value = None
def calculate(self, expression):
self.value = eval(expression)
class Talker:
def __init__(self):
self.value = None
def talk(self):
print('Hi, my value is', self.value)
class TalkingCalculator(Calculator, Talker):
pass
hasattr()
检查所需的方法是否存在
tc = TalkingCalculator()
print(hasattr(tc, 'talk'))
# True
print(hasattr(tc, 'fnord'))
# False
getattr()
检香属性是否是可调用
# getattr()能指定属性不存在时使用的默认值,这里为None,然后对返回的对象调用callable。
print(callable(getattr(tc, 'talk', None)))
# True
print(callable(getattr(tc, 'fnord', None)))
# False
setattr()
setattr与getattr功能相反,可用于设置对象的属性
setattr(tc, 'name', 'Mr. Gumby')
print(tc.name)
# 'Mr. Gumby
delattr()
删除对象属性
delattr(对象名, '对象属性名')
2)抽象基类
使用模块abc可创建抽象基类。抽象基类(Abstract Base Classes,简称abc)用于定义一组必须在子类中重写实现的抽象方法。抽象基类本身不能被实例化(不能创建对象),而是用于定义子类所需实现的接口。抽象基类就是定义各种方法而不做具体实现的类,任何继承自抽象基类的类必须实现这些方法,否则无法实例化。
from abc import ABC, abstractmethod
class Talker(ABC):
@abstractmethod # 装饰器,将方法标记为抽象的——在子类中必须实现的方法
def talk(self):
pass
不能实例化
抽象类(即包含抽象方法的类)最重要的特征是不能实例化
# Talker()
# 报错
# 派生的子类没有重写方法talk时子类也是抽象的,不能实例化,否则报错。可重新编写子类,使其实现要求的方法。
class Knigget(Talker):
def talk(self):
print("Ni!")
# 现在即可实例化。这是抽象基类的主要用途,最后只在这种情形下使用isinstance:
k = Knigget()
# 先检查给定实例确实是Talker对象,就能知道这个实例在需要时有方法talk。
print(isinstance(k, Talker))
# True
只要实现方法talk,即便不是Talker的子类,依然能够通过类型检查。
class Herring:
def talk(self):
print("Blub.")
# 这个类的实例能够通过是否为Talker对象的检查,但它不是Talker对象。
h = Herring()
print(isinstance(h, Talker))
# False
可将一个类注册成talk,这个类所有对象都将被视为Talker对象,但直接从抽象类派生提供的保障消失了
class Clam:
pass
print(Talker.register(Clam))
# <class '__main__.Clam'>
print(issubclass(Clam, Talker))
# True
c = Clam()
print(isinstance(c, Talker))
# True
c.talk()
# 报错
应用场景
检查某个类中是否有某种方法。如判断Demo中是否含有__len__
魔法方法
from collections.abc import Sized # 导入模块collections.abc的Sized类
# Sized源码如下:
# class Sized(metaclass=ABCMeta):
# __slots__ = ()
#
# @abstractmethod
# def __len__(self):
# return 0
#
# @classmethod
# def __subclasshook__(cls, C):
# if cls is Sized:
# return _check_methods(C, "__len__")
# return NotImplemented
class Demo(object):
def __init__(self, list1):
self.list = list1
def __len__(self):
return len(self.list)
d = Demo(["CW", "ls", 'age'])
print(hasattr(Sized, "__len__")) # 判断d的内部 是否含有__len__ ,返回值为布尔值,结果为true
print(isinstance(d, Sized)) # 检查d是否Sized类型 结果为:True
强制子类必须实现父类的方法
- 主动抛出异常。如下实例,子类RedisBase中未重写crea方法,在实例化子类RedisBase,没有报错,在调用crea()方法时报错
class CacheBase(object):
def dele(self):
# raise NotImplementedError意思是如果这个方法没有被子类重写,但是调用了就会报错。
raise NotImplementedError
def crea(self):
raise NotImplementedError
class RedisBase(CacheBase):
def dele(self):
print('I will delete')
# def crea(self):
# print('I will create')
r = RedisBase()
r.dele()
# I will delete
r.crea()
# raise NotImplementedError
- 子类RedisBase中未重写crea方法,在实例化子类RedisBase时就报错
import abc
class BaseClass(metaclass=abc.ABCMeta):
@abc.abstractmethod
def dele(self):
pass
@abc.abstractmethod
def crea(self):
pass
class SubClass(BaseClass):
def dele(self):
print('I will delete')
# def crea(self):
# print('I will create')
r = SubClass()
# TypeError: Can't instantiate abstract class SubClass with abstract methods crea
r.dele()
r.crea()
三、魔法方法
python中的魔法方法(magic methods)是指方法名以两个下划线开头并以两个下划线结尾的方法,因此也叫Dunder Methods (Double Underscores)。常用于运算符重载。魔法方法会在对类的某个操作时后端自动调用,而不需要自己直接调用。例如当使用+将两个数字相加时,在类内部会调用__add__()
方法,再比如创建一个类A的对象,a=A(),python就会自动调用__new__
和__init__
。
1.构造函数
__init__
构造函数(constructor)就是初始化方法,只是命名为__init__
。构造函数在对象创建后自动调用它们。
# 无需采用这种做法
f = FooBar()
f.init()
# 效果与上述代码相同
f = FooBar()
注意:__init__()
方法,在创建对象的时候就会被默认调用,不需要手动调用,self参数不需要传递,Python解释器会自动把当前对象引用传递过去。
class Washer: # Washer写成Washer()会显得冗余
def __init__(self): # 初始化魔法方法
# 添加属性
self.width = 500 # 也可以写None,类外赋值
self.height = 1000
def print_info(self):
print('洗衣机的高度是:', self.height) # 类 'Washer' 的未解析的特性引用 'height'
print("洗衣机的宽度是:", self.width) # 类 'Washer' 的未解析的特性引用 'width'
hai1 = Washer()
hai1.print_info()
# 洗衣机的高度是: 1000
# 洗衣机的宽度是: 500
hai2 = Washer()
hai2.print_info()
# 洗衣机的高度是: 1000
# 洗衣机的宽度是: 500
带参数的__init__
魔法方法:
一个类中有多个对象,对多个对象设置不同的属性值,可通过传参方式
class Washer():
def __init__(self, width, height): # 初始化魔法方法
# 添加属性
self.width = width
self.height = height
def print_info(self): # 实例方法,对象方法
print('洗衣机的高度是:', self.height)
print('洗衣机的宽度是:', self.width)
hai1 = Washer(500, 1000)
hai1.print_info()
# 洗衣机的高度是: 1000
# 洗衣机的宽度是: 500
hai2 = Washer(600, 1500)
hai2.print_info()
# 洗衣机的高度是: 1500
# 洗衣机的宽度是: 600
2.元素访问
基本的序列/映射协议
- Python中协议指规范行为的规则,类似接口。协议指定应实现哪些方法以及这些方法应做什么。
- Python中多态仅仅基于对象行为而不基于祖先(如属于哪个类或其超类等),只要对象遵循特定的协议。如要成为序列只需遵循序列协议。
- 序列和映射基本上是元素(item)的集合,要实现它们的基本行为(协议),不可变对象需要实现2个方法,而可变对象需要实现4个。
__len__
__len__(self)
方法应返回集合包含的项数,对序列来说为元素个数,对映射来说为键-值对数。如果__len__
返回零(且没有实现覆盖这种行为的__nonzero__
),对象在布尔上下文中将被视为假(就像空的列表、元组、字符串和字典一样)。
__getitem__
__getitem__(self, key)
方法返回与指定键相关联的值。对序列来说,键应该是0~n-1的整数(也可以是负数,这将在后面说明),其中n为序列的长度。对映射来说键可以是任何类型。
__setitem__
__setitem__(self, key, value)
方法与键相关联的方式存储值,能够使用__getitem__
获取。仅当对象可变时使用。
__delitem__
__delitem__(self, key)
在对对象的组成部分使用__del__
语句时被调用,删除与key相关联的值。同样仅当对象可变(且允许其项被删除)时使用。
以上方法额外的要求:
- 对于序列,如果键为负整数,应从末尾往前数。换而言之,x[-n]应与x[len(x)-n]等效。
- 如果键的类型不合适(如对序列使用字符串键 ),可能引发TypeError异常。
- 对于序列,如果索引的类型是正确的,但不在允许的范围内,应引发IndexError异常。
# 实现一个算术序列,其中任何两个相邻数字的差都相同。第一个值是由构造函数的参数start(默认为0)指定的,而相邻值之间的差是由参数step(默认为1)指定的。允许用户修改某些元素,这是通过将不符合规则的值保存在字典changed中实现的。如果元素未被修改,就使用公式self.start + key * self.step来计算它的值。
def check_index(key):
"""
指定的键是否是可接受的索引?
键必须是非负整数,才是可接受的。如果不是整数,
将引发TypeError异常;如果是负数,将引发Index
Error异常(因为这个序列的长度是无穷的)
"""
if not isinstance(key, int):
raise TypeError
if key < 0:
raise IndexError
class ArithmeticSequence:
def __init__(self, start=0, step=1):
"""
初始化这个算术序列
start -序列中的第一个值
step -两个相邻值的差
changed -一个字典,包含用户修改后的值
"""
self.start = start # 存储起始值
self.step = step # 存储步长值
self.changed = {} # 没有任何元素被修改
def __getitem__(self, key):
"""
从算术序列中获取一个元素
"""
check_index(key)
try:
return self.changed[key] # 修改过?
except KeyError: # 如果没有修改过,
return self.start + key * self.step # 就计算元素的值
def __setitem__(self, key, value):
"""
修改算术序列中的元素
"""
check_index(key)
self.changed[key] = value # 存储修改后的值
类没有方法__len__
,因为其长度是无穷的。
s = ArithmeticSequence(1, 2)
print(s[4])
# 9
s[4]=2
print(s[4])
# 2
print(s[5])
# 11
类没有设置__delitem__
,无法删除元素
s = ArithmeticSequence(1, 2)
del s[4]
# Traceback (most recent call last):
# File "text2.py", line 46, in <module>
# del s[4]
# ~^^^
# AttributeError: __delitem__
如果所使用索引的类型非法,将引发TypeError异常;如果索引的类型正确,但不在允许的范围内(即为负数),将引发IndexError异常。索引检查由函数check_index负责
s = ArithmeticSequence(1, 2)
print(s['four'])
# Traceback (most recent call last):
# File "text2.py", line 46, in <module>
# print(s['four'])
# ~^^^^^^^^
# File "text2.py", line 31, in __getitem__
# check_index(key)
# File "text2.py", line 9, in check_index
# raise TypeError
# TypeError
s = ArithmeticSequence(1, 2)
print(s[-42])
# Traceback (most recent call last):
# File "text2.py", line 46, in <module>
# print(s[-42])
# ~^^^^^
# File "text2.py", line 31, in __getitem__
# check_index(key)
# File "text2.py", line 11, in check_index
# raise IndexError
# IndexError
只想定制某种操作的行为可利用继承。标准库中模块collections提供了抽象和具体的基类,也可继承内置类型。如实现一种行为类似于内置列表的序列类型可直接继承list,一个带访问计数器的列表
class CounterList(list):
def __init__(self, *args):
super().__init__(*args)
self.counter = 0
# 重写__getitem__并不能保证一定会捕捉用户的访问操作,因为还有其他访问列表内容的方式,如通过方法pop。
def __getitem__(self, index):
self.counter += 1
return super(CounterList, self).__getitem__(index)
CounterList类依赖超类list的行为。CounterList没有重写的方法(如append、extend、index等)都可直接使用。在两个被重写的方法中,使用super来调用超类的相应方法,并添加了必要的行为:初始化属性counter(在__init__
中)和更新属性counter(在__getitem__
中)。
c1 = CounterList(range(10))
print(c1)
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
c1.reverse()
print(c1)
# [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
del c1[3:6]
print(c1.counter)
# 0
print(c1[4] + c1[2])
# 9
print(c1.counter)
# 2
CounterList在大多数方面都类似列表,但有一个counter属性(其初始值为0)。每当访问列表元素时,这个属性的值都加1。执行加法运算cl[4] + cl[2]后,counter的值递增两次,变成了2。
3.其他
在Python中,__xx__()
的函数就叫魔法方法,指的是具有特殊功能的函数
__doc__
描述类信息
__str__
对对象的描述信息。如果类中定义了__str__
方法,那么在打印对象时,默认输出该方法的返回值。__str__
方法必须返回一个字符串
__module__
表示当前操作的对象在那个模块
__class__
表示当前操作对象的类是什么
__str__
当print输出一个对象的时候,默认打印的是对象的内存空间地址,如果在类中定义__str__
魔法方法之后,那么此时在打印对象就是打印的这个方法的返回值。
class Washer:
def wash(self):
print('洗衣服')
def __str__(self): # 类的说明或者类的状态
return '这是洗衣机类的使用说明'
haier = Washer()
print(haier)
# 这是洗衣机类的使用说明
__del__
当删除对象时,Python解释器会自动调用__del__()
魔法方法。__del__
也称析构函数(destructor),在对象被销毁(作为垃圾被收集)前被调用,但有时无法知道准确调用时间,建议尽可能不要主动使用__del__
。
class Washer():
def wash(self):
print('洗衣服')
def __del__(self):
"""删除对象"""
print(f"{self}对象已经删除")
haier = Washer()
del haier
print(haier)
# <__main__.Washer object at 0x00000215C823B5E0>对象已经删除
# Traceback (most recent call last):
# File "E:\pythonProjectText\text\__init__.py", line 12, in <module>
# print(haier)
# NameError: name 'haier' is not defined. Did you mean: 'aiter'?
注意:
-
如果一个类有多个对象,每个对象的属性是各自独立保存的,都是独立的地址。
-
但是实例方法是所有对象所共享的,只占用一份内存空间,类会通过self参数来判断是那个对象调用了该实例方法。
class Washer(): def __init__(self, width, height): # 初始化魔法方法 # 添加属性 self.width = width self.height = height def print_info(self): # 实例方法,对象方法 print('洗衣机的高度是:', self.height) print('洗衣机的宽度是:', self.width) hai1 = Washer(500, 1000) # print(id(hai1.width)) print(id(hai1.print_info())) # 洗衣机的高度是: 1000 # 洗衣机的宽度是: 500 # 140721883916280 hai2 = Washer(600, 1500) # print(id(hai2.width)) print(id(hai2.print_info())) # 洗衣机的高度是: 1500 # 洗衣机的宽度是: 600 # 140721883916280
__mro__
子类的魔法属性__mro__
决定了属性和方法的查找顺序
class X: pass # 相当于class X(object): pass
class Y: pass # 相当于class X(object): pass
class A(X, Y): pass
class B(Y): pass
class C(A, B): pass
print(C.__mro__)
#(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.X'>, <class '__main__.B'>, <class '__main__.Y'>, <class 'object'>)
__mro__
是method resolution order,主要用于在对继承是判断方法、属性的调用路径【顺序】,其实也就是继承父类方法时的顺序表。在搜索方法时,是按照__mro__
的输出结果从左到右的顺序查找的
- 如果当前类中找到方法,就直接执行,不再搜索
- 如果没有找到,就查找下一个类中是否有对应的方法,如果找到,就直接执行,不再搜索
- 如果找到最后一个类,还是没有找到方法,程序报错
__new__
__new__()
需传递一个参数cls,__init__()
需要传递一个参数self,self代表实例对象本身,cls代表类对象本身。__new__
至少要有一个参数cls,代表要实例化的类,此参数在实例化时解释器自动提供,后面参数直接传给__init__
。__new__()
必须要有返回值,返回实例化出来的实例对象。__new__
对当前类进行了实例化,并将实例返回,传给__init__
的self。但执行__new__
不一定执行__init__
,仅__new__
返回当前类cls的实例,当前类的__init__
才会执行。若__new__
没有正确返回当前类cls的实例,那__init__
是不会被调用的,即使是父类的实例也不行,将没有__init__
被调用。__new__
方法主要继承一些不可变的class(比如int, str, tuple), 提供一个自定义这些类的实例化过程的途径。一般不去重写__new__()
方法,它作为构造函数用于创建对象,是一个工厂函数,专用于生产实例对象。单例模式可以通过此方法来实现。在写框架级的代码时,可能会用到它,也可以从开源代码中找到它的应用场景,例如微型 Web 框架 Bootle 就用到了。
__init__()
的第一个参数一定是self,__init__()
方法负责对象的初始化,系统执行该方法前,该实例对象已经存在。
class A:
def __new__(cls, *args, **kwargs):
print("执行new方法")
return object.__new__(cls) # return super(A,cls).__new__(cls) 两条return语句作用相同
# object.__new__(cls)既调用父类(object)的__new__()
def __init__(self):
print("执行init方法")
print(self)
print(self.__class__)
a = A()
# 执行new方法
# 执行init方法
# <__main__.A object at 0x000001CF7F3A4150>
# <class '__main__.A'>
- 实例化A类对象时,调用
__init__
初始化前首先调用类对象的__new__()
方法,若该对象无__init__()
方法则去父类中依次查找,直到object类。 - `
- 类开始实例化时,
__new__()
方法会返回cls(cls指代当前类)的实例,然后该类的__init__()
方法会接收这个示例(即self)作为自己的第一个参数,然后依次转入__new__()
方法中接收的位置参数和命名参数。如果__new__()
返回了其他类的实例,那么只会调用被返回的那个类的构造方法。例:
class A:
def __new__(cls, *args, **kwargs):
print("A的new方法")
return object.__new__(B)
def __init__(self):
print("A的init方法")
class B:
def __new__(cls, *args, **kwargs):
print("B的init方法")
return object.__new__(cls)
def __init__(self):
print("B的new方法")
a = A()
print(type(a))
print()
b = B()
print(type(b))
# A的new方法
# <class '__main__.B'>
#
# B的init方法
# B的new方法
# <class '__main__.B'>
__dict__
__dict__
是用来存储对象属性的一个字典,其键为属性名,值为属性的值。
class A():
def __init__(self):
self.name="liming"
def save_data(self,dicts):
self.__dict__.update(dicts)#添加字典元素
if isinstance(self.__dict__, dict):
print(True)
# 获取字典独有的属性
print(set(dir(self.__dict__))-set(dir(self)))
return self.__dict__
if __name__ == '__main__':
dicts={"a":1,"b":2,"c":3}
a=A()
print(a.save_data(dicts))
输出结果:
True
{'__delitem__', 'keys', 'update', '__len__', '__getitem__', 'get', 'clear', 'copy', 'popitem', '__iter__', 'items', '__contains__', 'pop', '__setitem__', 'fromkeys', 'values', 'setdefault'}
{'name': 'liming', 'a': 1, 'b': 2, 'c': 3}
isinstance() 函数,是Python中的一个内置函数,用来判断一个函数是否是一个已知的类型,类似 type()。
# 语法
isinstance(object,classinfo)
# 参数:
# object : 实例对象。
# classinfo : 可以是直接或者间接类名、基本类型或者由它们组成的元组。
# 返回值:如果对象的类型与参数二的类型(classinfo)相同则返回 True,否则返回 False。
下面来一个比较实用的例子来大大的减少你的代码,做到真正的pythonic。
我们在使用给对象的属性赋值的时候:
class A():
def __init__(self,dicts):
self.name=dicts["name"]
self.age=dicts["age"]
self.sex=dicts["sex"]
self.hobby=dicts["hobby"]
if __name__ == '__main__':
dicts={"name":"lisa","age":23,"sex":"women","hobby":"hardstyle"}
a=A(dicts)
上面代码简化为:
class A():
def __init__(self,dicts):
self.__dict__.update(dicts)
print(self.__dict__)
if __name__ == '__main__':
dicts={"name":"lisa","age":23,"sex":"women","hobby":"hardstyle"}
a=A(dicts)
__dict__
在单例模式中共享同一状态。拓展:部分内建函数不包含__dict__
属性比如list,查看list的属性用dir(list),dir方法也是查看对象的属性,包括内建对象的属性,但是它的输出形式为列表,而__dict__
是列表。
__getattr__
使用.获取属性的时候,如果该属性存在就输出其值,如果不存在则会去找_getatrr_,我们可以通过重写该方法可以实现动态属性的操作。(如果只允许添加指定的属性需要用__solts__
函数控制)。先来一段比较有意思的代码:
from requests_html import HTMLSession
class UrlGenerator(object):
def __init__(self, root_url):
self.url = root_url
self.session=HTMLSession()
def __getattr__(self, item):
if item == 'get':
self.get_html()
return UrlGenerator('{}.{}'.format(self.url, item))
def get_html(self):
req = self.session.get(self.url)
print(req.text)
url_gen = UrlGenerator('https://www')
url_gen.baidu.com.get
充分利用__getattr__
会在没有查找到相应实例属性时被调用的特点,方便的通过链式调用生成对应的url,在碰到get方法的时候调用函数获取其网页源码。
可调用的对象更加的优雅,链式的操作不仅优雅而且还能很好的说明调用的接口的意义。
下面展示一个__getattr__经典应用的例子,可以通过获取属性值的方式获取字典的键值。
class ObjectDict(dict):
def __init__(self, *args, **kwargs):
super(ObjectDict, self).__init__(*args, **kwargs)
def __getattr__(self, name):
value = self[name]
if isinstance(value, dict):
value = ObjectDict(value)
return value
if __name__ == '__main__':
od = ObjectDict(asf={'a': 1}, d=True)
print(od.asf,od.asf.a) # {'a': 1} 1
print(od.d) *# True*
__slots__
new-style class要求继承Python中的一个内建类型, 一般继承object,也可继承list或者dict等其他内建类型。在python新式类中定义变量__slots__
的作用是阻止在实例化类时为实例分配dict(默认情况下每个类都会有一个dict,通过__dict__
访问,这个dict维护了这个实例的所有属性)举例如下:
class base(object):
var = 9 # 类变量
def __init__(self):
pass
b = base()
print(b.__dict__)
b.x = 2 # 添加实例变量
print(b.__dict__)
# { }
# {‘x’: 2}
实例dict只保存实例的变量,对于类的属性(变量和函数)不保存,由于每次实例化一个类都要分配一个新的dict,因此存在空间的浪费,因此有了__slots__
。__slots__
是一个元组,包括了当前能访问到的属性。当定义了slots后,slots中定义的变量变成了类的描述符,类的实例只能拥有slots中定义的变量,不能再增加新的变量。注意:定义了slots后,就不再有dict。如下:
class Base(object):
__slots__ = 'x'
var = 8
def __init__(self):
pass
b = Base()
b.x = 88 # 添加实例变量
print(b.x)
b.y = 99
# 88
# Traceback (most recent call last):
# File "G:\python_project\Text\main.py", line 12, in <module>
# b.y = 99 # 无法添加slots之外的变量 (AttributeError: 'base' object has no attribute 'y')
# ^^^
# AttributeError: 'Base' object has no attribute 'y'
如果类变量与slots中的变量同名,则该变量被设置为read only!!!如下:
class Base(object):
__slots__ = 'y' # __slots__ 中的 'y' 与类变量冲突
y = 22 # y是类变量,y与__slots__中的变量同名
var = 11
def __init__(self):
pass
b = Base()
print(b.y)
print(Base.y)
# Traceback (most recent call last):
# File "G:\python_project\Text\main.py", line 1, in <module>
# class Base(object):
# ValueError: 'y' in __slots__ conflicts with class variable
Python为动态语言可在运行过程中修改实例的属性和增删方法。一般类的实例包含一个字典__dict__
,Python通过这个字典可以将任意属性绑定到实例上。若只想使用固定的属性而不想任意绑定属性,就可定义一个属性名称集合,只有在这个集合里的名称才可绑定。__slots__
就是这个"集合"。
class test_slots(object):
__slots__='x','y'
def printHello(self):
print 'hello!'
class test(object):
def printHello(self):
print 'hello'
print dir(test_slots) #可以看到test_slots类结构里面包含__slots__,x,y
print dir(test)#test类结构里包含__dict__
print '**************************************'
ts=test_slots()
t=test()
print dir(ts) #可以看到ts实例结构里面包含__slots__,x,y,不能任意绑定属性
print dir(t) #t实例结构里包含__dict__,可以任意绑定属性
print '***************************************'
ts.x=11 #只能绑定__slots__名称集合里的属性
t.x=12 #可以任意绑定属性
print ts.x,t.x
ts.y=22 #只能绑定__slots__名称集合里的属性
t.y=23 #可以任意绑定属性
print ts.y,t.y
#ts.z=33 #无法绑定__slots__集合之外的属性(AttributeError: 'test_slots' object has no attribute 'z')
t.z=34 #可以任意绑定属性
print t.z
运行结果:
[‘class’, ‘delattr’, ‘doc’, ‘format’, ‘getattribute’, ‘hash’, ‘init’, ‘module’, ‘new’, ‘reduce’, ‘reduce_ex’, ‘repr’, ‘setattr’, ‘sizeof’, ’ slots’, ‘str’, ‘subclasshook’, ‘printHello’, ’ x’, ‘y’]
[‘class’, ‘delattr’, ’ dict’, ‘doc’, ‘format’, ‘getattribute’, ‘hash’, ‘init’, ‘module’, ‘new’, ‘reduce’, ‘reduce_ex’, ‘repr’, ‘setattr’, ‘sizeof’, ‘str’, ‘subclasshook’, ‘weakref’, ‘printHello’]
[‘class’, ‘delattr’, ‘doc’, ‘format’, ‘getattribute’, ‘hash’, ‘init’, ‘module’, ‘new’, ‘reduce’, ‘reduce_ex’, ‘repr’, ‘setattr’, ‘sizeof’, ’ slots’, ‘str’, ‘subclasshook’, ‘printHello’, ’ x’, ‘y’]
[‘class’, ‘delattr’, ’ dict’, ‘doc’, ‘format’, ‘getattribute’, ‘hash’, ‘init’, ‘module’, ‘new’, ‘reduce’, ‘reduce_ex’, ‘repr’, ‘setattr’, ‘sizeof’, ‘str’, ‘subclasshook’, ‘weakref’, ‘printHello’]
11 12
22 23
34
正如上面所说的,默认情况下,Python的新式类和经典类的实例都有一个
dict来存储实例的属性。这在一般情况下还不错,而且非常灵活,
乃至在程序中可以
随意设置新的属性。但是,对一些在”编译”前就知道有几个固定属性的小class来说,这个dict就有点浪费内存了。
当需要创建大量实例的时候,这个问题变得尤为突出。一种解决方法是在
新式类中定义一个__slots__属性。
__slots__声明中包含若干实例变量,并为每个实例预留恰好足够的空间来保存每个变量;这样Python就不会再使用dict,从而节省空间。
【使用memory_profiler模块,memory_profiler模块是在逐行的基础上,测量代码的内存使用率。尽管如此,它可能使得你的代码运行的更慢。使用装饰器@profile来标记哪个函数被跟踪。】
下面,我们看一个例子:
from memory_profiler import profile
class A(object): #没有定义__slots__属性
def __init__(self,x):
self.x=x
@profile
def main():
f=[A(523825) for i in range(100000)]
if __name__=='__main__':
main()
1234567891011
运行结果,如下图:
第2列表示该行执行后Python解释器的内存使用情况,
第3列表示该行代码执行前后的内存变化。
在没有定义__slots__属性的情况下,该代码共使用了20.8MiB内存。
从结果可以看出,内存使用是以MiB为单位衡量的,表示的mebibyte(1MiB = 1.05MB)
from memory_profiler import profile
class A(object):#定义了__slots__属性
__slots__=('x')
def __init__(self,x):
self.x=x
@profile
def main():
f=[A(523825) for i in range(100000)]
if __name__=='__main__':
main()
123456789101112
运行结果,如下图:
可以看到,在定义了__slots__属性的情况下,该代码共使用了6.1MiB内存,比上面的20.8MiB节省了很多内存!
综上所述,在确定了
类的属性固定的情况下,可以
使用__slots__
来优化内存。
提醒:不要贸然进行这个优化,把它用在所有地方。这种做法不利于代码维护,而且只有生成数以千计的实例的时候才会有明显效果。
三、综合案例
烤地瓜
步骤:
- 被烤的时间和地瓜的状态
- 0-2:生的
- 2-5:半生不熟
- 5-9:烤熟了
- 9以上:烤糊了
- 添加佐料
- 用户可以根据喜好添加佐料
涉及到一个事物:地瓜 抽象出一个地瓜类
实现代码:
- 地瓜的属性
- 地瓜的状态
- 背烤的时间
- 添加的佐料
- 地瓜的方法
- 被烤
- 用户要指定烤的时间
- 查看地瓜的状态,并且随着被烤的时间修改状态
- 添加佐料
- 用户想添加什么就添加什么
- 保存用户添加的佐料
- 被烤
- 显示地瓜的状态
# 定义类: 初始化属性 被烤和添加佐料的方法,显示地瓜的状态
class SweetPotato():
def __init__(self):
# 被烤时间
self.cook_time = 0
# 状态
self.cook_state = '生的'
# 添加的佐料
self.condiments = []
def cook(self, time):
"""烤地瓜"""
self.cook_time += time
# 用被烤的时间判断地瓜的状态
if 0 < self.cook_time <= 2:
self.cook_state = '生的'
elif 2 < self.cook_time <= 5:
self.cook_state = '半生不熟'
elif 5 < self.cook_time <= 9:
self.cook_state = '烤熟了'
elif self.cook_time > 9:
self.cook_state = '烤糊了'
def add_condiments(self, condiment):
"""添加佐料"""
self.condiments.append(condiment)
def __str__(self):
return f"这个地瓜被烤的总时间为{self.cook_time}分钟,状态是{self.cook_state},添加的佐料有{self.condiments}、"
digua = SweetPotato()
digua.cook(1)
digua.add_condiments('番茄酱')
print(digua)
digua.cook(2)
digua.add_condiments('孜然粉')
print(digua)
# 这个地瓜被烤的总时间为1分钟,状态是生的,添加的佐料有['番茄酱']、
# 这个地瓜被烤的总时间为3分钟,状态是半生不熟,添加的佐料有['番茄酱', '孜然粉']、
搬家具
步骤:
将小于房子面积的家具搬到房间中
分析:涉及到 房子 家具
房子类
- 实例属性
- 房子的面积
- 房子剩余多少面积
- 房子里面的家具
- 实例方法
- 将家具搬进房子
- 显示房子的状态或者信息
家具类
- 实例属性
- 家具的名字
- 家具的占地面积
# 定义类 家具类 房屋类
class Furniture():
"""家具类"""
def __init__(self, name, area):
# 名字
self.name = name
# 大小
self.area = area
class Home():
"""房屋类"""
def __init__(self, area):
# 大小
self.area = area
# 剩余大小
self.free_area = area
# 家具
self.furniture = []
def add_furniture(self, item):
"""
搬家具
:param item: 家具对象
:return:
"""
if item.area < self.free_area:
self.furniture.append(item.name)
# 家具搬入之后,更新房屋面积
self.free_area -= item.area
else:
print('家具太大,无法容纳。')
def __str__(self):
return f"房子的大小为{self.area},剩余大小为{self.free_area},搬进的家具有{self.furniture}"
# 床
bed = Furniture('大圆床', 10)
# 房子
home1 = Home(100)
home1.add_furniture(bed)
print(home1)
五、类属性和类方法
(一)类属性和实例属性
1.设置和访问类属性
- 类属性就是类对象所拥有的属性,它被该类的所有实例对象所共有。
- 类属性可以使用类对象或者实例对象访问
class People:
def __init__(self, name, sex, age):
self.name = name
self.sex = sex
self.age = age
def print_people(self):
print(self.name + "\n" + self.sex + "\n" + self.age)
class Human(People):
def __init__(self, name, sex, age):
super(Human, self).__init__(name, sex, age)
gril1 = Human("谢大姐", "女", "18")
gril2 = Human("小大夫", "女", "18")
print(id(Human("谢大姐", "女", "18"))) # 2949721638544
print(id(Human("小大夫", "女", "18"))) # 2949721638608
print()
print(id(gril1.print_people()))
# 谢大姐
# 女
# 18
# 140725288004808
print(id(gril2.print_people()))
# 小大夫
# 女
# 18
# 140725288004808
print()
print(id(People("谢大姐", "女", "18"))) # 2949721637456
print(id(People("小大夫", "女", "18"))) # 2949721638608
总结:
- 记录的某项数据始终保持一致的时候,则可以定义类属性
- 实例属性要求每个对象为其开辟一份单独的内存空间用来记录属性值,而类属性为全局所共有,仅占用一份内存空间,更加的节省内存空间。
2.修改类属性
class Wife(object):
gender = '女'
def __init__(self, name, age):
self.name = name
self.age = age
def print_info(self):
print(self.name)
print(self.age)
fubicheng = Wife('符必程', 18)
xiaogong = Wife('小龚', 20)
# 通过类对象修改类属性
# print(Wife.gender)
# Wife.gender = '中性'
# print(Wife.gender)
# print(fubicheng.gender)
# print(xiaogong.gender)
# 不能通过实例对象修改类属性
fubicheng.gender = '中性'
print(id(fubicheng.gender))
print(id(xiaogong.gender))
print(id(Wife.gender))
类属性只能通过类对象来修改,不能通过实例对象来修改,如果这样操作了,只是重新为此实例对象重新添加了一个实例属性而已。
3.实例属性
class Dog(object):
def __init__(self):
self.age = 2
# def get_age(self):
# return Dog.age
wangcai = Dog()
print(wangcai.age)
# print(Dog.age) # 报错; 实例属性只能通过实例对象访问,不能通过类访问
# print(wangcai.get_age())
4.dir()查询对象属性方法
dir()传入对象返回对象的属性和方法。比如字符串,可用该函数查询字符串的所有方法。查询字符串,一个双引号就是字符串类,所以以下代码就是查询字符串的所有属性。
print(dir(""))
[’__add__’, ‘__class__’, ‘__contains__’, ‘__delattr__’, ‘__dir__’, ‘__doc__’, ‘__eq__’, ‘__format__’, ‘__ge__’, ‘__getattribute__’, ‘__getitem__’, ‘__getnewargs__’, ‘__gt__’, ‘__hash__’, ‘__init__’, ‘__init_subclass__’, ‘__iter__’, ‘__le__’, ‘__len__’, ‘__lt__’, ‘__mod__’, ‘__mul__’, ‘__ne__’, ‘__new__’, ‘__reduce__’, ‘__reduce_ex__’, ‘__repr__’, ‘__rmod__’, ‘__rmul__’, ‘__setattr__’, ‘__sizeof__’, ‘__str__’, ‘__subclasshook__’, ‘capitalize’, ‘casefold’, ‘center’, ‘count’, ‘encode’, ‘endswith’, ‘expandtabs’, ‘find’, ‘format’, ‘format_map’, ‘index’, ‘isalnum’, ‘isalpha’, ‘isdecimal’, ‘isdigit’, ‘isidentifier’, ‘islower’, ‘isnumeric’, ‘isprintable’, ‘isspace’, ‘istitle’, ‘isupper’, ‘join’, ‘ljust’, ‘lower’, ‘lstrip’, ‘maketrans’, ‘partition’, ‘replace’, ‘rfind’, ‘rindex’, ‘rjust’, ‘rpartition’, ‘rsplit’, ‘rstrip’, ‘split’, ‘splitlines’, ‘startswith’, ‘strip’, ‘swapcase’, ‘title’, ‘translate’, ‘upper’, ‘zfill’]
-
查询元组:print(dir(()))
-
查询列表:print(dir([]))
-
查询字典:print(dir({}))
-
查询非内置类
除了上述内置类可以查询,所有对象都可以查询,所以手动创建的类也可查询,查询方法一样。首先,定义一个类:class DIR_Test: def __init__(self, a): self.a = a self.b = 2 self.c = "hello dir" def train(self): pass def eval(self): pass # 不实例化查询: print(dir(DIR_Test)) # [’__class__’, ‘__delattr__’, ‘__dict__’, ‘__dir__’, ‘__doc__’, ‘__eq__’, ‘__format__’, ‘__ge__’, ‘__getattribute__’, ‘__gt__’, ‘__hash__’, ‘__init__’, ‘__init_subclass__’, ‘__le__’, ‘__lt__’, ‘__module__’, ‘__ne__’, ‘__new__’, ‘__reduce__’, ‘__reduce_ex__’, ‘__repr__’, ‘__setattr__’, ‘__sizeof__’, ‘__str__’, ‘__subclasshook__’, ‘__weakref__’, ‘eval’, ‘train’] # 实例化查询: dir_test = DIR_Test(a=1) print(dir(dir_test)) # [’__class__’, ‘__delattr__’, ‘__dict__’, ‘__dir__’, ‘__doc__’, ‘__eq__’, ‘__format__’, ‘__ge__’, ‘__getattribute__’, ‘__gt__’, ‘__hash__’, ‘__init__’, ‘__init_subclass__’, ‘__le__’, ‘__lt__’, ‘__module__’, ‘__ne__’, ‘__new__’, ‘__reduce__’, ‘__reduce_ex__’, ‘__repr__’, ‘__setattr__’, ‘__sizeof__’, ‘__str__’, ‘__subclasshook__’, ‘__weakref__’, ‘a’, ‘b’, ‘c’, ‘eval’, ‘train’]
不实例化后查询只能看到方法;实例化后再查询可以看到方法和属性。因为只有实例化后才能把属性的值定下来,所以正确的使用方法是先实例化再使用。比如上面查询列表,传入的是"[]"实际上就已经实例化了一个对象。dir查询到的属性是没有值的
(二)类方法和静态方法
1.类方法
类方法需要使用装饰器@classmethod
来标识其为类方法,对于类方法而言,第一个参数必须是类的对象,一般以cls
作为第一个参数。
使用
- 当方法中需要使用到类的对象的时候(比如说私有类属性),定义类方法。
- 类方法一般配合类属性使用
# 定义一个私有类属性,通过类方法获取这个私有类属性
class Dog(object):
__age = 2
@classmethod
def get_age(cls): # cls指的就是我们的类对象 此时 cls==Dog # 类方法
return cls.__age
wangcai = Dog()
print(wangcai.get_age())
2.静态方法
- 需要使用装饰器
@staticmethod
来进行装饰,静态方法即不需要传递类对象,也不需要传递实例对象,没有self/cls
- 静态方法是可以通过实例对象和类对象来进行访问的。
使用
- 当方法中即不需要使用实例对象,也不需要使用类对象的时候,就定义静态方法
- 取消不必要的参数传递,有利于减少不必要的内存占用和性能消耗
# 定义一个静态方法
class Dog(object):
def info(self):
print("这是info方法")
@staticmethod
def print_info(self): # 方法中不需要使用到实例对象和类对象
print('这是一个静态方法')
print(self)
d = Dog()
d.info()
d.print_info(1)
(三)单例模式
举个常见的单例模式例子,我们日常使用的电脑上都有一个回收站,在整个操作系统中,回收站只能有一个对象,整个系统都使用这个唯一的对象,而且回收站自行提供自己的对象。因此回收站是单例模式的应用。
确保某一个类只有一个对象,而且自行实例化并向整个系统提供这个对象,这个类称为单例类,单例模式是一种对象创建型模式。
1.非单例模式
class Singleton(object):
__instance = None
def __new__(cls, age, name):
if not cls.__instance:
cls.__instance = object.__new__(cls)
return cls.__instance
return cls.__instance
def __init__(self, age, name):
self.age = age
self.name = name
a = Singleton(18, "wk")
b = Singleton(8, "mm")
print(id(a) == id(b))
print(a.age, a.name)
print(b.age, b.name) # a,b共用一个类对象,最后赋值的会覆盖之前赋值的
a.size = 19 # 给a指向的对象添加一个属性
print(b.size) # 获取b指向的对象的size属性,因为a,b共用一个类对象,a.size = 19是对类对象赋值,对于共用该类对象的对象通用。
# True
# 8 mm
# 8 mm
# 19
2.单例模式
# 只执行一次__init__()方法。
class Singleton(object):
__instance = None
__first_init = False # 实例化一个单例
def __new__(cls, age, name):
if not cls.__instance:
cls.__instance = object.__new__(cls)
return cls.__instance
return cls.__instance
def __init__(self, age, name):
if not self.__first_init:
self.age = age
self.name = name
Singleton.__first_init = True
a = Singleton(18, "wk")
b = Singleton(8, "mm")
print(id(a) == id(b))
print(a.age, a.name) # 一旦创建该类的对象,则以该对象为主,再创建该类其他对象视为无效
print(b.age, b.name)
a.size = 19 # 给a指向的对象添加一个属性
print(b.size) # 获取b指向的对象的size属性
b.size = 17
print(a.size) # 获取a指向的对象的size属性,因为a,b共用一个类对象,b.size = 17是对类对象赋值,对于共用该类对象的对象通用。同时改变size = 19为size = 17
# True
# 18 wk
# 18 wk
# 19
# 17
(四)property属性
# 基本语法
class property([fget[, fset[, fdel[, doc]]]])
# fget – 获取属性值的函数
# fset – 设置属性值的函数
# fdel – 删除属性值函数
# doc – 属性描述信息
property会返回一个新式类属性,先看一个例子:
class C:
def __init__(self):
self._x = None
def getx(self):
return self._x
def setx(self, value):
self._x = value
def delx(self):
del self._x
a = property(getx, setx, delx, "I'm the 'x' property.")
if __name__ == "__main__":
A = C()
print("获取属性值")
print(A.a)
A.a = 1
print(A.a)
del A.a
print(A.a)
# 获取属性值
# None
# 1
# Traceback (most recent call last):
# File "G:\python_project\Text\main.py", line 24, in <module>
# print(A.a)
# ^^^
# File "G:\python_project\Text\main.py", line 6, in getx
# return self._x
# ^^^^^^^
# AttributeError: 'C' object has no attribute '_x'
- property中getx对应获取属性值的函数既c.x的返回值,所以尽管x在C中匿名(_x)但还可被c.x调用
- property中setx对应设置属性值的函数既c.x = value的返回值,所以尽管x在C中匿名,但还可被设置
- 同理property中的第三个参数代表了删除C属性x的函数
- 在没有property时不能从外表调用._x属性
1.参数args
python builtins文件中对类BaseException中参数args的定义
args = property(lambda self: object(), #获取值
lambda self, v: None, #设置值
lambda self: None) #删除值
2.装饰器的方式
class Person:
def __init__(self):
self.__age = 1
@property
def age(self):
return self.__age
@age.setter
def age(self, new_age): # new_age用来接受外部给p.age赋值的这个参数
if 0 < new_age < 200:
self.__age = new_age
else:
print('年龄设置错误,回去改改')
p = Person()
print(p.age)
p.age = 300
print(p.age)
p.age = 150
print(p.age)
# 1
# 年龄设置错误,回去改改
# 1
# 150
3.类属性
property(参数1,参数2)
第一个参数表示获取属性时会执行的方法名
第二个参数表示设置属性时会执行的方法名
class Person(object):
def __init__(self):
self.__age = 1
def get_age(self):
return self.__age
def set_age(self, new_age):
if 0 < new_age < 200:
self.__age = new_age
else:
print('年龄设置错误,回去改改')
age = property(get_age, set_age)
p = Person()
print(p.age)
p.age = 10
print(p.age)
(五)上下文管理器
普通打开文件的隐患:
f = open('1.txt', 'w')
f.write('hello world')
f.close()
with open('1.txt', 'w') as f:
f.write("hello world")
with语句之所以这么强大,就是因为背后是上下文管理器作为支撑的。
一个类中只要实现了__enter__()和 __exit__()这两个魔法方法,通过该类创建的对象就是上下文管理器对象。
__enter__(): 表示的是上文方法, 需要返回一个操作文件对象
__exit__():表示的是下文方法,with语句会自动执行下文方法,即使代码出现异常也会被调用。
"""
需求:定义一个上下文管理器类,模拟文件操作
定义一个File类
实现__enter__()和 __exit__()
然后用with语句调用来完成文件操作,观察现象。
"""
class File(object):
def __init__(self, file_name, file_model):
self.file_name = file_name
self.file_model = file_model
def __enter__(self):
"""上文方法"""
print('这是上文方法')
self.file = open(self.file_name, self.file_model)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
"""这是下文方法"""
print('这是下文方法')
self.file.close()
with File("1.txt", 'r') as f:
data = f.read()
1/0
print(data)
六、实例:面向对象版学员管理系统
-
了解面向对象开发过程中内部功能实现的分析方法
-
了解常用系统的功能
- 添加
- 删除
- 修改
- 查询
-
系统要求:学员的学员信息需要存储在文件中
-
系统功能:添加学员,删除学员,修改学员信息,查询学员信息,显示学员信息,保存学员信息,以及退出系统等功能。
(一)程序文件准备
- 角色分析
- 管理系统
- 学员
工作过程中需要注意的地方:
1.为了方便维护代码,一般一个角色一个程序文件
2.项目要有主程序入口,习惯命名为main.py
(二)程序文件创建
创建项目目录:比如说: StudentManageSystem
程序文件:
- 程序入口文件:main.py
- 学员文件:students.py
- 管理系统文件:managersystem.py
(三)书写程序
1.students.py
需求:
- 学员信息:姓名,性别,手机号
- 添加str魔法方法,方便查看学员信息
class Student(object):
def __init__(self, name, gender, tel):
self.name = name
self.gender = gender
self.tel = tel
def __str__(self):
return f"{self.name}, {self.gender}, {self.tel}"
if __name__ == '__main__':
tongxue = Student('8280同学', '男', 3247953)
print(tongxue)
tongxue2 = Student('7179同学', '女', 3453643)
print(tongxue2)
2.managersystem.py
需求:
- 系统功能
- 添加学员
- 删除学员
- 修改学员
- 查询学员
- 显示所有学员
- 保存学员
- 退出系统
- 存储数据文件的位置:文件(student.data)
- 加载文件数据
- 修改数据后保存在文件中
- 存储数据的形式:在系统中列表存储学员对象,数据文件中字典加列表组合。
1)定义类
class StudentManager(object):
def __init__(self):
# 存储数据的列表
self.student_list = []
2)管理系统的框架
需求:系统功能循环使用,用户输入不同的功能序号执行不同的功能。
- 步骤:
- 定义程序入口
- 加载学员信息
- 显示功能菜单
- 用户输入功能序号
- 根据用户输入的不同的序号执行不同的功能
- 定义系统功能函数,例如增删改查等
- 定义程序入口
def run(self):
"""程序的入口函数,也就是程序运行之后的函数"""
# 加载学员信息
self.load_student()
while True:
# 显示功能菜单
self.show_menu()
# 用户输入功能序号
menu_num = int(input('请输入您要执行的功能的序号:'))
# 根据用户输入的序号执行对应的不同方法
if menu_num == 1:
# 添加学员
self.add_student()
elif menu_num == 2:
# 删除学员
self.del_student()
elif menu_num == 3:
# 修改学员
self.modify_student()
elif menu_num == 4:
# 查询学员
self.search_student()
elif menu_num == 5:
# 显示所有学员
self.show_student()
elif menu_num == 6:
# 保存学员
self.save_students()
elif menu_num == 7:
# 退出系统
break
def add_student(self):
pass
def del_student(self):
pass
def modify_student(self):
pass
def search_student(self):
pass
def show_student(self):
pass
def save_students(self):
pass
@staticmethod
def show_menu():
print('请输入如下功能序号:')
print("1:添加学员")
print("2:删除学员")
print("3:修改学员")
print("4:查询学员")
print("5:显示所有学员")
print("6:保存学员信息")
print("7:退出系统")
def load_student(self):
# 加载学员信息
pass
3.main.py
# 导入managersystem模块
import managersystem
if __name__ == '__main__':
# 启动学员管理系统
students_manager = managersystem.StudentManager()
students_manager.run()
定义系统功能函数
1)添加功能
- 需求:用户输入学员信息,将用户输入的学员信息添加到系统模块中。
- 步骤:
- 用户输入姓名,性别,电话
- 创建学员对象
- 将学员对象添加到系统中。
- 代码
def add_student(self):
# - 用户输入姓名,性别,电话
name = input('请输入学员姓名:')
gender = input('请输入学员性别:')
tel = input('请输入学员电话:')
# - 创建学员对象
student = Student(name, gender, tel)
# - 将学员对象添加到系统中。
self.student_list.append(student)
# 打印学员信息
print(self.student_list)
print(student)
2)删除学员信息
- 步骤:
- 用户输入学员姓名
- 遍历学员列表,如果存在即删除,如果不存在即提示学员不存在
- 代码
def del_student(self):
# - 用户输入学员姓名
del_name = input('请输入您要删除学员的姓名:')
# - 遍历学员列表,如果存在即删除,如果不存在即提示学员不存在
for i in self.student_list:
if del_name == i.name:
self.student_list.remove(i)
break
else:
print('您要删除的学员不存在')
3)修改学员信息
- 用户输入姓名
- 遍历列表,如果存在即修改姓名性别电话,如果不存在就提示不存在
def modify_student(self):
# - 用户输入姓名
modify_name = input('请输入您要修改的学员姓名:')
# - 遍历列表,如果存在即修改姓名性别电话,如果不存在就提示不存在
for i in self.student_list:
if modify_name == i.name:
i.name = input('姓名:')
i.gender = input("性别:")
i.tel = input("电话:")
print(f"修改学员信息成功,姓名{i.name},性别{i.gender},电话{i.tel}")
break
else:
print('您要修改的学员信息不存在。')
4)保存学员信息
- 需求:将学员信息保存到存储数据的文件中
- 步骤:
- 打开文件
- 写入数据
- 关闭文件
思考:
文件写入的时候,写入的数据是学员具体信息还是对象地址
文件数据要求的数据类型是什么?
- 代码
def save_student(self):
# - 打开文件
f = open('student.data', 'w')
# - 写入数据
# 注意1:文件写入不能是学员对象的内存地址,必须是具体学员信息,需要把学员信息转换成字典再存储
new_list = [i.__dict__ for i in self.student_list]
print(new_list)
# 文件内的数据要求为字符串儿类型,故存入数据前需要强转
f.write(str(new_list))
# - 关闭文件
f.close()