Python面向对象之小白学习笔记(最新最全最实用)

面向对象之小白学习笔记


   最近一段时间,恶补Python之面向对象知识,同时也整理了很多的笔记,与大家分享,如果大家学习过程中有什么问题,可以评论留言,我们一起探讨。

面向对象

基本理论

什么是对象

万物皆对象
对象是具体物体:
  1. 拥有属性
  2. 拥有行为
  3. 把很多零散的东西,封装成为一个整体

举例:
王二小:

  1. 属性(姓名、年龄、身高、体重。。)
  2. 行为(走路、吃饭。。。)
Python是一门特别彻底的面向对象编程(OOP)的语言:
  1. 其他语言–基本类型(int、float、bool) 对象类型(string、array)
  2. python–对象类型:int、float、bool、list…

面向过程&面向对象

1.都是一种解决问题的思路(思想)

2.面向过程–再解决问题的时候,关注的是解决问题的每一个的过程(步骤)

3.面向对象–再解决问题的时候,关注的是解决问题所需要的对象

例子:做好饭后洗碗:过程(职工):洗菜-点火倒油-…-洗碗-…

​ 对象(BOSS):1.做饭 2.洗碗

对比

面向对象和面向过程都是解决问题的一种方式(思想)–面向对象本身是对象向过程的封装

面向过程编程最重要的是:按照步骤划分,把一个任务,分解成具体的每一个步骤。

面向对象编程最重要的是:按照对象功能进行划分,找到对象,确定对象属性和行为

如何从面向过程编程的思想,过度到面向对象编程?

  1. 列举出一个任务的具体实现步骤

  2. 试图分离这些实现步骤中的功能代码块

  3. 将这些功能代码块,划分到某一个对象中

  4. 根据这个对象以及对应的行为,抽象出对应的类–设计类

类的概念

某一个具体对象特征的抽象

例如:张三:属性(年龄:18,身高:180,体重:250)行为(吃喝嫖赌)

抽象出来的类:不良青年:属性()行为()

类的作用

根据抽象的类,生产具体的对象

类–统一模板–不良青年

属性–静态

行为–动态

类的组成

名称—属性—方法

以上属性和方法,都是抽象的概念

在产生对象之后,对象才拥有具体的属性值和方法实现

对象和类的关系

对象–抽象–类--实例化–对象

面向对象在python中的实践

如何定义一个类

class 类名:   # ---类名---首字母大写
​	pass

例子:

class Money:
​	pass

怎么通过类,创建出一个对象

类

类属性(黄色)----对象属性(椭圆)

属性相关

属性和变量的区别及判定依据

区别:

1.概念:

变量是“可以改变的量值”,属性是“属于某个对象的特性”。

2.访问权限:

变量:根据不同的位置,存在不同的访问权限(全局变量,局部变量)

属性:只能通过对象来进行访问(必须先找到对象;对象也是通过变量名来引用,而既然是变量,也有对应的访问权限)

判定依据:

是否存在宿主

对象属性

增加一个对象属性

1.直接通过对象,动态添加:

语法:对象.属性 = 值

2.通过类的初始化方法(构造方法)–——__init__方法

# 1.定义一个类
class Person:
	pass
	
# 2. 根据类,创建一个对象
p = Person()

# 3. 给p对象增加一些属性
p.age = 18
p.height = 180
# 4. 验证是否有添加成功
print(p.age)
print(p.__dict__)  # 当前对象李所有属性

对象中增加属性

访问一个对象的属性
class Person:
	age = 18
p = Person() # 创建实例对象
p.age # 表示访问类属性
p.__class__  # 表示访问实例对象的类
p.__dict___ # 表示访问实例对象中的类属性

id( ) 可以访问一个值的存储位置

更改一个对象的属性

p.age = 20

与新增相同,存在称为修改,不存在称为新增。

p.age.append()访问操作

删除一个对象的属性

del p.age

补充:查看对象的所有属性

obj.__dict__

p1.age() = 18

p2.address() = '上海'

print(p2.age)----错误

类属性

万物皆对象,类也是对象,特殊的对象

增加一个类拥用属性

方式1:

类名.类属性 = 值

class Money:
    pass
one = Money()
one.age = 10 # 增加
one.age = 18 # 修改

Money.count = 1 # 类增加属性

print(Money.count)

增加

class Money两个作用: 1. 创建类名空间, 2. 变量名称

Money

方式2(常用)

内部创建类属性

class Dog: # 
​	age = 2
class Money:
    count = 1
    age = 18
print(Money.count)
print(Money.age)
查询一个类的属性
  1. 通过类访问–类名.类属性
  2. 通过对象访问–对象.类属性(前提:自身没有对应的属性)

查找

class Money:
    count = 1
    age = 18
    num = 666

class Test:
    sex = "男"

one.sex = '女'
one = Money()
one.__class__ = Test  # 加上这步访问出问题。
print(one.__class__)

print(one.age)
print(one.count)
print(one.num)
print(one.sex) # 可以访问

注意:为什么可以通过对象访问到类属性?

答:和python对象的属性查找机制有关

一个对象优先到对象自身去查找属性,找到结束

如果没有找到,则根据__class__找到对象对应的类,到这个类里面去找

访问顺序

证明:一个对象的访问顺序为先访问自己的属性,找不到相应的属性再访问类属性。

修改一个类的属性
  1. 通过类名 (Money.age = 18)

  2. 通过对象修改(one.age = 777对象截断方法,直接输出对象对应的属性,而识别不到类属性)

删除一个类的属性
  1. 通过类名删除 del.类名.属性
  2. 能否通过对象删除??—不能!!!del语句只能删除直系属性

注意点

类属性的内存存储问题

对象访问过程中,是通过类属性中自带的__dict__属性形成字典。同时也可以通过one.__dict__ = {name: 'sz', .... }进行创建对象属性,通过one.__dict__["age"] = 20也可以修改对象。

类中的属性也是存储在__dict__属性中,但是只能读不能修改,即Money.__dict__ = {sex : "男"}错误!!!。

对象中的dict

类属性dict

  1. 一般情况下,属性存储在__dict__的字典当中–有些内置对象没有这个__dict__属性

  2. 一般对象可以直接修改__dict__属性

  3. 但类对象__dict__为只读;默认无法修改,可以通过__setattr__方法修改

类属性被各个对象共享

  可以通过类直接访问类属性,也可以通过对象访问类属性(前提,对象中没有对应的属性)

补充

查看一个类的所有属性

类名.__dict__

对象.__dict__查看对象的对应的类名称

限制对象属性的添加

  为什么要限制?因为从一个类中定义的不同对象可能产生不同属性的对象,形成杂乱无章的数据。因此,为了产生这种限制对象属性的不规则添加,增加了类属性中限制类的操作。

