面向对象

面向对象

基本概念

  • 软件编程的实质
    软件编程就是将我们的思维转变成计算机能够识别语言的一个过程
  • 面向过程
    • 自上而下顺序执行,逐步求精;
    • 其程序结构是按功能划分为若干个基本模块,这些模块形成一个树状结构;
    • 每一模块内部均是由顺序、选择和循环三种基本结构组成;
    • 其模块化实现的具体方法是使用子程序。
    • 程序流程在写程序时就已决定。
  • 面向对象
    • 把数据及对数据的操作方法放在一起,作为一个相互依存的整体——对象。
    • 对同类对象抽象出其共性,形成类。
    • 类中的大多数数据,只能用本类的方法进行处理
    • 类通过一个简单的外部接口与外界发生关系,对象与对象之间通过消息进行通信。
    • 程序流程由用户在使用中决定。
  • 理解面向对象
    • 面向对象是相对面向过程而言
    • 面向对象和面向过程都是一种思想
    • 面向过程
      • 强调的是功能行为
      • 关注的是解决问题需要哪些步骤
    • 面向对象
      • 将功能封装进对象,强调具备了功能的对象
      • 关注的是解决问题需要哪些对象
    • 面向对象是基于面向过程的。
  • 抽象化理解
    • 问题:把大象关进冰箱
    • 面向过程思想:打开冰箱门---->将大象装进冰箱---->关闭冰箱
    • 面向对象的思想:创造一个人,让这个人干这些事
  • 面向对象的特点:
    1. 是一种符合人们思考习惯的思想
    2. 可以将复杂的事情简单化
    3. 将程序员从执行者转换成了指挥者
    4. 先要去找具有所需的功能的对象来用。
    5. 如果该对象不存在,那么创建一个具有所需功能的对象。
  • 类与对象的关系
    • Python中描述事物通过类的形式体现,类是具体事物的抽象,概念上的定义。
    • 对象即是该类事物实实在在存在的个体
  • 类的定义
    • 生活中描述事物无非就是描述事物的名称/属性和行为。
      如:人有身高,体重等属性,有说话,打架等行为。
    • Python中用类来描述事物也是如此
      • 属性:对应类中的成员变量。
      • 行为:对应类中的成员方法。
    • 定义类其实在定义类中的成员(成员变量和成员方法)
    • 拥有相同(或者类似)属性和行为的对象都可以抽像出一个类
  • 类的设计(只关心3样东西)
    • 事物名称(类名):首字母大写,其他部分遵守驼峰原则
    • 属性:遵守标识符规则,见名知意
    • 行为(功能/方法/函数):遵守标识符规则,见名知意
  • 面向对象的4大特性:封装、继承、多态、抽象
    • 抽象: 我们在定义一个类的时候,实际上就是把一类事物的公有的属性和行为提取出来,形成一个物理模型,这种研究问题的方法称为抽象。
    • 封装:就是把抽象的数据和对数据进行的操作封装在一起,数据被保存在内部,程序的其他部分只有通过被授权的操作(成员方法)才能对数据进行操作。
    • 继承:可以解决代码复用问题,让我们编程更加靠近人类的思维,当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过继承就可以拥有父类的全部属性和方法
    • 多态:不同的子类对象调用相同的方法,产生不同的执行结果

类的创建及实例化

  • 格式
    class 类名(父类名):
    属性&行为

  • 说明
    父类名:父类的名字,定义的类继承自父类。可以不写,默认是object,所有类最终都会继承自object类,object是所有类的直接或者间接父类

  • 实例化类

    • 格式
      • 对象名 = 类名(参数列表)
      • 参数列表:为了后面给属性进行赋值的
class Person():
    pass
per1 = Person()
print(per1)
#<__main__.Person object at 0x00000000021FB828>
per2 = Person()
print(per2)

对象方法

  • 方法和函数的区别:

    • 方法作用域属于类,所以即便和普通函数重名,也不会被覆盖
    • 方法的第一个参数必须是self,但函数不要求
    • 方法必须通过对象调用,而函数不需要
  • 对象方法

    • 定义:对象方法其实就是函数,作用域在类内,成员方法的第一个参数必须是self
    • 特性:在对象方法内部可以引用类内部的任何属性和方法
    • 调用方式:通过实例调用
    • 调用格式:对象名.方法名(参数列表)
    • 注意:定义对象方法的第一个参数必须是self,其余参数按顺序排列,调用函数传参时忽略self
    • self:代表一个对象,哪个对象调用的对象方法,那么在该方法中self就代表那个对象,这个参数由系统传递。
  • 类方法

    • 定义:如下
    • 特性:在类方法内部可以调用类的属性和方法
    • 调用方式:用类名调用(也可以使用对象调用)
    • 调用格式:类名.方法名(参数列表)
    • 注意:定义类方法的第一个参数必须是cls,其余参数按顺序排列,调用函数传参时忽略cls
    • cls:代表当前类的类名
  • 静态方法
    无论用类调用还是用实例调用,都无法获取到内部的属性和方法,完全独立的一个方法,使用@staticmethod定义

  • 总结:
    类方法效率比对象方法高,因为类方法无需实例化对象,并且节省内存,当其中不涉及对象属性和方法时使用
    静态方法一般用于工具方法的封装

class Person():
    #定义对象方法
    def run(self):
        print("----run----", self)
        self.eat("banana")
        Person.play()
    def eat(self, food):
        print("----eat %s----"%food)
        return self


    #定义类方法
    @classmethod
    def play(cls):
        print("----play----")
        cls.slep()   #类的内部调用自己的类方法
    @classmethod
    def slep(cls):
        print("----slep----")


    #定义静态方法
    @staticmethod
    def modify():
        print("----modify----")

