define class
class
的三个组成部分:
- 类的名称:类名
- 类的属性: 一组数据
- 类的方法:允许对进行操作的方法(行为)
定义
class Student (object):
pass
class
后面定义类名(类名通常是大写开头的单词)(object)
,表示该类是从哪个类继承下来的
实例化
创建实例是通过类名+()
实现
stu = Student()
class Stu (): # 定义class
age = 10 # 属性
def show (self): # 方法
print(self.age) # 类中获取属性
print(self, 'self')
print(stu.name) # 获取类外添加属性
stu = Stu() # 实例化
stu.name = 'sf' # 添加属性
stu.show() # 调用方法
self
self
当前实例化的对象
在定义函数的时候,第一个参数需要self
class Stu ():
def show_name (self):
print(self.name)
stu = Stu()
stu.name = 'sf'
stu.show_name()
-
self
在定义时需要定义,但是在调用时会自动传入。 -
self
的名字并不是规定写死的,但是最好还是按照约定是self
。 -
self
总是指调用时的类的实例。
init
魔法方法:
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
__init__
: 类构造方法(魔法方法)
class Stu ():
# 初始化对象
def __init__ (self, new_name, new_age):
self.name = new_name
self.age = new_age
def show (self):
print('name: %s, age: %d' % (self.name, self.age))
stu = Stu('sf', 23)
stu.show()
创建对象的过程:
- 创建一个对象
-
Python
解释器会自动的调用__init__
方法 - 返回创建的对象的引用,给实例
__str__
: 实例化执行该方法,返回值。
当需要print
一个类的时候,需要先在类中定义__str__
方法,返回值,就是print()
输出的值
class Stu ():
def __init__ (self, new_name):
self.name = new_name
def __str__ (self):
return self.name
__new__
: 方法主要是当继承一些不可变的class
时(比如int, str, tuple), 提供一个自定义这些类的实例化过程.
class Stu(object):
def __new__(cls):
return object.__new__(cls) # 自定义实例化过程
# 自身没有能力创建实例,可以让父类创建
def __init__(self):
print('init')
stu = Stu()
创建单例对象
class Single(object):
__instance = None
def __new__(cls):
if cls.__instance != None:
return cls.__instance
else:
cls.__instance = object.__new__(cls)
return cls.__instance
s1 = Single()
s2 = Single()
私有方法和私有属性
私有属性
按照约定俗成的规定__
开头的属性表示私有属性, 不可以直接类名.变量名
访问
在类中存储的形式为:_Stu__age
, _类名__变量名
class Stu():
def __init__(self, new_name):
self.name = new_name
self.__age = 0 # 定义了一个私有的属性,属性的名字是`__age`
在类中访问形式:self.__变量名
私有方法(private)
按照约定俗成的规定__
开头的属性表示私有方法, 不可以直接类名.方法名
访问
存储的形式为:_Stu__get_age
, _类名__方法名
class Stu():
def __test(self): # 定义私有方法
pass
在类中调用私有方法:self.__方法名()
有些时候,会看到以一个下划线开头的实例变量名,比如_name
,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。
del
__del__
: 当删除一个对象时,python
解释器会默认调用一个魔术方法,__del__()
class Stu():
def __del__ (self):
print('remove obj')
stu = Stu()
del stu
在类的生命周期中,如果类销毁了,python
会自动调用__del__
方法。也就是说,不管是手动调用del
还是由python
自动回收都会触发__del__
方法执行。
对象引用个数
模块sys
中有一个getrefcount
方法可以测试对象的引用个数
返回的结果,会比实际结果大1
.
import sys
sys.getrefcount('变量/方法')
继承
class Animal(object):
def run(self):
print('Animal is running')
class Dog(Animal): # 继承
def run(self):
print('Dog is running')
class Cat(Animal):
pass
dog = Dog()
cat = Cat()
dog.run()
cat.run()
当子类和父类都存在相同的run()
方法时,子类的run()
覆盖了父类的run()
,在代码运行的时候,总是会调用子类的run()
-- 多态
重写
重写父类的方法,继承之后,子类定义和父类方法名一样的方法
class Animal(object):
def run(self):
print('Animal is running')
class Dog(Animal):
def run(self): # 重写
print('Dog is running')
class Cat(Animal):
pass
dog = Dog()
cat = Cat()
dog.run()
cat.run()
调用父类方法
- 类名调用
-
super
关键字调用
class Animal():
def run(self):
print('Animal is running')
class Dog (Animal):
def say(self):
# 第一种,类名调用。
# Animal.run(self) #方法必须传递参数`self`
# 第二种,super关键字
super().run()
print('Gog is running')
dog = Dog()
dog.say()
私有方法,私有属性在继承中的表现:
- 私有方法并不会被继承(子类外和子类内都不会让使用)
- 私有属性并不会被继承(子类外和子类内都不会让使用)
多继承
子类具有多个父类
class A(object): # object是所有最终类的终点
def test(self):
print('A')
class B:
def test(self):
print('B')
class C(A, B): # 多继承 (如果继承类中方法名或者属性名相同,生效的是参数先后顺序,`类名.__mro__`中的顺序)
pass
print(C.__mro__)
# (, , , )
# C3算法
c = C() # 子类也会重写父类的方法
多态
定义时的类型和运行时的类型不一样。
执行的时候确定
class D(object):
def _print(self):
print('D')
class X(D):
def _print(self):
print('X')
def introduce(temp):
temp._print()
d = D()
x = X()
introduce(d)
introduce(x)
isinstance()
判断一个对象是否是某种类型
实例属性属于各个实例所有,互不干扰;
类属性属于类所有,所有实例共享一个属性;
不要对实例属性和类属性使用相同的名字,否则将产生难以发现的错误。
类属性 & 实例属性
class A():
num = 1 # 类属性
def __init(self):
print(self.num)
a = A()
a.name = 100 # 实例属性
print(A.num) # 获取类属性
实例方法 & 类方法 & 静态方法
class A():
# 定义类方法
@classmethod
def add_num(cls): # 保存类的引用
pass
def get_num(self): # 实例方法
pass
@staticmethod
def set_num(): # 静态方法 # 可以没有任何参数引用
pass
a = A()
A.add_num() # 调用类方法
# a.add_num() # 实例调用类方法
A.set_num() # 调用静态方法
a.set_num() # 实例调用静态方法
私有化
xx
: 公有变量_x
: 单前置下划线,私有化属性或方法,from somemodule import *
禁止导入,类对象和子类可以访问__xx
: 双前置下划线,避免与子类中的属性名命名冲突,无法在外部直接访问__xx__
: 双前后下划线,命名空间的魔法对象或属性。__init__
。(开发时,不要使用这种定义变量方式)xx_
: 单后置下划线,用于避免与Python
关键词的冲突
property
作用:获取与设置自动调用方法
类属性方式调用property
:
class Money(object):
def __init(self):
pass
def getMoney(self):
pass
def setMoney(self, value):
pass
money = property(getMoney, setMoney) # key 为调用时候的key
m = Money()
m.money = 10
装饰器方式使用property
:
class Money(object):
def __init__(self):
self.__num = 0
@property
def money(self): # 函数名和调用key对应 # 获取
return self.__money
@money.setter
def money(self, value): # 获取
self.__num = value
m = Money()
print(m.money)
垃圾回收
小整数对象池
作用:为了优化速度,使用了小整数对象池,避免为整数频繁申请和销毁内存空间。
Python对小整数的定义是[-5, 257)这些整数对象都是提前建立好的,不会被垃圾回收。在一个Python的程序中,所有位于这个范围内的整数使用的都是同一个对象。
单个字母也是这样,但是定义2歌相同字符串时,引用计数为0,触发垃圾回收。
- 小整数[-5,257)共用对象,常驻内存
- 单个字符共用对象,常驻内存
大整数对象池
每一个大整数,均创建一个新的对象。
- 大整数不共用内存,引用计数为0,销毁
- 数值类型和字符串类型在Python中都是不可变的,意味着无法修改这个对象的值,每次对变量对修改,实际上是创建一个新的对象
intern机制
a1 = "HelloWorld"
a2 = "HelloWorld"
a3 = "HelloWorld"
intern机制,让它只占用一个”HelloWorld”所占的内存空间。靠引用计数去维护何时释放。
- 单个单词,不可修改,默认开启intern机制,共用对象,引用计数为0,则销毁
- 字符串(含有空格),不可修改,没开启intern机制,不共用对象,引用计数为0,销毁
Garbage collection(GC垃圾回收)
Python采用对是引用计数机制为主,标记-清除和分代收集两种机制为辅的策略
引用计数机制
Python里每一个东西都是对象,它们的核心就是一个结构体: PyObject
typedef struct_object {
int ob_refcnt; // 引用计数
struct_typeobject *ob_type;
} PyObject;
PyObject
是每个对象必有的内容,其中ob_refcnt
就是作为引用计数。当一个对象有新的引用时,它的ob_refcnt
就会增加,当引用它的对象被删除,它的ob_refcnt
就会减少。
当引用计数为0时,该对象生命就结束了。
引用计数机制的优点:
- 简单
- 实时性: 一旦没有引用,内存就直接释放了。不用像其它机制等待特定实际。
- 处理回收内存的时间分摊到了平时。
引用计数机制的缺点:
- 维护引用计数消耗资源
- 循环引用
垃圾回收机制
Python中的垃圾回收是以引用计数为主,分代收集为辅
- 导致引用计数+1的情况
对象被创建,例如:a = 23
对象被引用,例如:b = a
对象被作为参数,传入到一个函数中,例如:func(a)
对象作为一个元素,存储在容器中,例如:list1 = [a,a]
- 导致引用计数-1的情况
对象的别名被显式销毁,例如:del a
对象的别名被赋予新的对象,例如:a = 24
一个对象离开它的作用域,例如f函数
执行完毕时,func函数
中的局部变量(全局变量不会)
对象所在的容器被销毁,或从容器中删除对象 -
查看一个对象的引用计数
import sys a = "hello world" sys.getrefcount(a)
可以查看a对象的引用计数,但是比正常计数大1,因为调用函数的时候传入a,这会让a的引用计数+1
循环引用导致内存泄露
引用计数的缺陷是循环引用的问题
import gc
class ClassA():
def __init__(self):
print('object born,id:%s'%str(hex(id(self))))
def f2():
while True:
c1 = ClassA()
c2 = ClassA()
c1.t = c2
c2.t = c1
del c1
del c2
# 把python的gc关闭
gc.disable()
f2()
执行f2()
,进程占用的内存会不断增大。
- 创建了
c1
,c2
后这两块内存的引用计数都是1,执行c1.t = c2
和c2.t = c1
后,这两块内存的引用计数变成2. - 在
del c1
后,内存1的对象的引用计数变为1,由于不是为0,所以内存1的对象不会被销毁,所以内存2的对象的引用数依然是2,在del c2
后,应用计数也是1,内存1的对象,内存2的对象的引用数都是1. - 虽然他们两个对象都是可以被销毁的,但是由于循环引用,导致垃圾回收器不会回收它们,就导致内存泄漏.
垃圾回收
垃圾回收 = 垃圾检查 + 垃圾回收
有三种情况会触发垃圾回收:
- 调用
gc.coolect()
- 当
gc模块
的计数器达到阀值的时候 - 程序退出的时候
gc模块
gc模块
作用:解决循环引用的问题
常用函数:
-
gc.set_debug(flags)
设置gc
的debug
日志。一般设置为gc.DEBUG_LEAK
-
gc.collect([generation])
显式进行垃圾回收,可以输入参数,0表示只检查第一代的对象,1代表检查一,二代对象,2代表检查一,二,三代的对象。如果不传入参数,执行一个full collection
,也就是等于2,返回不可达(unreachable objects)
对象的数目 -
gc.get_threshold()
获取的gc
模块中自动执行垃圾回收的频率 -
gc.set_threshold(threshold0[, threshold1[, threshold2])
设置自动执行垃圾回收的频率 -
gc.get_count()
获取当前自动执行垃圾回收的计数器,返回一个长度为3的列表
Note:gc
模块唯一处理不了的是循环引用的类都有__del__
方法,项目中要避免定义__del__
方法。
class ClassA():
pass
# def __del__(self):
# print('object born,id:%s'%str(hex(id(self))))