class Person:
	__slots__ = ["类属性定义1","类属性定义2",·······]
    # 注意,这样会把类属性全部给清空,然后限制里面的新的属性,例如,__dict__已经不存在。

  当一个类需要创建大量的实例(对象)时,可以通过__dict__声明实例所需要的属性。

优点:

  1. 更快的属性访问速度
  2. 减少内存的消耗

  注意:默认情况下,创建一个类 ,会自动的创建一个字典__dict__来存储实例的属性。但如果定义了__slots__,类就不会再创建这个字典了。由于不存在__dict__来存储新的属性,所以使用一个不在__solts__中的属性时,程序会报错。


  • 更快的访问速度

  默认情况下,访问一个实例的属性是通过访问该实例的__dict__来实现的,如访问a.x就相当于访问a.__dict__['x']

__solts__方法中A.x就是一个有__get____set__方法的member_descriptor(描述器),并且在每个实例中可以通过直接访问内存(direct memory access)获得。具体实现是用偏移地址来记录描述器,通过公式可以直接计算出其在内存中的实际地址,访问__dict__也是用相同的方法。但字典访问属性多了一步a.x转化成a.__dict__['x']的过程(一个哈希函数的消耗)。

from timeit import repeat

class A(object): pass

class B(object): __slots__ = ('x')

def get_set_del_fn(obj):
	def get_set_del():
		obj.x = 1
		obj.x
		del obj.x
	return get_set_del

a = A()
b = B()
ta = min(repeat(get_set_del_fn(a)))
tb = min(repeat(get_set_del_fn(b)))
print("%.2f%%" % ((ta/tb - 1)*100))
  • 减少内存的消耗

  Python内置的字典本质是一个哈希表,它是一种用空间换时间的数据结构。为了解决冲突的问题,当字典使用量超过2/3时,Python会根据情况进行2-4倍的扩容。由此可预见,取消__dict__的使用可以大幅减少实例的空间消耗。

  下面用pympler模块测试在不同属性数目下,使用__slots__前后单个实例占用内存大小:

from string import ascii_letters
from pympler.asizeof import asizesof

def slots_memory(num=0):
    attrs = list(ascii_letters[:num])
    class Unslotted(object): pass
    class Slotted(object): __slots__ = attrs
    unslotted = Unslotted()
    slotted = Slotter()
    for attr in attrs:
        unslotted.__dict__[attr] = 0
        exec('slotted.%s = 0' % attr, globals(), locals())
    memory_use = asizesof(slotted, unslotted, unslotted.__dict__)
    return memory_use

def slots_test(nums):
    return [slots_memory(num) for num in nums]

测试结果(单位:字节)

属性数量slottedunslotted(__dict__)
080334(280)
1152408(344)
2168448(384)
82641456(1392)
163921776(1712)
255364440(4376)****

  从上述结果可看到使用__slots__能极大地减少内存空间的消耗,这也是最常见到的用法。

方法相关

方法的概念
  1. 描述一个目标的行为动作–比如描述一个人怎么吃、怎么喝、怎么玩。
  2. 和函数非常相似–都封装了一系列行为动作;都可以被调用之后,执行一系列行为动作;最主要的区别就是:调用方式
方法的划分

重新命名:类=====>>(实例化)====>> 对象(实例)

​ 类属性---------实例属性

​ 类 + 实例 ==== 对象

在这里插入图片描述
实例方法

默认第一个参数需要接受到一个实例

class类中def函数必须要(self)吗?

class Persondef eat(self): # 实例方法self是形参名称
        print("这是一个实例方法", self)
 
p = Person()
p.eat()

运行结果:

这是一个实例方法 <__main__.Person object at 0x000002B5C25C2EB0>
类方法

默认第一个参数需要接受到一个类

@classmethod(类方法一种使用)

class Person:
    @classmethod
    def leifangfa(cls, a): # 类方法, cls
        print("这是一个类方法", cls, a)

# 调用方法1:        
Person.leifangfa(123) # cls自动传递(Person), 只需要输入第二个参数即可
# 调用方法2:
p = Person()
p.leifangfa(666)
# 调用方法3:
func = Person.leifangfa
func(111)
# 装饰器的作用:在保证原本函数不改变的前提下,直接给这个函数增加一些功能

运行结果:

这是一个类方法方法 <class '__main__.Person'>
静态方法

静静的看着前面两个SB,第一参数啥也步默认接受

class Person:
    @staticmethod # 装饰器
    def jingtaifangfa(): # 静态方法,()内无
        print("这是一个静态方法")

运行结果:

这是一个静态方法
三种方法注意:
  1. 划分的依据是:方法的第一个参数必须要接受的数据类型
  2. 不管是哪一种类型的方法,都是存储在类当中;没有在实例当中的(因为都在class下创建的,而不是单独外面创建(实例))
  3. 不同类型方法的调用方法不同–把握一个原则:保证不同类型的方法第一个参数接收到的数据是它们想要的类型
使用
  1. 语法
  2. 不同类型的方法的规则
  3. 不同类型方法的调用
  4. 根据不同的问题,自己决定,到底该设计怎样的方法来解决问题
实例方法
class Persondef eat(self): # 实例方法self是形参名称
        pass

读懂self

标准调用(重点)
class Person:
    def eat(self, food): # self只是形参, 可以是任意名称,但默认指定使用self
        print("在吃饭", self, food)
       
p = Person()
p.eat("土豆") # 只需传递第二个参数,self解释器自动调用
  1. 使用实例调用实例方法,不用手动传,解释器会自动把调用对象本身传递过程。
  2. 注意:如果实例方法没有接受任何参数(self),则会报错,解释器自动传参数,但调用实例方法没有参数产生矛盾。
其他调用
  1. 使用类调用

    class Person:
        def eat(self, food):
            print("在吃饭", self, food)
            
    Person.eat(123, "abc") # 函数!!需要传递self, food
    
  2. 间接调用

    class Person:
        def eat(self, food):
            print("在吃饭", self, food)
    
    func = Person.eat
    func('abc', 999) # 函数!!需要传递self,  food
    

  本质:直接找到函数本身来调用

类方法
class Person:
    def run(self): # self接受一个实例

    @classmethod
    def leifangfa(cls): # 类方法, cls
        print("这是一个类方法", cls)
          
# 调用方法1:        
Person.leifangfa(123) # cls自动传递(Person), 只需要输入第二个参数即可
# 调用方法2:
p = Person()
p.leifangfa(666)
# 调用方法3:
func = Person.leifangfa
func(111)
# 装饰器的作用:在保证原本函数不改变的前提下,直接给这个函数增加一些功能
静态方法

