PY01-05:Python面向对象

  面向对象是个老的主题了,但不同语言中对面向对象的语法实现仍然体现了面向对象不是一个简单的思想,实际对其理解与应用是有差异变化的。本主题主要围绕Python的面向对象编程语法而来,而不是面向对象的分析与设计而来,尽管最后我们补充了类的关系设计。
  本文的内容包含:
  1.类与对象
  2.继承
  3.元类与装饰类
  4.类图


主题五:面向对象-技术知识

  • 应用目标

    1. 阶段实战:应用面向对象实现信息管理系统(CUI与GUI版本)
    2. 综合实战:应用面向对象来构建桌面应用程序
      • 计算器
      • 记事本
      • 扫雷
      • GUI编程
      • 图形图像处理
  • 技能目标

    1. 能模块化编程
    2. 具备良好的编码规范
    3. 熟练的参考文档阅读能力
    4. 具备基本的面向对象思维与设计能力
    5. 基本技术应用能力
      • 能熟练使用面向对象的语法实现类;
      • 能熟练使用面向对象的语法扩展类;
  • 知识点目标

    1. 类定义与应用

      • 类与对象;
      • 类成员与调用;
      • 类方法与静态方法;
      • 数据初始化与构造器;
      • 数据作用域
      • 数据访问限制与私有
      • 数据与属性;
      • 运算符;
      • 成员绑定与__slots__;
      • 类文档注释
    2. 继承

      • 单重继承与多重继承;
      • 子类与父类数据初始化;
      • override与overload;
      • 类型与成员测定;
      • 类接口;
      • 抽象超类;
    3. 对象的内存管理

      • __new__运算符;
      • 析构器;
      • 引用计数;
      • 垃圾回收
    4. 元类与类装饰器

      • 元类类型;
      • 类装饰器;
    5. 类、模块与包

      • 类与模块引入
      • 包与模块的引入;
      • 模块的加载与卸载;
      • 嵌套类
    6. 类设计

      • 类泛化关系的设计
      • 类关联关系的设计;
      • 类对象构建的设计;

类的定义与应用

  • 前面我们使用类型创建对象,其中的类型我们应该知道本质是一个类,这个类怎么实现的?这就是本主题需要讲解的内容。

  • 类就是类型,这是Python这种纯面向对象语言(万事万物皆对象)的核心思想:

    • 所谓类型就是一种数据格式,是创建对象的一种内存分配格式标准(分配多大,怎么分配),甚至是数据存储序列化的格式标准(某些语言就是这样的,Python中提供__reduce__来描述)。

类的定义与对象创建

  • 类的定义除了语法以外,核心工作在于两个方面:

    • 数据定义
    • 数据处理实现
      • 运算符
      • 函数操作
      • 属性
  • 数据定义:

    • 数据定义仅仅是数据的描述,Python解释器在执行的时候并不会分配内存空间。只有在使用类创建对象,解释器才会按照类中数据描述分配内存空间。
    • 换句话说,用户定义的类与函数一样,执行的时候,仅仅是加载,不会执行,函数只有在调用才会执行;类中定义的数据只有在创建对象(调用构造器)的时候分配(有的甚至在后面调用过程中才会陆续分配空间)。
  • 操作定义:

    • 类中数据操作的定义使用的是函数,这些函数可以访问类中定义的所有数据。因为类中数据只有创建对象才能分配空间,所以这些函数也只有对象创建才能调用,通过类直接调用的时候,因为数据内存没有分配,数据不存在,调用会失败(语法上就是不允许)。

    • 不过某些函数不调用类中数据的,这些函数是可以直接调用的,为了区别其他调用类中数据的函数,这些函数会在语法上特殊标记,这些函数称为类函数,使用了类中数据的函数称为对象函数(更加标准的称呼是实例函数,因为对象也称呼为一个类的实例,因为一个类型可以反复使用,用来创建很多对象、变量或者实例)

类定义的语法

  1. 类定义语法格式

class   类名(其他类1, ...... ):
    语句
    .....

  1. 类的语法格式:

    • 类头
      • class 是关键字,与函数的def一样,表明块的类型类型时类。
      • 类名遵循Python中标识字的命名规范。但不要与关键字冲突,也不要与标准库中的命名冲突。类名建议遵循驼峰命名法:每个单词的首字母大写(像不像驼峰?(⚈᷁‿᷇⚈᷁))。
      • 扩展表示类的创建不是重新定义,而是从其他类扩展定义(增加功能),这个后面会单独作为一个主题讲解。扩展的类可以是一个列表,使用逗号分隔。如果没有扩展类,默认是object类,这个可以省略()。
      • : 冒号是Python中块头结束的分隔界定符
    • 类体
      • 最简单的类体就是一个pass空语句
      • 类体中可以放任意语句;
        • 数据语句(在创建对象会得到调用)
        • 功能语句(在创建对象会得到调用)
      • 类体中可以放任意块;
        • 函数块
        • 其他类块
  2. 最简单的类就是一个空类

class  ClsA:   # class ClsA():  # 这是标准格式,默认是扩展object,可以省略()
    pass

print(ClsA)
print(dir(ClsA))     # 可以看见很对成员,都是来自object。
<class '__main__.ClsA'>
['__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__']
  • 说明:
    下面三个类的定义是等价的:
class  ClsA:
    pass

class ClsA():
    pass

class ClsA(object):
    pass
  1. 类体中的数据语句
    • 在类体中可以定义数据语句
class ClsA:
    a = int(20)             # 类块加载的时候得到执行

print(ClsA)
print(dir(ClsA))     #  可以看见上面定义的a
<class '__main__.ClsA'>
['__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']
  1. 类体中的功能语句
    • 在类体中可以使用功能语句;

    • 包含流程控制块;

    • 警告:一般情况不要在类总直接编写功能语句。

class ClsA:
    a = int(20)                                
    print('Hello,我是类块下的语句')     #  在类块加载的时候得到执行

print(ClsA)
print(dir(ClsA))
Hello,我是类块下的语句
<class '__main__.ClsA'>
['__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']
  1. 类块中定义函数
    • 类块中定义的函数在加载不会被调用,但会得到定义,定义后的函数才能调用。
class ClsA:
    a = int(20)                                
    print('Hello,我是类块下的语句')     #  在类块加载的时候得到执行
    
    def  func_a():        # 在dir的输出会看到这个定义的函数
        print(函数)

print(ClsA)
print(dir(ClsA))
Hello,我是类块下的语句
<class '__main__.ClsA'>
['__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', 'func_a']
  1. 类块中定义类
    • 类块中嵌套类的定义,该类也可以用来创建对象(后面专门主题讨论嵌套类的相关使用规则)
    • 这种嵌套可以无穷无尽继续下去(理论上应该无穷无尽,不过一般解释器都有层数限制)。
class ClsA:
    a = int(20)                                
    print('Hello,我是类块下的语句')     #  在类块加载的时候得到执行。
    
    def  func_a():        # 在dir的输出会看到这个定义的函数。
        print(函数)
    
    class  InnerCls:     # 在下面dir输出可以看到InnerCls的定义。
        pass