per = Person()
print(per)
#<__main__.Person object at 0x0000000002895438>
#对象方法的调用
per.run()
'''
----run---- <__main__.Person object at 0x0000000002874390>
----eat banana----
----play----
----slep----
'''
per.eat("apple")
#----eat apple----
per2 = Person()
print(per2)
#方法的连贯调用,必须返回self才可以
per2.eat('origin').run()

#调用类方法
Person.play()
per.play()

#调用静态方法
Person.modify()
per.modify()

类属性

  • 成员属性描述的是对象的静态特征,比如说狗名字、品种等,其实就是一个变量,作用域属于类,不会和类外的全局变量冲突。python中成员属性可以动态添加,也可以在构造函数中添加。可以通过类调用,也可以通过对象调用,当类的对象的属性值需要相同时可以使用类属性。
class Person():
    #类属性
    #可以通过类调用,也可以通过对象调用
    #当类的对象的属性值需要相同时可以使用类属性
    name = "jint"
    age  = 28
    sex  = 1

#在初始化对象时,每一个对象的属性值相同
per1 = Person()
per2 = Person()

#通过对象访问属性   对象名.类属性名
print(per1.name)
print(per2.name)

per2.name = "wuyg" #相当于给per2对象添加了一个对象属性
print(per2.name) #获取属性值时首先获取对象属性,如果没有则获取类属性
print(per1.name)

对象的初始状态

  • 需求:在实例化对象时给每个对象直接赋值属性的值
class Person():
    #这里是类属性的定义
    city = "北京"

    # 在内存中开辟空间,并返回空间的首地址
    # 以后可以用于定义单例类,否则基本不会使用
    # 是一个类方法啊,返回一个对象,在使用类实例化对象时自动调用,目的在堆区开辟一片内存空间并返回该空间的首地址,在__init__之前调用
    def __new__(cls, *args, **kwargs):
        return super().__new__(cls)

    #构造方法:在使用类创建实例对象时自动调用,目的是初始化对象的内容
    def __init__(self, name, age, sex):
        # 定义对象属性
        # 只能对象调用
        self.name = name
        self.age = age
        self.sex = sex

# 创建对象时给每个对象的属性进行赋值
per1 = Person("jint", 30, 1)
per2 = Person("wuyg", 28, 1)

print(per1.name)
print(per2.name)

# print(Person.name) #类中没有name类属性,且类无法调用对象属性

析构函数

  • 析构方法:在释放对象时自动调用
  • 作用:释放一些不必要的内存
class Person():
    def __init__(self, name):
        self.name = name
	#析构
    def __del__(self):
        #释放内存
        print("object delete")


per = Person("jint")
del per #删除对象
print(per)	#NameError: name 'per' is not defined
print(per.name)

私有属性和方法

  • 如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线
    • python中实例的变量以__开头,就变成了一个私有属性(private),只能在类的内部访问,无法在外部直接访问
    • 不能在外部直接访问__money的原因是python解释器对外把__money属性改成了_Person__money。所以仍然可以用_Person__money来直接访问,但是强烈建议不要这么来做,因为不同版本的python解释器可能会把__money改成不同的名字
  • 属性前加一个下划线的属性,这样的属性约定俗成的表示请把我看成私有属性来使用(虽然可以在外部直接访问),不要在外部直接访问
  • 在python中,变量名类似__xxx__的,属于特殊变量,特殊变量可以直接访问,不是私有属性
class Person():
    def __init__(self, name, age, sex, money, faceValue, word):
        self.name = name
        self.age = age
        self.sex = sex
        self.__money = money
        self._faceValue = faceValue
        self.__word__ = word


    #定义公有方法间接访问私有属性
    def getMoney(self):
        return self.__money
    def setMoney(self, money):
        if money < 0:
            self.__money = 0
        else:
            self.__money = money

    def getFaceValue(self):
        return self._faceValue
    def setFaceValue(self, faceValue):
        self._faceValue = faceValue

    def eat(self):
        print("下面我要执行run和play方法")
        self.__run()
        self._play()
    #私有方法,只能在类的内部使用
    def __run(self):
        print("-------------run")
    def _play(self):
        print("-------------play")

    #特殊方法,可以在外部直接访问,不是私有方法
    def __talk__(self):
        print("----------talk")

per = Person("liudh", 58, 1, 1000, 99, "good")

#在类的外部访问对象属性
print(per.name)	#liudh

# print(per.__money) #无法在类的外部直接访问私有属性
print(per.getMoney())	#1000	 # 通过调用公有方法间接访问私有属性(获取)
per.setMoney(100)
print(per.getMoney())	#100
# print(per._Person__money)  #以后不要使用

# print(per._faceValue) #不要如此使用
print(per.getFaceValue())	#99
per.setFaceValue(98)
print(per.getFaceValue())	#98

# per.__run()
per.eat()	
'''
下面我要执行run和play方法
-------------run
-------------play
'''

print(per.__word__)	#good
per.__talk__()	#----------talk

@property访问私有属性

class Person():
    def __init__(self, name, age, sex, money):
        self.name = name
        self.age = age
        self.sex = sex
        self.__money = money

    #取值,当 对象.money 时相当于访问的该方法
    @property
    def money(self):
        return self.__money
    #赋值,当 对象.money=值 时相当于调用该方法,值当做参数传递进来
    @money.setter
    def money(self, value):
        self.__money = value

per = Person("liudh", 58, 1, 1000)

#想让访问私有属性的方式类似访问普通属性,需要使用@property让私有属性可以使用点语法
print(per.money)	#1000
per.money = 1
print(per.money)	#1

动态给实例添加属性和方法

  • 想限制实例的属性,不让对象随意添加属性,只能添加我们规定一些属性
  • 添加属性