使用场景,实现过程中既不使用实例,也不使用类,选择静态方法最为方便。

class Person:
    @staticmethod
    def jingtai():
        print("这是一个静态方法:")

# 调用方法1
Person.jingtai()
# 调用方法2
p = Person()
p.jingtai()
# 调用方法3
func = Person.jingtai()
func()
类、实例和静态方法的访问权限问题
名称访问权限
类本身的内容
实例类的内容+实例本身的内容
静态方法类的内容

  因此,实例的访问权限最大,类和静态方法的访问权限局限于类本身内容中。

class Person:
    age = 0
    def shilifangfa(self):
        print(self)
        print(self.age)
        print(self.num)

    @classmethod
    def leifangfa(cls):
        print(cls)
        print(cls.age)
        # print(cls.num)

    @staticmethod
    def jingtaifangfa():
        print(Person.age)
p = Person()
p.num = 10
# p.shilifangfa() # 访问出错,类访问不到实例属性
# Person.leifangfa() # 访问出错,类访问不到实例属性
Person.jingtaifangfa()
补充
类相关补充
元类

概念:创建类对象的类

对象怎样产生的?答:由类创建的

类是不是对象?答:是

所有类对象是不是由另一个类创建出来的?答:是,元类(创造类者)

class Person:
    pass

print(int.__class__)
print(str.__class__)
print(Person.__class__)

输出结果:

<class 'type'>
<class 'type'>
<class 'type'>
结构

在这里插入图片描述

从上图看可以,实例可以通过__class__属性找到相应的类,最终找到元类(type),同时,类也可以实例化产生实例。一个类可以有多个实例,但每个实例只能有一个类对象。


类对象的创建方式以及创建流程

# 方式1
class Person:
    count = 0
    def run(self):
        pass
    
# 方式2
def run(self):
    print("----", self)

xxx = type("Dog", (), {"count": 0, "run": run})
print(xxx)

print(xxx.__dict__)

# 定义元类的方式
class Person: #方法1:Person(metaclass=xxx)
    __metaclass__ = xxx  #方法2
    pass
# 方法3,继承
__metaclass__ = xxx # 模块级别创建元类。 如果没有用type找。

class Animals:
    pass

class Person(Animal):
    pass

在第二种方法中可以看出,通过type函数创造类是不存在一个名称指针的,因此需要赋值给一个标识符来指导xxx,在之后可以通过xxx==之前的Person去进行使用。

__metaclass__作为元类的指引

类的创建流程

  1. 检测类中是否有明确__metaclass__属性
  2. 检测父类中是否存在__metaclass__属性
  3. 检测模块中是否存在__metaclass__属性
  4. 通过内置的type这个元类,来创建这个类对象

元类创建

类的应用场景

  1. 拦截类的创建
  2. 修改类
  3. 返回修改之后的类
  4. 后面补充
类的描述
  • 目的:
  1. 方便理清逻辑思路
  2. 方便多人合作开发时的沟通
  3. 方便生成项目文档

ctrl + 鼠标左键查看函数内置描述

class Person:
    """
    关于这个类的描述,类的作用,类的构造函数等等;
    类属性的描述
    Attributes:  ==>属性
        count: int 代表是人的个数
    """
    # 个人的注释,而不生成文档格式
    count = 1

    def run(self, distance, step):
        """
        这个方法的作用
        :param distance: 参数的含义,参数的类型int,是否有默认值
        :param step:
        :return: 返回的结果的含义(时间),返回数据的类型int
        """
        print("人在跑")
        return distance/step

help(Person)

结果:

Help on class Person in module __main__:

class Person(builtins.object)
 |  关于这个类的描述,类的作用,类的构造函数等等;
 |  类属性的描述
 |  Attributes:  ==>属性
 |      count: int 代表是人的个数
 |  
 |  Methods defined here:
 |  
 |  run(self, distance, step)
 |      这个方法的作用
 |      :param distance: 参数的含义,参数的类型int,是否有默认值
 |      :param step:
 |      :return: 返回的结果的含义(时间),返回数据的类型int
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)
 |  
 |  ----------------------------------------------------------------------
 |  Data and other attributes defined here:
 |  
 |  count = 1
  • 生成项目文档

方式1:使用内置模块----pydoc

具体步骤:

windows + r ===> cd python文档路径

python3 --help 代表python3中的包输出

python3 -m pydoc -h代表以脚本形式运行pydoc模块列举相关使用方式

python3 -m pydoc 类的描述模块的描述

python3 -m pydoc -p 1234 手动创建端口,

输入b–浏览,输入q–退出

python3 -m pydoc -b 1234 自动创建端口,

python3 -m pydoc -w 模块名称 自动保存html到文件夹

cls清楚屏幕内容

  1. 查看文档描述 – python3 -m pydoc 模块名称
  2. 启动本地服务器,浏览文档 – python3 -m pydoc -p 6666
  3. 生成指定模块html文档 – python3 -m pydoc -w 模块名称
属性相关补充
私有化属性

设置权限,限制在类内部访问,保证数据的安全性。例如,银行问题。

注意:Python并没有真正的私有化支持,但是,可以使用下划线完成伪私有的效果。类属性(方法)和实例属性(方法)遵循相同的规则。

x公有属性:类内部访问;子类内部访问;模块内其他位置访问;跨模块访问(import形式导入;from 模块 import 形式导入)

类的内部访问

class Animal:
    x = 10 # 公有属性
    def test(self):
        print(Animal.x)
        print(self.x)
        
a = Animal()
a.test

子类内部访问

class Animal:
    x = 10 # 公有属性
    def test(self):
        print(Animal.x)
        print(self.x)
        
class Dog:
    def test2(self):
        print(Dog.x)
        print(self.x)

d = Dog()
d.test2

模块内其他位置访问:

  • 类访问:1.父类 2.派生类
  • 实例访问:1.父类实例 2.派生类实例
class Animal:
    x = 10 # 公有属性
    def test(self):
        print(Animal.x)
        print(self.x)
        
class Dog:
    def test2(self):
        print(Dog.x)
        print(self.x)
a = Animal()
d = Dog()

print(Animal.x)
print(Dog.x)
print(a.x)
print(d.x)

跨模块访问:

  • import形式导入
  • from 模块 import * 形式导入
私有化属性.py
a = 666
import 私有化属性
print(私有化属性.a)

from 私有化属性 import *
print(a)

_y受保护的属性(一个下划线)

类内部访问;子类内部访问;模块内其他位置访问;跨模块访问(import形式导入;from 模块 import 形式导入)

类的内部访问

class Animal:
    _x = 10 # 公有属性
    def test(self):
        print(Animal._x)
        print(self._x)
        
a = Animal()
a.test

子类内部访问