print(ClsA)
print(dir(ClsA))
Hello,我是类块下的语句
<class '__main__.ClsA'>
['InnerCls', '__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', 'func_a']

对象创建

  • 从前面的应用经验中直到,使用类创建对象,需要使用类的构造器,默认情况下,没有定义构造器,类都会提供一个默认构造器__init__(),所以没有构造器的类,也可以用来创建对象。

  • 语法

    • 类名() # 创建的对象不保存,没有变量名引用,称呼为匿名对象。
    • 对象变量 = 类名()
  • 重点提示:

    • 使用类创建对象,是不是与函数调用语法一样?一样
    • 构造器是一个特殊的函数,也称构造器函数,专门用来初始化对象(不是分配对象内存空间,分配使用的分配器函数__new__(),关于内存分配在对象的内存管理中专门讲解)。
class ClsA:
    pass

obj_a = ClsA()
print(obj_a)
<__main__.ClsA object at 0x107c59550>

类中成员与调用

  • 类中成员指类中定义的数据与功能:
    • 数据成员
    • 函数成员

数据成员定义与使用

  1. 在类中直接定义数据:

    • 与普通的数据语句一样。 变量: 类型 = 值
  2. 使用方式有两种:

    • 通过类直接使用;(因为分配过空间就可以使用)
    • 通过对象直接使用;
class ClsA:
    m_a: int = 20


print(ClsA.m_a)

ClsA.m_a =88

obj_a = ClsA()
print(obj_a.m_a)
20
88
  1. 说明:
    • 只要分配过空间的变量就可以使用,上面使用方式很正常的。
    • 使用ClsA.m_a这是成员调用运算符号,表明m_a在ClsA中,或者m_a在对象obj_a中。
    • 但是上面例子中的m_a在对象与ClsA中是共享的,在全局作用域都可以访问(只是多了一个类前缀与对象前缀),本质是个全局变量。

函数成员定义与调用

  1. 类中函数的定义与普通函数的定义一样

    • def 函数名(参数......) -> 返回类型:
  2. 调用方式两种

    • 类名.函数(参数) # 无返回值
    • 变量 = 类名.函数(参数) # 接收返回值
class ClsA:
    def func_a(p1:int, p2:int)->int:
        return p1+p2

print(ClsA.func_a(45,55))

obj_a = ClsA()
# print(obj_a.func_a(45,55))      # 使用对象访问产生错误
100

类中类的定义域使用

  • 与普通类一样,只是多一个前缀(我们也称命名空间)。
  • 与函数的差异是:类也可以通过对象调用来定义该类型的对象。
class ClsA:
    class InnerCls:
        pass

obj_inner = ClsA.InnerCls()

print(obj_inner)

obj_a = ClsA()
obj_inner_a = obj_a.InnerCls()    # 使用对象作为前缀
print(obj_inner_a)
<__main__.ClsA.InnerCls object at 0x107c59908>
<__main__.ClsA.InnerCls object at 0x107c59a20>