class Person():
    #解决:在定义类时,定义一个特殊属性__slots__,限制该类实例能添加的属性
    __slots__ = ("name", "age", "money", "hobby", "height", "weight", "run")
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def say(self):
        return "my name is %s.I am %d years old"%(self.name, self.age)


per1 = Person("lilei", 18)
per2 = Person("hanmm", 17)

#实例化一个对象后,可以给对象绑定任意的属性和方法
# 如果属性不存在,则变为增加属性
per1.money = 100
print(per1.money)
# print(per2.money) #per2没有增加money属性

#可以增加money、hobby、height、weight属性,但是不能增加faceValue属性
per1.faceValue = 100      #AttributeError: 'Person' object has no attribute 'faceValue'

  • 添加方法
#增加方法
#1、增加的既不是对象方法也不是类方法,优点类似静态方法
def run():
     print("*******run")
 per1.run = run
 per1.run()

# 2、增加的确实是对象方法,但是需要手动给形参self传递参数
 def run(self):
    print("下面要执行say方法")
    print(self.say())
per1.run = run
per1.run(per1)

#3、使用该方式给对象增加对象方法,其余对象无法使用
def run(self):
    print("下面要执行say方法")
    print(self.say())
from types import MethodType
per1.run = MethodType(run,per1)
per1.run()




#4、添加类方法,添加的方法对于所有对象都生效
def eat(cls):
    print("----eat")
from types import MethodType
Person.eat = MethodType(eat, Person)
Person.eat()
per1.eat()
per2.eat()

单例

  • 单例:是一种软件设计模式,该模式的主要的目的是确保一个类只能实例化一个对象
  • 单例类:在一个程序中只能实例化一个对象的类称为单例类
  • 设计模式:前人总结的使用方案,我们现在直接使用,大约24种
  • 实现单例类的方式
    • 模块
    • __new __
    • 装饰器实现
    • 元类
# __new__实现单例
class BankCard():
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, "instance"):
            # 调用父类中的__new__得到混沌对象
            cls.instance = super().__new__(cls)
        return cls.instance

    def __init__(self):
        self.cardId = "888888"


#装饰器实现
def single(cls):
    instance = {}
    def inner(*args, **kwargs):
        if cls not in instance:
            instance[cls] = cls(*args, **kwargs)
        return instance[cls]
    return inner


@single #BankCard = single(BankCard)
class BankCard():
    def __init__(self):
        self.cardId = "888888"

@single
class Person():
    pass

c1 = BankCard()
c2 = BankCard()
print(c1 is c2)

p1 = Person()
p2 = Person()
print(p1 is p2)
     
#模块实现单例
#类模块
class BankCard():
    def __init__(self, cardId, passwd, money):
        self.cardId = cardId
        self.passwd = passwd
        self.money = money

card = BankCard("888888", "111111", 10000)
#主模块
from bankCard import card

if __name__ == "__main__":
    print(card.cardId)

重写__reper__和__str__方法

class Person():
    #重写:将继承的方法重写一遍,在原有的功能基础上添加一些新的功能
    def __init__(self, name, age):
        self.name = name
        self.age = age

    # 在用户调用print打印对象时调用
    # 显示给用户的
    def __str__(self):
        return "1:%s-%d"%(self.name, self.age)

    # 是给机器用的,在python解释器里直接敲对象后回车自动调用
    def __repr__(self):
        return "2:%s-%d" % (self.name, self.age)


per = Person("liudh", 58)
# print(per.name, per.age)
print(per)


继承

  • object类是所有类的直接或间接父类,也可以称为基类或超类
  • 继承者称为子类,被继承者称为父类
  • 继承:有两个类,A类和B类,当A继承自B时,那么A类就有了B类所有的属性和方法,A类还可以有B类没有的属性和方法,还可以重写B类的方法。
  • 继承的作用:
    • 1、简化代码,减少冗余
    • 2、提高了代码的健壮性
    • 3、提高了代码的安全性
    • 4、是多态的前提
  • 缺点:
    耦合与内聚是藐视类与类之间的关系,耦合度越低,内聚性越高,代码越好。

单继承的实现

  • 方法的使用原理:当对象在调用方法时,首先在本类查找同名方法,找到了就用,找不到去直接父类中查找,父类中有就用,没有的话再去上一级父类中直到找到object中截止,在哪里找到就用哪个方法。如果都没找到那么报错说明没有该方法
#抽象类
class Person(object):
    def __init__(self, name, age, money):
        self.name = name
        self.age = age
        #私有属性
        self.__money = money

    @property
    def money(self):
        return self.__money
    @money.setter
    def money(self, value):
        self.__money = value

    def say(self):
        print("my name is %s.I am %d years old!"%(self.name, self.age))

#学生类
from person import Person

class Student(Person):
    def __init__(self, name, age, money, stuId):
        #代表父类的一个对象,调用父对象中的__init__()方法
        super().__init__(name, age, money)
        #当前子类独有的属性
        self.stuId = stuId

    #重写say()函数
    def say(self):
        #执行父类中的say()
        super().say()
        print("my stuId is %d"%self.stuId)

#工人类
from person import Person

class Worker(Person):
    def __init__(self, name, age, money, gl):
        super().__init__(name, age, money)
        #当前子类独有的属性
        self.gl = gl

# 主模块
from person import Person
from student import Student
from worker import Worker

stu = Student("jint", 30, 1,111)
stu.say()
'''
my name is jint.I am 30 years old!
my stuId is 111
'''
print(stu.money) 	#1	#私有属性被继承

wor = Worker("wujg", 28, 2, 8)
wor.say()	#my name is wujg.I am 28 years old!

多继承的实现

#加法类
class Add(object):
    def __init__(self):
        self.a = "我是add"
        self.c = "我是add"
    def sum(self, x, y):
        return x + y
    def say(self):
        print("我是add")