class Animal:
    _x = 10 # 公有属性
    def test(self):
        print(Animal._x)
        print(self._x)
        
class Dog:
    def test2(self):
        print(Dog._x)
        print(self._x)

d = Dog()
d.test2

模块内其他位置访问:

  • 类访问:1.父类 2.派生类
  • 实例访问:1.父类实例 2.派生类实例

注意:有警告,不建议这样的访问方式

class Animal:
    _x = 10 # 公有属性
    def test(self):
        print(Animal._x)
        print(self._x)
        
class Dog:
    def test2(self):
        print(Dog._x)
        print(self._x)
a = Animal()
d = Dog()

print(Animal._x)
print(Dog._x)
print(a._x)
print(d._x)

跨模块访问:

  • import形式导入
  • from 模块 import * 形式导入:1.有__all__指明对应变量 2.没有__all__指明对应变量
私有化属性.py
__all__ = ["_a"] # 提供导出_a,保证from --成功
_a = 666
import 私有化属性  # 访问成功
print(私有化属性.a)

from 私有化属性 import * # 访问失败
print(a)

__z私有属性(两个下划线)

类的内部访问

class Animal:
    __x = 10 # 公有属性
    def test(self):
        print(Animal.__x)
        print(self.__x)
        
a = Animal()
a.test

子类内部访问(访问不到)

class Animal:
    __x = 10 # 公有属性
    def test(self):
        print(Animal.__x)
        print(self.__x)
        
class Dog:
    def test2(self):
        print(Dog.__x)
        print(self.__x)

d = Dog()
d.test2

模块内其他位置访问:(不能访问)

  • 类访问:1.父类 2.派生类
  • 实例访问:1.父类实例 2.派生类实例
class Animal:
    __x = 10 # 公有属性
    def test(self):
        print(Animal.__x)
        print(self.__x)
        
class Dog:
    def test2(self):
        print(Dog.__x)
        print(self.__x)
a = Animal()
d = Dog()

print(Animal.__x)
print(Dog.__x)
print(a.__x)
print(d.__x)

跨模块访问:

  • import形式导入
  • from 模块 import * 形式导入:1.有__all__指明对应变量 2.没有__all__指明对应变量
私有化属性
__all__ = ["__a"] # 提供导出_a,保证from --成功
__a = 666
import 私有化属性  # 访问成功
print(私有化属性.a)

from 私有化属性 import * # 访问失败,需要`__all`
print(a)
属性名称/访问权限公有属性保护属性私有属性
类内部访问
子类内部访问x
模块内其他位置访问
(父类/派生类)(父类实例/派生类实例)
x
跨模块访问import√
from 需要__all=[]
import√
from 需要__all=[]

私有属性的实现机制:

  私有化属性其实是伪私有化属性,只是解释器内部重新给私有化属性更换名称方式,进行名字重整,从而形成一种私有化属性保密现象,用户可通过重整后的命名方式访问,但不建议这样,毕竟目的是私有化属性,就不要再强制访问。

  • 名字重整(Name Mangling): 重改__x为另外一个名称,如_classname__x
  • 目的:1. 防止外界直接访问。2. 防止被子类同名称属性覆盖。

私有化属性应用场景:1.数据保护。2.数据过滤。

__init__(self)初始化方法。

主要作用:当我们创建好一个实例对象之后,会自动的调用这个方法,来初始化这个对象。

class Person:
    
    # 主要作用,当我们创建好一个实例对象之后,会自动的调用这个方法,来初始化这个对象。
    def __init__(self):
        self.__age = 18
    def setAge(self, value):
        if isinstance(value, int) and 0< val < 200:
	        self.__age = value
        else:
            print("你输入的数字有问题,请重新输入")
	def getAge(self):
        return self.__age
        
p = Person()
p.setAge(20)
print(p1.getAge)
    
# 私有属性在模块外访问,不是本身,而是新建的私有属性,只能在类内部进行修改。
p1 = Person()
p1.age = 10 # 错误,不能模块外修改
print(p1.__dict__)
# 强行访问,不建议 p1._Person__age

补充:

xx_ 与系统内置关键字区分时区分命名

__xx__系统内置命名

只读属性

概念:一个属性(一般指实例属性),只能读取,不能写入。

应用场景:有些属性,只限在内部根据不同场景进行修改,而对外界来说,不能修改,只能读取。比如,电脑类的网速属性,网络状态属性。

方式1:

方案:1.–全部隐藏–私有化–既不能读,也不能写。2.–部分公开–公开读的操作。

具体实现:1.私有化–通过“属性前置双下划线”实现。2.部分公开–通过公开的方法。

class Person:
    def __init__(self):
        self.__age = 18

    def get_age(self):
        return self.__age

p1 = Person()
print(p1.get_age())
# print(p1.age)
# p1.age = 5
# print(p1.age)

优化:调用不方便,使用函数的方式进行访问;外界赋值,通过直接赋值的方法,会进行创建键值属性,不能设置数值方便。

@property # 主要作用是可以以使用属性的方式,来使用这个方法。将一些“属性的操作方法”关联到某一个属性中。

概念补充:经典类–没有继承(object);新式类–继承(object)。python2.x版本定义一个类时,默认不继承(object)。python3.x版本定义一个类时,默认继承(object)。建议使用:新式类

# Python3
class Person:
    pass

print(Person.__base__) #确定类的父类

结果:

<class 'object'>

  python2.x 如果定义一个类,没有显示的继承自object,那么这个类就是一个经典类,必须显示的继承自object,它才是一个新式类

  python3.x,如果直接定义一个类,会隐式的继承object,默认情况下,就已经是一个新式类。

class Person(object):
    def __init__(self):
        self.__age = 18
    
    @property # 主要作用是可以以使用属性的方式,来使用这个方法
    def age(self):
        return self.__age

p1 = Person()
print(p1.age)
p1.age = 666 # 错误, age不可以

# class Person:
#     pass
#
# print(Person.__base__)
# 第一种使用方式, 整体!!!
class Person(object):
    def __init__(self):
        self.__age = 18

    def get_x(self):
        return self.__age

    def set_x(self, value):
        self.__age = value

    age = property(get_x, set_x)

p = Person()
print(p.age)
p.age = 90
print(p.age)
print(p.__dict__)

# 第二种使用方式-----分开绑定
class Person(object):
    def __init__(self):
        self.__age = 18
    @property
    def age(self):
        return self.__age

    @age.setter
    def age(self, value):
        self.__age = value

p = Person()
print(p.age)
p.age = 10
print(p.age)

python2.x 版本中,只有@property读操作,没有写和删除操作。

内置特殊属性

类属性:

__dict__:类的属性

__bases__:类的所有父类构成元组

__doc__:类的文档字符串

