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值不适时引发
ZeroDivisionError0作为除数时被引发

 
 
 
 
 
 
 
 

异常处理


 
 

捕获异常

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__ 进行实例属性的严格限制

 

posted @ 2022-06-05 20:49  阿伦alun  阅读(182)  评论(0编辑  收藏  举报