# 减法类
class Sub(object):
    def __init__(self):
        self.b = "我是sub"
        self.c = "我是sub"
    def sub(self, x, y):
        return x - y
    def say(self):
        print("我是sub")

#计算器类(多继承)
from add import Add
from sub import Sub

#多继承:在小括号中依次写入父类
class Calculator(Add, Sub):
    def __init__(self):
        #多继承调用每一个父类的__init__方法
        Add.__init__(self)
        Sub.__init__(self)

#主函数
if __name__ == "__main__":
    cal = Calculator()
    print(cal.sum(1, 2))	#3
    print(cal.sub(6, 2))	#4
    print(cal.a)	#我是add
    print(cal.b)	#我是sub


    #父类中的属性名形同,默认使用__init__方法中排名靠后的父类中的属性
    print(cal.c)	#我是sub
    #父类中的方法名相同,默认调用的是在括号中排名靠前的父类中的方法
    cal.say()	#我是add


mixin

  • 概述:
    Mixin编程是一种开发模式,是一种将多个类中的功能单元的进行组合的利用的方式,这听起来就像是有类的继承机制就可以实现,然而这与传统的类继承有所不同。通常mixin并不作为任何类的基类,也不关心与什么类一起使用,而是在运行时动态的同其他零散的类一起组合使用。
  • 优点:
    1、可以在不修改任何源代码的情况下,对已有类进行扩展
    2、可以保证组件的划分
    3、可以根据需要,使用已有的功能进行组合来实现新的类
    4、很好的避免了类继承的局限性,因为新的业务需求可能就需要创建新的子类
#定义mixin类,一般省略object
class FixMixin:
    def fixPhone(self):
        print("修手机")
    def fixComputer(self):
        print("修电脑")
    def say(self):
        print("修手机、电脑……")

class Person(object):
    def __init__(self, name):
        self.name = name
    def say(self):
        print("my name is %s"%self.name)

class Student(Person):
    def __init__(self, name, stuId):
        super().__init__(name)
        self.stuId = stuId




#给pyClass动态添加父类pyMixinClass
def mixin(pyClass, pyMixinClass, flag=0):
    if flag:
        pyClass.__bases__ += (pyMixinClass,)
    elif pyMixinClass not in pyClass.__bases__:
        pyClass.__bases__ += (pyMixinClass,)

#将FixMixin类动态设置为Student的父类
mixin(Student, FixMixin, 1)

stu = Student("lilei", 123456)
# stu.say()
# __bases__属性是元组类型,里面每个元素是该类的直接父类
print(Student.__bases__)	#(<class '__main__.Person'>, <class '__main__.FixMixin'>)
stu.fixPhone()	#修手机
# __mro__ 表示该类的直接和间接父类
print(Student.__mro__)	#(<class '__main__.Student'>, <class '__main__.Person'>, <class '__main__.FixMixin'>, <class 'object'>)

多继承的mro问题

  • 概念:方法解析顺序,是python用于处理二义性问题的算法
  • 二义性
    • 问题1:有两个基类A和B,A和B都定义了f()方法,C继承A和B,那么调用C的f()放方法会出现不确定性
    • 问题2:有一个基类A,定义了方法f(),B和C类都继承自A类,D类继承自B和C,此时出现一个问题,D类不知道该继承B的f()还是C的f()
  • C++解决二义性
    问题1:通过同名覆盖的方法解决的
    问题2::通过虚继承来解决
  • python解决二义性
    通过C3算法避免二义性的情况
  • 经历的过程:
    1、python2.2以前的版本(经典类时代)
    2、python2.2版本(新式类诞生)
    3、python2.3到python2.7
    4、python3至今(新式类一统江山)
    深度优先遍历

广度优先遍历

  • mro的BFS顺序
  • BFS:宽度优先搜索算法(又称广度优先搜索)是最简便的图的搜索算法之一,这一算法也是很多重要的图的算法的原型。Dijkstra单源最短路径算法和Prim最小生成树算法都采用了和宽度优先搜索类似的思想。其别名又叫BFS,属于一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,以找寻结果。换句话说,它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止。
    在这里插入图片描述
  • mro的方法为深度优先算法(DFS)
    1、把根阶压入栈中
    2、每次从栈中弹出一个元素,搜索所有它下一级的元素,把这些元素压入栈中,并把这个元素记为下一级元素的前驱
    3、找到所有的元素时结束程序
    4、如果遍历整个树还没有找到,程序结束