__name__:类名

__module__:类定义所在的模块

实例属性:

__dict__:实例属性

__class__:实例对应的类

class Person:
    """
    这是一个人
    """
    age = 19
    def __init__(self):
        self.name = 'sz'

    def run(self):
        print('run')

print(Person.__dict__)
print(Person.__base__)
print(Person.__doc__) # 类名
# help(Person) # 类的详细描述
print(Person.__name__)
print(Person.__module__)

p = Person()
print(p.__dict__)
print(p.__class__)
方法相关补充
私有化方法

  私有化方法就是将函数私有化,使得外界访问不到,但注意在定义了__run后,不要再定义函数_Person__run,否则重整后的run函数将被覆盖。

class Person:
    def __run(self):
        print("run")

p = Person()
# print(p.__run)
print(Person.__dict__)
内置特殊方法

定义类之后,系统会提前预制好一些方法,当我实现不同的内置方法时,会实现不同的内置功能。例如__str__内置方法,print()打印时检查__str__是否有,有直接打印方法内部,否则,将实例的类名和内存地址打印出来。

生命周期方法(下一节)

其他内置方法:

信息格式化操作__str__方法__repr__方法

解释一下:在模块外部调用打印实例时,首先访问类内部__str__方法,若方法存在,打印其返回值,若方法不存在,则寻找__repr__是否存在,若存在,打印其值,若不存在,则打印类的名称,地址等信息。在__str__存在的前提下,想要看__repr__内部的值,应该选择一种方法,即模块外部使用print(repr(p1))查看__repr__内置方法的内容。

  特殊的方法,在交互模式下(俗称终端),直接敲实例名称p,如果是print(p),则输出对应的'repr'

  __str__常用为面向用户使用时所返回的参数,而__repr__常用为开发人员或python解释器调试过程中返回的参数。

  __str__实现的方法为:1.通过print()函数。2.通过str()函数转换。__repr__实现的方法为:1.通过repr()函数实现。2.在交互模式下,直接把对象的变量名输入即可。

# class Person:
#     def __run(self):
#         print("run")
#
# p = Person()
# # print(p.__run)
# print(Person.__dict__)

class Person:
    def __init__(self, n = 'sz', a = 18):
        self.name = n
        self.age = a

    def __str__(self):
        return "这个人的姓名是%s,这个人的年龄是%s"%(self.name, self.age)

p1 = Person('szz',19)
# print(p1.name)
# print(p1.age)
print(p1)
p2 = Person('zhangsan', 10)
# print(p2.name)
# print(p2.age)
print(p2)

# 触发__str__方法,直接通过p1找到str,然后赋值给s打印出来,与上述方法存在着不同。
s = str(p1)
print(s)

通过eval函数将repr内容变为str内容。

import datetime
t = datetime.datetime.now()
print(t)
print(repr(t))
print(eval(repr(t)))

调用操作__call__方法

作用:使得“对象”(实例)具备当做函数,来调用的能力

class Person:
    def __call__(self, *args, **kwargs): # *args--未指明名称参数传入(元组), **kwargs--指明名称参数传入(字典);可变参数*args和关键字参数**kwargs
        print('***', args, kwargs)
    pass

p = Person()
p(123, 456, name = 'zxl')

functools[偏函数](如何使用python 中的functools.partial用法! - 湘九 - 博客园 (cnblogs.com))

# 创建很多个画笔,画笔的类型(钢笔,铅笔), 画笔的颜色(红,黄,青色,绿色)
def createPen(p_type, p_color):
    print("创建了一个{}这个类型的画笔,它是{}颜色".format(p_type, p_color))
import functools
gangbifunc = functools.partial(createPen, "钢笔") # 不能p_type = "钢笔",系统会自动定义,可写p_color = "铅笔", 输入参数默认为第一个
gangbifunc('黄色')

__call__函数应用场景:

class PenFactory:

    def __init__(self, p_type):
        self.p_type = p_type

    def __call__(self, p_color):
        print("创建了一个{}这个类型的画笔,它是{}颜色".format(self.p_type, p_color))

gangbiF = PenFactory("钢笔")
gangbiF('红色')
gangbiF('黄色')
gangbiF('绿色')

qianbiF = PenFactory("铅笔")
qianbiF('红色')
qianbiF('黄色')
qianbiF('绿色')

索引操作
作用:可以对一个实例对象进行索引操作

步骤:

  1. 实现三个内置方法:增/改,查,删

    __setitem__增加

    __getitem__查询

    __delitem__删除

    class Person:
    
        def __init__(self):
            self.cache = {}
    
        def __setitem__(self, key, value): # key,value 字典对
            self.cache[key] = value
    
        def __getitem__(self, item):
            return self.cache[item]
    
        def __delitem__(self, key):
            del self.cache[key]
    
    p = Person()
    p["name"] = 'sz'
    print(p["name"])
    del p["name"]
    # print(p["name"])
    print(p.cache)
    
  2. 可以以索引的形式操作对象

    增/改 p[1] = 666 ; p['name'] = 'sz'
    p['name'] ; p[1]
    del p['name'] ;delp[1]

切片操作

统一由“索引操作”进行管理。

def __setitem__(self, key, value):
def __getitem__(self, item):
def __delitem__(self, key):
class Person:

    def __init__(self, lis):
        if isinstance(lis, list):
            self.items = lis
        else:
            print("请输入一个列表:")
    def __setitem__(self, key, value):
        # print(key.start)
        # print(key.stop)
        # print(key.step)
        # print(value)
        if isinstance(key, slice):
            self.items[key.start: key.stop: key.step] = value

    def __getitem__(self, items):
        print("getitem", items)

    def __delitem__(self, key):
        print("delitem", key)

p = Person(list(range(5)))
p[0:4:2] = ["a", "b"]
print(p.items)

比较操作

  相等和不相等反向操作,实现一个后另一个自动实现。例如,实现了__eq__后,判断(p1 != p2)时,它会根据__eq__产生相反的结果,也可以实现__ne__不相等函数。__gt__大于,__ge__大于等于,__lt__小于等于,__le__小于等于。

class Person:

    def __init__(self, age, height):
        self.age = age
        self.height = height

# ==
    def __eq__(self, other):
        return self.age == other.age

# != 如果eq存在,ne不存在,不相等可以通过判断eq的相反结果输出,如果ne存在,直接判断ne的结果
    def __ne__(self, other):
        return self.age != other.age

# >
    def __gt__(self, other):
        return self.age > other.age

# >=
    def __ge__(self, other):
        return self.age >= other.age

# <
    def __lt__(self, other):
        return self.age < other.age

# <=
    def __le__(self, other):
        return self.age <= other.age

