Python —— 面向对象(Mixin类)、异常、魔术方法
面向对象
基本原则
1、隐藏细节并创建公共接口。在面向对象设计中建模对象的关键在于,决定该对象的公共接口是什么,接口是对象的一些属性与方法的集合,其他对象可以用接口与这个对象进行交互(如空调,只需要会用遥控就能得到想要的结果,不需要知道空调内部的结构)。
在设计公共接口时一定要谨慎,若是公共接口的名称、参数位置、参数类型、参数数量等信息时,会导致所有调用它的客户端全部需要修改,因此在设计公共接口的时候,应尽量保持简单,永远基于易用性而非编码的难度来设计对象接口
2、尽量避免使用多继承(Mixin 类除外)。Mixin类是通过创建一个新类来继承原来的类,以获取原来类中的属性及方法。同时在继承时,又多继承一个只有需要添加新功能的类,由此来做到添加新功能的同时避免出现多个基类出现同名属性、方法的尴尬情况
类
类的命名:严格使用大驼峰命名
类的构成:
1、实例化(_ _ init _ _,构造实例):实例对象(Instance Object)、实例属性(Instance Attribute)
2、引用(_ _ del _ _,删除引用):类对象引用(Object Reference)
3、类自身:类对象(Class Object)、类属性(Class Attritute)
4、方法:类方法(Class Method, @classmethod)、实例方法(Instance Method, def - selft)、自由方法(Namespace method, def)、静态方法(Static Method, @staticmethod)、保留方法(Reserved Method, _ _ XX _ _)
类的五种方法
1、实例方法
特征:定义时,第一个参数传入"self"(是__init__传入的第一个参数,也可以叫别的名字。不过最好叫self)
该方法是该类的所有实例都具有的方法
调用:实例对象调用
可修改:实例属性和方法、类属性和方法
2、类方法
特征:定义时,要在方法前加一个装饰器"@classmethod",第一个参数传入"cls"(也可以叫别的名字,不过最好叫cls)
该方法是该类的所有实例都具有的方法,类对象也有
调用:实例对象调用、类对象调用
可修改:类属性、其他类方法
注意:
1、一定要加装饰器,并且传入的第一个参数是cls
2、当实例对象调用类方法时,其实传入类方法的是实例对象的类。即先通过实例的 .__class__ 来获取实例的类,在将该类传入类方法的第一个参数
3、自由方法
特征:定义在类命名空间中的一个普通函数,第一个参数即不传self,也不传cls。仅仅是定义在类中的一个函数,类的实例对象无法使用类中的自由方法。
调用:自由方法仅可通过 “类名.方法名” 的形式调用
可修改:类属性、其他类方法
4、静态方法
特征:在自由方法上添加了一个装饰器 “@staticmethod” ,从而使该方法可以同时被类和实例使用
调用:实例对象调用、类对象调用
可修改:类属性、其他类方法
5、保留方法
特征:双下划线开头及结尾的方法,一般是python内部保留,与某些方法有对应的联系,如 _ _ len _ 对应len(),即在class内部定义 _ len _ _ 则可在类外使用 len() 的形式调用。 在类内部编写 _ _ len _ _的过程即重载,对python内部已经定义好的方法,进行重定义以在对该类实例使用时,其进行的操作与默认定义好的有所不同
类的公开、私有方法和属性
保护变量
定义:变量名以单下划线开头的变量,只是逻辑上约定俗成的,告诉其他程序员,这个变量不要直接改
类属性
定义:在类中直接定义的属性,如下图的 n
调用:类、实例对象都可 读/写 该属性
–
私有类属性
特点:私有类属性是类属性名以双下划线开头的属性,此类属性仅可在类的内部使用,类外或者子类都不可访问此属性
用处:对某些属性进行保护,仅可通过某个方法去修改其值
如:
class DemoClass:
count1 = 0
__count2 = 0
def __init__(self, name, age):
self.name = name
self.age = age
DemoClass.count1 += 1
DemoClass.__count2 += 1
@classmethod
def GetCount1(cls):
return DemoClass.count1
@classmethod
def GetCount2(cls):
return DemoClass.__count2
dc1 = DemoClass("小王", 18)
dc2 = DemoClass("小李", 20)
# 正常获得
print(DemoClass.count1)
# 抛出异常
print(DemoClass.__count2)
通过方法去获得
注意:python并不明确支持私有属性,实例的私有属性可以通过 “实例. _ 类名 _ _私有属性名” 的形式去获取
注意:私有变量只是解释器将双下滑线开头的变量改了个名字(改为 _类名__属性名),导致找不到对应变量而已,还是可以从 __dict__ 找到其修改后的名字,并直接修改
私有类方法
特征:双下划线开头的方法名,常用于实现类内部的某些功能,并不对外提供接口
类的保留属性(语法糖)
常用:
方法 | 用途 |
---|---|
className._ _ qualname _ _ | 获取该类全命名空间类名,如:定义在某个函数(f)内部,则返回 f.className |
className._ _ name _ _ | 返回该类的类名 |
className._ _ bases _ _ | 返回该类的基类 |
className._ _ dict _ _ | 返回类成员信息的字典,key是属性名和方法名,value是地址 |
instanceName._ _ dict _ _ | 返回实例对象属性信息的字典,key为属性名称, value为值 |
className._ _ class _ _ | 返回该类对应的类信息,即type信息 |
className._ _ doc _ _ | 返回该类的类描述,不可继承 |
className._ _ module _ _ | 类所在模块的名称 |
如:
def test():
class DemoClass:
count1 = 0
__count2 = 0
def __init__(self, name, age):
self.name = name
self.age = age
DemoClass.count1 += 1
DemoClass.__count2 += 1
@classmethod
def GetCount1(cls):
return DemoClass.count1
@classmethod
def GetCount2(cls):
return DemoClass.__count2
dc1 = DemoClass("小王", 18)
dc2 = DemoClass("小李", 20)
print("__name__: ", DemoClass.__name__)
print("__qualname__: ", DemoClass.__qualname__)
print("__bases__: ", DemoClass.__bases__)
print("DemoClass __dict__: ", DemoClass.__dict__)
print("dc1 __dict__: ", dc1.__dict__)
print("__class__: ", DemoClass.__class__)
print("__doc__: ", DemoClass.__doc__)
print("__module__: ", DemoClass.__module__)
test()
类方法的重写与重载
重写
使用相同的变量名,重新编写方法即可,会覆盖掉上一个同名函数
class GrandFather:
"this is grand father"
money = 10000
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def showMoney(cls):
print(GrandFather.money)
return GrandFather.money
@classmethod
def makeMoney(cls):
GrandFather.money += 100
print("Money now is: ", GrandFather.money)
return GrandFather.money
class Father(GrandFather):
"this is father"
money = GrandFather.money
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def makeMoney(cls):
Father.money += 50
print("Money now is: ", Father.money)
father = Father("小李", 30)
print(father.money)
father.makeMoney()
增量重载
通过借助 “spuer().基类方法” 获取基类的返回值,再在此值上进行功能的添加
class GrandFather:
"this is grand father"
money = 10000
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def showMoney(cls):
print(GrandFather.money)
return GrandFather.money
@classmethod
def makeMoney(cls):
GrandFather.money += 100
print("Money now is: ", GrandFather.money)
return GrandFather.money
class Father(GrandFather):
"this is father"
money = GrandFather.money
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def makeMoney(cls):
Father.money = super().makeMoney() + 1000
print("Money now is: ", Father.money)
father = Father("小李", 30)
print(father.money)
father.makeMoney()
命名空间
命名空间可以简化理解为作用域,在python中有两个保留字 global 和 nonlocal 分别代表 “全局” 和 “上一级作用域” , 若在上一级作用域中未找到相关变量则继续往上一级寻找。
装饰器
装饰器的一般含义,在不改变原方法的前提下,为原方法扩充某些功能。如不扩充运行时间计数、赋值判断等。
赋值判断属性装饰器
class DemoClass:
def __init__(self, name):
self.name = name
# 将age作为实例对象的属性
@property
def age(self):
return self._age
# 设置age时,通过下面的方法对值进行加工
@age.setter
def age(self, value):
if value < 0 or value > 100:
value = 30
self._age = value
dc1 = DemoClass("老王")
dc1.age = -100
print(dc1.age)
属性装饰器一般用法:
1、对某个方法添加装饰器 —— @property
2、设立同名的属性赋值判断方法,并添加为属性装饰器的赋值入口 —— @属性装饰器名.setter
自定义异常类型
使某一个类继承自Exception类,即可以设置自定义的异常类
如:
class TooHandsome(Exception):
print("TooHandsome init")
while True:
score = int(input("请输入我的颜值有多少分(满分10分): "))
if score >= 50:
raise TooHandsome()
else:
print("答错了,请重新输入")
使用装饰器装饰类
通过如下两个装饰器,分别给类的实例对象和类对象本身添加属性
# 在实例化对象时,不改变类而给实例添加属性,属性通过字典的形式传入
def add_instance_attr(attr_dict):
def _add_attr(cls):
def _wrapper(*args, **kwargs):
# 实例化
res = cls(*args, **kwargs)
# 添加某些属性
for key, value in attr_dict.items():
res.__dict__[key] = value
return res
return _wrapper
return _add_attr
# 添加类属性装饰器
def add_class_attr(attr_dict):
def _wrapper(cls):
for key, value in attr_dict.items():
setattr(cls, key, value)
return cls
return _wrapper
@add_instance_attr({"name": "张三"})
@add_class_attr({"lastname": "张"})
class Test:
pass
t = Test()
print(t.lastname)
print(t.name)
先验知识:当有多个装饰器对某函数或者类进行装饰时,最靠近函数或者类的最先起效,然后逐步向上
注意:此处的两个装饰器顺序必须是如上代码所示的这样
原因:装饰器 @add_class_attr() 当被装饰完后返回被装饰的那个类,而装饰器 @add_instance_attr() 则返回的是 _wrapper 这个函数对象 —— 这里是指类定义处的装饰器刚完成装饰,还没有使用类进行实例化,若进行实例化则装饰器 @add_instance_attr() 则返回的是实例对象
若调换两个装饰器的位置则会按顺序发生如下情况:
1、在类定义完后 装饰器 @add_instance_attr 开始装饰,(带参装饰器的参数会在装饰前,即解释器刚执行到 @add_instance_attr() 时,先进行 add_instance_attr() 的调用,对应此处则为将属性字典存储到 函数 add_instance_attr 的作用域中,并返回 _add_attr 函数对象),开始装饰即代表执行该语句 Test = _add_attr(Test)。该语句的执行结果返回 _wrapper 函数对象,则此时类 Test 被保存到 _add_attr 的作用域中,而变量 Test 此时已经被替换为了 _wrapper 函数对象;
2、紧接着装饰器 @add_class_attr 开始装饰,(带参装饰器在进行装饰前就进行了第一次的执行,此时实际是 @add_class_attr 中的 _wrapper 使用了变量 @add_class_attr),开始装饰则执行 Test = _wrapper(Test),但注意此时的 Test 已经是装饰器 @add_instance_attr 的 _wrapper 函数对象而非初始的 Test 类对象。也就是说,装饰器 @add_class_attr 实际是将本该给 Test 类的属性,赋予了装饰器 @add_instance_attr 的 _wrapper 函数对象;
3、在后续调用 t = Test() 时,实际执行的是 @add_instance_attr 的 _wrapper函数对象,并且该函数对象被 @add_class_attr 添加了属性。然而 @add_class_attr 添加的属性并没有给到 Test 类对象,此处进行实例化的还是原来的 Test 类,只不过会添加 @add_instance_attr 的功能,但是装饰器 @add_class_attr() 并没有起到应有的效果
使用装饰器给类添加一个方法
class Document:
def __init__(self, content):
self.content = content
def printable(cls):
def _wrapper():
printable = lambda self, content: print(content)
setattr(cls, "printable1", printable)
_wrapper()
return cls
@printable
class Word(Document): pass
class Pdf(Document): pass
w = Word("word")
w.printable1("alun zui shuai")
print(w)
print(w.__dict__)
print(w.__class__)
print(w.content)
print(Word.__dict__)
控制属性的读写性
常规方法
含义:
1、通过 @property 来给某个属性提供另外一个名字的可读性(通过将一个函数转换为属性,来完成对外的接口暴露)
2、通过 @属性名.setter (此处的属性名是指通过 @property 来设置的方法),来给某个被 @property 修饰的方法,提供可写性
3、通过 @属性名.deleter 来给某个被 @property 修饰的方法,提供可删性
class Test:
def __init__(self, name, age):
self._name = name
self._age = age
@property
def name(self):
return self._name
@property
def age(self):
return self._age
@age.setter
def age(self, value):
self._age = value
@age.deleter
def age(self):
del self._age
t = Test("张三", 18)
print(t.name)
print(t.age)
t.age = 20
print(t.age)
del t.age
# 没有 setter 无法写属性
t.name = "李四"
print(t.name)
更高级的写法(建议)
方法:借助 property(self, fget, fset, fdel, doc) 方法来给实例添加对外的接口属性,这种写法 pycharm 可以读取到其中的值,方便提供自动拼写
- fget —— 属性读取方法
- fset —— 属性写入方法
- fdel —— 属性删除方法
- doc —— 属性描述文本
如:
class Test:
def __init__(self, name, age):
self._name = name
self._age = age
# 定义属性的读写器
def get_name(self):
return self._name
def set_name(self, value):
self._name = value
def del_name(self):
del self._name
def set_age(self, value):
self._age = value
def del_age(self):
del self._age
# 通过 property 方法,给对外的接口属性绑定行为
name = property(get_name, set_name, del_name, "this is name")
age = property(lambda self: self._age, set_age, del_age, "this is age")
t = Test("张三", 18)
print(t.name)
print(t.age)
t.age = 20
print(t.age)
del t.age
t.name = "李四"
print(t.name)
多继承及多级继承
类的一些特殊属性和方法:
属性和方法 | 含义 |
---|---|
__base__ | 类的基类 |
__bases__ | 类的基类元组 |
__mro__ | 显示继承链 |
mro() | 同上 |
__subclasses__() | 类的子类列表 |
suepr().__init__():
功能:调用基类的实例化方法(__init__),给子类的实例对象添加基类中的实例属性(__init__中定义的属性)。当子类和基类中均具有__init__方法时,要在子类中使用 suepr().__init__() 来显式的调用,才可以给子类实例中添加基类定义的实例属性
不需要显式调用的情况:当子类中没有 __init__() 方法时,不需要使用 suepr().__init__() 来显式的调用,因为实例化的时候就已经根据继承链找到了基类中的 __init__() 方法(因为子类中没有该方法)
super(BaseClass, self).__init__():
- BaseClass —— 当前要类调用的某个基类,可以是多继承中的某个基类,也可以是多级继承中的某个基类
- self —— 当前的实例化对象,固定传入
功能:在当前类中调用某个具体的基类
Mixin 类
含义:Mixin 类是利用多继承的特性,通过定义新的 Mixin 类来给子类添加新功能
# 定义 Mixin 类,用来给子类添加功能
class PrintableMixin:
def print(self):
print(self.content, 'Mix')
class SuperPrintableMixin(PrintableMixin):
def print(self):
print("*" * 20)
super().print()
print("*" * 20)
# Dcoument 类用来模拟第三方库,不允许进行修改
class Document:
def __init__(self, content):
self.content = content
# Word 类继承 Document 和 PrintbaleMixin,通过 PrintbaleMixin 类来获取新功能
class Word(Document, PrintableMixin): pass
# Pdf 类继承 Document 和 SuperPrintableMixin,通过 SuperPrintableMixin 来完成调用 Printable 的同时,添加新的功能
class Pdf(Document, SuperPrintableMixin): pass
wd = Word('word')
wd.print()
pdf = Pdf('pdf')
pdf.print()
实例
属性查找顺序
一、当实例使用 “.” 来访问属性时
1、会先找自己的 __dict__
2、若 __dict__ 中不存在,则通过属性 __class__ 找到实例化自己的类,去类的 __dict__ 中查找
二、实例使用 __dict__[属性名]的方式访问变量,则不会去查找类中是否具有对应属性
异常
常见的异常类型
Exception | 所有异常的基类 |
---|---|
AttributeError | 特性引用或赋值失败时引发 |
IOError | 试图打开不存在的文件等情况时引发 |
IndexError | 使用序列中不存在的索引时引发 |
KeyError | 使用映射中不存在的键时引发 |
NameError | 找不到变量时引发 |
SyntaxError | 语法错误引发 |
TypeError | 数据类型错误引发 |
ValueError | 值不适时引发 |
ZeroDivisionError | 0作为除数时被引发 |
异常处理
捕获异常
try:
pass
except ValueError as e1:
pass
except (TypeError, NameError, KeyError) as e2:
pass
try…else…
else只有在不触发except的时候才会执行,若触发则不执行else
try:
pass
except:
pass
else:
pass
魔术方法
__new__
功能:类似装饰器,有对__init__进行补充的作用。即在调用__init__实例化对象前,进行一些操作,如实例化其他类的对象 或 检测已经实例化过一个对象,将现有对象返回等
触发:实例化对象时触发,优先于__init__
参数:传入一个类对象,可以是自己的类、自身类中的子类 或 其他类对象
特点:要有return,可以返回对象或者None
注意:
- __new__触发快于__init__
- __new__创建对象
- __init__初始化对象
- __new__若不返回本类对象,则__init__不会被调用
- __new__ 和 __init__ 传入的参数要一致,否则类无法初始化(由于__new__调用更早导致,若参数不一致则报错,也可以使用*args, **kwargs来给__new__接收参数)
使用方式:
- 返回本类自身对象
- 单例模式应用
__name__
功能:存储对象名
注:不定义也会有,但是只有类对象默认有,实例对象默认没有
__calss__
功能:存储对象类型
注:不定义也会有
__dict__
功能:对象的属性字典
__qualname__
功能:类的限定名
注:不定义也会有,但是只有类对象默认有,实例对象默认没有
__hash__()
功能:定义对象返回的 hash 值
使用:
1、直接定义 __hash__(self),并让其返回一个整数
2、定义__hash__ = None,则可让对象变为不可哈希对象(若不显示定义,则会从 object 中基础取哈希值的方法)
注: set() 的去重并非仅针对哈希值,而是直接以 is 和 __eq__(self) 函数作为依据,若均为 True 则去掉一个
可 hash 对象必须提供 __hash__ 方法,没有提供的话, isinstance(p1, collections.Hashable) 一定为 False
如:
class T:
def __hash__(self):
return 1
a = T()
print(hash(a))
>>> 打印1
__bool__()
当调用内建函数 bool(),或者对象放置到逻辑判断位置时,所返回的值。如果没有定义 __bool__() 就找 __len__() 返回长度,非 0 为真。如果 __len__() 也没有定义,则所有实例都返回真
__str__()
当被 str()、format、print() 调用时,返回的值
__repr__()
当被非 str()、format、print() 调用时,返回的值。如列表中的元素,当打印列表时,调用列表的 __str__() 而调用列表中元素的 __repr__()
__dir__()
返回类或者对象的所有成员名称列表
系统变量
__slots__
功能:此属性为类属性。用于声明实例具有哪些属性(类属性不受限制),提前分配好空间,同时也关闭了动态添加属性的能力
定义方法: __slots__ = (“属性1”, “属性2”)
意义:
- 在需要大量创建实例对象时,节省内存
- 在涉及严格的类时,要借助 __slots__ 进行实例属性的严格限制