- 深度优先搜索属于图算法的一种,英文缩写为DFS即Depth First Search.其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次.
![深度优先算法](https://img-blog.csdnimg.cn/20190722001229936.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3B5dGhvbmNzZG4xMTE=,size_16,color_FFFFFF,t_70)
- mro的C3算法
- C3算法:判断mro要先确定一个线性序列,然后查找路径由由序列中类的顺序决定。所以C3算法就是生成一个线性序列。
如果继承至一个基类:
class B(A)
这时B的mro序列为[B,A]
如果继承至多个基类
class B(A1,A2,A3 ...)
这时B的mro序列 mro(B) = [B] + merge(mro(A1), mro(A2), mro(A3) ..., [A1,A2,A3])
merge操作就是C3算法的核心。
遍历执行merge操作的序列,如果一个序列的第一个元素,是其他序列中的第一个元素,或不在其他序列出现,则从所有执行merge操作序列中删除这个元素,合并到当前的mro中。
merge操作后的序列,继续执行merge操作,直到merge操作的序列为空。
如果merge操作的序列无法为空,则说明不合法。
- 例子
例子:
class A(O):pass
class B(O):pass
class C(O):pass
class E(A,B):pass
class F(B,C):pass
class G(E,F):pass

A、B、C都继承至一个基类,所以mro序列依次为[A,O]、[B,O]、[C,O]
mro(E) = [E] + merge(mro(A), mro(B), [A,B])
       = [E] + merge([A,O], [B,O], [A,B])
执行merge操作的序列为[A,O]、[B,O]、[A,B]
A是序列[A,O]中的第一个元素,在序列[B,O]中不出现,在序列[A,B]中也是第一个元素,所以从执行merge操作的序列([A,O]、[B,O]、[A,B])中删除A,合并到当前mro,[E]中。
mro(E) = [E,A] + merge([O], [B,O], [B])
再执行merge操作,O是序列[O]中的第一个元素,但O在序列[B,O]中出现并且不是其中第一个元素。继续查看[B,O]的第一个元素B,B满足条件,所以从执行merge操作的序列中删除B,合并到[E, A]中。
mro(E) = [E,A,B] + merge([O], [O])
       = [E,A,B,O]
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190722010038758.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3B5dGhvbmNzZG4xMTE=,size_16,color_FFFFFF,t_70)
- C3算法
```python
#-*- encoding:GBK -*-#  
def mro_C3(*cls):  
        if len(cls)==1:  
            if not cls[0].__bases__:  
                return  cls  
            else:  
                return cls+ mro_C3(*cls[0].__bases__)  
        else:  
            seqs = [list(mro_C3(C)) for C in cls ] +[list(cls)]  
            res = []  
            while True:  
              non_empty = list(filter(None, seqs))  
              if not non_empty:  
                  return tuple(res)  
              for seq in non_empty:  
                  candidate = seq[0]  
                  not_head = [s for s in non_empty if candidate in s[1:]]  
                  if not_head:  
                      candidate = None  
                  else:  
                      break  
              if not candidate:  
                  raise TypeError("inconsistent hierarchy, no C3 MRO is possible")  
              res.append(candidate)  
              for seq in non_empty:  
                  if seq[0] == candidate:  
                      del seq[0]

C3算法继承
继承顺序图

常用魔术方法


魔术方法就是一个类的特殊方法,和普通方法唯一的不同时,普通方法需要调用!而魔术方法由系统自动调用。

1.__init__
初始化魔术方法
触发时机:初始化对象时触发(不是实例化触发,但是和实例化在一个操作中)
参数:至少有一个self,接收对象
返回值:无
作用:初始化对象的成员
2.__new__
实例化魔术方法  类方法
触发时机: 在实例化对象时触发
参数:至少一个cls 接收当前类
返回值:必须返回一个对象实例
作用:实例化对象
注意:实例化对象是Object类底层实现,其他类继承了Object的__new__才能够实现实例化对象。
没事别碰这个魔术方法,先触发__new__才会触发__init__ 
3.__del__
析构魔术方法
触发时机:当对象没有用(没有任何变量引用)的时候被触发
参数:一个self 
返回值:无
作用:在销毁对象时回收资源
注意:del不一定会触发当前方法,只有当前对象没有任何变量引用时才会触发
4.__call__
调用对象的魔术方法
触发时机:将对象当作函数调用时触发,方式: 对象()
参数:至少一个self接收对象,其余根据调用时参数决定
返回值:根据情况而定
作用:可以将复杂的步骤进行合并操作,减少调用的步骤,方便使用
注意:无
5.__len__
触发时机:使用len(对象) 的时候触发
参数:一个参数self
返回值:必须是一个整型
作用:可以设置为检测对象成员个数,但是也可以进行其他任意操作
注意:返回值必须必须是整数,否则语法报错,另外该要求是格式要求。
6.__str__
触发时机:使用print(对象)或者str(对象)的时候触发
参数:一个self接收对象
返回值:必须是字符串类型
作用:print(对象时)进行操作,得到字符串,通常用于快捷操作
注意:无
7.__repr__
触发时机:在使用repr(对象)的时候触发
参数:一个self接收对象
返回值:必须是字符串
作用:将对象转使用repr化为字符串时使用,也可以用于快捷操作
8.__bool__
触发时机: 使用bool(对象)的时候触发
参数:一个self接收对象
返回值:必须是布尔值
作用:根据实际情况决定,可以作为快捷方式使用
注意:仅适合于返回布尔值的操作
9.__format__
触发时机:使用字符串.format(对象)时候触发
参数:一个self接收对象,一个参数接收format的{}中的格式,例如:>5
返回值:必须是字符串
作用:设置对象可以作为format的参数,并且自定义对象格式化的规则
注意:无

描述符相关的魔术方法


1.__get__
触发时机:在获取指定描述符操作的成员属性的值的时候触发
参数:1描述符对象本身,2描述符描述的属性所在的对象,描述符描述的对象的类
返回值:必须有,不然无法获取相应属性值
注意:仅在描述符中使用
2.__set__
触发时机:在设置或者添加指定描述符操作的成员属性的时候触发
参数:1描述符对象本身,2描述符描述的属性所在的对象,3要设置的值
返回值:无
注意:仅在描述符中使用
3.__delete__
触发时机:在删除指定描述符操作的成员属性的时候触发
参数:1描述符对象本身,2描述符描述的属性所在的对象
返回值:无
注意:仅在描述符中使用

与属性操作相关的魔术方法


1.__getattr__
触发时机:获取不存在的对象成员时触发
参数:一个是接收当前对象的self,一个是获取成员名称的字符串
返回值:必须有值
作用:为访问不存在的属性设置值
注意:getattribute无论何时都会在getattr之前触发,触发了getattribute就不会在触发getattr了
2.__setattr__
触发时机:设置对象成员值的时候触发
参数:1个当前对象的self,一个是要设置的成员名称字符串,一个是要设置的值
返回值:无 过程操作
作用:接管设置操作,可以在设置前之前进行判断验证等行为
注意:在当前方法中无法使用成员=值的方式直接设置成员,否则会无限递归,必须借助object的设置方法来完成

object.__setattr__(参数1,参数2,参数3)
3.__delattr__
触发时机:删除对象成员时触发
参数:一个当前对象的self
返回值:无
作用:可以在删除成员时进行验证。
4.__getattribute__
触发时机:使用对象成员时触发,无论成员是否存在
参数:1个接收当前对象self,一个是获取的成员的名称字符串
返回值:必须有
作用:在具有封装操作(私有化时),为程序开部分访问权限使用
5.__dir__
触发时机:dir(对象)的时候触发
参数:1个接收当前对象self
返回值:必须为序列类型(列表,元组,集合等,)
作用:可以自定义成员列表的返回值

运算相关魔术方法


比较运算相关魔术方法


1.__lt__
格式:
    def __lt__(self,other):
   	  return 数据
特征:
    触发时机:进行小于判断时自动触发
    参数:2个参数第一个是self,第二个判断的第二个对象
    返回值:返回值可以任意类型,推荐布尔值
    作用:定义小于号的行为:x < y 调用 x.lt(y)
2.__le__
格式:
    def __le__(self):
         return bool

特征:
    触发时机:进行小于等于判断时自动触发
    参数:2个参数第一个是self,第二个判断的第二个对象
    返回值:返回值可以任意类型,推荐布尔值
    作用:定义小于等于号的行为:x <= y 调用 x.le(y)
3.__gt__
格式:
    def __gt__(self):
       return bool

特征:
    触发时机:进行大于判断时自动触发
    参数:2个参数第一个是self,第二个判断的第二个对象
    返回值:返回值可以任意类型,推荐布尔值
    作用:定义大于号的行为:x > y 调用 x.gt(y)
4.__ge__
格式:
    def __ge__(self):
       return bool

特征:
    触发时机:进行大于等于判断时自动触发
    参数:2个参数第一个是self,第二个判断的第二个对象
    返回值:返回值可以任意类型,推荐布尔值
    作用:定义大于等于号的行为:x >= y 调用 x.ge(y)
5.__eq__
格式:
    def __eq__(self):
        return bool

特征:
    触发时机:进行等于判断时自动触发
    参数:2个参数第一个是self,第二个判断的第二个对象
    返回值:返回值可以任意类型,推荐布尔值
    作用:定义大于等于号的行为:x == y 调用 x.eq(y)
6.__ne__
格式:
    def __ne__(self):
        return bool

特征:
    触发时机:进行不等于判断时自动触发
    参数:2个参数第一个是self,第二个判断的第二个对象
    返回值:返回值可以任意类型,推荐布尔值
    作用:定义不等号的行为:x != y 调用 x.ne(y)
算术运算相关魔术方法

__add__(self, other)           定义加法的行为:+
__sub__(self, other)           定义减法的行为:-
__mul__(self, other)           定义乘法的行为:
__truediv__(self, other)       定义真除法的行为:/
__floordiv__(self, other)      定义整数除法的行为://
__mod__(self, other)           定义取模算法的行为:%
__divmod__(self, other)        定义当被 divmod() 调用时的行为
__pow__(self, other[, modulo]) 定义当被 power() 调用或 ** 运算时的行为
__lshift__(self, other)        定义按位左移位的行为:<<
__rshift__(self, other)        定义按位右移位的行为:>>
__and__(self, other)           定义按位与操作的行为:&
__xor__(self, other)           定义按位异或操作的行为:^
__or__(self, other)            定义按位或操作的行为:|
反运算相关魔术方法

__radd__(self, other)      与上方相同,当左操作数不支持相应的操作时被调用
__rsub__(self, other)      与上方相同,当左操作数不支持相应的操作时被调用
__rmul__(self, other)      与上方相同,当左操作数不支持相应的操作时被调用
__rtruediv__(self, other)  与上方相同,当左操作数不支持相应的操作时被调用
__rfloordiv__(self, other) 与上方相同,当左操作数不支持相应的操作时被调用
__rmod__(self, other)      与上方相同,当左操作数不支持相应的操作时被调用
__rdivmod__(self, other)   与上方相同,当左操作数不支持相应的操作时被调用
__rpow__(self, other)      与上方相同,当左操作数不支持相应的操作时被调用
__rlshift__(self, other)   与上方相同,当左操作数不支持相应的操作时被调用
__rrshift__(self, other)   与上方相同,当左操作数不支持相应的操作时被调用
__rand__(self, other)      与上方相同,当左操作数不支持相应的操作时被调用
__rxor__(self, other)      与上方相同,当左操作数不支持相应的操作时被调用
__ror__(self, other)       与上方相同,当左操作数不支持相应的操作时被调用
赋值运算相关魔术方法

__iadd__(self, other)             定义赋值加法的行为:+=
__isub__(self, other)             定义赋值减法的行为:-=
__imul__(self, other)             定义赋值乘法的行为:=
__itruediv__(self, other)         定义赋值真除法的行为:/=
__ifloordiv__(self, other)        定义赋值整数除法的行为://=
__imod__(self, other)             定义赋值取模算法的行为:%=
__ipow__(self, other[, modulo])   定义赋值幂运算的行为:**=
__ilshift__(self, other)          定义赋值按位左移位的行为:<<=
__irshift__(self, other)          定义赋值按位右移位的行为:>>=
__iand__(self, other)             定义赋值按位与操作的行为:&=
__ixor__(self, other)             定义赋值按位异或操作的行为:^=
__ior__(self, other)              定义赋值按位或操作的行为:|=
一元运算相关魔术方法

__pos__(self)      定义正号的行为:+x
__neg__(self)      定义负号的行为:-x
__abs__(self)      定义当被 abs() 调用时的行为
__invert__(self)   定义按位求反的行为:~x
类型转换相关魔术方法

__complex__(self)      定义当被 complex() 调用时的行为(需要返回恰当的值)
__int__(self)          定义当被 int() 调用时的行为(需要返回恰当的值)
__float__(self)        定义当被 float() 调用时的行为(需要返回恰当的值)
__round__(self[, n])   定义当被 round() 调用时的行为(需要返回恰当的值)
__index(self)__        1. 当对象是被应用在切片表达式中时,实现整形强制转换
                       2. 如果你定义了一个可能在切片时用到的定制的数值型,你应该定义 index
                       3. 如果 index 被定义,则 int 也需要被定义,且返回相同的值
上下文管理相关魔术方法(with)

__enter____exit__

__enter__(self)
    1. 定义当使用 with 语句时的初始化行为
    2. enter 的返回值被 with 语句的目标或者 as 后的名字绑定

__exit__(self, exctype, excvalue, traceback)
    1. 定义当一个代码块被执行或者终止后上下文管理器应该做什么
    2. 一般被用来处理异常,清除工作或者做一些代码块执行完毕之后的日常工作
容器类型相关魔术方法

__len__(self)                  定义当被 len() 调用时的行为(返回容器中元素的个数)
__getitem__(self, key)         定义获取容器中指定元素的行为,相当于 self[key]
__setitem__(self, key, value)  定义设置容器中指定元素的行为,相当于 self[key] = value
__delitem__(self, key)         定义删除容器中指定元素的行为,相当于 del self[key]
__iter__(self)                 定义当迭代容器中的元素的行为
__reversed__(self)             定义当被 reversed() 调用时的行为
__contains__(self, item)       定义当使用成员测试运算符(in 或 not in)时的行为

经典案例(人开枪射击子弹)

  • 分析有哪些类

    • 人类
      类名:Person
      属性:gun
      行为:fire() reloadBullent(count) reloadBullentBox()
    • 枪类
      类名:Gun
      属性:bullentBox
      行为:shoot()
    • 弹夹类
      类名:BullentBox
      属性:bullentList bullentCount
      行为:
    • 子弹类
      类名:Bullent
      属性:kouj
      行为:
  • 人类的创建

from bullent import Bullent


class Person(object):
    def __init__(self, gun=None, bullentBoxList=[]):
        self.gun = gun
        self.bullentBoxList = bullentBoxList

    #给一个弹夹装子弹
    def reloadBullent(self, bullentBox, count):
        for i in range(count):
            #创建子弹对象放入弹夹对象中的bullentList列表中
            bullent = Bullent(7.62)
            bullentBox.bullentList.append(bullent)
    #换弹夹
    def reloadBullentBox(self, bullentBox):
        self.gun.bullentBox = bullentBox

    #开火
    def fireGun(self):
        self.gun.shootBullent()
  • 枪类
class Gun(object):
    def __init__(self, bullentBox=None):
        self.bullentBox = bullentBox

    def shootBullent(self):
        if len(self.bullentBox.bullentList) == 0:
            print("弹夹中没有子弹,请更换弹夹!")
        else:
            self.bullentBox.bullentList.pop()
            print("射击完成,剩余子弹%d发"%(len(self.bullentBox.bullentList)))
  • 弹夹类
class BullentBox(object):
    def __init__(self, bullentCount):
        self.bullentList = []
        #容量
        self.bullentCount = bullentCount
  • 子弹类
class Bullent(object):
    def __init__(self, kouj):
        self.kouj = kouj
  • 主模块
from person import Person
from gun import Gun
from bullentBox import BullentBox
from bullent import Bullent

if __name__ == "__main__":
    #创建一个人
    per = Person()

    #创建一把枪,将枪交给人
    per.gun = Gun()


    #给人3个弹夹
    for i in range(3):
        #创建弹夹对象,给到人,弹夹此时没有子弹
        bullentBox = BullentBox(5)
        per.bullentBoxList.append(bullentBox)

    #给每个弹夹都先填满子弹
    for bb in per.bullentBoxList:
        per.reloadBullent(bb, 5)

    #给将上弹夹
    per.reloadBullentBox(per.bullentBoxList[0])

    #开火
    per.fireGun()
    per.fireGun()
    per.fireGun()
    per.fireGun()
    per.fireGun()
    #装子弹
    # per.reloadBullent(per.bullentBoxList[0], 5)
    #换弹夹
    # per.reloadBullentBox(per.bullentBoxList[1])
    per.fireGun()

运算符魔术方法

class Person(object):
    #运算符重载:该类型的对象执行算术运算重新赋予新的功能
    def __add__(self, other):
        return self.x + other.x
    def __sub__(self, other):
        return self.x - other.x
    def __mul__(self, other):
        return self.x * other.x
    def __truediv__(self, other):
        return self.x / other.x
    def __len__(self):
        return self.x
    def __init__(self, x):
        self.x = x
per1 = Person(5)
per2 = Person(2)
#加号前的调用__add__()方法,加号后的当参数传递到__add__()中
print(per1 + per2)	#7
print(per1 - per2)	#3
print(per1 * per2)	#10
print(per1 / per2)	#2.5
print(len(per1))	#5

属性监听(getattr__和__setter

class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def __str__(self):
        return "My name is %s.I am %d years old!"%(self.name, self.age)

    #使用点语法访问不存在的属性时被被自动调用
    #调用某些不存在的属性时不想报错
    def __getattr__(self, item):
        print("---------------2")
        if item in ["height", "weight"]:
            return "height"
        else:
            #返回一个异常
            raise AttributeError

    #当调用点语法设置值是被自动调用
    def __setattr__(self, key, value):
        if key == "age" and value < 0:
            self.__dict__[key] = 0
        else:
            self.__dict__[key] = value

    #使用点语法获取属性就被会自动调用
    #避免使用该方法
    # def __getattribute__(self, item):
    #     return super().__getattribute__(item)

per = Person("jet", 30)
per.age = -10
print(per.age)  #0
print(per.height)   #---------------2   height

# print(per.money)  #raise AttributeError

枚举类

'''
当我们需要定义大量常量时,用大写变量定义
JAN = 1
FEB = 2
MAR = 3
'''
'''概念
枚举是指一组固定常量组成合法值的类型
'''
'''解决方法
定义一个类,每个常量作为类的一个唯一属性
'''

from enum import Enum, unique

Month = Enum("Month", ("A", "B", "C", "D"))
print(Month, type(Month))   #<enum 'Month'> <class 'enum.EnumMeta'>
m = Month.__members__.items()
print(m, type(m))   #odict_items([('A', <Month.A: 1>), ('B', <Month.B: 2>), ('C', <Month.C: 3>), ('D', <Month.D: 4>)]) <class 'odict_items'>

for name, member in m:
    print(name, member, member.value)
    """输出
    A Month.A 1
    B Month.B 2
    C Month.C 3
    D Month.D 4
    """
print(Month.A.value)    #1

#自定义枚举类
#继承了Enum的类称为枚举类
#装饰器检查并保证没有重复的变量
@unique
class Month(Enum):
    A = 1
    B = 2
    C = 3
    D = 44
print(Month.A.value)    #1

类装饰器

把对象当成函数后面加一对括号执行时,默认调用__call__方法

#在函数之前打印“tom is a good man”
class AddPrint(object):
    #传入待加功能的函数
    def __init__(self, f):
        self.f = f
    def __call__(self, *args, **kwargs):
        print("tom is a good man")
        res = self.f(*args, **kwargs)
        return res


#执行函数时会被执行3次
class Count(object):
    #带参的装饰的参数在__init__这里传递
    def __init__(self, count=3):
        self.count = count
    #接收待加功能的函数
    def __call__(self, f):
        def inner(*args, **kwargs):
            for i in range(self.count):
                f(*args, **kwargs)
        return inner

@AddPrint
@Count(5)
def func(name, age):
    print("My name is %s.I am %d years old!"%(name, age))

func("tom", 18)
"""输出
tom is a good man
My name is tom.I am 18 years old!
My name is tom.I am 18 years old!
My name is tom.I am 18 years old!
My name is tom.I am 18 years old!
My name is tom.I am 18 years old!
"""


'''装饰器执行过程
@AddPrint
obj = AddPrint(func)
func = obj
func()  ===   obj()     ===  obj.__call__()


@Count(5)
obj = Count(5)
@obj
inner = obj(func)  ===  obj.__call__(func)
func = inner

func()  ===  inner()
'''

垃圾回收机制

  • 垃圾回收:
    1、找到内存中都无用的垃圾资源
    2、清除这些垃圾并把内存释放出来给其他的对象使用
  • 引用计数器法:
    • 概念:是python中默认采用的垃圾回收机制
    • 原理:每个对象维护一个ob_ref字段(属性),用来记录该对象被引用的次数,每当新的引用指向该对象时,它的ob_ref加1,当对象的引用失效时ob_ref减1,一旦对象的引用计数器为0,该对象立即被回收,对象所占用的内存空间被释放
    • 优点:简单、实时高效
    • 缺点:
      1、需要额外的空间维护引用计数器字段
      2、循环引用
  • 标记清除:标记清除(Mark—Sweep)算法是一种基于追踪回收(tracing GC)技术实现的垃圾回收算法。它分为两个阶段:第一阶段是标记阶段,GC会把所有的活动对象打上标记,第二阶段是把那些没有标记的对象非活动对象进行回收。

对象之间通过引用(指针)连在一起,构成一个有向图,对象构成这个有向图的节点,而引用关系构成这个有向图的边。从根对象(root object)出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象。根对象就是全局变量、调用栈、寄存器。
在这里插入图片描述
在上图中,可以从程序变量直接访问块1,并且可以间接访问块2和3。程序无法访问块4和5。第一步将标记块1,并记住块2和3以供稍后处理。第二步将标记块2,第三步将标记块3,但不记得块2,因为它已被标记。扫描阶段将忽略块1,2和3,因为它们已被标记,但会回收块4和5。

标记清除算法作为Python的辅助垃圾收集技术,主要处理的是一些容器对象,比如list、dict、tuple等,因为对于字符串、数值对象是不可能造成循环引用问题。Python使用一个双向链表将这些容器对象组织起来。不过,这种简单粗暴的标记清除算法也有明显的缺点:清除非活动的对象前它必须顺序扫描整个堆内存,哪怕只剩下小部分活动对象也要扫描所有对象。

  • 分代回收:
    分代回收是建立在标记清除技术基础之上的,是一种以空间换时间的操作方式。
    Python将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,Python将内存分为了3“代”,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代),他们对应的是3个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python垃圾收集机制就会被触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。

"""
>>> a=123
>>> sys.getrefcount(a)
3
>>> b=666
>>> sys.getrefcount(b)
2

这里实际上123这个对象并没有在内存中新建,因为在Python启动解释器的时候会创建一个小整数池,在-5~256之间的整数对象会被自动加载到内存中等待调用。因此a=123是对123这个整数对象增加了一次引用。而666是不在整数池里的,需要创建对象,那么最后的引用次数是2呢?因为sys.getrefcount(b)也是一次引用。

"""

li1 = [1,2]       # li1->ob_ref = 1
li2 = [3,4]       # li2->ob_ref = 1
#
li1.append(li2)   #li2->ob_ref = 2
li2.append(li1)   #li1->ob_ref = 2
print(li1)  #[1, 2, [3, 4, [...]]] 发生了循环引用
#
# del li1  # li1->ob_ref = 1
# del li2  # li2->ob_ref = 1

class Person():
    pass
per = Person()
per1 = per
del per  #删除引用,内存不释放,per1还引用per
print(per1)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值