理解类中成员

  • 综上所述,类中定义的数据,函数,类与普通变量,函数,类差不多,就是多了一个命名限定符(就是使用类作为前缀类名.

  • 我们成上述数据、函数与类为类中成员:

    • 类中数据成员
    • 类中函数成员
    • 内部类(或者嵌套类)
  • 类中成员的共同特征,都可以通过类直接使用。

类方法与静态方法

  • 上面类中的成员,我们一直小心翼翼称为类中的成员,而没有称为类成员是有原因的:
    • 实际在Python语法中,存在可以通过对象与类同时调用的函数。这种函数称为静态函数,上面那种只有类能调用的函数称为类函数。

静态函数与@staticmethod

  1. 从前面例子可以看出,类中函数默认就是类静态函数,实际可以强制标明该函数为静态函数(本质是全局函数),这个通过一个函数装饰器可以实现。

    • @staticmethod:其定义如下:
class staticmethod(object)
     | - staticmethod(function) -> method

  1. 静态函数的定义与使用例子
class ClsA:
    @staticmethod
    def func_a(p1:int, p2:int)->int:
        return p1+p2

print(ClsA.func_a(45,55))

obj_a = ClsA()
print(obj_a.func_a(45,55))      # 运行正确
100
100

类函数与@classmethod

  • 默认情况下,类中函数式类函数,也可以显式定义函数为类函数,使用装饰@classmethod,其定义如下:

class classmethod(object)
     | - classmethod(function) -> method

  • 类函数必须提供一个self函数,因为类函数在解释器执行的时候,会传递一个类给函数。下面讲完self,我们还会讨论。
class ClsA:
    @classmethod
    def func_a(self, p1:int, p2:int)->int:     # 缺少self会错误,参数不匹配
        return p1+p2

print(ClsA.func_a(45,55))

obj_a = ClsA()
print(obj_a.func_a(45,55))      # 使用对象访问产生错误
100
100

数据作用域

  • 上面的数据、函数可以通过类直接使用,等同于全局变量、全局函数;这是类作为一个代码组织单位而使用,可以称为命名空间,使用场景可以用来防止与其他相同名字的数据与函数冲突。

    • 等于对全局变量归类,使用起来更加有逻辑与调理。
  • 使用类类划分数据的例子(后面因为这个系统单独引入了一个标准模块模式化这个常见问题:枚举类型)

class  Dir:
    UP = 1
    DOWN = 2
    LEFT = 3
    RIGHT = 4

# 在使用方向的时候,再也不怕与其他地方定义的UP冲突了。(命名冲突是个头疼的事情)
  • 上面的数据只要类加载就存在,Python引入了一种数据与函数定义方式,这种数据与函数不是全局,也不是局部:

    • 对象分配数据就分配,对象释放数据就释放;
    • 数据与函数只有对象能够调用,类不能调用;
  • 我们称这种依赖对象作为作用域的现象为:对象域,下面我们专门讲解对象域的语法与应用。对象域在Python中只有两个成员:

    • 对象域数据,也称对象成员数据,简称:成员变量
    • 对象与函数,也称对象成员函数,简称:成员函数

成员函数

  1. 定义- 在类中定义
    • 注意其中函数第一个参数必须是self,这是一个关键字。

class  类:
    def   函数名(self, 参数列表,.....)->返回类型:
        语句
        ....
        return 返回值     # 可选

  1. 定义- 在类外定义
    • 这是Python这种动态脚本语言的特色。

class  类:
    ......
    

def 函数名(self, 参数列表)->返回类型:
    语句
    ......
    return 返回值    # 可选

类.函数名 = 函数名     # 绑定
     
  • 说明使用lambda表达式也可以直接绑定。
  1. 成员函数的调用:

    • 变量 = 对象.函数(参数)
  1. 成员函数定义与使用的例子
class  ClsA:
    def add(self, p1, p2):
        return p1 + p2
    

def substract(self, p1, p2):
    return p1 - p2

ClsA.substract = substract    # 动态绑定

ClsA.product = lambda self, p1, p2: p1*p2     # 这里可以体会lambda表达式字面值之美。

obj_a = ClsA()
re =obj_a.add(45, 55)
print(re)
print(obj_a.substract(100,45))
print(obj_a.product(100,45))

# re = sClsA.add(45,55)    # 不能通过类调用
re = ClsA.add(obj_a, 45, 55)   # 除非传递一个对象(因为成员函数,成员变量都依赖对象才能运行)
print(re)
100
55
4500
100

特殊的构造器函数

  • 有一个特殊的函数,在对象定义的时候会自动调用,这就是构造器函数,其特殊之处在于:

    • 很少显式调用,对象创建隐式自动调用(所以成为构造器函数);
    • 函数名固定,必须是:__init__
    • 参数与普通函数一样,可以随意;在创建对象的时候,传递相应的参数,参数使用规则与函数一样。
    • 函数不需要返回值。
  • 下面是构造器函数的使用例子

class ClsA:
    def __init__(self, p1):
        print('构造器:', p1)

obj_a = ClsA(888)

构造器: 888
  • 尽管所有成员函数都可以传递参数,但是我们建议部分重要的参数还是通过构造器函数传递是最佳的。
  • 不像其他语言,Python不支持函数重载的语法(因为可选参数,默认值可以解决其他语言导入的重载语法功能),也不支持构造器函数重载。
  • 构造器函数也用作类型转换函数。比如int类,a = int('20',10)

成员变量

  1. 成员变量语法:
    • 语法:self.变量 = 值

    • 在类中任何成员函数中都可以定义成员变量(包含构造器函数),但不能在类下面直接定义成员变量。

    • 强烈建议成员变量的定义都最好集中在构造器函数中最佳 - 一种良好的编码规范。

  1. 定义成员量的例子
class ClsA:
    # self.m_c = 40    #  不允许在类下直接定义成员变量
    def __init__(self):
        self.m_a = 20
    
    def show(self):
        self.m_b = 30

obj_a = ClsA()
obj_a.m_a =88                    # 修改成员变量。

obj_a.show()                      # show不调用,m_b成员变量得不到定义。(这就是建议成员变量定义在构造器函数的原因)

print(obj_a.m_a, obj_a.m_b)    # 访问成员变量。
88 30
  1. 关于self
  • self代表的实际是类的实例。类似于函数的参数,类没有实例化(没有使用类构建对象)时,self数不存在的;当类实例化一个对象,这个self就代表这个对象。

    • 调用不同对象,self也不同。

    • 在类中调用成员变量与函数的时候,就是用self表示对象。

    • 成员函数中的self与成员变量前的self是一样的作用与含义。

  1. 再论静态函数与类函数
  • 静态函数与类函数还有同一个区别,就是对self的处理。@classmethod可以把成员函数转换成类函数。这个时候,解释器会永远给类函数的self传递类本身,没有self就不传递。

下面使用例子代码说明:

class   ClsA:
    
    @classmethod       # 不能使用@staticmethod
    def show(self):       # 使用@classmethod,没self就是ClsA类本身,而不是实例本身,就算使用实例调用也是类本身
        print(self)
        print("classmethod & staticmethod")
        
    def display(self):
        print('display:', self)      # 注意这个self与上面的self不同。
        

ClsA.show()


obj_a = ClsA()
obj_a.show()
obj_a.display()

<class '__main__.ClsA'>
classmethod & staticmethod
<class '__main__.ClsA'>
classmethod & staticmethod
display: <__main__.ClsA object at 0x107c59b00>
  • 注意一

    • 类下直接编写的语句,一般不具备self环境,是不能使用self的。self环境是解释器传递过来的。
    • 说白了其实是一种实例动态绑定数据变量。
  • 注意二

    • 成员变量会随对象消失而消失,但是类变量本质是全局变量,不会随对象消失而消失。只要类加载了,他就存在,直到卸载类为止才消失。
    • 遗憾的是Python在Python3不再提供模块或者类的重新加载与卸载,除非重新启动Python,所以大量使用类变量不是一个好的方式,这种模式只有在代码中显式使用del释放。不释放就意味着内存闲置(另外一种变相的内存泄露
    • 所有为了封装数据,最好采用成员变量。

数据访问限制与私有

  • Python提供了一种约定(下划线)来限制对成员(成员变量,成员函数)的访问:
    • _xxx 不能用’from module import *’导入
    • __xxx__ 系统定义名字
    • __xxx 类中的私有变量名

私有成员变量与成员函数

  • 所谓私有,只能在类内部访问。在类的作用域外访问就是非法语法。下面使用例子说明:
class ClsA:
    def __init__(self):
        self.__m_a =20
    
    def __show():
        print('private member function!')


obj_a = ClsA()
# print(obj_a.__m_a)     # 访问错误:AttributeError: 'ClsA' object has no attribute '__m_a'
obj_a.__show()             # 访问错误:AttributeError: 'ClsA' object has no attribute '__show'
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-27-623617688d55> in <module>()
      9 obj_a = ClsA()
     10 # print(obj_a.__m_a)     # 访问错误:AttributeError: 'ClsA' object has no attribute '__m_a'
---> 11 obj_a.__show()             # 访问错误:AttributeError: 'ClsA' object has no attribute '__show'


AttributeError: 'ClsA' object has no attribute '__show'

访问私有成员的场景

  • 下面是一个例子说明,怎么访问私有成员,类中访问:

    • 直接访问:使用self访问;
    • 实例访问:使用对象访问;
  • 下面使用的是成员变量,对成员函数一样的道理。

class  ClsA:
    def __init__(self, v):
        self.__m_a = v
    
    def is_equal(self, obj):
        if self.__m_a == obj.__m_a:         # obj.__m_a使用实例访问私有    self.__m_a在类内部使用self直接访问
            return True
        else:
            return False

obj_a = ClsA(30)
obj_b = ClsA(40)
obj_c = ClsA(30)

print(obj_a.is_equal(obj_b))
print(obj_a.is_equal(obj_c))
False
True

访问是有类变量与类函数的例子

  • 对本质全局的类变量与类函数是否也一样有效呢?下面使用例子说明:
class ClsA:
    __cls_a = 20
    
    def __func_a():
        print('private')

    def meth_a():
        print(ClsA.__cls_a)       # 内部可以调用,函数一样的。
     
    @classmethod
    def meth_b(self):
        print(self.__cls_a)       # 内部可以调用,函数一样的。
        self.__func_a();

print(dir(ClsA))
# print(ClsA.__cls_a)     # 无法调用
ClsA.meth_a()
ClsA.meth_b()
['_ClsA__cls_a', '_ClsA__func_a', '__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__', 'meth_a', 'meth_b']
20
20
private

数据与属性

  • 为什么计算机语言要引入这么奇怪的私有化语法,限制别人访问,这个是因为直接访问数据导致数据不安全:
    • 比如:学生成绩,直接访问,没办法检测数据的合理性,有可能出现成绩为负数的可能性。
    • 对函数私有化,主要不想暴露某些函数接口给用户,而是内部自己使用。

使用函数访问数据

  • 在私有化数据的情况,建议使用函数访问数据:

    • 修改数据
    • 获取数据
  • 下面是这种访问方式的使用例子代码:

class Stu:
    def __init__(self):
        self.__name = ''
        self.__score =0.0
    
    def set_name(self, name_):
        self.__name = name_
        
    def get_name(self):
        return self.__name
    
    def set_score(self,score_):    
        if 0 <= score_ <= 100:         # 直接访问就没有提供处理机制
            self.__socre=score_
        else:
            self.__socre=0.0
            # 或者抛出异常,等我们学习完异常后就这么干!
            
    def get_score(self):
        return self.__score
    
    def __str__(self):
        return F'<{self.__name},{self.__score}>'
    
    
stu = Stu()
stu.set_name('Louis')
stu.set_score(200)

print(stu)    # 数据得到检测处理
<Louis,0.0>

使用构造器提供数据初始化

  • 强烈建议对成员变量首选构造器初始化方式
class Stu:
    def __init__(self,name_= '', score_=0.0):
        self.__name = name_
        if 0 <= score_ <= 100:         
            self.__score=score_
        else:
            self.__score=0.0
    
    def set_name(self, name_):
        self.__name = name_
        
    def get_name(self):
        return self.__name
    
    def set_score(self, score_):    
        if 0 <= score_ <= 100:         # 直接访问就没有提供处理机制
            self.__score = score_
        else:
            self.__score = 0.0
            # 或者抛出异常,等我们学习完异常后就这么干!
            
    def get_score(self):
        return self.__score
    
    def __str__(self):
        return F'<{self.__name},{self.__score}>'
    
    
stu = Stu('Louis', 200)
print(stu)    # 数据得到检测处理

stu.set_score(98.00)
print(stu)
<Louis,0.0>
<Louis,98.0>

属性的定义与使用

  • 使用函数访问数据确实可以提供数据更多处理,确保数据安全,但是这种方式使用起来没有直接使用数据方便,所以某些语言提出的了属性的语法(Java就没有这种语法,C#有这种语法)

    • 属性像函数一样可以处理数据的检测;
    • 属性像公有成员变量一样方便使用;
  • 属性的语法实现依赖函数装饰器的语法,装饰器本身是函数,可以采用两种方式调用:

    • 直接调用
    • 装饰器调用
  1. 属性的定义
    
    属性名 = property(fget=None, fset=None, fdel=None, doc=None) 

  • 说明:
    • 属性定义必须定义成类数据,不能定义成成员变量。
  1. property的帮助:

class property(object)
     |-   property(fget=None, fset=None, fdel=None, doc=None) -> property attribute
            |- fget:访问函数
            |- fset:修改函数
            |- fdel:释放函数
            |- fdel:注释文档

  1. 属性的使用例子:时候在原有基础上直接添加
class Stu:
    def __init__(self,name_= '', score_=0.0):
        self.__name = name_
        if 0 <= score_ <= 100:         
            self.__score=score_
        else:
            self.__score=0.0
    
    def set_name(self, name_):
        self.__name = name_
        
    def get_name(self):
        return self.__name
    
    def set_score(self, score_):    
        if 0 <= score_ <= 100:         # 直接访问就没有提供处理机制
            self.__score = score_
        else:
            self.__score = 0.0
            # 或者抛出异常,等我们学习完异常后就这么干!
            
    def get_score(self):
        return self.__score
    
    def __str__(self):
        return F'<{self.__name},{self.__score}>'
    
    # ----------------------------
    name = property(get_name, set_name, None, "Name属性")     # 没有使用self,因为不需要实例
    # ----------------------------
    
stu = Stu('Louis', 200)
print(stu)    # 数据得到检测处理

stu.name= 'Jack'
print(stu)
print(stu.name)
help(Stu)    # 查看属性名
<Louis,0.0>
<Jack,0.0>
Jack
Help on class Stu in module __main__:

class Stu(builtins.object)
 |  Methods defined here:
 |  
 |  __init__(self, name_='', score_=0.0)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |  
 |  __str__(self)
 |      Return str(self).
 |  
 |  get_name(self)
 |  
 |  get_score(self)
 |  
 |  set_name(self, name_)
 |  
 |  set_score(self, score_)
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)
 |  
 |  name
 |      Name属性
  1. 属性使用例子:装饰器方式(推荐方式,我个人喜欢上面那种)
  • 遵循规则:
    • getter属性使用:@property装饰

    • setter属性使用:@属性名.setter装饰

    • deleter属性使用:@属性名.deleter装饰

    • 属性注释文档:在getter函数中定义

    • 首先使用@property定义gtter,才能定义setter等。

    • getter,setter,deleter使用相同的函数名

    • getter,setter,deleter不是全部都必须的

      • 只有getter称为只读属性
      • 有setter称为可写属性
class Stu:
    def __init__(self,name_= '', score_=0.0):
        self.__name = name_
        if 0 <= score_ <= 100:         
            self.__score=score_
        else:
            self.__score=0.0
    
    @property
    def name(self):
        """
        这是装饰器属性
        """
        return self.__name
    
    @name.setter
    def name(self, name_):
        self.__name = name_
    
    @name.deleter
    def name(self):
        print('释放')
        del self.__name
    
    def set_score(self, score_):    
        if 0 <= score_ <= 100:         # 直接访问就没有提供处理机制
            self.__score = score_
        else:
            self.__score = 0.0
            # 或者抛出异常,等我们学习完异常后就这么干!
            
    def get_score(self):
        return self.__score
    
    def __str__(self):
        return F'<{self.__name},{self.__score}>'

stu = Stu('Louis', 200)
print(stu)    # 数据得到检测处理

stu.name= 'Jack'
print(stu)
print(stu.name)
del stu.name
help(Stu)    # 查看属性名
<Louis,0.0>
<Jack,0.0>
Jack
释放
Help on class Stu in module __main__:

class Stu(builtins.object)
 |  Methods defined here:
 |  
 |  __init__(self, name_='', score_=0.0)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |  
 |  __str__(self)
 |      Return str(self).
 |  
 |  get_score(self)
 |  
 |  set_score(self, score_)
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)
 |  
 |  name
 |      这是装饰器属性

运算符

  • Python有一大特色就是支持运算符重载,每个运算符对应一个系统函数

    • 比如:+ 对应 __add__
  • 想实现定制的运算符,就覆盖对应的函数即可。

  • 下面使用两个例子说明:

    • 给上面的Stu实现一个乘法:数乘,来对乘积调整
    • 我们前面讲过基本类型的一个运算符@没有实现,我们可以实现一个玩玩。

给类实现乘法运算

class Stu:
    def __init__(self,name_= '', score_=0.0):
        self.__name = name_
        if 0 <= score_ <= 100:         
            self.__score=score_
        else:
            self.__score=0.0
    
    def set_name(self, name_):
        self.__name = name_
        
    def get_name(self):
        return self.__name
    
    def set_score(self, score_):    
        if 0 <= score_ <= 100:         # 直接访问就没有提供处理机制
            self.__score = score_
        else:
            self.__score = 0.0
            # 或者抛出异常,等我们学习完异常后就这么干!
            
    def get_score(self):
        return self.__score
    
    def del_score(self):
        del self__score
    
    def __str__(self):
        return F'<{self.__name},{self.__score}>'
    # ----------------------------
    def __mul__(self, value):   # 左乘(对象在左边)
        self.__score *=value
        print('左乘')
        return self
    def __rmul__(self, value):   # 左乘(对象在右边)
        self.__score *=value
        print('右乘')
        return self
    # ----------------------------
    
    # ----------------------------
    name = property(get_name, set_name, None, "Name属性")     # 没有使用self,因为不需要实例
    score = property(
        fget=get_score,
        fset=set_name, 
        fdel=del_score, 
        doc= "Score属性")     # 没有使用self,因为不需要实例
    # ----------------------------
    

stu = Stu('Louis', 70)
stu = 1.2 * stu 
print(stu)
stu =  stu  * 1.2
print(stu)
右乘
<Louis,84.0>
左乘
<Louis,100.8>

实现@运算符号

  • @符号在矩阵运算中表示内积,在矩阵中*表示哈马达积;
class mint(int):
    def __matmul__(self, other):     
        self = self * other              # 对标量,我们赋予内积就是普通乘积的运算操作
        return self


a = mint(20)
b = mint(30)

print(a @ b)
600

成员绑定与__slots__

  • 上面可以看见,类的成员是可以随意绑定的,通过类绑定是类函数与变量,通过实例绑定的是成员变量与成员函数。
  • 绑定的成员,可以通过dir查看,或者直接访问__dict__

类变量与类函数、静态函数绑定

class ClsA:
    pass

# 类变量
ClsA.a = 20

print(ClsA.a)

# 绑定实例变量
obj_a = ClsA()
obj_a.m_a = 30
print(obj_a.m_a)

# 绑定静态函数
def show_1():
    print('show_1')
ClsA.func_1 = show_1

ClsA.func_1()
# obj_a.func_1()


# 绑定类函数
@classmethod
def show_2(self):
    print('show_2')

ClsA.func_2 = show_2
obj_a.func_2()
ClsA.func_2()
20
30
show_1
show_2
show_2

成员变量与函数绑定

class ClsA:
    pass

# 绑定实例变量
obj_a = ClsA()
obj_a.m_a = 30
print(obj_a.m_a)

# 绑定静态函数
def show_1(self):
    print('show_1',self.m_a)
    
ClsA.func_1 = show_1     # 不能绑定对象(因为代码都在类中,不在函数中)
obj_a.func_1()
30
show_1 30

使用__slots__限定属性绑定

  • __slots__用来限定属性绑定,无法限定函数的绑定,函数绑定在类代码空间的,不在对象中
class ClsA:
    __slots__ = ['m_a','func_2']      # 你绑定其他属性试试?

# 绑定实例变量
obj_a = ClsA()
obj_a.m_a = 30
print(obj_a.m_a)

# 绑定静态函数
def show_1(self):
    print('show_1',self.m_a)
    
ClsA.func_1 = show_1     # 不能绑定对象(因为代码都在类中,不在函数中)
obj_a.func_1()
30
show_1 30

可调用对象

  • Python提供了一个语法机制,可以把对象当函数调用,这种语法现象在很多框架中大量存在。因为Python万事万物皆对象,所以可调用对象有很多;

    • 函数(内置函数,标准库函数,用户定义函数,lambda表达式)
    • 生成器(本质也是函数)
    • 类下面的语句
  • 我们指的可调用对象是指类的实例可以像函数一样调用。

    • 可调用对象的类中必须实现__call__,参数根据调用的数据来确定。
  • 下面是例子

class CallableCls:
    def __call__(self, p1):
        print('对象被当成函数调用:', p1)
        

obj_a = CallableCls()
obj_a('Hello')
对象被当成函数调用: Hello

类文档注释

  • 在类下面的第一个字符串被当成文档注释处理,关于注释参考我们前面的知识讲解。
class ClsA:
    """
    类注释文档,就是帮助咯。
    """
    
help(ClsA)
Help on class ClsA in module __main__:

class ClsA(builtins.object)
 |  类注释文档,就是帮助咯。
 |  
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)

