python全栈之路—十分钟搞定面向对象-类的结构-类的空间问题,建议收藏

本文围绕Python面向对象编程展开,对比了面向过程、函数式与面向对象编程,介绍了类的结构、空间问题、类间关系,如依赖、关联等。还阐述了继承、多态、类的约束等概念,以及super的使用,最后提及类的成员、反射、双下方法和单例模式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

面向对象本节整理知识点

面向过程编程vs函数式编程
面向对象初识
    函数式编程vs面向对象编程
    类的结构
从类名的角度研究类
    类名操作静态属性
    类名操作动态方法
从对象的角度研究类
    什么是对象
    对象操作对象空间属性
    对象查看类中的属性
    对象操作类中的方法
    self 是什么?
    一个类可以实例化多个对象
Python 类的空间问题
    何处可以添加对象属性
    何处可以添加类的静态属性
    对象如何找到类的属性
类与类之间的关系
    依赖关系
    关系
        关联关系.
        聚合关系
        组合关系.
什么是面向对象的继承
继承的分类
单继承
    类名,对象执行父类方法
    执行顺序
    同时执行类以及父类方法
多继承
    经典类的多继承
    新式类的多继承
        mro序列
        表头和表尾
        列表之间的+操作
封装
继承
多态
类的约束
super
细分类的组成成员
类的私有成员
    公有静态属性(字段)
    私有静态属性(字段)
    公有普通字段
    私有对象属性
    公有方法
    私有方法
类的其他成员
    类方法
    静态方法
    属性
isinstace 与 issubclass
isinstance(a,b)
issubclass(a,b)
反射
    对象的反射
    对类的反射
    当前模块的反射
    其他模块的反射
    反射的应用
双下方法
    __len__
    __hash__
    __str__
    __repr__
    __call__
    __eq__
    __del__
    __new__
    __item__系列
面向过程编程vs函数式编程

我们在没有学习函数的时候,写的代码都是面向过程式编程

# 面向过程编程 测量对象的元素的个数。
s1 = 'fjdsklafsjda'
count = 0
for i in s1:
    count += 1
l1 = [1,2,3,4]
count = 0
for i in l1:
    count += 1

在我们学习函数后就是在面向函数编程

def func(s):
    count = 0
    for i in s:
        count += 1
    return count
func('fdsafdsa')
func([1,2,3,4])

通过对比可知:函数编程较之面向过程编程最明显的两个特点:

1,减少重复的代码。
2,增强代码的可读性。

面向对象初识
函数式编程vs面向对象编程

函数式编程

# 函数式编程
# auth 认证相关
def login():
    pass
def regisgter():
    pass
# account 账户相关
def func1():
    pass
def func2():
    pass
# 购物车相关
def shopping(username,money):
    pass
def check_paidgoods(username,money):
    pass
def check_unpaidgoods(username,money):
    pass
def save(username,money):
    pass

面向对象编程

class LoginHandler:
    def login(self):
        pass
    def regisgter(self):
        pass
class Account:
    def func1(self):
        pass
    def func2(self):
        pass
class ShoppingCar:
    def shopping(username,money):
        pass
    def check_paidgoods(username,money):
        pass
    def check_unpaidgoods(username,money):
        pass
    def save(username,money):
        pass

通过对比可以看出面向对象第一个优点:

面向对象编程:是一类相似功能函数的集合,使你的代码更清晰化,更合理化。

说第二个优点之前,先看看什么是面向对象。

面向对象的程序设计的核心是对象(上帝式思维),要理解对象为何物,必须把自己当成上帝,上帝眼里世间存在的万物皆为对象,不存在的也可以创造出来。

问题来了,那什么是类?什么又是对象?

类:就是具有相同属性和功能的一类事物

对象:就是类的具体表现形式

具体一些:先解释解释什么是车? 有轱辘, 有方向盘, 有发动机, 会跑的是车. 好. 在解释一个. 什么是人. 有名字, 年龄, 爱好, 会唱歌跳舞思考的是人.那么广义上车,人就是类:但是具体的我的车,你这个人这是一个对象。

猫,是一类,你们家养的 大橘。

狗,是一类,隔壁家养的那只二哈就是对象。

面向对象思维, 要自己建立对象. 自己建立场景. 你是就是面向对象世界中的上帝. 你想让车干嘛就干嘛. 你想让人干嘛人就能干嘛。

再说第二个优点:面向对象,要拥有上帝的视角看问题,类其实就是一个公共模板(厂房),对象就从具体的模板实例化出来(慢慢体会)。

类的结构

class Human:
    """
    此类主要是构建人类
    """
    mind = '有思想'  # 第一部分:静态属性 属性 静态变量 静态字段
    dic = {}
    l1 = []
    def work(self): # 第二部分:方法 函数 动态属性
        print('人类会工作')
class 是关键字与def用法相同,定义一个类。
Human是此类的类名,类名使用驼峰(CamelCase)命名风格,首字母大写,私有类可用一个下划线开头。
类的结构从大方向来说就分为两部分:
静态变量。
动态方法。
从类名的角度研究类

类名操作静态属性

第一种,查看类中的所有内容:类名.__dict__方式。

class Human:
    """
    此类主要是构建人类
    """
    mind = '有思想'  # 第一部分:静态属性 属性 静态变量 静态字段
    dic = {}
    l1 = []
    def work(self): # 第二部分:方法 函数 动态属性
        # print(self)
        print('人类会工作')
print(Human.__dict__)
print(Human.__dict__['mind'])
Human.__dict__['mind'] = '无脑'
print(Human.__dict__)  # 错误
#通过这种方式只能查询,不能增删改.
# 第一种方式只用户查询全部内容(一般不用单独属性查询).

第二种:万能的点.

class Human:
    """
    此类主要是构建人类
    """
    mind = '有思想'  # 第一部分:静态属性 属性 静态变量 静态字段
    dic = {}
    l1 = []
    def work(self): # 第二部分:方法 函数 动态属性
        # print(self)
        print('人类会工作')
print(Human.mind)  # 查
Human.mind = '无脑'  # 改
print(Human.mind)
del Human.mind  # 删
Human.walk = '直立行走'
print(Human.walk)
# 通过万能的点 可以增删改查类中的单个属性

对以上两种做一个总结:如果想查询类中的所有内容,通过 第一种__dict__方法,如果只是操作单个属性则用万能的点的方式

类名操作动态方法

前提:除了两个特殊方法:静态方法,类方法之外,一般不会通过类名操作一个类中的方法。

class Human:
    """
    此类主要是构建人类
    """
    mind = '有思想'  # 第一部分:静态属性 属性 静态变量 静态字段
    dic = {}
    l1 = []
    def work(self): # 第二部分:方法 函数 动态属性
        # print(self)
        print('人类会工作')
    def tools(self):
        print('人类会使用工具')
Human.work(111)
Human.tools(111)
下面可以做,但不用。
Human.__dict__['work'](111)
从对象的角度研究类

什么是对象

对象是从类中出来的,只要是类名加上(),这就是一个实例化过程,这个就会实例化一个对象。

执行下列代码会发生什么事情?

class Human:
    mind = '有思想'
    def __init__(self):
        print(666)
        print(self)  # <__main__.Human object at 0x00000191508AA828>
    def work(self):
        print('人类会工作')
    def tools(self):
        print('人类会使用工具')
obj = Human() # 只要实例化对象,它会自动执行__init__方法
print(obj)  # <__main__.Human object at 0x00000191508AA828>
# 并且obj的地址与self的地址相同

其实实例化一个对象总共发生了三件事:

1,在内存中开辟了一个对象空间。

2,自动执行类中的__init__方法,并将这个对象空间(内存地址)传给了__init__方法的第一个位置参数self。

3,在__init__ 方法中通过self给对象空间添加属性。

示例:

class Human:
    mind = '有思想'
    language = '使用语言'
    def __init__(self,name,sex,age,hobby):
        # self 和 obj 指向的是同一个内存地址同一个空间,下面就是通过self给这个对象空间封装四个属性。
        self.n = name
        self.s = sex
        self.a = age
        self.h = hobby
