面向对象之小白学习笔记
最近一段时间,恶补Python之面向对象知识,同时也整理了很多的笔记,与大家分享,如果大家学习过程中有什么问题,可以评论留言,我们一起探讨。
面向对象
基本理论
什么是对象
万物皆对象
对象是具体物体:
- 拥有属性
- 拥有行为
- 把很多零散的东西,封装成为一个整体
举例:
王二小:
- 属性(姓名、年龄、身高、体重。。)
- 行为(走路、吃饭。。。)
Python是一门特别彻底的面向对象编程(OOP)的语言:
- 其他语言–基本类型(int、float、bool) 对象类型(string、array)
- python–对象类型:int、float、bool、list…
面向过程&面向对象
1.都是一种解决问题的思路(思想)
2.面向过程–再解决问题的时候,关注的是解决问题的每一个的过程(步骤)
3.面向对象–再解决问题的时候,关注的是解决问题所需要的对象
例子:做好饭后洗碗:过程(职工):洗菜-点火倒油-…-洗碗-…
对象(BOSS):1.做饭 2.洗碗
对比
面向对象和面向过程都是解决问题的一种方式(思想)–面向对象本身是对象向过程的封装
面向过程编程最重要的是:按照步骤划分,把一个任务,分解成具体的每一个步骤。
面向对象编程最重要的是:按照对象功能进行划分,找到对象,确定对象属性和行为
如何从面向过程编程的思想,过度到面向对象编程?
-
列举出一个任务的具体实现步骤
-
试图分离这些实现步骤中的功能代码块
-
将这些功能代码块,划分到某一个对象中
-
根据这个对象以及对应的行为,抽象出对应的类–设计类
类
类的概念
某一个具体对象特征的抽象
例如:张三:属性(年龄: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. 变量名称
方式2(常用):
内部创建类属性
class Dog: #
age = 2
class Money:
count = 1
age = 18
print(Money.count)
print(Money.age)
查询一个类的属性
- 通过类访问–类名.类属性
- 通过对象访问–对象.类属性(前提:自身没有对应的属性)
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__
找到对象对应的类,到这个类里面去找
证明:一个对象的访问顺序为先访问自己的属性,找不到相应的属性再访问类属性。
修改一个类的属性
-
通过类名 (
Money.age = 18
) -
通过对象修改(
one.age = 777
对象截断方法,直接输出对象对应的属性,而识别不到类属性)
删除一个类的属性
- 通过类名删除
del.类名.属性
- 能否通过对象删除??—不能!!!del语句只能删除直系属性
注意点
类属性的内存存储问题
对象访问过程中,是通过类属性中自带的__dict__
属性形成字典。同时也可以通过one.__dict__ = {name: 'sz', .... }
进行创建对象属性,通过one.__dict__["age"] = 20
也可以修改对象。
类中的属性也是存储在__dict__
属性中,但是只能读不能修改,即Money.__dict__ = {sex : "男"}
错误!!!。
-
一般情况下,属性存储在
__dict__
的字典当中–有些内置对象没有这个__dict__
属性 -
一般对象可以直接修改
__dict__
属性 -
但类对象
__dict__
为只读;默认无法修改,可以通过__setattr__
方法修改
类属性被各个对象共享
可以通过类直接访问类属性,也可以通过对象访问类属性(前提,对象中没有对应的属性)
补充
查看一个类的所有属性
类名.__dict__
对象.__dict__
查看对象的对应的类名称
限制对象属性的添加
为什么要限制?因为从一个类中定义的不同对象可能产生不同属性的对象,形成杂乱无章的数据。因此,为了产生这种限制对象属性的不规则添加,增加了类属性中限制类的操作。
class Person:
__slots__ = ["类属性定义1","类属性定义2",·······]
# 注意,这样会把类属性全部给清空,然后限制里面的新的属性,例如,__dict__已经不存在。
当一个类需要创建大量的实例(对象)时,可以通过__dict__
声明实例所需要的属性。
优点:
- 更快的属性访问速度
- 减少内存的消耗
注意:默认情况下,创建一个类 ,会自动的创建一个字典__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]
测试结果(单位:字节)
属性数量 | slotted | unslotted(__dict__ ) |
---|---|---|
0 | 80 | 334(280) |
1 | 152 | 408(344) |
2 | 168 | 448(384) |
8 | 264 | 1456(1392) |
16 | 392 | 1776(1712) |
25 | 536 | 4440(4376)**** |
从上述结果可看到使用__slots__
能极大地减少内存空间的消耗,这也是最常见到的用法。
方法相关
方法的概念
- 描述一个目标的行为动作–比如描述一个人怎么吃、怎么喝、怎么玩。
- 和函数非常相似–都封装了一系列行为动作;都可以被调用之后,执行一系列行为动作;最主要的区别就是:调用方式
方法的划分
重新命名:类=====>>(实例化)====>> 对象(实例)
类属性---------实例属性
类 + 实例 ==== 对象
实例方法
默认第一个参数需要接受到一个实例
class Person:
def 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("这是一个静态方法")
运行结果:
这是一个静态方法
三种方法注意:
- 划分的依据是:方法的第一个参数必须要接受的数据类型
- 不管是哪一种类型的方法,都是存储在类当中;没有在实例当中的(因为都在class下创建的,而不是单独外面创建(实例))
- 不同类型方法的调用方法不同–把握一个原则:保证不同类型的方法第一个参数接收到的数据是它们想要的类型
使用
- 语法
- 不同类型的方法的规则
- 不同类型方法的调用
- 根据不同的问题,自己决定,到底该设计怎样的方法来解决问题
实例方法
class Person:
def eat(self): # 实例方法self是形参名称
pass
标准调用(重点)
class Person:
def eat(self, food): # self只是形参, 可以是任意名称,但默认指定使用self
print("在吃饭", self, food)
p = Person()
p.eat("土豆") # 只需传递第二个参数,self解释器自动调用
- 使用实例调用实例方法,不用手动传,解释器会自动把调用对象本身传递过程。
- 注意:如果实例方法没有接受任何参数(self),则会报错,解释器自动传参数,但调用实例方法没有参数产生矛盾。
其他调用
-
使用类调用
class Person: def eat(self, food): print("在吃饭", self, food) Person.eat(123, "abc") # 函数!!需要传递self, food
-
间接调用
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__
作为元类的指引
类的创建流程:
- 检测类中是否有明确
__metaclass__
属性 - 检测父类中是否存在
__metaclass__
属性 - 检测模块中是否存在
__metaclass__
属性 - 通过内置的type这个元类,来创建这个类对象
类的应用场景:
- 拦截类的创建
- 修改类
- 返回修改之后的类
- 后面补充
类的描述
- 目的:
- 方便理清逻辑思路
- 方便多人合作开发时的沟通
- 方便生成项目文档
- …
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
清楚屏幕内容
- 查看文档描述 –
python3 -m pydoc
模块名称 - 启动本地服务器,浏览文档 –
python3 -m pydoc -p 6666
- 生成指定模块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('绿色')
索引操作
作用:可以对一个实例对象进行索引操作
步骤:
-
实现三个内置方法:增/改,查,删
__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)
-
可以以索引的形式操作对象
增/改
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,两个参数互换。
补充:
- 使用装饰器,自动生成“方向”“组合”的方法。(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__)
- 上下文环境中的布尔值–
__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)
描述器:
概念:可以描述一个属性操作的对象
- 对象
- 属性的操作(增/改、删、查)
- 描述 :一个类中有一个属性,这个属性指向一个特殊的对象,只要这个对象里面实现了三个实例方法
__set__
,__get__
,__delete__
,即可,成为描述器。
定义:
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)
- 通过设置单独类设定描述器后,模块外部调用时应注意
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
语句并没有执行描述器中的相应函数,因此,使用过程中,最好使用实例调用的方法。
不能够顺利转换的场景:
- 描述器只能在新式类中生效,宿主类和描述器类必须全部都是新式类(object)。
- 方法拦截。
一个实例属性的正常访问顺序:
- 实例对象自身的
__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时如何掌控一个对象的生命?
*监听对象的生命周期
__new__
方法:当我们创建一个对象时,用于给这个对象分配内存的方法;通过拦截这个方法,可以修改对象的创建过程–比如:单例设计模式
class Person:
def __new__(cls, *args, **kwargs):
print("新建了一个对象,但是,被我拦截了")
pass
p = Person()
print(p)
__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()
内存管理机制
存储方面
- 在python中万物皆对象(不存在基本数据类型;0,1,2,3, True, "abc"全部都是对象)
- 所有对象,都会在内存中开辟一块空间进行存储。(会根据不同类型以及内容,开辟不同的空间大小进行存储;返回该空间的地址给外界接受(称为“引用”),用于后续对这个对象的操作(可通过id()函数获取内存地址(10进制),通过hex()函数可以查看对应的16进制地址))
- 对于整数和短小的字符,python会进行缓存,不会创建多个相同的对象
- 容器对象,存储的其他对象,仅仅是其他对象的引用,并不是其他对象本身
# -----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场景
-
对象被创建----
p1 = Person()
-
对象被引用----
p2=p1
-
对象被作为参数,传入到一个函数中
import sys class Person: pass p1 = Person() # 1 print(sys.getrefcount(p1)) def log(obj): # +2 函数对象内部有两个属性引用 print(sys.getrefcount(p1)) log(p1)
-
对象作为一个元素,存储在容器中–
l=[p1]
import sys
class Person:
pass
p1 = Person() # 1
print(sys.getrefcount(p1))
l = [p1] # 2
print(sys.getrefcount(p1))
引用计数-1场景
- 对象的别名被显示销毁–
del p1
- 对象的别名被赋予新的对象–
p1=123
- 一个对象离开它的作用域–一个函数执行完毕时,内部的局部变量关联对象,它的引用完毕时
- 对象所在的容器被销毁,或从容器中删除对象
引用计数器机制-特殊场景-循环引用问题
循环引用
内存管理机制 = 引用计数器机制(高效)+ 垃圾回收机制(低效)
当一个对象,如果被应用+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
引用计数器无法清理。需要使用垃圾回收机制起作用解决循环引用问题。
垃圾回收
引用计数器机制的补充机制(两个对象产生了循环引用,无法释放)。
主要作用:从经历过“引用计数器机制”仍未被释放的对象中,找到“循环引用”,干掉相关对象。
底层机制(了解–难):
怎样找到“循环引用”?
- 收集所有的“容器对象”,通过一个双向链表进行引用。
- 容器对象:可以引用其他对象的对象(列表、元组、字典、自己定义的对象…)
- 非容器对象:str,int…
- 针对于每一个“容器对象”,通过一个变量gc_refs来记录当前对应的引用计数。
- 对于每个“容器对象”,找到它引用的“容器对象”,并将这个“容器对象”的引用计数-1
- 经过步骤3之后,如果一个“容器对象“的引用计数为0,就代表着玩意可以被回收了,肯定是“循环引用”导致它活到现在的
问题:检测所有的”容器对象“,效率很低。
如何提升查找”循环引用“的性能?
-
如果程序当中创建了很多个对象,而针对每一个对象都要参与”检测“过程,则会非常的耗费性能。
-
所以基于这个问题,产生了一个假设:越命大的对象,越长寿。假设一个对象10此检测都没给它干掉,那认定这个对象一定很长寿,就减少这对象的”检测频率“
-
基于这种假设,设计了一套机制:
分代回收
- 机制:
- 默认一个对象被创建出来后,属于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代检测
垃圾回收器中,新增的对象个数-消亡的对象个数,达到一定的阈值,才会触发垃圾检测。
*垃圾回收时机(掌握–简单)
-
自动回收
触发条件:
- 开启垃圾回收机制:
- 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代检测
- 开启垃圾回收机制:
-
手动回收
不论垃圾回收机制是否开启,手动回收设置可以独立执行。# 手动回收 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结果,需要等待函数执行完成的结果