继承

  • 继承这个词准确的描述是扩展,在已经实现的类型基础上,扩展新的数据与功能函数。

    • 被扩展的类称为父类
    • 扩展的新类称为子类(有的也称诱导类)
  • Python的类的语法中提供扩展接口:

    • class 类型(被扩展的类, .......):
  • 继承最大的好处,可以在别人基础上轻松实现强大的功能

  • 在继承中需要明确新增加的内容与原来的内容之间的冲突问题。

    • 如果扩展的成员变量冲突怎么办?
    • 如果扩展的成员函数冲突怎么办?
    • 类变量,类函数,静态函数能否冲突?

单重继承与多重继承

  • 实际上在Python中存在一个默认继承,就是所有类都默认继承object类。

继承的优点


    from PyQt5.QtWidgets import *

    # 继承窗体,我们就可以为继承的类添加新的功能
    class MyWidget(QWidget):
        pass


    app = QApplication([])

    widget = MyWidget()
    widget.show()

    app.exec()

扩展数据

  • 扩展数据解决如下几个问题:

    • 命名冲突问题;
    • 子类对象化的时候,父类的构造器怎么得到调用(父类数据也需要初始化);
    • 子类对象调用的冲突成员变量的时候,怎么区分?
  • 下面使用例子代码说明:

继承中构造器的问题

  • 子类对象化,如果没有构造器器,可以调用父类构造器。
  • 子类对象化,如果子类存在构造器,不会自动调用父类构造器,必须显式调用父类构造器:
    • 一般建议在子类构造器中调用;
    • 调用语法:
      • 直接调用父类构造器函数:父类.__init__(self, 参数......)(不推荐:耦合性太强,直接引用)
      • 使用super函数:
        • 老式调用: super(父类或者子类, self).__init_(参数......)(推荐:耦合性地低,间接引用)

        • 新式调用:super(父类或者子类, self).__init_(参数......)(推荐:耦合性地低,间接引用)

  1. 使用父类构造器初始化

class SuperCls:
    def __init__(self, p_data):
        print('父类初始化')
        self.m_a = p_data


class SubCls(SuperCls):
    def show(self):
        print(self.m_a)
    
        
obj_sub = SubCls(888)

父类初始化
  1. 使用子类构造器初始化父类数据
# 子类对象怎么初始化父类中成员变量?
class SuperCls:
    def __init__(self):
        print('父类初始化')
        self.m_a = 10


class SubCls(SuperCls):
    def __init__(self):
        SuperCls.__init__(self)              # 方式一:(耦合性太高,父类改名,这个也要修改)
        super(SubCls, self).__init__()    #方式二:如果有参数带参数(Python2)
        super().__init__()                    #  方式三:这三种方式只需要一个即可(Python3)
        print('子类初始化')
        self.m_a = 20
    
        
obj_sub = SubCls()


父类初始化
父类初始化
父类初始化
子类初始化

super类

  • super类本质是一个代理,用于创建一个对象去调用父类的方法,其工作机制是通过python描述符工作,通过mro来查找父类的 其帮助如下:
class super(object)
     |  super() -> same as super(__class__, <first argument>)
     |  super(type) -> unbound super object
     |  super(type, obj) -> bound super object; requires isinstance(obj, type)
     |  super(type, type2) -> bound super object; requires issubclass(type2, type)
  • 因为我们这里还没有掌握mro与Python描述符的概念,所以不做深入的分析,能掌握应用方式即可。