obj = Human('barry','男',18,'运动')
对象操作对象空间属性

对象查询对象中所有属性。 对象.dict

class Human:
    mind = '有思想'
    language = '实用语言'
    def __init__(self,name,sex,age,hobby):
        # self 和 obj 指向的是同一个内存地址同一个空间,下面就是通过self给这个对象空间封装四个属性。
        self.n = name
        self.s = sex
        self.a = age
        self.h = hobby
obj = Human('barry','男',18,'运动')
print(obj.__dict__)  # {'n': 'barry', 'h': '运动', 's': '男', 'a': 18}

对象操作对象中的单个属性。 万能的点 .

class Human:
    mind = '有思想'
    language = '实用语言'
    def __init__(self,name,sex,age,hobby):
        # self 和 obj 指向的是同一个内存地址同一个空间,下面就是通过self给这个对象空间封装四个属性。
        self.n = name
        self.s = sex
        self.a = age
        self.h = hobby
obj = Human('barry','男',18,'运动')
obj.job = 'IT'  # 增
del obj.n  # 删
obj.s = '女' # 改
print(obj.s)  # 查
print(obj.__dict__)
对象查看类中的属性
class Human:
    mind = '有思想'
    language = '实用语言'
    def __init__(self,name,sex,age,hobby):
        self.n = name
        self.s = sex
        self.a = age
        self.h = hobby
obj = Human('barry','男',18,'运动')
print(obj.mind)
print(obj.language)
obj.a = 666
print(obj.a)
对象操作类中的方法
class Human:
    mind = '有思想'
    language = '实用语言'
    def __init__(self,name,sex,age,hobby):
        self.n = name
        self.s = sex
        self.a = age
        self.h = hobby
    def work(self):
        print(self)
        print('人类会工作')
    def tools(self):
        print('人类会使用工具')
obj = Human('barry','男',18,'运动')
obj.work()
obj.tools()

类中的方法一般都是通过对象执行的(出去类方法,静态方法外),并且对象执行这些方法都会自动将对象空间传给方法中的第一个参数self.

self 是什么?

self其实就是类中方法(函数)的第一个位置参数,只不过解释器会自动将调用这个函数的对象传给self。所以咱们把类中的方法的第一个参数约定俗成设置成self, 代表这个就是对象。

一个类可以实例化多个对象
obj1= Human('小胖','男',20,'美女')
obj2= Human('相爷','男',18,'肥女')
print(obj1,obj2)
print(obj1.__dict__)
print(obj2.__dict__)
Python 类的空间问题
何处可以添加对象属性
class A:
    def __init__(self,name):
        self.name = name
    def func(self,sex):
        self.sex = sex

# 类外面可以:
obj = A('barry')
obj.age = 18
print(obj.__dict__)  # {'name': 'barry', 'age': 18}
# 类内部也可以:
obj = A('barry') # __init__方法可以。
obj.func('男')  # func 方法也可以。

总结:对象的属性不仅可以在__init__里面添加,还可以在类的其他方法或者类的外面添加。

何处可以添加类的静态属性
class A:
    def __init__(self,name):
        self.name = name
    def func(self,sex):
        self.sex = sex
    def func1(self):
        A.bbb = 'ccc'
# 类的外部可以添加
A.aaa = 'taibai'
print(A.__dict__)
# 类的内部也可以添加。
A.func1(111)
print(A.__dict__)

总结:类的属性不仅可以在类内部添加,还可以在类的外部添加。

对象如何找到类的属性

之前咱们都学习过,实例化一个对象,可以通过点的方式找到类中的属性,那么他为什么可以找到类中的属性呢?

通过图解说明:

对象查找属性的顺序:先从对象空间找 ———> 类空间找 ———> 父类空间找 ———->……

类名查找属性的顺序:先从本类空间找 ———-> 父类空间找————> ………

上面的顺序都是单向不可逆,类名不可能找到对象的属性。

类与类之间的关系

大千世界, 万物之间皆有规则和规律. 我们的类和对象是对大千世界中的所有事物进行归类. 那事物之间存在着相对应的关系. 类与类之间也同样如此. 在面向对象的世界中. 类与类中存在以下关系:

依赖关系
关联关系
组合关系
聚合关系
实现关系
继承关系(类的三大特性之一:继承。)
依赖关系

首先, 我们设计一个场景. 还是最初的那个例子. 要把大象装冰箱. 注意. 在这个场景中, 其实是存在了两种事物的. 一个是大象, 大象负责整个事件的掌控者, 还有一个是冰箱, 冰箱负责被大象操纵.

首先, 写出两个类, 一个是大象类, 一个是冰箱类

class Elphant:
    def __init__(self, name):
        self.name = name
    def open(self):
        '''
        开门
        '''
        pass
    def close(self):
        '''
        关门
        '''
        pass
class Refrigerator:
    def open_door(self):
        print("冰箱门被打开了")
    def close_door(self):
        print("冰箱门被关上了")

冰箱的功能非常简单, 只要会开门, 关门就行了. 但是大象就没那么简单了. 想想.
大象开门和关门的时候是不是要先找个冰箱啊. 然后呢? 打开冰箱门. 是不是打开刚才找到的那个冰箱门.
然后装自己. 最后呢? 关冰箱门, 注意, 关的是刚才那个冰箱吧. 也就是说. 开门和关门用的是同一个冰箱.
并且. 大象有更换冰箱的权利, 想进那个冰箱就进那个冰箱. 这时, 大象类和冰箱类的关系并没有那么的紧密.
因为大象可以指定任何一个冰箱. 接下来. 我们把代码完善一下.动作发起的主体是大象。

class Elphant:
    def __init__(self, name):
        self.name = name
    def open(self,obj1):
        '''
        开门
        '''
        print('大象要开门了,默念三声,开')
        obj1.open_door()
    def close(self):
        '''
        关门
        '''
        print('大象要关门了,默念三声,关')
class Refrigerator:
    def open_door(self):
        print("冰箱门被打开了")
    def close_door(self):
        print("冰箱门被关上了")
elphant1 = Elphant('大象')
haier = Refrigerator()
elphant1.open(haier)

依赖关系:将一个类的对象或者类名传到另一个类的方法使用。此时, 我们说, 大象和冰箱之间就是依赖关系.
我用着你. 但是你不属于我. 这种关系是最弱的.比如. 公司和雇员之间.
对于正式员工, 肯定要签订劳动合同. 还得小心伺候着. 但是如果是兼职. 那无所谓. 需要了你就来.
不需要你就可以拜拜了.
这里的兼职(临时工) 就属于依赖关系.我用你. 但是你不属于我
关系

其实这三个在代码上写法是一样的. 但是, 从含义上是不一样的.
关联关系.

两种事物必须是互相关联的. 但是在某些特殊情况下是可以更改和更换的.
聚合关系

属于关联关系中的一种特例. 侧重点是xxx和xxx聚合成xxx. 各自有各自的声明周期.
比如电脑. 电脑里有CPU, 硬盘, 内存等等. 电脑挂了. CPU还是好的. 还是完整的个体
组合关系.

属于关联关系中的一种特例. 写法上差不多. 组合关系比聚合还要紧密.
比如人的大脑, 心脏, 各个器官. 这些器官组合成一个人. 这时. 人如果挂了. 其他的东西也跟着挂了

先看关联关系:

这个最简单. 也是最常用的一种关系. 比如. 大家都有男女朋友. 男人关联着女朋友. 女人关联着男朋友.
这种关系可以是互相的, 也可以是单方面的.

定义类

class Boy:
    def __init__(self,name,girlFriend=None):
        self.name = name
        self.girlFriend = girlFriend
    def have_a_diner(self):
        if self.girlFriend:
            print('%s 和 %s 一起晚饭'%(self.name,self.girlFriend.name))
        else:
            print('单身狗,吃什么饭')