p1 = Person(18, 180)
p2 = Person(12, 190)
print(p1 == p2)
print(p1 > p2)
print(p1 >= p2)

步骤:实现6个方法:相等__eq__不相等__eq__小于__lt__小于或等于__le__大于__gt__大于或等于__ge__

注意:如果方向操作的比较符,只定义了其中一个方法,但使用的是另外一种比较运算,那么,解释器会采用调换参数的方式进行调用该方法。但是,不支持叠加操作(没有< + = ==> ≤)。

class Person:

    def __init__(self, age, height):
        self.age = age
        self.height = height
        
    def __le__(self, other):
        print(self.age)
        print(other.age)
        return self.age <= other.age
        
p1 = Person(18, 180)
p2 = Person(12, 190)
print(p1 >= p2) # self实例为p2,other实例为p1,两个参数互换。

补充:

  1. 使用装饰器,自动生成“方向”“组合”的方法。(functools)
import functools

@functools.total_ordering
class Person:

    def __eq__(self, other):
        print("eq")

    def __gt__(self, other):
        print("gt")


    def __lt__(self, other):
        print("lt")
        return True

p1 = Person()
p2 = Person()
print(p1 <= p2)
print(Person.__dict__)
  1. 上下文环境中的布尔值–__bool__
class Person:
	
    def __init__(self):
        self.age = 20
    def __bool__(self):
        return self.age >= 18
    pass

p = Person()

if p:
    print("***")

注意:

  通过functools工具可以实现< + = ==> ≤类似叠加,判断时通过单个True优先原则确定,即如果满足<就不会继续判断=

遍历操作:

  怎样让我们自己创建的对象可以使用for in 进行遍历? 实现__getitem__方法或者实现__iter__方法。

class Person:
    def __init__(self):
        self.result = 1

    def __getitem__(self, item):
        self.result += 1
        if self.result >= 6:
            raise StopIteration("停止遍历")
        return self.result

p = Person()

for i in p:
    print(i)

__iter__的优先级高于__getitem__优先级。

class Person:
    def __init__(self):
        self.result = 1

    def __iter__(self):
        # print('iter')
        return self
    
    def __next__(self):
        self.result += 1
        if self.result >= 6:
            raise StopIteration("停止遍历")
        return self.result

p = Person()

# 方式1
for i in p:
    print(i)

# 方式2, 不需要def __iter__ 方法
print(next(p))
print(next(p))
print(next(p))
print(next(p))
# 一个迭代器一定是一个可迭代对象,但一个可迭代对象不一定是迭代器。
class Person:
    def __init__(self):
        self.age = 1

    def __iter__(self):
        return self

    def __next__(self):
        self.age += 1
        if self.age >= 6:
            raise StopIteration("停止迭代")
        return self.age
p = Person()

import collections
print(isinstance(p, collections.Iterator))

迭代器实现需要两个条件:__iter__,__next__

  迭代器可以使用next函数访问,能通过next函数访问的不一定是迭代器,next函数访问要求有实例方法,即__next__

for i in p:
    print(i)

  在通过上述循环访问时,一般只能访问一次迭代器,p-->__iter__-->__next__访问结束,因此为了保证能重复访问迭代器,可以直接在__iter__函数中增加初始条件self.age = 1,实现多次输出迭代器。

# 判断是否为迭代器
import collections
print(isinstance(p, collections.Iterator)) # 需要验证__iter__和__next
# 判断__iter__是否存在
print(isinstance(p, collections.Iterable))

  一个迭代器一定是一个可迭代对象,但一个可迭代对象不一定是迭代器。

  一个可迭代对象一定可以通过for in访问,但能通过for in遍历访问的不一定是可迭代对象。例如__getitem__函数

  __iter__函数返回的值就是迭代器,可以在模块外调用iter(p),还可以iter(p.__next__,4)限制最大的值4,为了方便,也可以采用__call__实例方法,直接使用iter(p, 4)

描述器

概念:可以描述一个属性操作的对象

  1. 对象
  2. 属性的操作(增/改、删、查)
  3. 描述 :一个类中有一个属性,这个属性指向一个特殊的对象,只要这个对象里面实现了三个实例方法__set__,__get__,__delete__,即可,成为描述器。

定义:

  1. property
class Person:

    def __init__(self):
        self.__age = 10

    def get_age(self):
        return self.__age

    def set_age(self, value):
        if value < 0:
            value = 0
        self.__age = value

    def del_age(self):
        del self.__age

    age = property(get_age, set_age, del_age)

p = Person()
print(p.age)
p.age = -5
print(p.age)
del p.age
print(p.age)
class Person:

    def __init__(self):
        self.__age = 10
    @property
    def age(self):
        return self.__age
    @age.setter
    def age(self, value):
        if value < 0:
            value = 0
        self.__age = value
    @age.deleter
    def age(self):
        del self.__age

p = Person()
print(p.age)
p.age = -5
print(p.age)
del p.age
print(p.age)
  1. 通过设置单独类设定描述器后,模块外部调用时应注意p = Person()形式实例调用,若Person.age = 19类调用,不能确保调用成功。
class Age:
    def __get__(self, instance, owner):
        print("get")

    def __set__(self, instance, value):
        print("set")

    def __delete__(self, instance):
        print("delete")

class Person:
    age = Age()

print(Person.age)
Person.age = 19
del Person.age

结果

get
None
19

  上述结果可以发现,Person.age = 19;del Person.age语句并没有执行描述器中的相应函数,因此,使用过程中,最好使用实例调用的方法。

不能够顺利转换的场景:

  1. 描述器只能在新式类中生效,宿主类和描述器类必须全部都是新式类(object)。
  2. 方法拦截。

一个实例属性的正常访问顺序:

  • 实例对象自身的__dict__字典
  • 对应类对象的__dict__字典
  • 如果有父类,会再往上层的__dict__字典中检测
  • 如果没找到,又定义了__getattr__方法,就会调用这个方法

  而再上述的整个过程中当中,是如何将描述器的__get__方法给嵌入到查找机制当中?

答: 就是通过这个方法实现:__getattribute__

  内部实现模拟:如果实现了描述器方法__get__就会直接调用;如果没有,则按照上面的机制去查找。

  • 资料描述器:实现了get,set方法

  • 非资料描述器:只实现了get方法

  • 优先级:资料描述器>实例属性>非资料描述器

# 资料描述器
class Age:

    def __get__(self, instance, owner):
        print('get')

    def __set__(self, instance, value):
        print('set')

    def __del__(self):
        print('del')

class Person:
    age = Age()

    def __init__(self):
        self.age = 10

p = Person()
p.age
del p.age
# 非资料描述器
class Age:

    def __get__(self, instance, owner):
        print('get')

    # def __set__(self, instance, value):
    #     print('set')
    # 
    # def __del__(self):
    #     print('del')