super与实例

  1. super()不是对象实例,实际上属性访问的代理,通过属性描述符访问。

    • Python中属性的访问是通过对象字典__dict__来查找,对象的字典__dict__与类型的字典是有区别的。
      • 对象字典存放实例的属性与方法;
      • 类型字典存放类的属性与方法;
  • 下面是一个例子代码:
class ClsA:
    cls_a = 10
    def __init__(self):
        self.m_a = 20
        
    @property
    def propa(self):
        return self.m_a
    
    @propa.setter
    def propa(self, value):
        self.m_a = value
    
    @propa.deleter
    def propa(self):
        del self.m_a
        
    def show(self):
        print("member method")
        
    @classmethod
    def display(self):
        print('class method')
    
    @staticmethod
    def print():
        print('static method')

obj_a = ClsA()
print(obj_a.__dict__)
print(ClsA.__dict__)
{'m_a': 20}
{'__module__': '__main__', 'cls_a': 10, '__init__': <function ClsA.__init__ at 0x107c50ea0>, 'propa': <property object at 0x107c2a3b8>, 'show': <function ClsA.show at 0x107c500d0>, 'display': <classmethod object at 0x107c6bf60>, 'print': <staticmethod object at 0x107c6b2e8>, '__dict__': <attribute '__dict__' of 'ClsA' objects>, '__weakref__': <attribute '__weakref__' of 'ClsA' objects>, '__doc__': None}
  1. 属性的访问过程
    • 首先方法对象的字典
    • 类型的字典
    • 类型父类的字典(元类除外)
print(obj_a.m_a)   # 直接从对象字典访问
obj_a.show()        # 先从对象字典,然后从类型字典访问
obj_a.display()     # 先从对象字典,然后从类型字典访问

ClsA.print()          # 直接从类型字典访问
20
member method
class method
static method
  1. 使用字典访问属性
    • 其中存在三个Python类型:
      • function : 动态生成,不能使用help(function)查看帮助。
      • staticmethod:查看帮助help(staticmethod)
      • classmethod:查看帮助help(classmethod)
    • 上述三个类型有一个共同的函数,可以得到对象的实例。
      • __get__(self, instance, owner, /)
        • instance : 实例:可以是类型的实例-对象,也可以是元类的实例-类型。
  • 说明
    • 关于更多的类的内部访问与实现机制,这里不深入讲述,在类的高级编程技巧中会详细描述;
print(obj_a.__dict__['m_a'])     # 对象字典
print(ClsA.__dict__['cls_a'])        # 类型字典

# 成员函数
# help(type(ClsA.__dict__['show']))
ClsA.__dict__['show'](obj_a)
ClsA.__dict__['show'].__call__(obj_a)

ClsA.__dict__['show'].__get__(obj_a)  # 得到函数
ClsA.__dict__['show'].__get__(obj_a)()

# 类函数
print(ClsA.__dict__['display'])
# ClsA.__dict__['display'](ClsA)
ClsA.__dict__['display'].__get__(ClsA,ClsA)  # 得到函数
ClsA.__dict__['display'].__get__(ClsA,ClsA)()
ClsA.__dict__['display'].__get__(obj_a,ClsA)  # 得到函数
ClsA.__dict__['display'].__get__(obj_a,ClsA)()

# 静态函数
print(ClsA.__dict__['print'])
ClsA.__dict__['print'].__get__(ClsA,ClsA)  # 得到函数
ClsA.__dict__['print'].__get__(ClsA,ClsA)()

print('属性--------')
print(ClsA.__dict__['propa'])
p = ClsA.__dict__['propa'].__get__(obj_a,ClsA)
print(p, type(p))
ClsA.__dict__['propa'].__set__(obj_a,88)
print(obj_a.propa)
# 删除
ClsA.__dict__['propa'].__delete__(obj_a)
20
10
member method
member method
member method
<classmethod object at 0x107c6bf60>
class method
class method
<staticmethod object at 0x107c6b2e8>
static method
属性--------
<property object at 0x107c2a3b8>
20 <class 'int'>
88

扩展功能

  • 扩展功能应该解决几个问题:

    • 新的函数命名相同的问题
      • 同一个类不允许命名冲突,但父子类中函数名可以相同。
    • 子类访问父类中成员变量的语法
      • 父类与子类成员变量不冲突,直接访问即可;
      • 父类与子类成员变量冲突,直接访问,子类优先;
      • 父类与子类成员变量冲突,想访问父类成员变量,使用super关键字。
  • 下面使用例子代码来说明:

class SuperCls:
    def __init__(self):
        print('super_init')
        self.m_a = 10

    def show(self):
        print('Super')


class SubCls(SuperCls):
    def __init__(self):
        super().__init__()
        print('sub_init')
        self.m_a = 20
    
    def show(self):
        print('Sub')
    
    def call_super(self):
#         print(super(SuperCls, self).m_a)
        print(dir(SuperCls))
        print(self.m_a)
        self.show()
        super().show()


obj_sub = SubCls()
obj_sub.call_super()
super_init
sub_init
['__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__', 'show']
20
Sub
Super

多重继承

多重继承的问题

- 下面例子说明多重继承的问题,实际多重的继承的目的是解决功能扩展,不是数据扩展。
- 而且多重继承还要遵循一定的编程模式。
  • 下面是一个实际应用场景中的多重继承产生的问题例子:
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *


class MyWidget(QWidget, QThread):
    idx = 0

    def __init__(self, parent=None):
        super(MyWidget, self).__init__(parent=parent)
        super(MyWidget, self).__init__(parent=parent)
        self.start()
        self.show()

    def run(self):
        while True:
            self.setWindowTitle(F'{MyWidget.idx:08d}')
            MyWidget.idx += 1
            QThread.msleep(100)


app = QApplication([])
widget = MyWidget()

app.exec()

---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-47-b4d080aef042> in <module>()
     20 
     21 app = QApplication([])
---> 22 widget = MyWidget()
     23 
     24 app.exec()


<ipython-input-47-b4d080aef042> in __init__(self, parent)
      9         super(MyWidget, self).__init__(parent=parent)
     10         super(MyWidget, self).__init__(parent=parent)
---> 11         self.start()
     12         self.show()
     13 


AttributeError: 'MyWidget' object has no attribute 'start'
  • 下面例子使用典型的钻石结构来说明多重继承中的问题与编程注意的问题。

    • 数据扩展
    • 功能扩展
  • 下面是后面例子中使用的继承结构:

        A
      /  \
     /    \
    B     C
    \      /
      \   /
        D

多重继承中的构造器调用

  1. 多重继承中的构造器属于方法,Python采用方法解析顺序管理,每个类中提供一个mro来管理,使用super代理,super代理就按照MRO管理顺序调用。
class  A:
    def __init__(self):
        self.m_a = 10
        print('A')

class B(A):
    def __init__(self):
        super().__init__()
        self.m_b = 20
        print('B')

class C(A):
    def __init__(self):
        super().__init__()
        self.m_c = 30
        print('C')

class D(B,C):
    def __init__(self):
        super().__init__()
        self.m_d = 40
        print('D')

obj_d = D()
print(obj_d.m_a,obj_d.m_b,obj_d.m_c,obj_d.m_d,sep=',')