class Girl:
    def __init__(self,name):
        self.name = name

实例(创建)日天对象

b = Boy('日天')
b.have_a_diner() # 此时是单身狗
# 突然有一天,日天牛逼了
b.girlFriend = '如花'
b.have_a_diner()  #共进晚餐

实例(创建)wusir对象

# wusir 生下来就有女朋友 服不服
gg = Girl('小花')
bb = Boy('wusir', gg)
bb.have_a_diner()
# 结果嫌他有点娘,弯的,分了
bb.girlFriend = None
bb.have_a_diner()

注意 此时Boy和Girl两个类之间就是关联关系. 两个类的对象紧密练习着. 其中一个没有了.
另一个就孤单的不得了. 关联关系, 其实就是 我需要你. 你也属于我. 这就是关联关系.
像这样的关系有很多很多. 比如. 学校和老师之间的关系.

学校和老师示例:

# 老师属于学校,必须有学校才可以工作
class School:
    def __init__(self,name,address):
        self.name = name
        self.address = address
class Teacher:
    def __init__(self,name,school):
        self.name = name
        self.school = school
s1 = School('北京校区','美丽的沙河')
s2 = School('上海校区','上海迪士尼旁边')
s3 = School('深圳校区','南山区')
t1 = Teacher('武大',s1)
t2 = Teacher('海峰',s2)
t3 = Teacher('日天',s3)
print(t1.school.name)
print(t2.school.name)
print(t3.school.name)

但是学校也是依赖于老师的,所以老师学校应该互相依赖。

class School:
    def __init__(self,name,address):
        self.name = name
        self.address = address
        self.teacher_list = []
    def append_teacher(self,teacher):
        self.teacher_list.append(teacher)
class Teacher:
    def __init__(self,name,school):
        self.name = name
        self.school = school
s1 = School('北京校区','美丽的沙河')
s2 = School('上海校区','上海迪士尼旁边')
s3 = School('深圳校区','南山区')
t1 = Teacher('武大',s1)
t2 = Teacher('海峰',s2)
t3 = Teacher('日天',s3)
s1.append_teacher(t1)
s1.append_teacher(t2)
s1.append_teacher(t3)
# print(s1.teacher_list)
# for teacher in s1.teacher_list:
#     print(teacher.name)

好了. 这就是关联关系. 当我们在逻辑上出现了. 我需要你. 你还得属于我. 这种逻辑
就是关联关系. 那注意. 这种关系的紧密程度比上面的依赖关系要紧密的多. 为什么呢? 想想吧

至于组合关系和聚合关系,其实代码上差别不大,咱们就以组合举例:

组合:将一个类的对象封装到另一个类的对象的属性中,就叫组合。

咱们设计一个游戏人物类,让实例化几个对象让这几个游戏人物实现互殴的效果。

class Gamerole:
    def __init__(self,name,ad,hp):
        self.name = name
        self.ad = ad
        self.hp = hp
    def attack(self,p1):
        p1.hp -= self.ad
        print('%s攻击%s,%s掉了%s血,还剩%s血'%(self.name,p1.name,p1.name,self.ad,p1.hp))
gailun = Gamerole('盖伦',10,200)
yasuo= Gamerole('亚索',50,80)
#盖伦攻击亚索
gailun.attack(yasuo)
# 亚索攻击盖伦
yasuo.attack(盖伦)

但是这样互相攻击没有意思,一般游戏类的的对战方式要借助武器,武器是一个类,武器类包含的对象很多:
刀枪棍剑斧钺钩叉等等,所以咱们要写一个武器类。

class Gamerole:
    def __init__(self,name,ad,hp):
        self.name = name
        self.ad = ad
        self.hp = hp
    def attack(self,p1):
        p1.hp -= self.ad
        print('%s攻击%s,%s掉了%s血,还剩%s血'%(self.name,p1.name,p1.name,self.ad,p1.hp))
class Weapon:
    def __init__(self,name,ad):
        self.name = name
        self.ad = ad
    def weapon_attack(self,p1,p2):
        p2.hp = p2.hp - self.ad - p1.ad
        print('%s 利用 %s 攻击了%s,%s还剩%s血' %(p1.name,self.name,p2.name,p2.name,p2.hp))

接下来借助武器攻击对方:

pillow = Weapon('绣花枕头',2)
pillow.weapon_attack(barry,panky)
# 但是上面这么做不好,利用武器攻击也是人类是动作的发起者,所以不能是pillow武器对象,
而是人类利用武器攻击对方

所以,对代码进行修改。

class Gamerole:
    def __init__(self,name,ad,hp):
        self.name = name
        self.ad = ad
        self.hp = hp
    def attack(self,p1):
        p1.hp -= self.ad
        print('%s攻击%s,%s掉了%s血,还剩%s血'%(self.name,p1.name,p1.name,self.ad,p1.hp))
    def equip_weapon(self,wea):
        self.wea = wea  # 组合:给一个对象封装一个属性改属性是另一个类的对象
class Weapon:
    def __init__(self,name,ad):
        self.name = name
        self.ad = ad
    def weapon_attack(self,p1,p2):
        p2.hp = p2.hp - self.ad - p1.ad
        print('%s 利用 %s 攻击了%s,%s还剩%s血'
              %(p1.name,self.name,p2.name,p2.name,p2.hp))
# 实例化三个人物对象:
barry = Gamerole('太白',10,200)
panky = Gamerole('金莲',20,50)
pillow = Weapon('绣花枕头',2)
# 给人物装备武器对象。
barry.equip_weapon(pillow)
# 开始攻击
barry.wea.weapon_attack(barry,panky)

上面就是组合,只要是人物.equip_weapon这个方法,那么人物就封装了一个武器对象,
再利用武器对象调用其类中的weapon_attack方法
什么是面向对象的继承

比较官方的说法就是:

继承(英语:inheritance)是面向对象软件技术当中的一个概念。如果一个类别A“继承自”另一个类别B,
就把这个A称为“B的子类别”,而把B称为“A的父类别”也可以称“B是A的超类”。
继承可以使得子类别具有父类别的各种属性和方法,而不需要再次编写相同的代码。
在令子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,
即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能。另外,为子类别追加新的属性和方法也是常见的做法。 一般静态的面向对象编程语言,继承属于静态的,意即在子类别的行为在编译期就已经决定,无法在执行期扩充。

字面意思就是:子承父业,合法继承家产,就是如果你是独生子,而且你也很孝顺,不出意外,
你会继承你父母所有家产,他们的所有财产都会由你使用(败家子儿除外)。

那么用一个例子来看一下继承:

class Person:
    def __init__(self,name,sex,age):
        self.name = name
        self.age = age
        self.sex = sex
class Cat:
    def __init__(self,name,sex,age):
        self.name = name
        self.age = age
        self.sex = sex
class Dog:
    def __init__(self,name,sex,age):
        self.name = name
        self.age = age
        self.sex = sex
# 继承的用法:
class Aniaml(object):
    def __init__(self,name,sex,age):
            self.name = name
            self.age = age
            self.sex = sex
class Person(Aniaml):
    pass
class Cat(Aniaml):
    pass
class Dog(Aniaml):
    pass

继承的有点也是显而易见的:

1,增加了类的耦合性(耦合性不宜多,宜精)。

2,减少了重复代码。

3,使得代码更加规范化,合理化。
继承的分类

就向上面的例子:

Aminal 叫做父类,基类,超类。
Person Cat Dog: 子类,派生类。
继承:可以分单继承,多继承。

这里需要补充一下python中类的种类(继承需要):

在python2x版本中存在两种类.:
  一个叫经典类. 在python2.2之前. 一直使用的是经典类. 经典类在基类的根如果什么都不写.
  一个叫新式类. 在python2.2之后出现了新式类. 新式类的特点是基类的根是object类。
python3x版本中只有一种类:
python3中使用的都是新式类. 如果基类谁都不继承. 那这个类会默认继承 object
单继承
类名,对象执行父类方法