class Person:
    age = Age()

    def __init__(self):
        self.age = 10

p = Person()
p.age
print(p.__dict__)

描述器值的存储问题

class Age:

    def __get__(self, instance, owner):
        print('get')
        return instance.v

    def __set__(self, instance, value):
        print('set', self, instance, value)
        instance.v = value

    def __delete__(self, instance):
        print('del')

class Person:
    age = Age()

p1 = Person()
p2 = Person()
p1.age = 11
print(p1.age)
p2.age = 12
print(p2.age)
print(p1.age)

结果:

set <__main__.Age object at 0x00000269182144F0> <__main__.Person object at 0x0000026919EB0C70> 11
get
11
set <__main__.Age object at 0x00000269182144F0> <__main__.Person object at 0x0000026919EE2880> 12
get
12
get
11

  可以发现self对应的内容地址是相同的,也就是说,当改变任意一个p.age值时,其余的相关值都会改变,这并不是我们想要的结果,因此,应该选择instance作为我们数据存储的地址,上述结果证明,p.age之间不会相互影响。

面向对象-装饰器-类实现

(详解Python的装饰器 - Toby Qin - 博客园 (cnblogs.com))

((12 封私信 / 80 条消息) 如何理解Python装饰器? - 知乎 (zhihu.com))

# 函数实现方法:
def check(func):
    print("登录验证:")
    func()

@check # 或者下方注释两种形式均可
def fashuoshuo():
    print("发说说")
# fashuoshuo = check(fashuoshuo)
fashuoshuo
# 类实现方法:
class check:
    def __init__(self, func):
        self.f = func

    def __call__(self, *args, **kwargs):
        print("登录验证")
        self.f()

# @check
def fashuoshuo():
    print("发说说")
fashuoshuo = check(fashuoshuo)
fashuoshuo()

Python对象的生命周期,以及周期方法

概念

  生命周期指的是一个对象,从诞生到消亡的过程,当一个对象被创建时,会在内存中分配相应的内存空间及逆行存储,当这个对象不再使用,为了节约内存,就会把这个对象释放。

涉及问题

如何监听一个对象的生命过程?

python时如何掌控一个对象的生命?

*监听对象的生命周期

  1. __new__方法:当我们创建一个对象时,用于给这个对象分配内存的方法;通过拦截这个方法,可以修改对象的创建过程–比如:单例设计模式
class Person:
    def __new__(cls, *args, **kwargs):
        print("新建了一个对象,但是,被我拦截了")
    pass

p = Person()
print(p)
  1. __init__方法
class Person:

    def __init__(self):
        print("初始化方法:")
        self.name = 'Sz'

    def __del__(self):
        print('这个对象被释放了')

p = Person()
del p
print(p)
print(p.name)

  监听对象生命周期的方法–小案例

# Person,打印一下,当前这个时刻,由Person类,产生的实例,有多少个
#创建一个实例,计数+1, 如果,删除了一个实例,计数-1

#全局变量计数
personcount = 0
class Person:

    def __init__(self):
        global personcount
        print('计数+1')
        personcount += 1
    def __del__(self):
        global personcount
        print('计数-1')
        personcount -= 1
p = Person()
p1 = Person()
print('当前人的个数是%s个', personcount)
del p
print('当前人的个数是%s个', personcount)
# 问题1:多次打印,可以写成方法。问题2:全局变量

  改进方法:

class Person:
    __personcount = 0
    def __init__(self):
        print('计数+1')
        Person.__personcount += 1
    def __del__(self):
        print('计数-1')
        self.__class__.__personcount -= 1 # self.__class__ =====> Person

    # @staticmethod
    # def log():
    #     print('当前人的个数是%s个', Person.__personcount)

    @classmethod
    def log(cls):
        print('当前人的个数是%s个', cls.__personcount)
p = Person()
p1 = Person()
Person.log()
del p
Person.log()

内存管理机制

存储方面
  1. 在python中万物皆对象(不存在基本数据类型;0,1,2,3, True, "abc"全部都是对象)
  2. 所有对象,都会在内存中开辟一块空间进行存储。(会根据不同类型以及内容,开辟不同的空间大小进行存储;返回该空间的地址给外界接受(称为“引用”),用于后续对这个对象的操作(可通过id()函数获取内存地址(10进制),通过hex()函数可以查看对应的16进制地址))
  3. 对于整数和短小的字符,python会进行缓存,不会创建多个相同的对象
  4. 容器对象,存储的其他对象,仅仅是其他对象的引用,并不是其他对象本身
# -----2
class Person:
    pass

p = Person()
p2 = Person()
print(id(p))
print(id(p2))
# print(id(p))
# print(hex(id(p)))

# ----3
num1 = 2
num2 = 2
print(id(num1))
print(id(num2))
垃圾回收方面
引用计数器

概念:一个对象,会记录着自身被引用的个数;每增加一个引用,这个对象的引用计数会自动+1, 每减少一个引用,这个对象的引用计数会自动-1

主要:检测一般的对象,对于循环引用对象不适合使用,需要采用垃圾回收机制检测。

查看引用计数

import sys

sys.getrefcount(对象)–注意会多增加一,因为调用

import sys
class Person:
    pass

p1 = Person() # 1

print(sys.getrefcount(p1))

p2 = p1 # 2

print(sys.getrefcount(p1))

del p2

print(sys.getrefcount(p1))

del p1

print(sys.getrefcount(p1))
举例
引用次数+1场景
  1. 对象被创建----p1 = Person()

  2. 对象被引用----p2=p1

  3. 对象被作为参数,传入到一个函数中

    import sys
    
    class Person:
        pass
    
    p1 = Person() # 1
    print(sys.getrefcount(p1))
    
    def log(obj): # +2 函数对象内部有两个属性引用
        print(sys.getrefcount(p1))
    
    log(p1)
    
  4. 对象作为一个元素,存储在容器中–l=[p1]

import sys

class Person:
    pass

p1 = Person() # 1
print(sys.getrefcount(p1))

l = [p1] # 2
print(sys.getrefcount(p1))
引用计数-1场景
  1. 对象的别名被显示销毁–del p1
  2. 对象的别名被赋予新的对象–p1=123
  3. 一个对象离开它的作用域–一个函数执行完毕时,内部的局部变量关联对象,它的引用完毕时
  4. 对象所在的容器被销毁,或从容器中删除对象
引用计数器机制-特殊场景-循环引用问题

循环引用

内存管理机制 = 引用计数器机制(高效)+ 垃圾回收机制(低效)

当一个对象,如果被应用+1,删除一个引用:-1 , 0:被自动释放。

循环机制