A
C
B
D
10,20,30,40
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
(<class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
  1. 使用传统方式处理多重继承中的构造器调用
  • 传统构造器的调用,会导致父类构造器重复调用,但是可能更加直观。
class  A:
    def __init__(self):
        self.m_a = 10
        print('A')

class B(A):
    def __init__(self):
        A.__init__(self)
        self.m_b = 20
        print('B')

class C(A):
    def __init__(self):
        A.__init__(self)
        self.m_c = 30
        print('C')

class D(B,C):
    def __init__(self):
        B.__init__(self)
        C.__init__(self)
        self.m_d = 40
        print('D')

obj_d = D()
print(obj_d.m_a,obj_d.m_b,obj_d.m_c,obj_d.m_d,sep=',')
A
B
A
C
D
10,20,30,40

多重继承中数据扩展的冲突

  • 因为多重继承,两个父类中方法、属性、成员变量可能会产生冲突;在语法上,这种冲突是允许的,所以我们在编程中需要注意,形成自己的编程技巧。

  • 成员变量冲突的编程在新老调用方式上的注意事项:

  1. 老式调用方式,覆盖方式由调用顺序决定。
class  A:
    def __init__(self):
        self.m_a = 10
        print('A')

class B(A):
    def __init__(self):
        A.__init__(self)
        self.m_a = 20
        print('B')

class C(A):
    def __init__(self):
        A.__init__(self)
        self.m_a = 30
        print('C')

class D(B,C):
    def __init__(self):
        C.__init__(self)
        B.__init__(self)
        
        # self.m_a = 40
        print('D')

obj_d = D()
print(obj_d.m_a)
A
C
A
B
D
20
  1. 新式的覆盖由继承的顺序决定;继承顺序是从后往前。
class  A:
    def __init__(self):
        self.m_a = 10
        print('A')

class B(A):
    def __init__(self):
        super().__init__()
        self.m_a = 20
        print('B')

class C(A):
    def __init__(self):
        super().__init__()
        self.m_a = 30
        print('C')

class D(C,B):      # 继承的先后,继承顺序是从后往前
    def __init__(self):
        super().__init__()
        self.m_a = 40
        print('D')

obj_d = D()
print(obj_d.m_a)
A
B
C
D
30
  • 这种语法机制本身还是存在漏洞的,如果扩展函数的时候,容易产生不一致的情况。B中的函数可能调用C中的成员变量。
  • 下面我们来看看函数扩展的问题

多重继承中的功能扩展

class  A:
    def __init__(self):
        self.m_a = 10

    def show(self):
        print('A的方法:',self.m_a)

class B(A):
    def __init__(self):
        super().__init__()
        self.m_a = 20

#     def show(self):
#         print(self.m_a)

class C(A):
    def __init__(self):
        super().__init__()
        self.m_a = 30

#     def show(self):
#         print(self.m_a)

class D(C,B):      # 继承的先后,继承顺序是从后往前
    def __init__(self):
        super().__init__()
        self.m_a = 40
#     def show(self):
#         print(self.m_a)

obj_d = D()
obj_d.show()

A的方法: 40
  • 调用是A中定义的成员方法,实际上该方法使用的是D类的数据,因为这种多重继承中的覆盖,导致数据一些数据环境产生改变,从而导致运行问题。

  • 该怎么使用多重继承?一般根据经验,建议主要数据按照主线继承,需要扩展的方法,采用多重继承来实现,而且方法中使用的数据尽量不要冲突覆盖,这种编程模式称为继承组合模式【Mixin】(区别于依赖、聚合与组合关系)。

类型与成员测定

  • Python提供了两个函数来对对象进行常见的测定:
    • 获取类型
      • type
    • 实例的类型判定
      • isinstance

type类型

  • type说明
class type(object)
     |  type(object_or_name, bases, dict)
     |  type(object) -> the object's type
     |  type(name, bases, dict) -> a new type
  • 使用type创建类型在下面介绍,但是使用type可以返回对象的类型,该类型还可以继续创建对象。这是Python万事万物皆对象的理念,类型也是对象,类型的类型就是type,这是元类的概念。
a = int('FF', 16)
print(a)
cls = type(a)
print(cls)

b = cls('0X0F', 0)
print(b)
255
<class 'int'>
15

isinstance运算符

  • isinstance函数定义
isinstance(obj, class_or_tuple, /)
    Return whether an object is an instance of a class or of a subclass thereof.
  • 参数说明:
    • obj:一个实例对象
    • class_or_tuple:类型或者类型元组
class  A:
    pass

class B(A):
    pass

class C(A):
    pass

class D(C,B): 
    pass

obj_d = B()
bl = isinstance(obj_d, (C, D))
print(bl)
bl = isinstance(obj_d, (A, B, C, D))
print(bl)
False
True

类接口与抽象类

  • 在很多语言中,有一种特殊的继承:接口,接口是为了降低类之间耦合度而提出的语法设计。但是在Python这种弱类型语言,接口的语法在Python中基本上没有意义。

  • 但是接口还具有另外一个应用需求:规范调用的方法

    • 强制子类覆盖父类的方法,从而强制要求子类遵循父类定义的规范。
  • 抽象类:

    • 定义方法规范的类称为接口或者抽象类,在Python中称呼抽象类会更加规范。因为接口与抽象类的在传统意义上还有有一些区别,接口是纯方法规范,抽象类一般是传统实现方法与规范接口方法混合。因为Python语法的特点,纯粹的接口规范类不存在。
  • 抽象类的定义

    • 抽象类的接口方法定义使用abc模块中的abstractmethod函数装饰器;
    • 同时需要指定元类为abc.ABCMeta
abstractmethod(funcobj)
    A decorator indicating abstract methods.
import abc

class AbstractCls(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def meth(self):
        pass
    

class  ImplementionCls(AbstractCls):
#      def meth(self):
#         pass

# obj = AbstractCls()
obj = ImplementionCls()
  File "<ipython-input-77-5cd5007a98d3>", line 14
    obj = ImplementionCls()
      ^
IndentationError: expected an indented block
  • 如果不实现抽象方法,则子类也是抽象类,抽象类是不能实例化的。

抽象超类

对象的内存管理

  • 因为Python使用虚拟机,实现自动内存管理,开发者实际上一般情况下是不需要关心内存的管理啊的。我们这里讲的对象的内存管理主要是对象的分配与释放。实际上在编程中还有很多内存相关的细节,这里不详细描述,比如:对bit的操作。

__new__运算符

  • 一般我们很少重载这个运算符,但在某些场合还是会使用new运算符,更主要的是要区别__new____init__

    • __new__负责内存分配;__new__是静态的!
    • __init__负责数据初始化;在__new__调用后调用!
  • 实际上Python不提供直接的内存分配,在object中提供一个内存分配的封装,当类没有提供__new__运算符的时候,都会调用父类的__new__,父类没有重载,在会一直逆源到object的__new__运算符。

  • 但是Python封装提供了一些内存相关的操作函数,在今后的学习中会逐步涉及。

  • 下面是__new__运算符重载的例子

class ClsA(object):
    def __init__(self):
        print('构造器')
        
    def __new__(cls, *args, **kwargs):
        print('内存分配器')  #  类型
        return object.__new__(cls, *args, **kwargs)   
    

obj_a = ClsA()

内存分配器
构造器

析构器

  • python也提供析构机制。
    • Python的析构也是使用运算符__del__
    • __del__在对象释放时自动调用(垃圾回收器自动工作),或者调用del释放;主要用来提供对象内部的一些资源释放。比如在对象释放前:关闭网络,释放成员变量等。
  • 下面是析构器使用例子
class ClsA(object):
    def __del__(self):
        print('释放')


obj_a = ClsA()
del obj_a
释放

引用计数

  • Python的sys模块提供一个对象被引用的次数:
import sys

class  A:
    pass

obj=A()
print(sys.getrefcount(obj))
#计数是2。与Python解释器有关
#如果两个对象交叉引用,则很难释放,这时候垃圾回收器开始作用。
2

垃圾回收

  • Python 采用垃圾回收机制来清理不再使用的对象;Python 提供gc模块释放不再使用的对象,Python 采用‘引用计数’ 的算法方式来处理回收,即:当某个对象在其作用域内不再被其他对象引用的时候,Python 就自动清除对象;Python 的函数collect()可以一次性收集所有待处理的对象(gc.collect())

  • gc模块说明:


    enable() -- Enable automatic garbage collection.
    disable() -- Disable automatic garbage collection.
    isenabled() -- Returns true if automatic collection is enabled.
    collect() -- Do a full collection right now.
    get_count() -- Return the current collection counts.
    get_stats() -- Return list of dictionaries containing per-generation stats.
    set_debug() -- Set debugging flags.
    get_debug() -- Get debugging flags.
    set_threshold() -- Set the collection thresholds.
    get_threshold() -- Return the current the collection thresholds.
    get_objects() -- Return a list of all objects tracked by the collector.
    is_tracked() -- Returns true if a given object is tracked.
    get_referrers() -- Return the list of objects that refer to an object.
    get_referents() -- Return the list of objects that an object refers to.
    
#!/usr/bin/python
#coding=utf-8
#垃圾回收器,gc模块

import  gc,sys
gc.enable()
print(gc.get_threshold())   #垃圾对象分代
gc.set_threshold(2,1,1)
class A:
    def __init__(self):
        print("__init__")
    def __del__(self):
        print("__del__")

a=A()
print(sys.getrefcount(a))

b=a
c=b
d=c

del a
gc.collect()
print(gc.garbage)
del b
gc.collect()
print(gc.garbage)
del c
print (gc.garbage)
del d
#gc.collect()
print(gc.garbage)
#垃圾回收主要寻找循环引用并决定释放
(2, 1, 1)
__init__
2
[]
[]
[]
__del__
[]

元类与类装饰器

元类类型

  • 前面我们介绍过使用对象来得到type实例(类),从而判定对象的类型,实际上使用type构造类型。这种能构造类的类型就是元类(就是type类)。
  • type的帮助:
class type(object)
     |  type(object_or_name, bases, dict)
     |  type(name, bases, dict) -> a new type
  • 说明:

    • object_or_name 对象与对象名(字符串)
    • bases父类(元组类型)
    • dict :字典属性(字典类型)
  • 下面是使用元类创建类的例子

class A:
    pass

class B:
    pass

TypeCls = type('cls_name', (A, B), {'m_a': 20})

a = TypeCls()
print(a.m_a)
print(TypeCls.__name__)

20
cls_name

定制元类

  • type是Python解释器内置的元类,用户可以实现自己的元类;
    • 继承type
class Meta(type):
    
    def __new__(meta, *args, **kwargs):
        print( '创建类:')
        print( '\t*args:', args)
        print( '\t**kwargs:', kwargs)
        return super().__new__(meta, *args)

    def __init__(cls, *args, **kwargs):
        print('类型初始化')
        print( '\t*args:', args)
        print( '\t**kwargs:', kwargs)
        super().__init__(*args)

使用定制元类定义类

  • 使用metaclass指定元类。
class ClsA(metaclass=Meta, p1='参数值'):

    def __init__(self, *args, **kwargs):
        print('创建对象')


        
obj_a = ClsA()
创建类:
    *args: ('ClsA', (), {'__module__': '__main__', '__qualname__': 'ClsA', '__init__': <function ClsA.__init__ at 0x10fe83ea0>})
    **kwargs: {'p1': '参数值'}
类型初始化
    *args: ('ClsA', (), {'__module__': '__main__', '__qualname__': 'ClsA', '__init__': <function ClsA.__init__ at 0x10fe83ea0>})
    **kwargs: {'p1': '参数值'}
创建对象

类装饰器

  • 上面通过元类类控制类的定义,实际也可以通过类装饰器来实现。

  • 类装饰器本质是函数,参数是类。

    • 在装饰器函数中,可以对类进行处理,比如增加一些字典,属性等。
  • 使用装饰器与函数一样,差别在于类装饰器使用在类的前面。

    • @ 装饰器名
def decorate(cls):
    cls.cls_a = 20
    return cls


@decorate
class  ClsA:
    pass


print(ClsA.cls_a)
20

使用UML描述类

类图

  1. 工具:

    • https://www.processon.com
  2. 类图的UML表示


    UML表示类图
  1. 类图说明

    • 区域说明:
      • 类名:指定类名;
      • 数据成员:定义成员变量;
      • 函数成员:定义成员函数;

    -符号说明:
    - + 表示public;
    - - 表示private;
    - # 表示protected;

    • 成员说明:
      • 成员变量说明:成员变量名:类型 = 默认值
      • 成员函数说明:函数名(参数:类型,......):返回类型

类关系

关联关系

单向关联

  • 一般用来描述实体关系
    • 实体ClsA 有 ClsB作为成员;

    • 比如:User类都有一个地址Address类型的数据;

单项关联

双向关联

  • 一般用来描述实体类关系:
    • 表示双向的拥有关系;

    • 比如:User类都有一个购买Product列表;某个Product被哪个User购买;


      双向关联

自关联

  • 一般用来描述实体;
    • 自己拥有自己;
      • 比如数据结构的链表节点,就是自己拥有自己;
自关联

依赖关系

  • 一般用来描述调用关系
    • ClsA的call需要ClsB的called才能工作;
    • 一般是一个类的函数参数使用了另外一个类型的对象;
依赖关系

聚合关系

  • 一般用来描述调用关系
    • ClsA的实现需要ClsB;
    • 并且ClsB作为ClsA的成员变量,但是ClsB对象的构建不在ClsA中构建,ClsB对象可以独立存在,不会随ClsA对象的释放而释放。
聚合关系

组合关系

  • 一般用来描述调用关系
    • ClsA的实现需要ClsB;
    • 并且ClsB作为ClsA的成员变量,ClsB对象的构建在ClsA中构建;
    • ClsB对象不是独立存在的,ClsA对象释放时,ClsB对象也会被释放。
组合关系

继承关系

  • 继承关系是面向对象中最基本的关系了;
继承关系

接口继承关系

  • 这个关系在Python非常淡化,因为Python在语法上是弱类型语言(因为类型是动态判定的);
    • 但是在Python从编程技巧上,可以实现与接口类似的程序结构。
接口继承关系

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容