class Aniaml(object):
    type_name = '动物类'
    def __init__(self,name,sex,age):
            self.name = name
            self.age = age
            self.sex = sex
    def eat(self):
        print(self)
        print('吃东西')
class Person(Aniaml):
    pass
class Cat(Aniaml):
    pass
class Dog(Aniaml):
    pass
# 类名:
print(Person.type_name)  # 可以调用父类的属性,方法。
Person.eat(111)
print(Person.type_name)
# 对象:
# 实例化对象
p1 = Person('春哥','男',18)
print(p1.__dict__)
# 对象执行类的父类的属性,方法。
print(p1.type_name)
p1.type_name = '666'
print(p1)
p1.eat()

执行顺序

class Aniaml(object):
    type_name = '动物类'
    def __init__(self,name,sex,age):
            self.name = name
            self.age = age
            self.sex = sex
    def eat(self):
        print(self)
        print('吃东西')
class Person(Aniaml):
    def eat(self):
        print('%s 吃饭'%self.name)
class Cat(Aniaml):
    pass
class Dog(Aniaml):
    pass
p1 = Person('barry','男',18)
# 实例化对象时必须执行__init__方法,类中没有,从父类找,父类没有,从object类中找。
p1.eat()
# 先要执行自己类中的eat方法,自己类没有才能执行父类中的方法。

同时执行类以及父类方法

方法一:

如果想执行父类的func方法,这个方法并且子类中夜用,那么就在子类的方法中写上:

父类.func(对象,其他参数)

举例说明:

class Aniaml(object):
    type_name = '动物类'
    def __init__(self,name,sex,age):
            self.name = name
            self.age = age
            self.sex = sex
    def eat(self):
        print('吃东西')
class Person(Aniaml):
    def __init__(self,name,sex,age,mind):
        '''
        self = p1
        name = '春哥'
        sex = 'laddboy'
        age = 18
        mind = '有思想'
        '''
        # Aniaml.__init__(self,name,sex,age)  # 方法一
        self.mind = mind
    def eat(self):
        super().eat()
        print('%s 吃饭'%self.name)
class Cat(Aniaml):
    pass
class Dog(Aniaml):
    pass
# 方法一: Aniaml.__init__(self,name,sex,age)
# p1 = Person('春哥','laddboy',18,'有思想')
# print(p1.__dict__)
# 对于方法一如果不理解:
# def func(self):
#     print(self)
# self = 3
# func(self)

方法二:

利用super,super().func(参数)

class Aniaml(object):
    type_name = '动物类'
    def __init__(self,name,sex,age):
            self.name = name
            self.age = age
            self.sex = sex
    def eat(self):
        print('吃东西')
class Person(Aniaml):
    def __init__(self,name,sex,age,mind):
        '''
        self = p1
        name = '春哥'
        sex = 'laddboy'
        age = 18
        mind = '有思想'
        '''
        # super(Person,self).__init__(name,sex,age)  # 方法二
        super().__init__(name,sex,age)  # 方法二
        self.mind = mind
    def eat(self):
        super().eat()
        print('%s 吃饭'%self.name)
class Cat(Aniaml):
    pass
class Dog(Aniaml):
    pass
# p1 = Person('春哥','laddboy',18,'有思想')
# print(p1.__dict__)

多继承

class ShenXian: # 神仙
    def fei(self):
        print("神仙都会飞")
class Monkey: # 猴
    def chitao(self):
        print("猴子喜欢吃桃子")
class SunWukong(ShenXian, Monkey): # 孙悟空是神仙, 同时也是一只猴
    pass
sxz = SunWukong() # 孙悟空
sxz.chitao() # 会吃桃子
sxz.fei() # 会飞

此时, 孙悟空是一只猴子, 同时也是一个神仙. 那孙悟空继承了这两个类.
孙悟空自然就可以执行这两个类中的方法. 多继承用起来简单. 也很好理解. 但是多继承中, 存在着这样一个问题.
当两个父类中出现了重名方法的时候. 这时该怎么办呢? 这时就涉及到如何查找父类方法的这么一个问题.
即MRO(method resolution order) 问题. 在python中这是一个很复杂的问题.
因为在不同的python版本中使用的是不同的算法来完成MRO的.
经典类的多继承

虽然在python3中已经不存在经典类了. 但是经典类的MRO最好还是学一学. 这是一种树形结构遍历的一个最直接的案例.
在python的继承体系中. 我们可以把类与类继承关系化成一个树形结构的图. 来, 上代码:

class A:
    pass
class B(A):
    pass
class C(A):
    pass
class D(B, C):
    pass
class E:
    pass
class F(D, E):
    pass
class G(F, D):
    pass
class H:
    pass
class Foo(H, G):
    pass

对付这种mro画图就可以:
在这里插入图片描述
继承关系图已经有了. 那如何进行查找呢? 记住一个原则. 在经典类中采用的是深度优先,遍历方案. 什么是深度优先. 就是一条路走到头. 然后再回来. 继续找下一个.
在这里插入图片描述
图中每个圈都是准备要送鸡蛋的住址. 箭头和黑线表示线路. 那送鸡蛋的顺序告诉你入口在最下面R. 并且必须从左往右送. 那怎么送呢?

在这里插入图片描述
如图. 肯定是按照123456这样的顺序来送. 那这样的顺序就叫深度优先遍历. 而如果是142356呢? 这种被称为⼴度优先遍历. 好了. 深度优先就说这么多. 那么上面那个图怎么找的呢? MRO是什么呢? 很简单. 记住. 从头开始. 从左往右. 一条路跑到头, 然后回头. 继续一条路跑到头. 就是经典类的MRO算法.

类的MRO: Foo-> H -> G -> F -> E -> D -> B -> A -> C. 你猜对了么?
新式类的多继承
mro序列

MRO是一个有序列表L,在类被创建时就计算出来。
通用计算公式为:

mro(Child(Base1,Base2)) = [ Child ] + merge( mro(Base1), mro(Base2), [ Base1, Base2] )
(其中Child继承自Base1, Base2)

如果继承至一个基类:class B(A)
这时B的mro序列为

mro( B ) = mro( B(A) )
= [B] + merge( mro(A) + [A] )
= [B] + merge( [A] + [A] )
= [B,A]

如果继承至多个基类:class B(A1, A2, A3 …)
这时B的mro序列

mro(B) = mro( B(A1, A2, A3 …) )
= [B] + merge( mro(A1), mro(A2), mro(A3) ..., [A1, A2, A3] )
= ...

计算结果为列表,列表中至少有一个元素即类自己,如上述示例[A1,A2,A3]。merge操作是C3算法的核心。
表头和表尾

表头:
  列表的第一个元素

表尾:
  列表中表头以外的元素集合(可以为空)

示例
  列表:[A, B, C]
  表头是A,表尾是B和C
列表之间的+操作

+操作:

[A] + [B] = [A, B]
(以下的计算中默认省略)
-——————————

merge操作示例:

如计算merge( [E,O], [C,E,F,O], [C] )
有三个列表 :  ①      ②          ③
1 merge不为空,取出第一个列表列表①的表头E,进行判断
   各个列表的表尾分别是[O], [E,F,O],E在这些表尾的集合中,因而跳过当前当前列表
2 取出列表②的表头C,进行判断
   C不在各个列表的集合中,因而将C拿出到merge外,并从所有表头删除
   merge( [E,O], [C,E,F,O], [C]) = [C] + merge( [E,O], [E,F,O] )
3 进行下一次新的merge操作 ......
---------------------

在这里插入图片描述
计算mro(A)方式:

mro(A) = mro( A(B,C) )
原式= [A] + merge( mro(B),mro(C),[B,C] )
  mro(B) = mro( B(D,E) )
         = [B] + merge( mro(D), mro(E), [D,E] )  # 多继承
         = [B] + merge( [D,O] , [E,O] , [D,E] )  # 单继承mro(D(O))=[D,O]
         = [B,D] + merge( [O] , [E,O]  ,  [E] )  # 拿出并删除D
         = [B,D,E] + merge([O] ,  [O])
         = [B,D,E,O]
  mro(C) = mro( C(E,F) )
         = [C] + merge( mro(E), mro(F), [E,F] )
         = [C] + merge( [E,O] , [F,O] , [E,F] )
         = [C,E] + merge( [O] , [F,O]  ,  [F] )  # 跳过O,拿出并删除
         = [C,E,F] + merge([O] ,  [O])
         = [C,E,F,O]
原式= [A] + merge( [B,D,E,O], [C,E,F,O], [B,C])
    = [A,B] + merge( [D,E,O], [C,E,F,O],   [C])
    = [A,B,D] + merge( [E,O], [C,E,F,O],   [C])  # 跳过E
    = [A,B,D,C] + merge([E,O],  [E,F,O])
    = [A,B,D,C,E] + merge([O],    [F,O])  # 跳过O
    = [A,B,D,C,E,F] + merge([O],    [O])
    = [A,B,D,C,E,F,O]
---------------------

结果OK. 那既然python提供了. 为什么我们还要如此麻烦的计算MRO呢? 因为笔
试…….你在笔试的时候, 是没有电脑的. 所以这个算法要知道. 并且简单的计算要会. 真是项目
开发的时候很少有人这么去写代码.

这个说完了. 那C3到底怎么看更容易呢? 其实很简单. C3是把我们多个类产生的共同继
承留到最后去找. 所以. 我们也可以从图上来看到相关的规律. 这个要大家自己多写多画图就
能感觉到了. 但是如果没有所谓的共同继承关系. 那几乎就当成是深度遍历就可以了
封装

把很多数据封装到一个对象中. 把固定功能的代码封装到一个代码块, 函数, 对象, 打包成模块. 这都属于封装的思想. 具体的情况具体分析. 比如. 你写了一个很牛B的函数. 那这个也可以被称为封装. 在面向对象思想中. 是把一些看似无关紧要的内容组合到一起统一进行存储和使用. 这就是封装.

封装,顾名思义就是将内容封装到某个地方,以后再去调用被封装在某处的内容。

所以,在使用面向对象的封装特性时,需要:

将内容封装到某处
从某处调用被封装的内容

第一步:将内容封装到某处
在这里插入图片描述
self 是一个形式参数,当执行 obj1 = Foo(‘wupeiqi’, 18 ) 时,self 等于 obj1

​ 当执行 obj2 = Foo(‘alex’, 78 ) 时,self 等于 obj2

所以,内容其实被封装到了对象 obj1 和 obj2 中,每个对象中都有 name 和 age 属性,在内存里类似于下图来保存。
在这里插入图片描述
第二步:从某处调用被封装的内容

调用被封装的内容时,有两种情况:

通过对象直接调用
通过self间接调用

1、通过对象直接调用被封装的内容

上图展示了对象 obj1 和 obj2 在内存中保存的方式,根据保存格式可以如此调用被封装的内容:对象.属性名

class Foo:
    def __init__(self, name, age):
        self.name = name
        self.age = age
obj1 = Foo('wupeiqi', 18)
print obj1.name    # 直接调用obj1对象的name属性
print obj1.age     # 直接调用obj1对象的age属性
obj2 = Foo('alex', 73)
print obj2.name    # 直接调用obj2对象的name属性
print obj2.age     # 直接调用obj2对象的age属性

2、通过self间接调用被封装的内容

执行类中的方法时,需要通过self间接调用被封装的内容

class Foo:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def detail(self):
        print self.name
        print self.age
obj1 = Foo('wupeiqi', 18)
obj1.detail()  # Python默认会将obj1传给self参数,即:obj1.detail(obj1),所以,此时方法内部的 self = obj1,即:self.name 是 wupeiqi ;self.age 是 18
obj2 = Foo('alex', 73)
obj2.detail()  # Python默认会将obj2传给self参数,即:obj1.detail(obj2),所以,此时方法内部的 self = obj2,即:self.name 是 alex ; self.age 是 78

综上所述,对于面向对象的封装来说,其实就是使用构造方法将内容封装到 对象 中,然后通过对象直接或者self间接获取被封装的内容。
继承

子类可以自动拥有父类中除了私有属性外的其他所有内容. 说白了, 儿子可以随便用爹的东西. 但是朋友们, 一定要认清楚一个事情. 必须先有爹, 后有儿子. 顺序不能乱, 在python中实现继承非常简单. 在声明类的时候, 在类名后面添加一个小括号,就可以完成继承关系. 那么什么情况可以使用继承呢? 单纯的从代码层面上来看. 两个类具有相同的功能或者特征的时候. 可以采用继承的形式. 提取一个父类, 这个父类中编写着两个类相同的部分. 然后两个类分别取继承这个类就可以了. 这样写的好处是我们可以避免写很多重复的功能和代码. 如果从语义中去分析的话. 会简单很多. 如果语境中出现了x是一种y. 这时, y是一种泛化的概念. x比y更加具体. 那这时x就是y的子类. 比如. 猫是一种动物. 猫继承动物. 动物能动. 猫也能动. 这时猫在创建的时候就有了动物的”动”这个属性. 再比如, 白骨精是一个妖怪. 妖怪天生就有一个比较不好的功能叫”吃人”, 白骨精一出生就知道如何”吃人”. 此时 白骨精继承妖精.
多态

同一个对象, 多种形态. 这个在python中其实是很不容易说明白的. 因为我们一直在用. 只是没有具体的说. 比如. 我们创建一个变量a = 10 , 我们知道此时a是整数类型. 但是我们可以通过程序让a = “alex”, 这时, a又变成了字符串类型. 这是我们都知道的. 但是, 我要告诉你的是. 这个就是多态性. 同一个变量a可以是多种形态。

多态,同一个对象,多种形态。python默认支持多态。

# 在java或者c#定义变量或者给函数传值必须定义数据类型,否则就报错。
def func(int a):
    print('a必须是数字')
# 而类似于python这种弱定义类语言,a可以是任意形态(str,int,object等等)。
def func(a):
    print('a是什么都可以')
# 再比如:
class F1:
    pass
class S1(F1):
    def show(self):
        print 'S1.show'
class S2(F1):
    def show(self):
        print 'S2.show'
# 由于在Java或C#中定义函数参数时,必须指定参数的类型
# 为了让Func函数既可以执行S1对象的show方法,又可以执行S2对象的show方法,所以,定义了一个S1和S2类的父类
# 而实际传入的参数是:S1对象和S2对象
def Func(F1 obj):
"""Func函数需要接收一个F1类型或者F1子类的类型"""
    print obj.show()
s1_obj = S1()
Func(s1_obj)  # 在Func函数中传入S1类的对象 s1_obj,执行 S1 的show方法,结果:S1.show
s2_obj = S2()
Func(s2_obj)  # 在Func函数中传入Ss类的对象 ss_obj,执行 Ss 的show方法,结果:S2.show
Python伪代码实现Java或C  # 的多态

鸭子类型

python中有一句谚语说的好,你看起来像鸭子,那么你就是鸭子。
对于代码上的解释其实很简答:
class A:
    def f1(self):
        print('in A f1')
    def f2(self):
        print('in A f2')
class B:
    def f1(self):
        print('in A f1')
    def f2(self):
        print('in A f2')
obj = A()
obj.f1()
obj.f2()
obj2 = B()
obj2.f1()
obj2.f2()
# A 和 B两个类完全没有耦合性,但是在某种意义上他们却统一了一个标准。
# 对相同的功能设定了相同的名字,这样方便开发,这两个方法就可以互成为鸭子类型。
# 这样的例子比比皆是:str  tuple list 都有 index方法,这就是统一了规范。
# str bytes 等等 这就是互称为鸭子类型。

类的约束

首先, 你要清楚. 约束是对类的约束.

用一个例子说话:

公司让小明给他们的网站完善一个支付功能,小明写了两个类,如下:

class QQpay:
    def pay(self,money):
        print('使用qq支付%s元' % money)
class Alipay:
    def pay(self,money):
        print('使用阿里支付%s元' % money)
a = Alipay()
a.pay(100)
b = QQpay()
b.pay(200)

但是上面这样写不太放方便,也不合理,老大说让他整改,统一一下付款的方式,小明开始加班整理:

class QQpay:
    def pay(self,money):
        print('使用qq支付%s元' % money)
class Alipay:
    def pay(self,money):
        print('使用阿里支付%s元' % money)
def pay(obj,money):  # 这个函数就是统一支付规则,这个叫做: 归一化设计。
    obj.pay(money)
a = Alipay()
b = QQpay()
pay(a,100)
pay(b,200)

写了半年的接口,小明终于接了大项目了,结果公司没品位,招了一个野生的程序员春哥接替小明的工作,老大给春哥安排了任务,让他写一个微信支付的功能:

class QQpay:
    def pay(self,money):
        print('使用qq支付%s元' % money)
class Alipay:
    def pay(self,money):
        print('使用阿里支付%s元' % money)
class Wechatpay:  # 野生程序员一般不会看别人怎么写,自己才是最好,结果......
    def fuqian(self,money):
        print('使用微信支付%s元' % money)
def pay(obj,money):
    obj.pay(money)
a = Alipay()
b = QQpay()
pay(a,100)
pay(b,200)
c = Wechatpay()
c.fuqian(300)

结果春哥,受惩罚了,限期整改,那么春哥,发奋图强,python的相关资料,重新梳理的代码:

class Payment:
  """ 此类什么都不做,就是制定一个标准,谁继承我,必须定义我里面的方法。
   """
    def pay(self,money):pass
class QQpay(Payment):
    def pay(self,money):
        print('使用qq支付%s元' % money)
class Alipay(Payment):
    def pay(self,money):
        print('使用阿里支付%s元' % money)
class Wechatpay(Payment):
    def fuqian(self,money):
        print('使用微信支付%s元' % money)
def pay(obj,money):
    obj.pay(money)
a = Alipay()
b = QQpay()
pay(a,100)
pay(b,200)
c = Wechatpay()
c.fuqian(300)

但是,这样还会有问题,如果再来野生程序员,他不看其他的支付方式,也不知道为什么继承的类中要定义一个没有意义的方法,所以他会是会我行我素:

class Payment:
  """ 此类什么都不做,就是制定一个标准,谁继承我,必须定义我里面的方法。
   """
    def pay(self,money):pass
class QQpay(Payment):
    def pay(self,money):
        print('使用qq支付%s元' % money)
class Alipay(Payment):
    def pay(self,money):
        print('使用阿里支付%s元' % money)
class Wechatpay(Payment):
    def fuqian(self,money):
        print('使用微信支付%s元' % money)
def pay(obj,money):
    obj.pay(money)
a = Alipay()
b = QQpay()
pay(a,100)
pay(b,200)
c = Wechatpay()
c.fuqian(300)

所以此时我们要用到对类的约束,对类的约束有两种:

1.提取父类. 然后在父类中定义好方法. 在这个方法中什么都不用干. 就抛一个异常就可以了. 这样所有的子类都必须重写这个方法. 否则. 访问的时候就会报错.

2.使用元类来描述父类. 在元类中给出一个抽象方法. 这样子类就不得不给出抽象方法的具体实现. 也可以起到约束的效果.

先用第一种方式解决:

class Payment:
    """
    此类什么都不做,就是制定一个标准,谁继承我,必须定义我里面的方法。
    """
    def pay(self,money):
        raise Exception("你没有实现pay方法")
class QQpay(Payment):
    def pay(self,money):
        print('使用qq支付%s元' % money)
class Alipay(Payment):
    def pay(self,money):
        print('使用阿里支付%s元' % money)
class Wechatpay(Payment):
    def fuqian(self,money):
        print('使用微信支付%s元' % money)
def pay(obj,money):
    obj.pay(money)
a = Alipay()
b = QQpay()
c = Wechatpay()
pay(a,100)
pay(b,200)
pay(c,300)

第二种方式:引入抽象类的概念处理

from abc import ABCMeta,abstractmethod
class Payment(metaclass=ABCMeta):    # 抽象类 接口类  规范和约束  metaclass指定的是一个元类
    @abstractmethod
    def pay(self):pass  # 抽象方法
class Alipay(Payment):
    def pay(self,money):
        print('使用支付宝支付了%s元'%money)
class QQpay(Payment):
    def pay(self,money):
        print('使用qq支付了%s元'%money)
class Wechatpay(Payment):
    # def pay(self,money):
    #     print('使用微信支付了%s元'%money)
    def recharge(self):pass
def pay(a,money):
    a.pay(money)
a = Alipay()
a.pay(100)
pay(a,100)    # 归一化设计:不管是哪一个类的对象,都调用同一个函数去完成相似的功能
q = QQpay()
q.pay(100)
pay(q,100)
w = Wechatpay()
pay(w,100)   # 到用的时候才会报错
# 抽象类和接口类做的事情 :建立规范
# 制定一个类的metaclass是ABCMeta,
# 那么这个类就变成了一个抽象类(接口类)
# 这个类的主要功能就是建立一个规范

总结: 约束. 其实就是父类对子类进行约束. 子类必须要写xxx方法. 在python中约束的方式和方法有两种:

1. 使用抽象类和抽象方法, 由于该方案来源是java和c#. 所以使用频率还是很少的
2. 使用人为抛出异常的方案. 并且尽量抛出的是NotImplementError. 这样比较专业, 而且错误比较明确.(推荐)

super

super是严格按照类的继承顺序执行!!!

class A:
    def f1(self):
        print('in A f1')
    def f2(self):
        print('in A f2')
class Foo(A):
    def f1(self):
        super().f2()
        print('in A Foo')
obj = Foo()
obj.f1()
super可以下一个类的其他方法

super()严格按照类的mro顺序执行

class A:
    def f1(self):
        print('in A')
class Foo(A):
    def f1(self):
        super().f1()
        print('in Foo')
class Bar(A):
    def f1(self):
        print('in Bar')
class Info(Foo,Bar):
    def f1(self):
        super().f1()
        print('in Info f1')
obj = Info()
obj.f1()
'''
in Bar
in Foo
in Info f1
'''
print(Info.mro())  # [<class '__main__.Info'>, <class '__main__.Foo'>, <class '__main__.Bar'>, <class '__main__.A'>, <class 'object'>]

细分类的组成成员

之前咱们讲过类大致分两块区域,如下图所示:
在这里插入图片描述
每个区域详细划分又可以分为:

class A:
    company_name = '老男孩教育'  # 静态变量(静态字段)
    __iphone = '1353333xxxx'  # 私有静态变量(私有静态字段)
    def __init__(self,name,age): #特殊方法
        self.name = name  #对象属性(普通字段)
        self.__age = age  # 私有对象属性(私有普通字段)
    def func1(self):  # 普通方法
        pass
    def __func(self): #私有方法
        print(666)
    @classmethod  # 类方法
    def class_func(cls):
        """ 定义类方法,至少有一个cls参数 """
        print('类方法')
    @staticmethod  #静态方法
    def static_func():
        """ 定义静态方法 ,无默认参数"""
        print('静态方法')
    @property  # 属性
    def prop(self):
        pass

类的私有成员

对于每一个类的成员而言都有两种形式:

公有成员,在任何地方都能访问
私有成员,只有在类的内部才能方法

私有成员和公有成员的访问限制不同:

静态字段(静态属性)

公有静态字段:类可以访问;类内部可以访问;派生类中可以访问
私有静态字段:仅类内部可以访问;

公有静态属性(字段)

class C:
    name = "公有静态字段"
    def func(self):
        print C.name
class D(C):
    def show(self):
        print C.name
C.name         # 类访问
obj = C()
obj.func()     # 类内部可以访问
obj_son = D()
obj_son.show() # 派生类中可以访问
公有静态字段

