看着小张准备回家换衣服了,小明有点失落,又有点孤单,于是说道:“逗逼张,你还要听吗?我准备讲类相关的知识了,这些可是我课后自学的哦~”
小张转了转身,一念间就留了下来~
类相关的基础知识如果忘记,可以查看之前的文章:https://www.cnblogs.com/dotnetcrazy/p/9202988.html
当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性:
# 定义一个类
class Person(object):
def __init__(self, name):
self.__name = name
def show(self):
print("中国欢迎你~", self.__name)
xiaoming = Person("小明")
xiaoming.show() # 正常调用
# 给实例动态添加一个属性
xiaoming.age = 22
print(xiaoming.age)
中国欢迎你~ 小明
22
# 其他实例是访问不到这个属性的
xiaopan = Person("小潘")
xiaopan.age
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-22-efcec543fe3f> in <module>()
1 # 其他实例是访问不到这个属性的
2 xiaopan = Person("小潘")
----> 3 xiaopan.age
AttributeError: 'Person' object has no attribute 'age'
"这个以前不是讲过嘛,动态添加属性,还有没有啥我不知道的知识了?"小张不屑的说道.
小明故作悬疑,抬头看着小张说道:“你知道怎么添加类属性吗?知道怎么添加方法吗?”
小张沉默不语,默默的看着小明讲课,随后心里想到:“这个坑货,话也不说全,还好现在是夏天,不然我早着凉了”
要想添加其他实例都可以访问的属性,可以给类添加一个类属性,用法和上面差不多,只是把对象改成类。
来看个案例:
# 给类动态添加一个属性
Person.age = 22
xiaoming = Person("小明")
print(xiaoming.age)
xiaopan = Person("小潘")
print(xiaopan.age)
22
22
小张,还记得讲装饰器的时候有这么一句代码吗?
types.MethodType(self, instance)
小张:"记得当时用类装饰实例方法的时候出现了问题,然后才加的?"
对头,以上面Person类为例,来一起看怎么动态添加方法
import types
class Person(object):
def __init__(self, name):
self.__name = name
def test(self):
print("测试一下")
def main():
xiaoming = Person("小明")
xiaoming.test = types.MethodType(test, xiaoming)
xiaoming.test()
if __name__ == '__main__':
main()
测试一下
你可以思考一下,为什么必须通过 types.MethodType
才行?(提示: self
)
注意一点,当你在新方法中调用类中私有方法时就会出问题
其实这个本质相当于通过实例对象调用里面公开属性
import types
class Person(object):
def __init__(self, name):
self.__name = name
# 一样的代码,只是调用了私有属性
def test(self):
print("中国欢迎你,%s" % self.__name)
def main():
xiaoming = Person("小明")
xiaoming.test = types.MethodType(test, xiaoming)
xiaoming.test() # 其实这个本质相当于通过实例对象调用里面公开属性
if __name__ == '__main__':
main()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-25-2bf92b457fc8> in <module>()
15
16 if __name__ == '__main__':
---> 17 main()
<ipython-input-25-2bf92b457fc8> in main()
12 xiaoming = Person("小明")
13 xiaoming.test = types.MethodType(test, xiaoming)
---> 14 xiaoming.test() # 其实这个本质相当于通过实例对象调用里面公开属性
15
16 if __name__ == '__main__':
<ipython-input-25-2bf92b457fc8> in test(self)
7 # 一样的代码,只是调用了私有属性
8 def test(self):
----> 9 print("中国欢迎你,%s" % self.__name)
10
11 def main():
AttributeError: 'Person' object has no attribute '__name'
看一下类方法和静态方法的案例:
# 类方法案例
class Person(object):
pass
@classmethod
def test(cls):
print(cls)
def main():
Person.test = test # 直接赋值即可
xiaoming = Person()
xiaoming.test()
if __name__ == '__main__':
main()
<class '__main__.Person'>
# 静态方法案例
class Person(object):
pass
@staticmethod
def test():
print("test")
def main():
Person.test = test
xiaoming = Person()
xiaoming.test()
if __name__ == '__main__':
main()
test
__slots__
这下小张急了,怎么又和上次讲得模块一样,无法无天了啊?有没有办法限制一下呢?
小明哈哈一笑,娓娓道来:
如果我们想要限制实例的属性怎么办?比如,只允许添加指定属性和方法?
# 定义一个类
class Person(object):
__slots__ = ("age", "name") # 用tuple定义允许绑定的属性名称
def show(self):
print("中国欢迎你~")
xiaoming = Person()
xiaoming.name="小明"
xiaoming.age = 22
xiaoming.qq = 110 # 不允许的属性就添加不了
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-28-2f9e13cdc435> in <module>()
9 xiaoming.name="小明"
10 xiaoming.age = 22
---> 11 xiaoming.qq = 110 # 不允许的属性就添加不了
AttributeError: 'Person' object has no attribute 'qq'
说几个测试后的结论:
__slots__
不一定是元组,你用列表也一样(推荐和官方一致)# 列表定义__slots__不会报错
class Person(object):
__slots__ = ["__name", "age", "gender"]
def __init__(self, name):
self.__name = name
def show(self):
print("中国欢迎你~")
xiaoming = Person("小明")
xiaoming.age = 22
xiaoming.gender = "男"
# 注意一个东西,如果你定义的私有属性不在元组内,也会报错
class Person(object):
__slots__ = ("age")
def __init__(self, name):
self.__name = name
def show(self):
print("中国欢迎你~")
xiaoming = Person("小明")
xiaoming.age = 22
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-30-0b85ac9c18af> in <module>()
9 print("中国欢迎你~")
10
---> 11 xiaoming = Person("小明")
12 xiaoming.age = 22
<ipython-input-30-0b85ac9c18af> in __init__(self, name)
4
5 def __init__(self, name):
----> 6 self.__name = name
7
8 def show(self):
AttributeError: 'Person' object has no attribute '_Person__name'
这个限制对实例方法一样有效,再复习下给实例对象添加方法:
import types
class Person(object):
__slots__ = ("__name", "age", "test")
def __init__(self, name):
self.__name = name
def show(self):
print("中国欢迎你~")
def test(self):
print("test")
xiaoming = Person("小明")
xiaoming.age = 22
xiaoming.test = types.MethodType(test, xiaoming)
xiaoming.test()
看看被限制之后:(Python中定义的方法相当于定义了一个属性,然后指向了定义的函数)
# 这个限制对实例方法一样有效
import types
class Person(object):
__slots__ = ("__name", "age")
def __init__(self, name):
self.__name = name
def show(self):
print("中国欢迎你~")
def test(self):
print("test")
xiaoming = Person("小明")
xiaoming.age = 22
xiaoming.test = types.MethodType(test, xiaoming)
xiaoming.test()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-31-d1bab7d57b40> in <module>()
15 xiaoming = Person("小明")
16 xiaoming.age = 22
---> 17 xiaoming.test = types.MethodType(test, xiaoming)
18 xiaoming.test()
AttributeError: 'Person' object has no attribute 'test'
小明讲得唾沫横飞,然后故作神秘的和小张说道:
测试结果:不影响
# 类方法案例
class Person(object):
__slots__ = ("name", "age")
pass
@classmethod
def test1(cls):
print("类方法")
@staticmethod
def test2():
print("静态方法")
def main():
Person.qq = 110
Person.test1 = test1 # 类方法
Person.test2 = test2 # 静态方法
xiaoming = Person()
print(xiaoming.qq)
xiaoming.test1()
xiaoming.test2()
if __name__ == '__main__':
main()
110
类方法
静态方法
__getattribute__
属性拦截器有点像C#里面的 Attribute
标签, AOP
其实就是这类的思想
更多可以参考如下链接:
动态添加属性和方法
反射以及魔法方法相关内容
制定类以及魔法方法相关内容
class Person(object):
def __init__(self, name):
self.__name = name
def show(self):
print(self.__name)
# 属性拦截器里面不要调用self.方法 or self.属性
def __getattribute__(self, obj):
print("obj:", obj)
if obj == "show":
print("do something")
elif obj == "_Person__name": # 注意这种情况,如果你想要访问私有属性,需要写出类名.属性
print("Log info : xxx")
return object.__getattribute__(self, obj) # 你重写了属性、方法获取的方式,别忘记返回对应的属性
def main():
p = Person("小明")
p.show()
if __name__ == '__main__':
main()
obj: show
do something
obj: _Person__name
Log info : xxx
小明
小张一脸懵逼的看着小明,然后说道:”就没有类似于C#里面的反射机制?“
小明背着手,缓缓的绕着小张走了一圈,那眼神仿佛是在看一件工艺艺术品一样,然后随口说道:
前面我们讲过了 type()
函数可以查看一个类型或变量的类型。比如说:
Person
是一个class,它的类型就是 type
,而 xiaoming
是一个实例,它的类型就是 classPerson
看个例子:
class Person(object):
pass
def main():
xiaoming = Person()
print(type(Person))
print(type(xiaoming))
if __name__ == '__main__':
main()
<class 'type'>
<class '__main__.Person'>
其实还可以通过 __class__
来查看创建对象的是谁:
class Person(object):
pass
def main():
xiaoming = Person()
print(Person.__class__)
print(xiaoming.__class__)
if __name__ == '__main__':
main()
<class 'type'>
<class '__main__.Person'>
小张被小明看的发毛,然后赶紧扯开话题说道:”怎么都是type?难道这个就是接下来准备讲的内容?“
小明点头说道:”是滴~“
我们说 class
的定义是运行时动态创建的,而创建 class
的方法就是使用 type()
函数
那怎么创建呢?以上面那个案例为摸版,来个案例:
类名=type("类名",父类们的Tuple,Dict)
def main():
Person = type("Person", (object, ), {})
xiaoming = Person()
print(Person.__class__)
print(xiaoming.__class__)
if __name__ == '__main__':
main()
<class 'type'>
<class '__main__.Person'>
小张感叹道:”Python的这种‘反射’太过简单了吧,我直接都可以写案例了“
比如,实现如下内容:
class Person(object):
def show(self):
print("父类方法:mmd")
class Student(Person):
gender = "男"
def __init__(self, name):
self.__name = name
def eat(self):
print("%s实例方法:大口吃饭" % self.__name)
@classmethod
def run(cls):
print("我是类方法:跑着上课")
@staticmethod
def sleep():
print("静态方法:晚安")
def main():
print(Student.gender)
xiaoming = Student("小明")
xiaoming.show()
xiaoming.eat()
xiaoming.run()
xiaoming.sleep()
if __name__ == '__main__':
main()
男
父类方法:mmd
小明实例方法:大口吃饭
我是类方法:跑着上课
静态方法:晚安
def show(self):
print("父类方法:mmd")
def __init__(self, name):
self.__name = name
def eat(self):
print("%s实例方法:大口吃饭" % self.__name)
@classmethod
def run(cls):
print("我是类方法:跑着上课")
@staticmethod
def sleep():
print("静态方法:晚安")
def main():
Person = type("Person", (object, ), {"show": show})
Student = type(
"Student", (Person, ), {
"gender": "男",
"__init__": __init__,
"eat": eat,
"run": run,
"sleep": sleep
})
print(Student.gender)
xiaoming = Student("小明")
xiaoming.show()
xiaoming.eat()
xiaoming.run()
xiaoming.sleep()
if __name__ == '__main__':
main()
男
父类方法:mmd
小明实例方法:大口吃饭
我是类方法:跑着上课
静态方法:晚安
metaclass
小明又仔细端详了小张一次,然后继续讲到:
当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。
但是如果我们想创建出类呢?那就必须根据 metaclass
创建出类,所以:先定义 metaclass
,然后创建类。
总的流程就是:先定义 metaclass
,再创建类,最后创建实例
type
就是Python在背后用来创建所有类的那个元类
小张有点恐慌的看了一眼小明,然后继续听讲
Python2是看看类里面有没有 __metaclass__
这个属性,有就通过它指向的函数或者方法来创建类
Python3简化了一下,在Class定义的时候就可以指定了,eg: classPerson(object,metaclass=type)
# 这三个参数其实就是type对应的三个参数
def create_class(name, bases, attrs):
attrs["name"] = "小明"
return type(name, bases, attrs)
class Person(object, metaclass=create_class):
pass
def main():
# 判断一个对象有没有某个属性
hasattr(Person, "name")
print(Person.name)
if __name__ == '__main__':
main()
小明
其实原类有点像刚刚讲的属性拦截器了,大概流程如下:
来一个正规化的写法,eg:给MyList添加一个 add
方法(list是append方法,别混淆了)
# metaclass是类的模板,所以必须从`type`类型派生:
class ListMetaclass(type):
def __new__(cls, name, bases, attrs):
attrs['add'] = lambda self, value: self.append(value)
return type.__new__(cls, name, bases, attrs)
class MyList(list, metaclass=ListMetaclass):
pass
def main():
mylist = MyList()
mylist.add("mmd")
print(mylist)
if __name__ == '__main__':
main()
['mmd']
元类一般ORM用的比较多(映射),如果你不编写ORM框架的话,基本上用不到
这方面可以参考这篇文章:尝试编写一个ORM框架
枚举类经常用,代码也很简单,继承一下Enum类就可以了, unique
用来防止重复的(重复会提示你)
from enum import Enum, unique
@unique
class StatusEnum(Enum):
# 待审核状态(0)默认
Pendding = 0
# 审核已通过(1)正常
Normal = 1
# 审核不通过(2)未删
Cancel = 2
# 已删除状态(99)假删
Delete = 99
# 调用:
StatusEnum.Delete
<StatusEnum.Delete: 99>
# 重复项测试
from enum import Enum, unique
@unique
class StatusEnum(Enum):
# 审核已通过(1)正常
Normal = 1
# 已删除状态(99)假删
Delete = 99
# 重复测试
Test = 99
# 调用:
StatusEnum.Delete
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-42-6a79f45cf1d9> in <module>()
3
4 @unique
----> 5 class StatusEnum(Enum):
6 # 审核已通过(1)正常
7 Normal = 1
~/anaconda3/lib/python3.6/enum.py in unique(enumeration)
832 ["%s -> %s" % (alias, name) for (alias, name) in duplicates])
833 raise ValueError('duplicate values found in %r: %s' %
--> 834 (enumeration, alias_details))
835 return enumeration
836
ValueError: duplicate values found in <enum 'StatusEnum'>: Test -> Delete
之前写的文章里面有提到过,可以简单回顾一下:(可变类型和不可变类型 引用数的引入)
其实程序员基本上关注,实在要关注的就是怎么显示回收:
import gc # 需要导入gc模块
print(gc.collect()) # 显式垃圾回收
print(gc.garbage) # 看回收了哪些
先看看之前讲可变类型和不可变类型说的一句话:
Python对int类型和较短的字符串进行了缓存,无论声明多少个值相同的变量,实际上都指向同个内存地址
看个案例:
a=10
b=10
c=10
print(id(a))
print(id(b))
print(id(c))
94747627400000
94747627400000
94747627400000
上面的ID都一样,那较短到底是多短呢?
先贴一下逆天的测试结果:(不要在编辑器里面测试,建议进入官方的python3交互模式,用vscode测试的结果不准)
小整数[-5,257)共用对象,常驻内存
,不在这个范围内的均创建一个新的对象单个字符共用对象,常驻内存
字符串:
其实也很好理解,第一个范围是程序员经常用的范围,字符串系列嘛就更正常了,老外肯定不管中文什么的,要是中国人发明的可以常用汉字常驻内存 ^_^
然后一篇文章里面单词出现频率肯定比词组和句子高,所以都能解释通了
来简单验证一下:
# 257的时候就取不到了,这时候都是不同的ID
# 这个就是所谓的大整数了(每一个大整数,均创建一个新的对象)
a=257
b=257
c=257
print(id(a))
print(id(b))
print(id(c))
140602139583728
140602139584112
140602139583792
# 单个字符
d='a'
e='a'
f='a'
print(id(d))
print(id(e))
print(id(f))
140602366927792
140602366927792
140602366927792
# 英文单词
str1 = "dog"
str2 = "dog"
str3 = "dog"
print(id(str1))
print(id(str2))
print(id(str3))
140602139175376
140602139175376
140602139175376
# 英文中有空格(句子,词组)
str4 = "big dog"
str5 = "big dog"
str6 = "big dog"
print(id(str4))
print(id(str5))
print(id(str6))
140602139174984
140602139174816
140602139175544
# 不共享对象,计数为0就删除
str7 = "明"
str8 = "明"
str9 = "明"
print(id(str7))
print(id(str8))
print(id(str9))
140602139296272
140602139296352
140602139296192
str10 = "小明"
str11 = "小明"
str12 = "小明"
print(id(str10))
print(id(str11))
print(id(str12))
140602139147320
140602139146616
140602139146792
str13 = "小 明"
str14 = "小 明"
str15 = "小 明"
print(id(str10))
print(id(str11))
print(id(str12))
140602139147320
140602139146616
140602139146792
再说说查看引用的时候注意一下: sys.getrefcount的参数object也会占1个引用计数
(sys.getrefcount(a)可以查看a对象的引用计数,但是比正常计数大1,因为调用函数的时候传入a,这会让a的引用计数+1)
这个是Python主要的一种垃圾回收方式(计数引用),看看源码:
参考链接:https://github.com/python/cpython/blob/master/Include/object.h
// 实际上没有任何东西被声明为PyObject,但是每个指向Python对象的指针都可以强制转换为PyObject(这是手工制作的继承)
typedef struct _object {
_PyObject_HEAD_EXTRA
Py_ssize_t ob_refcnt; /* 引用计数 */
struct _typeobject *ob_type;
} PyObject;
// 类似地,每个指向可变大小Python对象的指针都可以转换为PyVarObject
typedef struct {
PyObject ob_base;
Py_ssize_t ob_size; /* 可变变量引用计数 */
} PyVarObject;
# 引用计数
import sys
# 定义一个临时类
class Temp(object):
def __del__(self):
print("你被干掉了")
t1 = Temp()
print(sys.getrefcount(t1)) #(结果比实际引用大1)【object也会占1个引用计数】
t2 = t1
print(sys.getrefcount(t1))
print(sys.getrefcount(t2))
del t1
print(sys.getrefcount(t2))
# sys.getrefcount(t1)#被删掉自然没有了
del t2
print("-" * 10)
2
3
3
2
你被干掉了
----------
引用计数基本上可以解决大部分的问题,用起来比较简单,而且实时性比较高(一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时)
但对于循环引用,或者对于像双向链表这样的方式,就算引用对象删除了,它的计数还是1(相互引用嘛)
所以Python解释器用了另一种方法解决这个:
分代回收(隔代回收)
Python解释器设置了某些阀值,当达到了阀值就进行第一轮回收(大概是有循环引用的-1,然后看两个相互引用的对象现在的引用结果是不是都是0,如果都是0说明没有外部引用,那就是垃圾了),不是垃圾的移到第二个链表里面,当第二轮达到阀值的时候,进行第二轮回收(一轮的也回收下),不是垃圾的"老对象"移到第三个链表里面,当第三轮达到阀值的时候统统回收一波)
gc.get_count()
获取当前自动执行垃圾回收的计数器
gc.get_threshold()
获取的gc模块中自动执行垃圾回收的频率(可以自己设置)默认是:(700, 10, 10)
来看看阀值情况:
import gc
print(gc.get_count())
print(gc.get_threshold())
(234, 8, 1)
(700, 10, 10)
比如你新创建了1000个对象,才释放20个,就已经超过默认的700阀值,Python第一代检测就上场了(以此类推)
一般能活到最后的都不大可能是垃圾了,比如配置文件之类的,基本上不太改动的(越老越成精嘛)
小张若有所思的说道:
小明左右端详小张,终于忍不住说出了那句话:“小张,你能不能..."
话没说完就被小张打断了:”我是男的,不搞基!就是搞基也只喜欢我们班的培哥!“
小明吃惊的说道:”你想啥呢?我只是看你骨骼清奇,想要收你为徒罢了...“
(完)
经典引用:(参考1 参考2)
在Python中,每个对象都保存了一个称为引用计数的整数值,来追踪到底有多少引用指向了这个对象。无论何时,如果我们程序中的一个变量或其他对象引用了目标对象,Python将会增加这个计数值,而当程序停止使用这个对象,则Python会减少这个计数值。一旦计数值被减到零,Python将会释放这个对象以及回收相关内存空间。
从六十年代开始,计算机科学界就面临了一个严重的理论问题,那就是针对引用计数这种算法来说,如果一个数据结构引用了它自身,即如果这个数据结构是一个循环数据结构,那么某些引用计数值是肯定无法变成零的。
刚刚说到的例子中,我们以一个不是很常见的情况结尾:我们有一个“孤岛”或是一组未使用的、互相指向的对象,但是谁都没有外部引用。换句话说,我们的程序不再使用这些节点对象了,所以我们希望Python的垃圾回收机制能够足够智能去释放这些对象并回收它们占用的内存空间。但是这不可能,因为所有的引用计数都是1而不是0。Python的引用计数算法不能够处理互相指向自己的对象。
这就是为什么Python要引入Generational GC算法的原因!
Python使用一种不同的链表来持续追踪活跃的对象。而不将其称之为“活跃列表”,Python的内部C代码将其称为零代(Generation Zero)。每次当你创建一个对象或其他什么值的时候,Python会将其加入零代链表。
因为循环引用的原因,并且因为你的程序使用了一些比其他对象存在时间更长的对象,从而被分配对象的计数值与被释放对象的计数值之间的差异在逐渐增长。一旦这个差异累计超过某个阈值,则Python的收集机制就启动了,并且触发上边所说到的零代算法,释放“浮动的垃圾”,并且将剩下的对象移动到一代列表。
随着时间的推移,程序所使用的对象逐渐从零代列表移动到一代列表。而Python对于一代列表中对象的处理遵循同样的方法,一旦被分配计数值与被释放计数值累计到达一定阈值,Python会将剩下的活跃对象移动到二代列表。
通过这种方法,你的代码所长期使用的对象,那些你的代码持续访问的活跃对象,会从零代链表转移到一代再转移到二代。通过不同的阈值设置,Python可以在不同的时间间隔处理这些对象。Python处理零代最为频繁,其次是一代然后才是二代。
参考链接:
Python垃圾回收机制详解
经典之~画说 Ruby 与 Python 垃圾回收
使用 GC、Objgraph 干掉 Python 内存泄露与循环引用