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()

创建对象的过程:

  1. 创建一个对象
  2. Python解释器会自动的调用__init__方法
  3. 返回创建的对象的引用,给实例

__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__) 
# (<class '__main__.A'>, <class '__main__.Base'>, <class '__main__.T'>, <class 'object'>) 
# 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)共用对象,常驻内存
  • 单个字符共用对象,常驻内存
大整数对象池

每一个大整数,均创建一个新的对象。

clipboard.png

  • 大整数不共用内存,引用计数为0,销毁
  • 数值类型和字符串类型在Python中都是不可变的,意味着无法修改这个对象的值,每次对变量对修改,实际上是创建一个新的对象
intern机制
a1 = "HelloWorld"
a2 = "HelloWorld"
a3 = "HelloWorld"

intern机制,让它只占用一个”HelloWorld”所占的内存空间。靠引用计数去维护何时释放。

clipboard.png

  • 单个单词,不可修改,默认开启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. 导致引用计数+1的情况
    对象被创建,例如: a = 23
    对象被引用,例如: b = a
    对象被作为参数,传入到一个函数中,例如: func(a)
    对象作为一个元素,存储在容器中,例如: list1 = [a,a]
  2. 导致引用计数-1的情况
    对象的别名被显式销毁,例如: del a
    对象的别名被赋予新的对象,例如: a = 24
    一个对象离开它的作用域,例如f函数执行完毕时,func函数中的局部变量(全局变量不会)
    对象所在的容器被销毁,或从容器中删除对象
  3. 查看一个对象的引用计数

    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 = c2c2.t = c1后,这两块内存的引用计数变成2.
  • del c1后,内存1的对象的引用计数变为1,由于不是为0,所以内存1的对象不会被销毁,所以内存2的对象的引用数依然是2,在del c2后,应用计数也是1,内存1的对象,内存2的对象的引用数都是1.
  • 虽然他们两个对象都是可以被销毁的,但是由于循环引用,导致垃圾回收器不会回收它们,就导致内存泄漏.
垃圾回收

垃圾回收 = 垃圾检查 + 垃圾回收

有三种情况会触发垃圾回收:

  • 调用gc.coolect()
  • gc模块的计数器达到阀值的时候
  • 程序退出的时候
gc模块

gc模块作用:解决循环引用的问题

常用函数:

  1. gc.set_debug(flags)设置gcdebug日志。一般设置为gc.DEBUG_LEAK
  2. gc.collect([generation]) 显式进行垃圾回收,可以输入参数,0表示只检查第一代的对象,1代表检查一,二代对象,2代表检查一,二,三代的对象。如果不传入参数,执行一个full collection,也就是等于2,返回不可达(unreachable objects)对象的数目
  3. gc.get_threshold()获取的gc模块中自动执行垃圾回收的频率
  4. gc.set_threshold(threshold0[, threshold1[, threshold2])设置自动执行垃圾回收的频率
  5. gc.get_count()获取当前自动执行垃圾回收的计数器,返回一个长度为3的列表

Note:
gc模块唯一处理不了的是循环引用的类都有__del__方法,项目中要避免定义__del__方法。

class ClassA():
    pass
    # def __del__(self):
    #     print('object born,id:%s'%str(hex(id(self))))

alogy
1.3k 声望119 粉丝

// Designer and Developer


« 上一篇
Linux_压缩