如上图所示,当p,d两个引用删除后,dog和master两者互相引用,计数为1,但引用删除后,无法再继续使用,造成内存溢出。垃圾回收机制可以解决此现象出现的问题。

# objgraph
# objgraph.count() 可以查看,垃圾回收器,跟踪的对象个数

import objgraph

class Person:
    pass

class Dog:
    pass

p = Person()
d = Dog()

print(objgraph.count("Person"))
print(objgraph.count("Dog"))

p.dog = d
d.master = p

del p
del d

print(objgraph.count("Person"))
print(objgraph.count("Dog"))

结果:

1
1
1
1

引用计数器无法清理。需要使用垃圾回收机制起作用解决循环引用问题。

垃圾回收

引用计数器机制的补充机制(两个对象产生了循环引用,无法释放)。

主要作用:从经历过“引用计数器机制”仍未被释放的对象中,找到“循环引用”,干掉相关对象。

底层机制(了解–难):

怎样找到“循环引用”?

  1. 收集所有的“容器对象”,通过一个双向链表进行引用。
    • 容器对象:可以引用其他对象的对象(列表、元组、字典、自己定义的对象…)
    • 非容器对象:str,int…
  2. 针对于每一个“容器对象”,通过一个变量gc_refs来记录当前对应的引用计数。
  3. 对于每个“容器对象”,找到它引用的“容器对象”,并将这个“容器对象”的引用计数-1
  4. 经过步骤3之后,如果一个“容器对象“的引用计数为0,就代表着玩意可以被回收了,肯定是“循环引用”导致它活到现在的

垃圾查找机制

  问题:检测所有的”容器对象“,效率很低。

如何提升查找”循环引用“的性能?

  1. 如果程序当中创建了很多个对象,而针对每一个对象都要参与”检测“过程,则会非常的耗费性能。

  2. 所以基于这个问题,产生了一个假设:越命大的对象,越长寿。假设一个对象10此检测都没给它干掉,那认定这个对象一定很长寿,就减少这对象的”检测频率“

  3. 基于这种假设,设计了一套机制:

分代回收

  • 机制:
    • 默认一个对象被创建出来后,属于0代。
    • 如果经历过这一代”垃圾回收“后,依然存活,则划分到下一代。
    • ”垃圾回收“的周期顺序为0代”垃圾回收“一定次数,会触发0代和1代回收,1代”垃圾回收“一定次数,会触发0代,1代和2代回收
  • 查看和设置相关参数
    • import gc
    • print(gc.get_threshold())
    • gc.set_threshold(700,10,5)–新增的对象个数-消亡的对象个数差异为700触发垃圾回收机制,0代检测10次时触发1代检测,1代检测5次时触发2代检测

垃圾回收器中,新增的对象个数-消亡的对象个数,达到一定的阈值,才会触发垃圾检测。

*垃圾回收时机(掌握–简单)
  1. 自动回收

    触发条件:

    • 开启垃圾回收机制:
      • gc.enable():开启垃圾回收机制(默认开启)
      • gc.disenable():关闭垃圾回收机制
      • gc.disable():关闭垃圾回收机制
      • gc.isenabled():判定是否开启
    • 达到垃圾回收的阈值
      • import gc
      • print(gc.get_threshold())
      • gc.set_threshold(700,10,5)–新增的对象个数-消亡的对象个数差异为700触发垃圾回收机制,0代检测10次时触发1代检测,1代检测5次时触发2代检测
  2. 手动回收
    不论垃圾回收机制是否开启,手动回收设置可以独立执行。

    # 手动回收
    import objgraph
    import gc
    # 1. 定义了两个类
    class Person:
        pass
    
    class Dog:
        pass
    
    # 2. 根据这两个类,创建出两个实例对象
    p = Person()
    d = Dog()
    
    # 3. 让两个实例对象之间互相引用,造成循环引用
    p.dog = d
    d.master = p
    
    # 4. 尝试删除可到达引用以后,测试真实对象是否有被回收
    del p
    del d
    
    # 5. 通过”引用计数机制“无法回收;需要借助”垃圾回收机制“进行回收
    gc.collect() # 垃圾回收,默认全部代,无论回收机制是否开启,手动回收都进行。
    
    print(objgraph.count("Person"))
    print(objgraph.count("Dog"))
    

    注:python2.x中如果存在def __del__(self):垃圾回收机制不会自动释放。

  • 便面产生循环引用的方法

通过增加弱引用,避免循环引用的产生

# -----------------------弱引用——————————————————————————————————
import objgraph
import gc
import weakref

# 1. 定义了两个类
class Person:
    pass

class Dog:
    pass

# 2. 根据这两个类,创建出两个实例对象
p = Person()
d = Dog()

# 3. 让两个实例对象之间互相引用,造成循环引用
p.dog = d
d.master = weakref.ref(p) # 仅仅解决的是一个对象的弱引用,可以使用弱引用字典
# weakref.WeakValueDictionary({"dog":d1, "cat": cl})
#等同于p.pets = ({"dog": weakref.ref(d1), "cat": weakref.ref(cl)})

# 4. 尝试删除可到达引用以后,测试真实对象是否有被回收
del p
del d

# 5. 通过”引用计数机制“无法回收;需要借助”垃圾回收机制“进行回收
# gc.collect() # 垃圾回收,默认全部代,无论回收机制是否开启,手动回收都进行。

print(objgraph.count("Person"))
print(objgraph.count("Dog"))

弱引用

治疗:在循环引用的前提下,进行手动破环循环

import objgraph
import gc
import weakref

# 1. 定义了两个类
class Person:
    pass

class Dog:
    pass

# 2. 根据这两个类,创建出两个实例对象
p = Person()
d = Dog()

# 3. 让两个实例对象之间互相引用,造成循环引用
p.dog = d
d.master = p
# d.master = weakref.ref(p)

# 4. 尝试删除可到达引用以后,测试真实对象是否有被回收
p.dog = None
del p
del d

# 5. 通过”引用计数机制“无法回收;需要借助”垃圾回收机制“进行回收
# gc.collect() # 垃圾回收,默认全部代,无论回收机制是否开启,手动回收都进行。

print(objgraph.count("Person"))
print(objgraph.count("Dog"))

手动破坏循环引用

测量对象的引用个数

辅助工具:objgraph:xdot;graphviz

函数带不带括号

1、不带括号时,调用的是这个函数本身 ,是整个函数体,是一个函数对象,不需等该函数执行完成
2、带括号(此时必须传入需要的参数),调用的是函数的return结果,需要等待函数执行完成的结果

综合实例略,有需求的评论留言邮箱

面向对象三大特性:封装、继承、多态

面向对象三大特性详解

  • 10
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fighting_1997

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值