私有静态属性(字段)

class C:
    __name = "私有静态字段"
    def func(self):
        print C.__name
class D(C):
    def show(self):
        print C.__name
C.__name       # 不可在外部访问
obj = C()
obj.__name  # 不可在外部访问
obj.func()     # 类内部可以访问
obj_son = D()
obj_son.show() #不可在派生类中可以访问
私有静态字段

普通字段(对象属性)

公有普通字段:对象可以访问;类内部可以访问;派生类中可以访问
私有普通字段:仅类内部可以访问;

公有普通字段

class C:
    def __init__(self):
        self.foo = "公有字段"
    def func(self):
        print self.foo  # 类内部访问
class D(C):
    def show(self):
        print self.foo # 派生类中访问
obj = C()
obj.foo     # 通过对象访问
obj.func()  # 类内部访问
obj_son = D();
obj_son.show()  # 派生类中访问

私有对象属性

class C:
    def __init__(self):
        self.__foo = "私有字段"
    def func(self):
        print self.foo  # 类内部访问
class D(C):
    def show(self):
        print self.foo # 派生类中访问
obj = C()
obj.__foo     # 通过对象访问    ==> 错误
obj.func()  # 类内部访问        ==> 正确
obj_son = D();
obj_son.show()  # 派生类中访问  ==> 错误
私有普通字段

方法:

公有方法:对象可以访问;类内部可以访问;派生类中可以访问
私有方法:仅类内部可以访问;
公有方法

class C:
    def __init__(self):
        pass
    def add(self):
        print('in C')
class D(C):
    def show(self):
        print('in D')
    def func(self):
        self.show()
obj = D()
obj.show()  # 通过对象访问
obj.func()  # 类内部访问
obj.add()  # 派生类中访问

私有方法

class C:
    def __init__(self):
        pass
    def __add(self):
        print('in C')
class D(C):
    def __show(self):
        print('in D')
    def func(self):
        self.__show()
obj = D()
obj.__show()  # 通过不能对象访问
obj.func()  # 类内部可以访问
obj.__add()  # 派生类中不能访问

总结:

对于这些私有成员来说,他们只能在类的内部使用,不能再类的外部以及派生类中使用.

ps:非要访问私有成员的话,可以通过 对象.类_属性名,但是绝对不允许!!!

为什么可以通过.类__私有成员名访问呢?因为类在创建时,如果遇到了私有成员(包括私有静态字段,私有普通字段,私有方法)它会将其保存在内存时自动在前面加上类名.
类的其他成员

这里的其他成员主要就是类方法:

方法包括:普通方法、静态方法和类方法,三种方法在内存中都归属于类,区别在于调用方式不同。

实例方法

​ 定义:第一个参数必须是实例对象,该参数名一般约定为“self”,通过它来传递实例的属性和方法(也可以传类的属性和方法);

​ 调用:只能由实例对象调用。

类方法

​ 定义:使用装饰器@classmethod。第一个参数必须是当前类对象,该参数名一般约定为“cls”,通过它来传递类的属性和方法(不能传实例的属性和方法);

​ 调用:实例对象和类对象都可以调用。

静态方法

​ 定义:使用装饰器@staticmethod。参数随意,没有“self”和“cls”参数,但是方法体中不能使用类或实例的任何属性和方法;

​ 调用:实例对象和类对象都可以调用。

双下方法(后面会讲到)

定义:双下方法是特殊方法,他是解释器提供的 由爽下划线加方法名加爽下划线 __方法名__的具有特殊意义的方法,双下方法主要是python源码程序员使用的,

我们在开发中尽量不要使用双下方法,但是深入研究双下方法,更有益于我们阅读源码。

调用:不同的双下方法有不同的触发方式,就好比盗墓时触发的机关一样,不知不觉就触发了双下方法,例如:init
类方法

使用装饰器@classmethod。

原则上,类方法是将类本身作为对象进行操作的方法。假设有个方法,且这个方法在逻辑上采用类本身作为对象来调用更合理,那么这个方法就可以定义为类方法。另外,如果需要继承,也可以定义为类方法。

如下场景:

假设我有一个学生类和一个班级类,想要实现的功能为:
执行班级人数增加的操作、获得班级的总人数;
学生类继承自班级类,每实例化一个学生,班级人数都能增加;
最后,我想定义一些学生,获得班级中的总人数。

思考:这个问题用类方法做比较合适,为什么?因为我实例化的是学生,但是如果我从学生这一个实例中获得班级总人数,在逻辑上显然是不合理的。同时,如果想要获得班级总人数,如果生成一个班级的实例也是没有必要的。

class Student:
    __num = 0
    def __init__(self,name,age):
        self.name = name
        self.age= age
        Student.addNum()  # 写在__new__方法中比较合适,但是现在还没有学,暂且放到这里
    @classmethod
    def addNum(cls):
        cls.__num += 1
    @classmethod
    def getNum(cls):
        return cls.__num
a = Student('太白金星', 18)
b = Student('武sir', 36)
c = Student('alex', 73)
print(Student.getNum())

静态方法

使用装饰器@staticmethod。

静态方法是类中的函数,不需要实例。静态方法主要是用来存放逻辑性的代码,逻辑上属于类,但是和类本身没有关系,也就是说在静态方法中,不会涉及到类中的属性和方法的操作。可以理解为,静态方法是个独立的、单纯的函数,它仅仅托管于某个类的名称空间中,便于使用和维护。

譬如,我想定义一个关于时间操作的类,其中有一个获取当前时间的函数。

import time
class TimeTest(object):
    def __init__(self, hour, minute, second):
        self.hour = hour
        self.minute = minute
        self.second = second
    @staticmethod
    def showTime():
        return time.strftime("%H:%M:%S", time.localtime())
print(TimeTest.showTime())
t = TimeTest(2, 10, 10)
nowTime = t.showTime()
print(nowTime)

如上,使用了静态方法(函数),然而方法体中并没使用(也不能使用)类或实例的属性(或方法)。若要获得当前时间的字符串时,并不一定需要实例化对象,此时对于静态方法而言,所在类更像是一种名称空间。

其实,我们也可以在类外面写一个同样的函数来做这些事,但是这样做就打乱了逻辑关系,也会导致以后代码维护困难。
属性
什么是特性property

property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值

例一:BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解)
成人的BMI数值:
过轻:低于18.5
正常:18.5-23.9
过重:24-27
肥胖:28-32
非常肥胖, 高于32
  体质指数(BMI)=体重(kg)÷身高^2(m)
  EX:70kg÷(1.75×1.75)=22.86

例一代码

class People:
    def __init__(self,name,weight,height):
        self.name=name
        self.weight=weight
        self.height=height
    @property
    def bmi(self):
        return self.weight / (self.height**2)
p1=People('egon',75,1.85)
print(p1.bmi)

为什么要用property

将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则

由于新式类中具有三种访问方式,我们可以根据他们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除

class Foo:
    @property
    def AAA(self):
        print('get的时候运行我啊')
    @AAA.setter
    def AAA(self,value):
        print('set的时候运行我啊')
    @AAA.deleter
    def AAA(self):
        print('delete的时候运行我啊')
#只有在属性AAA定义property后才能定义AAA.setter,AAA.deleter
f1=Foo()
f1.AAA
f1.AAA='aaa'
del f1.AAA
或者:
class Foo:
    def get_AAA(self):
        print('get的时候运行我啊')
    def set_AAA(self,value):
        print('set的时候运行我啊')
    def delete_AAA(self):
        print('delete的时候运行我啊')
    AAA=property(get_AAA,set_AAA,delete_AAA) #内置property三个参数与get,set,delete一一对应
f1=Foo()
f1.AAA
f1.AAA='aaa'
del f1.AAA

商品示例:

class Goods(object):
    def __init__(self):
        # 原价
        self.original_price = 100
        # 折扣
        self.discount = 0.8
    @property
    def price(self):
        # 实际价格 = 原价 * 折扣
        new_price = self.original_price * self.discount
        return new_price
    @price.setter
    def price(self, value):
        self.original_price = value
    @price.deltter
    def price(self, value):
        del self.original_price
obj = Goods()
obj.price         # 获取商品价格
obj.price = 200   # 修改商品原价
del obj.price     # 删除商品原价

isinstace 与 issubclass

class A:
    pass
class B(A):
    pass
obj = B()
print(isinstance(obj,B))
print(isinstance(obj,A))

isinstance(a,b)

判断a是否是b类(或者b类的派生类)实例化的对象

class A:
    pass
class B(A):
    pass
class C(B):
    pass
print(issubclass(B,A))
print(issubclass(C,A))

issubclass(a,b)

判断a类是否是b类(或者b的派生类)的派生类

思考:那么 list str tuple dict等这些类与 Iterble类 的关系是什么?

from collections import Iterable
print(isinstance([1,2,3], list))  # True
print(isinstance([1,2,3], Iterable))  # True
print(issubclass(list,Iterable))  # True
# 由上面的例子可得,这些可迭代的数据类型,list str tuple dict等 都是 Iterable的子类。

反射

反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。

python面向对象中的反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)

四个可以实现自省的函数

下列方法适用于类和对象(一切皆对象,类本身也是一个对象)
对象的反射

class Foo:
    f = '类的静态变量'
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def say_hi(self):
        print('hi,%s'%self.name)
obj=Foo('egon',73)
#检测是否含有某属性
print(hasattr(obj,'name'))
print(hasattr(obj,'say_hi'))
#获取属性
n=getattr(obj,'name')
print(n)
func=getattr(obj,'say_hi')
func()
print(getattr(obj,'aaaaaaaa','不存在啊')) #报错
#设置属性
setattr(obj,'sb',True)
setattr(obj,'show_name',lambda self:self.name+'sb')
print(obj.__dict__)
print(obj.show_name(obj))
#删除属性
delattr(obj,'age')
delattr(obj,'show_name')
delattr(obj,'show_name111')#不存在,则报错
print(obj.__dict__)
对实例化对象的示例

对类的反射

class Foo(object):
    staticField = "old boy"
    def __init__(self):
        self.name = 'wupeiqi'
    def func(self):
        return 'func'
    @staticmethod
    def bar():
        return 'bar'
print getattr(Foo, 'staticField')
print getattr(Foo, 'func')
print getattr(Foo, 'bar')

当前模块的反射

import sys
def s1():
    print 's1'
def s2():
    print 's2'
this_module = sys.modules[__name__]
hasattr(this_module, 's1')
getattr(this_module, 's2')

其他模块的反射

#一个模块中的代码
def test():
    print('from the test')
"""
程序目录:
    module_test.py
    index.py
当前文件:
    index.py
"""
# 另一个模块中的代码
import module_test as obj
#obj.test()
print(hasattr(obj,'test'))
getattr(obj,'test')()

反射的应用

了解了反射的四个函数。那么反射到底有什么用呢?它的应用场景是什么呢?

现在让我们打开浏览器,访问一个网站,你单击登录就跳转到登录界面,你单击注册就跳转到注册界面,等等,其实你单击的其实是一个个的链接,每一个链接都会有一个函数或者方法来处理。

没学反射之前的解决方式

class User:
    def login(self):
        print('欢迎来到登录页面')
    def register(self):
        print('欢迎来到注册页面')
    def save(self):
        print('欢迎来到存储页面')
while 1:
    choose = input('>>>').strip()
    if choose == 'login':
        obj = User()
        obj.login()
    elif choose == 'register':
        obj = User()
        obj.register()
    elif choose == 'save':
        obj = User()
        obj.save()

学了反射之后解决方式

class User:
    def login(self):
        print('欢迎来到登录页面')
    def register(self):
        print('欢迎来到注册页面')
    def save(self):
        print('欢迎来到存储页面')
user = User()
while 1:
    choose = input('>>>').strip()
    if hasattr(user,choose):
        func = getattr(user,choose)
        func()
    else:
        print('输入错误。。。。')

这样就可以明确的感觉到反射的好处
双下方法

定义:双下方法是特殊方法,他是解释器提供的 由爽下划线加方法名加双下划线 __方法名__的具有特殊意义的方法,双下方法主要是python源码程序员使用的,我们在开发中尽量不要使用双下方法,但是深入研究双下方法,更有益于我们阅读源码。

调用:不同的双下方法有不同的触发方式,就好比盗墓时触发的机关一样,不知不觉就触发了双下方法,例如:init
len

class B:
    def __len__(self):
        print(666)
b = B()
len(b) # len 一个对象就会触发 __len__方法。
class A:
    def __init__(self):
        self.a = 1
        self.b = 2
    def __len__(self):
        return len(self.__dict__)
a = A()
print(len(a))

hash

class A:
    def __init__(self):
        self.a = 1
        self.b = 2
    def __hash__(self):
        return hash(str(self.a)+str(self.b))
a = A()
print(hash(a))

str

如果一个类中定义了__str__方法,那么在打印 对象 时,默认输出该方法的返回值。

class A:
    def __init__(self):
        pass
    def __str__(self):
        return '宝元'
a = A()
print(a)
print('%s' % a)

repr

如果一个类中定义了__repr__方法,那么在repr(对象) 时,默认输出该方法的返回值。

class A:
    def __init__(self):
        pass
    def __repr__(self):
        return '宝元'
a = A()
print(repr(a))
print('%r'%a)

call

对象后面加括号,触发执行。

注:构造方法__new__的执行是由创建对象触发的,即:对象 = 类名() ;而对于 call 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()

class Foo:
    def __init__(self):
        pass
    def __call__(self, *args, **kwargs):
        print('__call__')
obj = Foo() # 执行 __init__
obj()       # 执行 __call__

eq

class A:
    def __init__(self):
        self.a = 1
        self.b = 2
    def __eq__(self,obj):
        if  self.a == obj.a and self.b == obj.b:
            return True
a = A()
b = A()
print(a == b)

del

析构方法,当对象在内存中被释放时,自动触发执行。

注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。
new

class A:
    def __init__(self):
        self.x = 1
        print('in init function')
    def __new__(cls, *args, **kwargs):
        print('in new function')
        return object.__new__(A, *args, **kwargs)
a = A()
print(a.x)

使用__new__实现单例模式

class A:
    __instance = None
    def __new__(cls, *args, **kwargs):
        if cls.__instance is None:
            obj = object.__new__(cls)
            cls.__instance = obj
        return cls.__instance
单例模式

单例模式具体分析:

单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
【采用单例模式动机、原因】
对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。如在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。
如何保证一个类只有一个实例并且这个实例易于被访问呢?定义一个全局变量可以确保对象随时都可以被访问,但不能防止我们实例化多个对象。一个更好的解决办法是让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。这就是单例模式的模式动机。
【单例模式优缺点】
【优点】
一、实例控制
单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。
二、灵活性
因为类控制了实例化过程,所以类可以灵活更改实例化过程。
【缺点】
一、开销
虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题。
二、可能的开发混淆
使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用new关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。
三、对象生存期
不能解决删除单个对象的问题。在提供内存管理的语言中(例如基于.NET Framework的语言),只有单例类能够导致实例被取消分配,因为它包含对该实例的私有引用。在某些语言中(如 C++),其他类可以删除对象实例,但这样会导致单例类中出现悬浮引用
__item__系列

class Foo:
    def __init__(self,name):
        self.name=name
    def __getitem__(self, item):
        print(self.__dict__[item])
    def __setitem__(self, key, value):
        self.__dict__[key]=value
    def __delitem__(self, key):
        print('del obj[key]时,我执行')
        self.__dict__.pop(key)
    def __delattr__(self, item):
        print('del obj.key时,我执行')
        self.__dict__.pop(item)
f1=Foo('sb')
f1['age']=18
f1['age1']=19
del f1.age1
del f1['age']
f1['name']='alex'
print(f1.__dict__)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值