澳门新浦京娱乐场网站-www.146.net-新浦京娱乐场官网
做最好的网站

澳门新浦京娱乐场网站:高档编制程序④元类,

 

1.1==,is的使用

 

一元类

浅谈Python的垃圾回收机制,浅谈python垃圾回收

一.垃圾回收机制

Python中的垃圾回收是以引用计数为主,分代收集为辅。引用计数的缺陷是循环引用的问题。
在Python中,如果一个对象的引用数为0,Python虚拟机就会回收这个对象的内存。

#encoding=utf-8
__author__ = '[email protected]'

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

def f1():
  while True:
    c1=ClassA()
    del c1

执行f1()会循环输出这样的结果,而且进程占用的内存基本不会变动

object born,id:0x237cf58
object del,id:0x237cf58

c1=ClassA()会创建一个对象,放在0x237cf58内存中,c1变量指向这个内存,这时候这个内存的引用计数是1
del c1后,c1变量不再指向0x237cf58内存,所以这块内存的引用计数减一,等于0,所以就销毁了这个对象,然后释放内存。

导致引用计数 1的情况
对象被创建,例如a=23
对象被引用,例如b=a
对象被作为参数,传入到一个函数中,例如func(a)
对象作为一个元素,存储在容器中,例如list1=[a,a]
导致引用计数-1的情况
对象的别名被显式销毁,例如del a
对象的别名被赋予新的对象,例如a=24
一个对象离开它的作用域,例如f函数执行完毕时,func函数中的局部变量(全局变量不会)
对象所在的容器被销毁,或从容器中删除对象

demo

def func(c,d):
  print 'in func function', sys.getrefcount(c) - 1


print 'init', sys.getrefcount(11) - 1
a = 11
print 'after a=11', sys.getrefcount(11) - 1
b = a
print 'after b=1', sys.getrefcount(11) - 1
func(11)
print 'after func(a)', sys.getrefcount(11) - 1
list1 = [a, 12, 14]
print 'after list1=[a,12,14]', sys.getrefcount(11) - 1
a=12
print 'after a=12', sys.getrefcount(11) - 1
del a
print 'after del a', sys.getrefcount(11) - 1
del b
print 'after del b', sys.getrefcount(11) - 1
# list1.pop(0)
# print 'after pop list1',sys.getrefcount(11)-1
del list1
print 'after del list1', sys.getrefcount(11) - 1

输出:

init 24
after a=11 25
after b=1 26
in func function 28
after func(a) 26
after list1=[a,12,14] 27
after a=12 26
after del a 26
after del b 25
after del list1 24

问题:为什么调用函数会令引用计数 2

查看一个对象的引用计数
sys.getrefcount(a)可以查看a对象的引用计数,但是比正常计数大1,因为调用函数的时候传入a,这会让a的引用计数 1

二.循环引用导致内存泄露

def f2():
  while True:
    c1=ClassA()
    c2=ClassA()
    c1.t=c2
    c2.t=c1
    del c1
    del c2

执行f2(),进程占用的内存会不断增大。

object born,id:0x237cf30
object born,id:0x237cf58

创建了c1,c2后,0x237cf30(c1对应的内存,记为内存1),0x237cf58(c2对应的内存,记为内存2)这两块内存的引用计数都是1,执行c1.t=c2和c2.t=c1后,这两块内存的引用计数变成2.

在del c1后,内存1的对象的引用计数变为1,由于不是为0,所以内存1的对象不会被销毁,所以内存2的对象的引用数依然是2,在del c2后,同理,内存1的对象,内存2的对象的引用数都是1。

虽然它们两个的对象都是可以被销毁的,但是由于循环引用,导致垃圾回收器都不会回收它们,所以就会导致内存泄露。

三.垃圾回收

deff3():
  # print gc.collect()
  c1=ClassA()
  c2=ClassA()
  c1.t=c2
  c2.t=c1
  del c1
  del c2
  print gc.garbage
  print gc.collect() #显式执行垃圾回收
  print gc.garbage
  time.sleep(10)
if __name__ == '__main__':
  gc.set_debug(gc.DEBUG_LEAK) #设置gc模块的日志
  f3()

输出:

Python

gc: uncollectable <ClassA instance at 0230E918>
gc: uncollectable <ClassA instance at 0230E940>
gc: uncollectable <dict 0230B810>
gc: uncollectable <dict 02301ED0>
object born,id:0x230e918
object born,id:0x230e940

4
垃圾回收后的对象会放在gc.garbage列表里面
gc.collect()会返回不可达的对象数目,4等于两个对象以及它们对应的dict

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

1.调用gc.collect(),
2.当gc模块的计数器达到阀值的时候。
3.程序退出的时候

四.gc模块常用功能解析

Garbage Collector interface

gc模块提供一个接口给开发者设置垃圾回收的选项。上面说到,采用引用计数的方法管理内存的一个缺陷是循环引用,而gc模块的一个主要功能就是解决循环引用的问题。

常用函数:

gc.set_debug(flags)
设置gc的debug日志,一般设置为gc.DEBUG_LEAK

gc.collect([generation])
显式进行垃圾回收,可以输入参数,0代表只检查第一代的对象,1代表检查一,二代的对象,2代表检查一,二,三代的对象,如果不传参数,执行一个full collection,也就是等于传2。
返回不可达(unreachable objects)对象的数目

gc.set_threshold(threshold0[, threshold1[, threshold2])
设置自动执行垃圾回收的频率。

gc.get_count()
获取当前自动执行垃圾回收的计数器,返回一个长度为3的列表

gc模块的自动垃圾回收机制

必须要import gc模块,并且is_enable()=True才会启动自动垃圾回收。
这个机制的主要作用就是发现并处理不可达的垃圾对象。
垃圾回收=垃圾检查 垃圾回收
在Python中,采用分代收集的方法。把对象分为三代,一开始,对象在创建的时候,放在一代中,如果在一次一代的垃圾检查中,改对象存活下来,就会被放到二代中,同理在一次二代的垃圾检查中,该对象存活下来,就会被放到三代中。

gc模块里面会有一个长度为3的列表的计数器,可以通过gc.get_count()获取。
例如(488,3,0),其中488是指距离上一次一代垃圾检查,Python分配内存的数目减去释放内存的数目,注意是内存分配,而不是引用计数的增加。例如:

print gc.get_count() # (590, 8, 0)
a = ClassA()
print gc.get_count() # (591, 8, 0)
del a
print gc.get_count() # (590, 8, 0)

3是指距离上一次二代垃圾检查,一代垃圾检查的次数,同理,0是指距离上一次三代垃圾检查,二代垃圾检查的次数。

gc模快有一个自动垃圾回收的阀值,即通过gc.get_threshold函数获取到的长度为3的元组,例如(700,10,10)
每一次计数器的增加,gc模块就会检查增加后的计数是否达到阀值的数目,如果是,就会执行对应的代数的垃圾检查,然后重置计数器

例如,假设阀值是(700,10,10):

当计数器从(699,3,0)增加到(700,3,0),gc模块就会执行gc.collect(0),即检查一代对象的垃圾,并重置计数器为(0,4,0)
当计数器从(699,9,0)增加到(700,9,0),gc模块就会执行gc.collect(1),即检查一、二代对象的垃圾,并重置计数器为(0,0,1)
当计数器从(699,9,9)增加到(700,9,9),gc模块就会执行gc.collect(2),即检查一、二、三代对象的垃圾,并重置计数器为(0,0,0)

其他

如果循环引用中,两个对象都定义了__del__方法,gc模块不会销毁这些不可达对象,因为gc模块不知道应该先调用哪个对象的__del__方法,所以为了安全起见,gc模块会把对象放到gc.garbage中,但是不会销毁对象。

五.应用

  • 项目中避免循环引用
  • 引入gc模块,启动gc模块的自动清理循环引用的对象机制
  • 由于分代收集,所以把需要长期使用的变量集中管理,并尽快移到二代以后,减少GC检查时的消耗
  • gc模块唯一处理不了的是循环引用的类都有__del__方法,所以项目中要避免定义__del__方法,如果一定要使用该方法,同时导致了循环引用,需要代码显式调用gc.garbage里面的对象的__del__来打破僵局

一.垃圾回收机制 Python中的垃圾回收是以引用计数为主,分代收集为辅。引用计数的缺陷是循...

上次知识回顾:

·is是比较两个引用是否指向了同一个对象(引用比较)。

在线编程
在线预览:http://github.lesschina.com/python/base/oop/1.封装.html

1类也是对象

在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段。在Python中这一点仍然成立。

但是,Python中的类还远不止如此。类同样也是一种对象。

类对象拥有创建对象(实例对象)的能力。但是,它的本质仍然是一个对象,于是乎你可以对它做如下的操作:

1.你可以将它赋值给一个变量

2.你可以拷贝它

3.你可以为它增加属性

4.你可以将它作为函数参数进行传递

澳门新浦京娱乐场网站 1

代码裤子:

·==是比较两个对象是否相等。

这次尽量用故事模式来讲知识,上次刚说美化,这次算是第一篇了。步入正题:

2动态的创建类

澳门新浦京娱乐场网站 2

在线编程:

1.2深拷贝、浅拷贝

1.定义一个类¶

类的组成:类名、属性(没有字段)、方法

3使用type创建类

type还有一种完全不同的功能,动态的创建类。

type可以接受一个类的描述作为参数,然后返回一个类。(要知道,根据传入参数的不同,同一个函数拥有两种完全不同的用法是一件很傻的事情,但这在Python中是为了保持向后兼容性)

type可以像这样工作:

type(类名,由父类名称组成的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))

澳门新浦京娱乐场网站 3

在线预览:http://github.lesschina.com/python/base/ext/基础拓展.html

1.2.1浅拷贝

1.1创建一个类¶

In [1]:

# 类名首字母大写
class Student(object):
    """创建一个学生类"""
    # 没有属性定义,直接使用即可
    # 定义一个方法,方法里面必须有self(相当于C#的this)
    def show(self):
        print("name:%s age:%d"%(self.name,self.age))

In [2]:

# 实例化一个张三
zhangsan=Student()
# 给name,age属性赋值
zhangsan.name="张三"
zhangsan.age=22
# 调用show方法
zhangsan.show()

 

name:张三 age:22

In [3]:

# 打印一下类和类的实例
print(Student)
print(zhangsan) #张三实例的内存地址:0x7fb6e8502d30

 

<class '__main__.Student'>
<__main__.Student object at 0x7fe961195b70>

 

和静态语言不同,Python允许对实例变量绑定任何数据 ==> 对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称可能都不同

说的比较抽象,举个例子就明了了:

In [4]:

xiaoming=Student("小明",22)
xiaoming.mmd="mmd"
print(xiaoming.mmd)

# 小明和小潘都是Student类,但是小明有的mmd属性,小潘却没有
xiaopan=Student("小潘",22)
print(xiaopan.mmd)

 

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-500940527165> in <module>()
----> 1xiaoming=Student("小明",22)
      2 xiaoming.mmd="mmd"
      3 print(xiaoming.mmd)
      4 
      5 # 小明和小潘都是Student类,但是小明有的mmd属性,小潘却没有

TypeError: object() takes no parameters

 

4使用type创建带有属性的类

澳门新浦京娱乐场网站 4

Foochild继承了Test2的bar属性

澳门新浦京娱乐场网站 5

·type的第2个参数,元组中是父类的名字,而不是字符串

·添加的属性是类属性,并不是实例属性

终于期末考试结束了,聪明的小明同学现在当然是美滋滋的过暑假了,左手一只瓜,右手一本书~正在给老乡小张同学拓展他研究多日的知识点

浅拷贝是对于一个对象的顶层拷贝

1.2使用__init__初始化赋值¶

创建对象后,python解释器默认调用__init__方法,对必要字段进行初始化赋值

需要注意的是:__init__并不是C#中的构造函数,__new__ (后面会说) __init__ 等价于构造函数

第一个参数和类的其他方法一样,都是self(相当于C#里面的this,表示创建的实例本身)调用的时候直接忽略它

In [5]:

class Student(object):
    # 初始化赋值
    def __init__(self,name,age):
        self.name=name
        self.age=age

    def show(self):
        print("name:%s age:%d"%(self.name,self.age))

In [6]:

# 有了__init__方法,在创建实例的时候,就不能传入空的参数了
lisi=Student()

 

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-6-1ba88e24910b> in <module>()
      1 # 有了__init__方法,在创建实例的时候,就不能传入空的参数了
----> 2lisi=Student()

TypeError: __init__() missing 2 required positional arguments: 'name' and 'age'

In [7]:

# 创建一个正确的实例
xiaowang=Student("小王",22)
xiaowang.show()

 

name:小王 age:22

 

5使用type创建带有方法的类

为type创建的类添加实例方法

澳门新浦京娱乐场网站 6

为type创建的类添加类方法

澳门新浦京娱乐场网站 7

为type创建的类添加静态方法

澳门新浦京娱乐场网站 8

1.NetCore装饰器模式¶

装饰器这次从C#开始引入,上次刚讲迭代器模式,这次把装饰器模式也带一波(纯Python方向的可以选择性跳过,也可以当扩展)

其实通俗讲就是,给原有对象动态的添加一些额外的职责(毕竟动不动就改类你让其他调用的人咋办?也不符合开放封闭原则是吧~)

举个简单的例子:()

BaseComponent.cs

/// <summary>
/// 组件的抽象父类
/// </summary>
public abstract class BaseComponent
{
    /// <summary>
    /// 定义一个登录的抽象方法
    /// 其他方法,这边省略
    /// </summary>
    public abstract string Login();
}

LoginComponent.cs

/// <summary>
/// 默认登录组件(账号 密码)
/// 其他方法省略
/// 友情提醒一下,抽象类里面可以定义非抽象方法
/// </summary>
public class LoginComponent : BaseComponent
{
    public override string Login()
    {
        return "默认账号密码登录";
    }
}

默认调用:

static void Main(string[] args)
{
    var obj = new LoginComponent();
    var str = obj.Login();
    Console.WriteLine(str);
}

如果这时候平台需要添加微信第三方登录,怎么办?一般都是用继承来解决,其实还可以通过灵活的装饰器来解决:(好处可以自己体会)

先定义一个通用装饰器(不一定针对登录,注册等等只要在BaseComponent中的都能用)

/// <summary>
/// 装饰器
/// </summary>
public class BaseDecorator : BaseComponent
{
    protected BaseComponent _component;
    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="obj">登录组件对象</param>
    protected BaseDecorator(BaseComponent obj)
    {
        this._component = obj;
    }
    public override string Login()
    {
        string str = string.Empty;
        if (_component != null) str = _component.Login();
        return str;
    }
}

现在根据需求添加微信登录:(符合开放封闭原则)

/// <summary>
/// 默认登录组件(账号 密码)
/// 其他方法省略
/// </summary>
public class WeChatLoginDecorator : BaseDecorator
{
    public WeChatLoginDecorator(BaseComponent obj) : base(obj)
    {
    }
    /// <summary>
    /// 添加微信第三方登录
    /// </summary>
    /// <returns></returns>
    public string WeChatLogin()
    {
        return "add WeChatLogin";
    }
}

调用:(原有系统该怎么用就怎么用,新系统可以使用装饰器来添加新功能)

static void Main(string[] args)
{
    #region 登录模块V2
    // 实例化登录装饰器
    var loginDecorator = new WeChatLoginDecorator(new LoginComponent());
    // 原有的登录方法
    var str1 = loginDecorator.Login();
    // 现在新增的登录方法
    var str2 = loginDecorator.WeChatLogin();
    Console.WriteLine($"{str1}n{str2}");
    #endregion
}

结果:

默认账号密码登录
add WeChatLogin

如果再加入QQ和新浪登录的功能就再添加一个V3版本的装饰器,继承当时V2版本的登录即可(版本迭代特别方便)

/// <summary>
/// 默认登录组件(账号 密码)
/// 其他方法省略
/// </summary>
public class LoginDecoratorV3 : WeChatLoginDecorator
{
    public LoginDecoratorV3(BaseComponent obj) : base(obj)
    {
    }

    /// <summary>
    /// 添加QQ登录
    /// </summary>
    /// <returns></returns>
    public string QQLogin()
    {
        return "add QQLogin";
    }

    /// <summary>
    /// 添加新浪登录
    /// </summary>
    /// <returns></returns>
    public string SinaLogin()
    {
        return "add SinaLogin";
    }
}

调用:

static void Main(string[] args)
{
    #region 登录模块V3
    // 实例化登录装饰器
    var loginDecoratorV3 = new LoginDecoratorV3(new LoginComponent());
    // 原有的登录方法
    var v1 = loginDecoratorV3.Login();
    // 第二个版本迭代中的微信登录
    var v2 = loginDecoratorV3.WeChatLogin();
    // 新增的QQ和新浪登录
    var qqLogin = loginDecoratorV3.QQLogin();
    var sinaLogin = loginDecoratorV3.SinaLogin();
    Console.WriteLine($"{v1}n{v2}n{qqLogin}n{sinaLogin}");
    #endregion
}

结果:

默认账号密码登录
add WeChatLogin
add QQLogin
add SinaLogin

其实还有很多用处,比如原有系统缓存这块当时考虑不到,现在并发来了,已经上线了,原有代码又不太敢大幅度修改,这时候装饰器就很方便的给某些功能添加点缓存、测试、日记等等系列功能(AOP里面很多这种概念)

实际场景说的已经很明白了,其他的自己摸索一下吧

 

拷贝了引用,并没有拷贝内容

1.3使用魔法方法__str__¶

在print(类名)的时候自定义输出

这个有点像C#类里面重写ToString,eg:

public override string ToString()
{
    return "Name:"   this.Name   " Age:"   this.Age;
}

In [8]:

# Python的__str__()方法
class Student(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    # self别忘记写了,return也别忘了
    def __str__(self):
        return "姓名:%s,年龄:%s" % (self.name, self.age)

In [9]:

lisi = Student("李四", 22)
print(lisi) #现在打印就是你DIV的输出了

 

姓名:李四,年龄:22

 

6什么是元类?

元类就是创建类的‘东西’,在python中万物皆对象,类也是对象。

type就是一个元类,可以用来创建类。

Python中所有的东西,注意,我是指所有的东西——都是对象。这包括整数、字符串、函数以及类。它们全部都是对象,而且它们都是从一个类创建而来,这个类就是type。

元类就是创建类这种对象的东西。type就是Python的内建元类,当然了,你也可以创建自己的元类。

2.Python装饰器¶

那Python怎么实现装饰器呢?小胖问道。

小明屁颠屁颠的跑过去说道,通过闭包咯~(闭包如果忘了,可以回顾一下)

1.2.2深拷贝

1.4 私有属性、方法¶

C#、Java里面都是有访问修饰符的,Python呢?

Python规定,如果以双下划线__开头的属性或者方法就是私有的

变量名类似xxx的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量。特殊变量是可以直接访问的,不是private变量

在说私有属性前,我们来个案例说说属性不私有的弊端,eg:

小明同学学了点C#,然后学习了上面的知识,心想 ~ Python这么搞安全性呢?不行,我得构造构造,于是有了下面的代码:

In [10]:

class Student(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def get_name(self):
        return self.name

    def set_name(self, name):
        self.name = name

    def get_age(self):
        return self.age

    def set_age(self, age):
        if age > 0:
            self.age = age
        else:
            print("age must > 0")

    def show(self):
        print("name:%s,age:%d" % (self.name, self.age))

 

小明心想,想要修改age属性,你通过set_age我就可以判断了哇,还是本宝宝聪明

这时候小潘过来了,淡淡的一笑,看我怎么破了你 ~ 看代码:

In [11]:

zhangsan = Student("张三", -20)
zhangsan.show()  # name:张三,age:-20
zhangsan.age = -1  # set_age方法形同虚设,我完全可以直接访问字段了
zhangsan.show()  # name:张三,age:-1

 

name:张三,age:-20
name:张三,age:-1

 

小潘傲气的说道~大叔,给你脸呢。我就是不去访问你设定的方法怎么滴呢?

小明急的啊,赶紧去找伟哥求经。不一会,傲气的贴出自己的New Code,心想着我私有属性都用上了还怕个毛毛:

In [12]:

class Student(object):
    def __init__(self, name, age):
        self.__name = name
        # 一般需要用到的属性都直接放在__init__里面了
        # self.__age = age
        self.set_age(age)

    def get_name(self):
        return self.__name

    def set_name(self, name):
        self.__name = name

    def get_age(self):
        return self.__age

    def set_age(self, age):
        if age > 0:
            self.__age = age
        else:
            print("age must > 0")

    def show(self):
        print("name:%s,age:%s" % (self.__name, self.__age))

 

小潘冷笑道~呵呵,然后使用了上次的绝招:

In [13]:

zhangsan = Student("张三", -20)
zhangsan.__age = -1  # 同样的代码,只是属性前面加了下划线
zhangsan.show()

 

age must > 0

 

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-13-82c41ff46846> in <module>()
      1 zhangsan = Student("张三", -20)
      2 zhangsan.__age = -1  # 同样的代码,只是属性前面加了下划线
----> 3zhangsan.show()

<ipython-input-12-1dec32486a19> in show(self)
     22 
     23     def show(self):
---> 24print("name:%s,age:%s" % (self.__name, self.__age))

AttributeError: 'Student' object has no attribute '_Student__age'

 

这次小潘同志傻眼了,完全不能访问了啊?不行,怎么能被小明大叔笑话呢?

于是上网翻资料,国内不行就国外,外文不好就翻译,终于找到一个新破解方式:

双下划线开头的实例变量不能直接访问,是因为Python解释器对外把__age变量改成了_Studentage,所以,仍然可以通过**_Studentage**来访问:

In [14]:

# 搞事情
zhangsan._Student__age = -1
zhangsan.show()

 

name:张三,age:-1

 

建议你不要这么干,不同版本的Python解释器可能会把__age改成不同的变量名

有些时候,你会看到以一个下划线开头的实例变量名,比如_age这样的实例变量,外部是可以访问的。

但是,请把它视为私有变量,不要随意访问(Python很多东西全凭自觉~捂脸@_@)

小潘终于长叹一口气,然后还不忘取笑小明同学~你这属性搞的,真麻烦,总是通过方法调用,太累了 <_> 鄙视!

这可把小明急的啊,学习的积极性都没有了,吃了碗牛肉面就去伟哥那边好好取经了~

In [15]:

# 私有方法一笔带过
class Student(object):
    """私有方法"""
    def __go_home(self):
        pass


zhangsan = Student()
zhangsan.__go_home() # 访问不到

 

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-15-45c76191b808> in <module>()
      7 
      8 zhangsan = Student()
----> 9zhangsan.__go_home() # 访问不到

AttributeError: 'Student' object has no attribute '__go_home'

 

7__metaclass__属性

你可以在定义一个类的时候为其添加__metaclass__属性。

classFoo(object):

__metaclass__ = something…

...省略...

如果你这么做了,Python就会用元类来创建类Foo。小心点,这里面有些技巧。你首先写下class Foo(object),但是类Foo还没有在内存中创建。Python会在类的定义中寻找__metaclass__属性,如果找到了,Python就会用它来创建类Foo,如果没有找到,就会用内建的type来创建这个类。把下面这段话反复读几次。当你写如下代码时:

classFoo(Bar):

pass

Python做了如下的操作:

1.Foo中有__metaclass__这个属性吗?如果是,Python会通过__metaclass__创建一个名字为Foo的类(对象)

2.如果Python没有找到__metaclass__,它会继续在Bar(父类)中寻找__metaclass__属性,并尝试做和前面同样的操作。

3.如果Python在任何父类中都找不到__metaclass__,它就会在模块层次中去寻找__metaclass__,并尝试做同样的操作。

4.如果还是找不到__metaclass__,Python就会用内置的type来创建这个类对象。

现在的问题就是,你可以在__metaclass__中放置些什么代码呢?答案就是:可以创建一个类的东西。那么什么可以用来创建一个类呢?type,或者任何使用到type或者子类化type的东东都可以。

2.1.装饰器引入¶

来看一个应用场景,以前老版本系统因为并发比较小,没考虑到缓存

def get_data():
    print("直接数据库读取数据")

def main():
    get_data()

if __name__ == '__main__':
    main()

在不修改原有代码的前提下咋办?我们参照C#和Java写下如下代码:

In [1]:

# 添加一个闭包
def cache(func):
    def decorator():
        print("给功能添加了缓存")
        if True:
            pass
        else:
            func()# 如果缓存失效则读取数据库获取新的数据
    return decorator

def get_data():
    print("直接数据库读取数据")

def main():
    f1 = cache(get_data)
    f1()
    print(type(f1))

if __name__ == '__main__':
    main()

 

给功能添加了缓存
<class 'function'>

 

小张问道:“怎么也这么麻烦啊,C#的那个我就有点晕了,怎么Python也这样啊?”f1 = cache(get_data) f1()

小明哈哈一笑道:“人生苦短,我用Python~这句话可不是随便说着玩的,来来来,看看Python的语法糖”:

In [2]:

def cache(func):
    def wrapper():
        print("给功能添加了缓存")
        if True:
            pass
        else:
            func()  # 如果缓存失效则读取数据库获取新的数据
    return wrapper

@cache
def get_data():
    print("直接数据库读取数据")

def main():
    get_data()

if __name__ == '__main__':
    main()

 

给功能添加了缓存

 

其实

@cache
def get_data()

等价于

# 把f1改成函数名字罢了。可以这么理解:get_data重写指向了一个新函数
get_data = cache(get_data)

小张同学瞪了瞪眼睛,努力回想着以前的知识点,然后脱口而出:“这不是我们之前讲的属性装饰器吗?而且好方便啊,这完全符合开放封闭原则啊!“

class Student(object):
    def __init__(self, name, age):
        # 一般需要用到的属性都直接放在__init__里面了
        self.name = name
        self.age = age

    @property
    def name(self):
        return self.__name

    @name.setter
    def name(self, name):
        self.__name = name

    @property
    def age(self):
        return self.__age

    @age.setter
    def age(self, age):
        if age > 0:
            self.__age = age
        else:
            print("age must > 0")

    def show(self):
        print("name:%s,age:%s" % (self.name, self.age))

小明也愣了愣,说道:”也对哦,你不说我都忘了,我们学习面向对象三大特性的时候经常用呢,怪不得这么熟悉呢“

随后又嘀咕了一句:”我怎么不知道开放封闭原则...“

小张嘲笑道:”这你都不知道?对扩展开放,对已经实现的代码封闭嘛~“

In [3]:

# 需要注意一点
def cache(func):
    print("装饰器开始装饰")
    def wrapper():
            print("给功能添加了缓存")
            if True:
                pass
            else:
                func()  # 如果缓存失效则读取数据库获取新的数据
    return wrapper

@cache # 当你写这个的时候,装饰器就开始装饰了,闭包里面的功能是你调用的时候执行
def get_data():
    print("直接数据库读取数据")

 

装饰器开始装饰

 

深拷贝是对于一个对象所有层次的拷贝(递归)

1.5 装饰器,让方法像属性那样便利¶

Python内置的@property装饰器就是负责把一个方法变成属性调用的,来个例子

In [16]:

class Student(object):
    def __init__(self, name, age):
        # 一般需要用到的属性都直接放在__init__里面了
        self.name = name
        self.age = age

    @property
    def name(self):
        return self.__name

    @name.setter
    def name(self, name):
        self.__name = name

    @property
    def age(self):
        return self.__age

    @age.setter
    def age(self, age):
        if age > 0:
            self.__age = age
        else:
            print("age must > 0")

    def show(self):
        print("name:%s,age:%s" % (self.name, self.age))

In [17]:

xiaoming = Student("小明", 22)
xiaoming.name = "小潘"
xiaoming.age = -2
xiaoming.show()

 

age must > 0
name:小潘,age:22

 

把一个getter方法变成属性,只需要加上@property就可以了

@方法名.setter,负责把一个setter方法变成属性赋值

当然了,如果只想读 ==> 就只打上@property标签

小明同学高兴坏了,赶紧大吃了一顿~


8自定义元类

newAttr = {}

forname,valueinfuture_class_attr.items():

ifnotname.startswith("__"):

newAttr[name.upper()] = value

#调用type来创建一个类

returntype(future_class_name, future_class_parents, newAttr)

classFoo(object, metaclass=upper_attr):

bar ='bip'

print(hasattr(Foo,'bar'))

print(hasattr(Foo,'BAR'))

f = Foo()

print(f.BAR)

现在让我们再做一次,这一次用一个真正的class来当做元类。

#coding=utf-8

classUpperAttrMetaClass(type):

# __new__是在__init__之前被调用的特殊方法

# __new__是用来创建对象并返回之的方法

#而__init__只是用来将传入的参数初始化给对象

#你很少用到__new__,除非你希望能够控制对象的创建

#这里,创建的对象是类,我们希望能够自定义它,所以我们这里改写__new__

#如果你希望的话,你也可以在__init__中做些事情

#还有一些高级的用法会涉及到改写__call__特殊方法,但是我们这里不用

def__new__(cls, future_class_name, future_class_parents, future_class_attr):

#遍历属性字典,把不是__开头的属性名字变为大写

newAttr = {}

forname,valueinfuture_class_attr.items():

ifnotname.startswith("__"):

newAttr[name.upper()] = value

#方法1:通过'type'来做类对象的创建

# return type(future_class_name, future_class_parents, newAttr)

#方法2:复用type.__new__方法

#这就是基本的OOP编程,没什么魔法

# return type.__new__(cls, future_class_name, future_class_parents, newAttr)

#方法3:使用super方法

returnsuper(UpperAttrMetaClass, cls).__new__(cls, future_class_name, future_class_parents, newAttr)

#python2的用法

classFoo(object):

__metaclass__ = UpperAttrMetaClass

bar ='bip'

# python3的用法

# class Foo(object, metaclass = UpperAttrMetaClass):

#     bar = 'bip'

print(hasattr(Foo,'bar'))

#输出: False

print(hasattr(Foo,'BAR'))

#输出:True

f = Foo()

print(f.BAR)

1.拦截类的创建

2.修改类

3.返回修改之后的类

2.2.多个装饰器¶

小明赶紧扯开话题,”咳咳,我们接下来我们接着讲装饰器"

小张问道,像上面那个第三方登录的案例,想加多少加多少,Python怎么办呢?

小明一笑而过~

现在项目又升级了,要求每次调用都要打印一下日记信息,方便以后纠错,小张先用自己的理解打下了这段代码,然后像小明请教:

In [4]:

def log(func):
    def wrapper():
        print("输出日记信息")
        cache(func)()
    return wrapper

def cache(func):
    def wrapper():
        print("给功能添加了缓存")
        if True:
            pass
        else:
            func()  # 如果缓存失效则读取数据库获取新的数据
    return wrapper

@log
def get_data():
    print("直接数据库读取数据")

def main():
    get_data()

if __name__ == '__main__':
    main()

 

输出日记信息
给功能添加了缓存

 

小明刚美滋滋的喝着口口可乐呢,看到代码后一不小心喷了小张一脸,然后尴尬的说道:“Python又不是只能装饰一个装饰器,来看看我的代码”:

In [5]:

def log(func):
    print("开始装饰Log模块")
    def wrapper():
        print("输出日记信息")
        func()
    return wrapper

def cache(func):
    print("开始装饰Cache模块")
    def wrapper():
        print("给功能添加了缓存")
        if True:
            pass
        else:
            func()  # 如果缓存失效则读取数据库获取新的数据
    return wrapper

@log
@cache
def get_data():
    print("直接数据库读取数据")

def main():
    get_data()

if __name__ == '__main__':
    main()

 

开始装饰Cache模块
开始装饰Log模块
输出日记信息
给功能添加了缓存

 

小张耐心的看完了代码,然后说道:“咦,我发现它装饰的时候是从下往上装饰,执行的时候是从上往下啊?执行的时候程序本来就是从上往下,按照道理应该是从上往下装饰啊?”

小明神秘的说道:“你猜啊~你可以把它理解为寄快递和拆快递

小张兴奋的跳起来了:

装饰器:装快递,先包装里面的物品,然后再加个盒子。执行装饰器:拆快递,先拆外面的包装再拆里面的~简直妙不可言啊

进一步理解拷贝

1.6 __del__ and __new__¶

创建对象后,python解释器默认调用__init__() 方法

当删除一个对象时,python解释器也会默认调用__del__() 方法(有点析构函数的味道)

当有1个变量保存了对象的引用时,此对象的引用计数就会加1

当使用del删除变量指向的对象时,如果对象的引用计数不为1,那么每次删除计数减1,当计数为1的时候再调del就真把对象删了

这个可以结合我之前说过的链接来理解:于链接文件的探讨

看着老师夸夸其谈,小明愣了楞,摸摸肚子想到,真BB,我先搞个例子练练:

In [1]:

# 定义一个临时类
class Temp(object):
    def __del__(self):
        print("你被干掉了")

 

验证方面用编辑器比较合适,交互模式下可能不是真正的结果

# 对象被s1和s2引用
s1 = Temp()
s2 = s1

del s1  # 只删除s1,新创建的对象并没有被删除
print("-" * 10)

输出:(最后的被干掉是程序退出了)

# ----------
# 你被干掉了

如果用链接来解释就是这样的: 澳门新浦京娱乐场网站 9

这次两个都删掉:

t1 = Temp()
t2 = t1

del t1
del t2
print("-" * 10)

输出:

# 你被干掉了
# ----------

都删了,自然就真删掉了


这样搞比较麻烦,我们引入一下获取引用个数:getrefcount(object也会占1个引用计数)来个案例:

# 程序退出的时候,在他运行期间所有占用资源归还操作系统
# 引用计数
import sys
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
你被干掉了
----------

我再贴一种情况,你可以思考下为啥:

t1 = Temp()
t2 = Temp()

del t1
del t2
print("-" * 10)

输出:

# 你被干掉了
# 你被干掉了
# ----------

 


小潘扭过头瞅了一眼说道:“大叔,你__new__忘记写案例了”

小明一愣神,立马反应过来说:“我这叫谋而后动~”

当你实例化一个对象的时候,就会执行new 方法里面的方法。new方法在类定义中不是必须写的,如果没定义,默认会调用object.new去创建一个对象

__new__方法中至少要有一个参数cls,代表要实例化的类,此参数在实例化时由Python解释器自动提供

__new__方法中必须要有返回值(返回实例化出来的实例)

小明翻阅了官方文档,淡定的打下了如下标准格式的代码:

In [3]:

class Dog(object):
    def __init__(self, name):
        self.name = name
        print("初始化完毕")

    def __str__(self):
        return "Dog的名字叫:%s" % self.name

    def __new__(cls, name):
        # 注意参数,是cls,然后其他参数和init保持一致即可
        print("创建对象完毕")
        # 别忘记写返回值哦
        return object.__new__(cls)


def main():
    happy = Dog("Happy")
    print(happy)


if __name__ == '__main__':
    main()

 

创建对象完毕
初始化完毕
Dog的名字叫:Happy

 


关于__name__在模块调用的时候会详细说,你可以先这样理解:如果直接运行py文件就执行,别人调用那么你的main就不执行了

标准写法:

# 1.导入的模块
# 2.class的定义
# 3.其他方法定义

def main():
    pass

if __name__ == '__main__':
    main()

其他内容后面会继续说,封装部分再说说静态方法类方法之类的就结束了(和C#还是有很大区别的)


二垃圾回收

2.3.带参装饰器¶

小明继续讲述他哥哥的血泪历史:

需求时刻在变,系统使用范围更广了,为了不砸场子,抠门的老板决定每年多花5W在技术研发的硬件支持上,这下子技术部老开心了,想想以前前端只能通过CDN和HTTP请求来缓存,后端只能依赖页面缓存和数据库缓存就心塞,于是赶紧新增加一台Redis的云服务器。为了以后和现在缓存代码得变一变了,需要支持指定的缓存数据库:(如果不是维护别人搞的老项目,你这么玩保证被打死,开发的时候老老实实的工厂模式搞起)

带参数的装饰器一般都是用来记录logo日记比较多,自己开发知道debug模式,生产指定except模式等等

In [6]:

# 可以理解为,在原来的外面套了一层
def cache(cache_name):
    def decorator(func):
        def wrapper():
            if cache_name == "redis":
                print("给功能添加了Redis缓存")
            elif cache_name == "memcache":
                pass
            else:
                func()
        return wrapper
    return decorator

@cache("redis") # 相当于是:get_data = cache(”redis“)(get_data)
def get_data():
    print("直接数据库读取数据")

def main():
    get_data()

if __name__ == '__main__':
    main()

 

给功能添加了Redis缓存

 

小张很高兴,然后练了练手,然后质问小明道:”你是不是藏了一手!“

代码如下:

In [7]:

def log(func):
    def inner():
        print("%s log_info..." % func.__name__)
        func()
    return inner

@log
def login_in(name_str, pass_str):
    return "欢迎登录:%s" % (name_str)

@log
def login_out():
    print("已经退出登录")

@log
def get_data(id):
    print("%s:data xxx" % id)

def main():
    login_out()
    get_data(1)
    print(login_in("小明", "xxx"))

if __name__ == '__main__':
    main()

 

login_out log_info...
已经退出登录

 

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-7-dcb695819107> in <module>()
     23 
     24 if __name__ == '__main__':
---> 25main()

<ipython-input-7-dcb695819107> in main()
     19 def main():
     20     login_out()
---> 21get_data(1)
     22     print(login_in("小明", "xxx"))
     23 

TypeError: inner() takes 0 positional arguments but 1 was given

 

In [23]: a = [11,22,33]

1.7 类属性、实例属性¶

小明问老师:“老师老师,怎么没有静态类,静态属性之类的东西呢?”

老师笑而不语道:“小家伙原来不仅仅是体重增加啊,这求知欲也是大大的增加呢 ~ 且听我慢慢道来”


类在程序里面也是对象(你姑且可以认为所有的类都类似于C#里面的静态类),而通过类实例化的对象,叫实例化对象

实例属性 --> 实例对象相互之间不共享 一般我们都是在__init__中定义

类属性(类似于C#里面的静态字段) --> 属于类对象,多个实例对象之间共享

注意一下:相同名称的实例属性将屏蔽掉类属性(尽量别同名)

类属性除了可以通过 类名.类属性 访问外,还可以直接 实例对象.类属性 (C#中抽象类和静态类是不能被实例化的)

来个案例更直观点:

In [1]:

class Person(object):
    # age为类属性
    age = 1

    def __init__(self, name):
        # name为实例属性
        self.name = name


def main():
    # 类名.类属性
    print(Person.age)
    xiaoming = Person("小明")
    # 对象.类属性
    print(xiaoming.age)


if __name__ == '__main__':
    main()

 

1
1

 


如果需要在类外 修改类属性,必须通过类对象去引用然后进行修改

如果通过实例对象去引用会产生一个同名的实例属性,这种方式修改的是实例属性,不会影响到类属性

如果通过实例对象去引用该名称的属性,实例属性会强制 屏蔽掉类属性,即引用的是实例属性,除非del了该实例属性才能正常访问类属性

你可以理解为,Python这么做只是为了方便你获取,该怎么修改还得怎么做。来看个案例:

In [3]:

class Person(object):
    # age为类属性
    age = 1

    def __init__(self, name):
        # name为实例属性
        self.name = name


def main():
    # 类名.类属性
    print(Person.age)

    # 通过对象.类属性修改
    xiaoming = Person("小明")
    xiaoming.age = 100
    print(xiaoming.age)  # 其实,并没有修改成功,只是产生了一个同名age
    print(Person.age)  # 对吧,类属性并没有被修改

    # 通过类名修改
    Person.age = 22  # 如果需要在类外修改类属性,必须通过类对象去引用然后进行修改
    print(xiaoming.age)  # 刚才已经创建一个同名age,所以现在显示的是刚才的值
    print(Person.age)  # 通过类名.类属性 就可以看到值被修改了

    # 如果你还是不信,可以创建一个新对象看看
    xiaopan = Person("小潘")
    print(xiaopan.age)

    # xiaoming实例对象想访问怎么办?
    # 除非del了该实例属性才能正常访问类属性
    del xiaoming.age
    print(xiaoming.age)  # 这时候访问的就是 类属性 了


if __name__ == '__main__':
    main()

 

1
100
1
100
22
22
22

 

1小整数池对象

为了优化速度,python使用了小整数对象池,避免为整数频频申请和销毁内存空间。

Python对小整数的定义是[-5, 257)这些整数对象是提前建立好的,不会被垃圾回收。在一个Python的程序中,所有位于这个范围内的整数使用的都是同一个对象.

同理,单个字母也是这样的。

但是当定义2个相同的字符串时,引用计数为0,触发垃圾回收

澳门新浦京娱乐场网站 10

2.4.通用装饰器¶

小明尴尬的笑了下,然后赶紧倾囊相授,定义一个通用的装饰器:(传参数就在外面套一层)

def log(func):
    @functools.wraps(func) # 签名下面一个案例就会讲
    def wrapper(*args,**kv):
        """可变参   关键字参数"""
        print("%s log_info..." % func.__name__)
        return func(*args,**kv)
    return wrapper

这部分知识如果忘记了可以回顾一下,我们之前讲的函数系列:https://www.cnblogs.com/dotnetcrazy/p/9175950.html

In [8]:

def log(func):
    # 可变参   关键字参数
    def wrapper(*args,**kv):
        print("%s log_info..." % func.__name__)
        return func(*args,**kv)
    return wrapper

@log
def login_in(name_str, pass_str):
    return "欢迎登录:%s" % (name_str)

@log
def login_out():
    print("已经退出登录")

@log
def get_data(id):
    print("%s:data xxx" % id)

def main():
    login_out()
    get_data(1)
    print(login_in("小明", "xxx"))

if __name__ == '__main__':
    main()

 

login_out log_info...
已经退出登录
get_data log_info...
1:data xxx
login_in log_info...
欢迎登录:小明

 

In [24]: b = [44,55,66]

1.8 实例方法、类方法、静态方法¶

先说说 实例方法,实例方法第一个定义的参数只能是实例本身引用self,只能通过实例调用(就是我们之前用的 def func_name(self,xxx):

类方法:是类对象所拥有的方法,需要用修饰器@classmethod来标识,第一个参数必须是类对象cls,可以通过类或者实例直用

静态方法:定义静态方法使用装饰器@staticmethod,没有默认的必须参数,通过类和实例直接调用

静态方法中不需要额外定义参数,因此在静态方法中引用类属性的话,必须通过 类对象来引用(访问)

小明眼花缭乱的对老师说道,老师给我看几个案例吧:

In [1]:

class Dog(object):
    # 类属性
    name = "小汪"

    # 实例方法
    def __init__(self, age):
        # 实例属性
        self.age = age
        # 打印看看
        print("self id:%s" % id(self))

    # 类方法
    @classmethod
    def show_name(cls):
        # 访问类属性 cls.xxx
        print("我叫%s" % cls.name)
        # 打印看看
        print("cls id:%s" % id(cls))

    # 静态方法
    @staticmethod
    def say_hello():
        print("汪汪汪")


def main():
    # 类名方式访问
    Dog.show_name()
    Dog.say_hello()  # 类名的方式可以访问静态方法

    # 实例对象方式访问
    dog = Dog(2)
    dog.show_name()
    dog.say_hello()


if __name__ == '__main__':
    main()

 

我叫小汪
cls id:94310818174200
汪汪汪
self id:140392216464016
我叫小汪
cls id:94310818174200
汪汪汪

 


一般都是这样用的(供参考):

实例方法:一般平时用的都是它

类方法:类方法用在模拟C#多个构造函数(Python里面不能有同名函数) or 你需要 对类属性、类方法操作之类的

静态方法:一般 都是独立功能,类似于函数,只不过在面向对象里面一般这么用

 


2大整数对象池

每一个对象均创建一个新的对象

澳门新浦京娱乐场网站 11

2.5.扩展补充¶

其实装饰器可以做很多事情,比如强制类型检测等,先看几个扩展:

In [25]: c = (a,b)

1.9 C#封装案例¶

C#面向对象比较优美,来个封装的案例基本上就搞定了:

using System;

namespace _1Encapsulation
{
    public class Student
    {
        /// <summary>
        /// 字段
        /// </summary>
        private int _age;
        /// <summary>
        /// 属性
        /// </summary>
        public int Age
        {
            get
            {
                return _age;
            }

            set
            {
                if (value > 1)
                {
                    _age = value;
                }
            }
        }

        /// <summary>
        /// 自动化属性
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 自动属性必须要有get访问器
        /// </summary>
        public string SNum { get; }

        private int _gender;
        public int Gender
        {
            set
            {
                _gender = value;
            }
        }

        /// <summary>
        /// 构造函数的名字必须与类名一致
        /// 构造函数没有返回值也没有viod
        /// 默认自动生成一个无参构造函数,当有一个有参构造函数的时候无参构造函数便不会自动创建
        /// </summary>
        public Student() { }

        /// <summary>
        /// 有参构造函数
        /// </summary>
        /// <param name="name"></param>
        /// <param name="age"></param>
        public Student(string name, int age)
        {
            this.Name = name;
            this.Age = age;
        }

        /// <summary>
        /// this调用当前类的某个有参构造函数
        /// </summary>
        /// <param name="name"></param>
        /// <param name="age"></param>
        /// <param name="gender"></param>
        public Student(string name, int age, int gender) : this(name, age)
        {
            this.Gender = gender;
        }

        /// <summary>
        /// 某个方法
        /// </summary>
        public void Show()
        {
            Console.WriteLine("Name:"   this.Name   " Age:"   this.Age   "n");
        }

        public override string ToString()
        {
            return "Name:"   this.Name   " Age:"   this.Age;
        }
    }
}

调用部分:

using System;

namespace _1Encapsulation
{
    class Program
    {
        static void Main(string[] args)
        {
            Student s = new Student() { Name = "mmd", Age = 13, Gender = 1 };
            s.Show();

            Student s1 = new Student("dmm", 20);
            s1.Show();

            Console.WriteLine(s);
        }
    }
}

3intern机制

在python中,对于相同的字符串只开辟一个内存空间,靠引用计数来维护何时释放。

澳门新浦京娱乐场网站 12

·小整数[-5,257)共用对象,常驻内存

·单个字符共用对象,常驻内存

·单个单词,不可修改,默认开启intern机制,共用对象,引用计数为0,则销毁

�在字符串中,若含有空格,不开启intern机制,不共用对象,引用计数为0销毁

澳门新浦京娱乐场网站 13

大整数不共用内存,引用计数为0,销毁

数值类型和字符串类型在Python中都是不可变的,这意味着你无法修改这个对象的值,每次对变量的修改,实际上是创建一个新的对象

澳门新浦京娱乐场网站 14

1.装饰器方法签名的问题¶

成也装饰器,败也装饰器,来个案例看看,装饰器装饰的函数真的就对原函数没点影响?

In [9]:

# 添加一个闭包
def cache(func):
    def wrapper(*args,**kv):
        if True:
            print("缓存尚未失效:直接返回缓存数据")
        else:
            func(*args,**kv)
    return wrapper

def get_data(id):
    """获取数据"""
    print("通过%d直接数据库读取数据"%id)

In [10]:

# 进行装饰
get_data = cache(get_data)
# 调用原有名称的函数
get_data(110)
# 发现虽然函数调用时候的名字没有变
# 但是内部签名却变成了闭包里面的函数名了
print(get_data.__name__)
print(get_data.__doc__)
# print(get_data.__annotations__)

 

缓存尚未失效:直接返回缓存数据
wrapper
None

 

发现虽然函数调用时候的名字没有变,但是内部签名却变成了闭包里面的函数名了!

玩过逆向的人都知道,像你修改了apk文件,它看似一样,但签名就变了,得再处理才可能绕过原来的一些自效验的验证措施

这边一样的道理,你写了一个装饰器作用在某个函数上,但是这个函数的重要的元信息比如名字、文档字符串、注解和参数签名都丢失了。

functools里面的wraps就帮我们干了这个事情(之前讲模块的时候引入了functools,随后讲衍生的时候用了里面的偏函数,这边讲讲wraps

上面代码改改:

In [11]:

from functools import wraps

# 添加一个闭包
def cache(func):
    @wraps(func)
    def wrapper(*args,**kv):
        if True:
            print("缓存尚未失效:直接返回缓存数据")
        else:
            func(*args,**kv)
    return wrapper

def get_data(id):
    """获取数据"""
    print("通过%d直接数据库读取数据"%id)

# 进行装饰
get_data = cache(get_data)
# 调用原有名称的函数
get_data(110)
# 签名已然一致
print(get_data.__name__)
print(get_data.__doc__)
# print(get_data.__annotations__)

 

缓存尚未失效:直接返回缓存数据
get_data
获取数据

 

另外:@wraps有一个重要特征是它能让你通过属性 __wrapped__ 直接访问被包装函数,eg:

In [12]:

get_data.__wrapped__(100)

 

通过100直接数据库读取数据

 

In [26]: e = copy.deepcopy(c)

4Garbage collection(GC垃圾回收)

①python采用的是引用计数机制为主,标记-清除和分代收集两种机制为辅的策略

python里每一个东西都是对象,它们的核心就是一个结构体:PyObject

typedefstruct_object {

intob_refcnt;

struct_typeobject *ob_type;

} PyObject;

PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少

当引用计数为0时,该对象生命就结束了。

②引用计数机制的优点:

·简单

·实时性:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时。

③引用计数机制的缺点:

·维护引用计数消耗资源

·循环引用

list1 = []

list2 = []

list1.append(list2)

list2.append(list1)

list1与list2相互引用,如果不存在其他对象对它们的引用,list1与list2的引用计数也仍然为1,所占用的内存永远无法被回收,这将是致命的。 对于如今的强大硬件,缺点1尚可接受,但是循环引用导致内存泄露,注定python还将引入新的回收机制。(标记清除和分代收集)

2.装饰器传参的扩展(可传可不传)¶

In [13]:

import logging
from functools import wraps, partial

def logged(func=None, *, level=logging.DEBUG, name=None, message=None):
    if func is None:
        return partial(logged, level=level, name=name, message=message)

    logname = name if name else func.__module__
    log = logging.getLogger(logname)
    logmsg = message if message else func.__name__

    @wraps(func)
    def wrapper(*args, **kwargs):
        log.log(level, logmsg)
        return func(*args, **kwargs)
    return wrapper

@logged
def add(x, y):
    return x   y

@logged(level=logging.CRITICAL, name='测试')
def get_data():
    print("读数据ing")

def main():
    add(1,2)
    get_data()

if __name__ == '__main__':
    main()

 

get_data

 

读数据ing

 

In [27]: a.append(77)

5 Ruby与Python垃圾回收

①应用程序那颗跃动的心

GC系统所承担的工作远比"垃圾回收"多得多。实际上,它们负责三个重要任务。它们

·    为新生成的对象分配内存

·     识别那些垃圾对象

      ·从垃圾对象那回收内存

        我认为垃圾回收就是应用程序那颗跃动的心。像心脏为身体其他器官提供血液和营养物那样,垃圾回收器为你的应该程序提供内存和对象。如果心脏停跳,过不了几秒钟人就完了。如果垃圾回收器停止工作或运行迟缓,像动脉阻塞,你的应用程序效率也会下降,直至最终死掉。

②一个简单的例子

classNode:

def__init__(self,val):

self.value=val

print(Node(1))

print(Node(2))

③Python的对象分配

澳门新浦京娱乐场网站 15

与Ruby不同,当创建对象时Python立即向操作系统请求内存。(Python实际上实现了一套自己的内存分配系统,在操作系统堆之上提供了一个抽象层。但是我今天不展开说了。)

当我们创建第二个对象的时候,再次像OS请求内存:

澳门新浦京娱乐场网站 16

④Python住在卫生之家

澳门新浦京娱乐场网站 17

在内部,创建一个对象时,Python总是在对象的C结构体里保存一个整数,称为引用数。期初,Python将这个值设置为1:

澳门新浦京娱乐场网站 18

值为1说明分别有个一个指针指向或是引用这三个对象。假如我们现在创建一个新的Node实例,JKL:

澳门新浦京娱乐场网站 19

与之前一样,Python设置JKL的引用数为1。然而,请注意由于我们改变了n1指向了JKL,不再指向ABC,Python就把ABC的引用数置为0了。 此刻,Python垃圾回收器立刻挺身而出!每当对象的引用数减为0,Python立即将其释放,把内存还给操作系统。

Python的这种垃圾回收算法被称为引用计数。

假如我们让n2引用n1:

澳门新浦京娱乐场网站 20

'DEF'上的引用数被python减少了,垃圾回收器立刻回收DEF实例,同时JKL的引用数已经变为了2,因为n1和n2都指向它

⑤标记-删除 vs 引用计数

引用计数并不像第一眼看上去那样简单。有许多原因使得不许多语言不像Python这样使用引用计数GC算法:

首先,它不好实现。Python不得不在每个对象内部留一些空间来处理引用数。这样付出了一小点儿空间上的代价。但更糟糕的是,每个简单的操作(像修改变量或引用)都会变成一个更复杂的操作,因为Python需要增加一个计数,减少另一个,还可能释放对象。

第二点,它相对较慢。虽然Python随着程序执行GC很稳健(一把脏碟子放在洗碗盆里就开始洗啦),但这并不一定更快。Python不停地更新着众多引用数值。特别是当你不再使用一个大数据结构的时候,比如一个包含很多元素的列表,Python可能必须一次性释放大量对象。减少引用数就成了一项复杂的递归过程了

最后,它不是总奏效的。引用计数不能处理环形数据结构--也就是含有循环引用的数据结构。

⑥在Python中的零代(Generation Zero)

我们希望Python的垃圾回收机制能够足够智能去释放这些对象并回收它们占用的内存空间。但是这不可能,因为所有的引用计数都是1而不是0。Python的引用计数算法不能够处理互相指向自己的对象。

Python使用一种不同的链表来持续追踪活跃的对象。而不将其称之为“活跃列表”,Python的内部C代码将其称为零代(Generation Zero)。每次当你创建一个对象或其他什么值的时候,Python会将其加入零代链表:

澳门新浦京娱乐场网站 21

现在零代包含了两个节点对象。(他还将包含Python创建的每个其他值,与一些Python自己使用的内部值。)

⑦检测循环引用

澳门新浦京娱乐场网站 22

从上面可以看到ABC和DEF节点包含的引用数为1.有三个其他的对象同时存在于零代链表中,蓝色的箭头指示了有一些对象正在被零代链表之外的其他对象所引用。(接下来我们会看到,Python中同时存在另外两个分别被称为一代和二代的链表)

澳门新浦京娱乐场网站 23

通过识别内部引用,Python能够减少许多零代链表对象的引用计数。在上图的第一行中你能够看见ABC和DEF的引用计数已经变为零了,这意味着收集器可以释放它们并回收内存空间了。剩下的活跃的对象则被移动到一个新的链表:一代链表。

⑧Python中的GC阈值

Python什么时候会进行这个标记过程?随着你的程序运行,Python解释器保持对新创建的对象,以及因为引用计数为零而被释放掉的对象的追踪。从理论上说,这两个值应该保持一致,因为程序新建的每个对象都应该最终被释放掉。

当然,事实并非如此。因为循环引用的原因,并且因为你的程序使用了一些比其他对象存在时间更长的对象,从而被分配对象的计数值与被释放对象的计数值之间的差异在逐渐增长。一旦这个差异累计超过某个阈值,则Python的收集机制就启动了,并且触发上边所说到的零代算法,释放“浮动的垃圾”,并且将剩下的对象移动到一代列表。

随着时间的推移,程序所使用的对象逐渐从零代列表移动到一代列表。而Python对于一代列表中对象的处理遵循同样的方法,一旦被分配计数值与被释放计数值累计到达一定阈值,Python会将剩下的活跃对象移动到二代列表。

3.类中定义装饰器¶

在类里面定义装饰器很简单,但是你首先要确认它的使用方式。比如到底是作为一个实例方法还是类方法:(别忘记写selfcls

In [14]:

from functools import wraps

class A(object):
    # 实例方法
    def decorator1(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print("实例方法装饰器")
            return func(*args, **kwargs)
        return wrapper

    # 类方法
    @classmethod
    def decorator2(cls, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print("类方法装饰器")
            return func(*args, **kwargs)
        return wrapper

In [15]:

# 装饰方式不一样
a = A()
@a.decorator1 # 实例方法调用
def test1():
    pass

@A.decorator2 # 类方法调用
def test2():
    pass

In [16]:

# 调用一下
test1()
test2()

 

实例方法装饰器
类方法装饰器

 

在涉及到继承的时候。 例如,假设你想让在A中定义的装饰器作用在子类B中。你需要像下面这样写:

class B(A):
    @A.decorator2
    def test(self):
        pass

也就是说,装饰器要被定义成类方法并且你必须显式的使用父类名去调用它。

你不能使用 @B.decorator2 ,因为在方法定义时,这个类B还没有被创建。

In [28]: a

三 gc模块

4.类装饰器¶

看这个之前,我们先来看看怎么把类当函数一样使用:

In [17]:

class A(object):
    def __call__(self):
        print("让类对象能像函数一样调用的~魔法方法")

def main():
    a = A()
    a()

if __name__ == '__main__':
    main()

 

让类对象能像函数一样调用的~魔法方法

 

重载这些魔法方法一般会改变对象的内部行为。上面这个例子就让一个类对象拥有了被调用的行为。

装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。

在Python中一般callable对象都是函数,但也有例外。只要某个对象重写了 __call__() 方法,那么这个对象就是callable的

用类来实现呢?我们可以让类的构造函数__init__()接受一个函数,然后重载__call__()并返回一个函数,也可以达到装饰器函数的效果

我们拿之前说的通用装饰器的例子继续说:(一般来说装饰器就定义成方法,然后给需要添加的函数或者类方法添加就基本够用了

In [18]:

from functools import wraps

class Log(object):
    def __init__(self, func):
        wraps(func)(self)  # @wraps(func) 访问不到,所以用这种方式
        self.__func = func

    def __call__(self, *args, **kvs):
        print("%s log_info..." % self.__func.__name__)
        return self.__func(*args, **kvs)

@Log # 相当于 login_in=Log(login_in)
def login_in(name_str, pass_str):
    return "欢迎登录:%s" % (name_str)

@Log
def login_out():
    print("已经退出登录")

@Log
def get_data(id):
    print("%s:data xxx" % id)

def main():
    login_out()
    get_data(1)
    print(login_in("小明", "xxx"))

if __name__ == '__main__':
    main()

 

login_out log_info...
已经退出登录
get_data log_info...
1:data xxx
login_in log_info...
欢迎登录:小明

 

对类进行装饰的测试:(以上一个案例为例)

装饰实例方法的时候容易出现莫名其妙的错误,所以一般加上get方法(反射系列的稍后会讲)

eg:show() missing 1 required positional argument: 'self'

完整写法:(你可以去除__get__试试)

In [19]:

import types
from functools import wraps

class Log(object):
    def __init__(self, func):
        wraps(func)(self)  # @wraps(func) 访问不到,所以用这种方式
        self.__func = func

    def __call__(self, *args, **kvs):
        print("%s log_info..." % self.__func.__name__)
        return self.__func(*args, **kvs)

    # 装饰实例方法的时候容易出现莫名其妙的错误,所以一般加上get方法
    # eg:show() missing 1 required positional argument: 'self'
    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            return types.MethodType(self, instance)

class LoginComponent(object):
    def __init__(self, name):
        self.__name = name

    @Log
    def show(self):
        """实例方法"""
        print("欢迎你:%s" % self.__name)

    @classmethod
    @Log  # 写在下面("从下往上装,从上往下拆")
    def login_in(cls):
        """类方法"""
        print("登录ing")

    @staticmethod
    @Log
    def show_news():
        """静态方法"""
        print("今天的新闻是...")

def main():
    LoginComponent.login_in()
    LoginComponent.show_news()
    login = LoginComponent("小明")
    login.show()

if __name__ == '__main__':
    main()

 

login_in log_info...
登录ing
show_news log_info...
今天的新闻是...
show log_info...
欢迎你:小明

 

更多的可以参考如下链接:

详解Python装饰器

将装饰器定义为类

Python中的__init__()和__call__()函数

python中装饰器的使用和类装饰器在类中方法的使用


Out[28]: [11,22,33,77]

1垃圾回收机制

Python中的垃圾回收是以引用计数为主,分代收集为辅。

3.面向对象系列扩展¶

看着小张准备回家换衣服了,小明有点失落,又有点孤单,于是说道:“逗逼张,你还要听吗?我准备讲类相关的知识了,这些可是我课后自学的哦~”

小张转了转身,一念间就留了下来~

In [29]: b

2导致引用计数 1的情况

①对象被创建,例如a=123

②对象被引用,例如b=a

③对象作为参数,传入一个函数中,例如func(a)

④对象作为一个元素,存储在容器中,例如list1=[a,a]

3.1.动态添加属性和方法¶

类相关的基础知识如果忘记,可以查看之前的文章:https://www.cnblogs.com/dotnetcrazy/p/9202988.html

当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性:

In [20]:

# 定义一个类
class Person(object):
    def __init__(self, name):
        self.__name = name

    def show(self):
        print("中国欢迎你~", self.__name)

In [21]:

xiaoming = Person("小明")
xiaoming.show() # 正常调用

# 给实例动态添加一个属性
xiaoming.age = 22
print(xiaoming.age)

 

中国欢迎你~ 小明
22

In [22]:

# 其他实例是访问不到这个属性的
xiaopan = Person("小潘")
xiaopan.age

 

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-22-efcec543fe3f> in <module>()
      1 # 其他实例是访问不到这个属性的
      2 xiaopan = Person("小潘")
----> 3xiaopan.age

AttributeError: 'Person' object has no attribute 'age'

 

"这个以前不是讲过嘛,动态添加属性,还有没有啥我不知道的知识了?"小张不屑的说道.

小明故作悬疑,抬头看着小张说道:“你知道怎么添加类属性吗?知道怎么添加方法吗?”

小张沉默不语,默默的看着小明讲课,随后心里想到:“这个坑货,话也不说全,还好现在是夏天,不然我早着凉了”

要想添加其他实例都可以访问的属性,可以给类添加一个类属性,用法和上面差不多,只是把对象改成类。

来看个案例:

In [23]:

# 给类动态添加一个属性
Person.age = 22

xiaoming = Person("小明")
print(xiaoming.age)

xiaopan = Person("小潘")
print(xiaopan.age)

 

22
22

 

Out[29]: [44,55,66]

3导致引用计数-1的情况

①对象的别名被显式销毁,例如del a

②对象的别名被赋予新的对象,例如a=124

③一个对象离开它的作用域,例如f函数执行完毕时,func函数中的局部变量(全局变量不会)

④对象所在的容器被销毁,或从容器中删除对象

1.添加实例方法¶

小张,还记得讲装饰器的时候有这么一句代码吗?

types.MethodType(self, instance)

小张:"记得当时用类装饰实例方法的时候出现了问题,然后才加的?"

对头,以上面Person类为例,来一起看怎么动态添加方法

In [24]:

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

注意一点,当你在新方法中调用类中私有方法时就会出问题

其实这个本质相当于通过实例对象调用里面公开属性

In [25]:

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__':
---> 17main()

<ipython-input-25-2bf92b457fc8> in main()
     12     xiaoming = Person("小明")
     13     xiaoming.test = types.MethodType(test, xiaoming)
---> 14xiaoming.test() # 其实这个本质相当于通过实例对象调用里面公开属性
     15 
     16 if __name__ == '__main__':

<ipython-input-25-2bf92b457fc8> in test(self)
      7 # 一样的代码,只是调用了私有属性
      8 def test(self):
----> 9print("中国欢迎你,%s" % self.__name)
     10 
     11 def main():

AttributeError: 'Person' object has no attribute '__name'

 

In [30]: c

4查看一个对象的引用计数

import sys

a=‘hello world’

sys.getrefcount(a)

可以查看a对象的引用计数,但比正常计数大1,因为调用查看引用计数的函数时将a传入,使a的引用计数 1

2.添加类方法和静态方法¶

看一下类方法和静态方法的案例:

In [26]:

# 类方法案例
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'>

In [27]:

# 静态方法案例
class Person(object):
    pass

@staticmethod
def test():
    print("test")

def main():
    Person.test = test
    xiaoming = Person()
    xiaoming.test()

if __name__ == '__main__':
    main()

 

test

 

Out[30]: ([11,22,33,77], [44,55,66])

5循环引用导致内存泄漏问题

引用计数的缺陷是循环引用的问题

importgc

classClassA():

def__init__(self):

print('object born,id:%s'%str(hex(id(self))))

deff2():

whileTrue:

c1 = ClassA()

c2 = ClassA()

c1.t = c2

c2.t = c1

delc1

delc2

#把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的对象,内存2的对象的引用数都是1。

·虽然它们两个的对象都是可以被销毁的,但是由于循环引用,导致垃圾回收器都不会回收它们,所以就会导致内存泄露。

3.2.__slots__

这下小张急了,怎么又和上次讲得模块一样,无法无天了啊?有没有办法限制一下呢?

小明哈哈一笑,娓娓道来:

In [31]: e

说明:

垃圾回收后的对象会放在gc.garbage列表里面

·gc.collect()会返回不可达的对象数目,4等于两个对象以及它们对应的dict

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

1.调用gc.collect(),

2.当gc模块的计数器达到阀值的时候。

3.程序退出的时候

1.指定实例属性¶

如果我们想要限制实例的属性怎么办?比如,只允许添加指定属性和方法?

In [28]:

# 定义一个类
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
---> 11xiaoming.qq = 110 # 不允许的属性就添加不了

AttributeError: 'Person' object has no attribute 'qq'

 

说几个测试后的结论:

  1. __slots__不一定是元组,你用列表也一样(推荐和官方一致)
  2. 如果你定义的私有属性不在元组内,也会报错

In [29]:

# 列表定义__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 = "男"

In [30]:

# 注意一个东西,如果你定义的私有属性不在元组内,也会报错
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 
---> 11xiaoming = Person("小明")
     12 xiaoming.age = 22

<ipython-input-30-0b85ac9c18af> in __init__(self, name)
      4 
      5     def __init__(self, name):
----> 6self.__name = name
      7 
      8     def show(self):

AttributeError: 'Person' object has no attribute '_Person__name'

 

Out[31]: ([11,22,33], [44,55,66])

6gc模块常用功能解析

gc模块提供一个接口给开发者设置垃圾回收的选项,上面说到,采用引用计数的方法管理内存的一个缺陷是循环引用,而gc模块的一个主要功能是解决循环引用的问题。

2.指定实例“方法”¶

这个限制对实例方法一样有效,再复习下给实例对象添加方法:

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中定义的方法相当于定义了一个属性,然后指向了定义的函数)

In [31]:

# 这个限制对实例方法一样有效
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
---> 17xiaoming.test = types.MethodType(test, xiaoming)
     18 xiaoming.test()

AttributeError: 'Person' object has no attribute 'test'

 

小明讲得唾沫横飞,然后故作神秘的和小张说道:

In [32]:

7常用函数

1、gc.set_debug(flags)设置gc的debug日志,一般设置为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的列表

3.扩展:看看对类有啥影响¶

测试结果:不影响

In [32]:

# 类方法案例
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
类方法
静态方法

 

In [32]:

gc模块的自动垃圾回收机制

必须要import gc模块,并且is_enable()=True才会启动自动垃圾回收。

这个机制的主要作用就是发现并处理不可达的垃圾对象。

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

例如(488,3,0),其中488是指距离上一次一代垃圾检查,Python分配内存的数目减去释放内存的数目,注意是内存分配,而不是引用计数的增加。例如:

printgc.get_count()# (590, 8, 0)

a = ClassA()

printgc.get_count()# (591, 8, 0)

del a

printgc.get_count()# (590, 8, 0)

3是指距离上一次二代垃圾检查,一代垃圾检查的次数,同理,0是指距离上一次三代垃圾检查,二代垃圾检查的次数。

gc模快有一个自动垃圾回收的阀值,即通过gc.get_threshold函数获取到的长度为3的元组,例如(700,10,10)每一次计数器的增加,gc模块就会检查增加后的计数是否达到阀值的数目,如果是,就会执行对应的代数的垃圾检查,然后重置计数器

例如,假设阀值是(700,10,10):

澳门新浦京娱乐场网站 24

注意点

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

扩展:__getattribute__属性拦截器¶

有点像C#里面的Attribute标签,AOP其实就是这类的思想

更多可以参考如下链接:

动态添加属性和方法

反射以及魔法方法相关内容

制定类以及魔法方法相关内容

In [33]:

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
小明

 

In [32]: f = copy.copy(c)

3.3.元类系列¶

小张一脸懵逼的看着小明,然后说道:”就没有类似于C#里面的反射机制?“

小明背着手,缓缓的绕着小张走了一圈,那眼神仿佛是在看一件工艺艺术品一样,然后随口说道:

In [33]: a.append(88)

3.3.1.type动态创建类¶

前面我们讲过了type()函数可以查看一个类型或变量的类型。比如说:

Person是一个class,它的类型就是type,而xiaoming是一个实例,它的类型就是class Person

看个例子:

In [34]:

class Person(object):
    pass

def main():
    xiaoming = Person()
    print(type(Person))
    print(type(xiaoming))

if __name__ == '__main__':
    main()

 

<class 'type'>
<class '__main__.Person'>

 

其实还可以通过 __class__ 来查看创建对象的是谁:

In [35]:

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)

In [36]:

def main():
    Person = type("Person", (object, ), {})
    xiaoming = Person()
    print(Person.__class__)
    print(xiaoming.__class__)

if __name__ == '__main__':
    main()

 

<class 'type'>
<class '__main__.Person'>

 

小张感叹道:”Python的这种‘反射’太过简单了吧,我直接都可以写案例了“

比如,实现如下内容:

In [37]:

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
小明实例方法:大口吃饭
我是类方法:跑着上课
静态方法:晚安

In [38]:

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
小明实例方法:大口吃饭
我是类方法:跑着上课
静态方法:晚安

 

In [34]: a

3.3.2.元类~metaclass

小明又仔细端详了小张一次,然后继续讲到:

当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。

但是如果我们想创建出类呢?那就必须根据metaclass创建出类,所以:先定义metaclass,然后创建类。

总的流程就是:先定义metaclass,再创建类,最后创建实例

type就是Python在背后用来创建所有类的那个元类


小张有点恐慌的看了一眼小明,然后继续听讲

Python2是看看类里面有没有__metaclass__这个属性,有就通过它指向的函数或者方法来创建类

Python3简化了一下,在Class定义的时候就可以指定了,eg:class Person(object, metaclass=type)

In [39]:

# 这三个参数其实就是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()

 

小明

 

其实原类有点像刚刚讲的属性拦截器了,大概流程如下:

  1. 拦截类的创建
  2. 修改类
  3. 返回修改之后的类

来一个正规化的写法,eg:给MyList添加一个add方法(list是append方法,别混淆了)

In [40]:

# 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框架


Out[34]: [11,22,33,77,88]

3.4.枚举类¶

枚举类经常用,代码也很简单,继承一下Enum类就可以了,unique用来防止重复的(重复会提示你)

In [41]:

from enum import Enum, unique

@unique
class StatusEnum(Enum):
    # 待审核状态(0)默认
    Pendding = 0

    # 审核已通过(1)正常
    Normal = 1

    # 审核不通过(2)未删
    Cancel = 2

    # 已删除状态(99)假删
    Delete = 99

# 调用:
StatusEnum.Delete

Out[41]:

<StatusEnum.Delete: 99>

In [42]:

# 重复项测试
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
----> 5class 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

 

In [35]: b

3.5.垃圾回收¶

之前写的文章里面有提到过,可以简单回顾一下:(可变类型和不可变类型 引用数的引入)

其实程序员基本上关注,实在要关注的就是怎么显示回收:

import gc # 需要导入gc模块

print(gc.collect()) # 显式垃圾回收
print(gc.garbage)   # 看回收了哪些

先看看之前讲可变类型和不可变类型说的一句话:

Python对int类型和较短的字符串进行了缓存,无论声明多少个值相同的变量,实际上都指向同个内存地址

看个案例:

澳门新浦京娱乐场网站,In [2]:

a=10
b=10
c=10
print(id(a))
print(id(b))
print(id(c))

 

94747627400000
94747627400000
94747627400000

 

上面的ID都一样,那较短到底是多短呢?

先贴一下逆天的测试结果:(不要在编辑器里面测试,建议进入官方的python3交互模式,用vscode测试的结果不准)

  1. 小整数[-5,257)共用对象,常驻内存不在这个范围内的均创建一个新的对象
  2. 单个字符共用对象,常驻内存
  3. 字符串:
    • 英文单词,共用对象,引用计数为0就删除
    • 英文中有空格(英文句子、词组),不共用,引用计数为0的时候就删掉
    • 中文字符串:不共用,引用计数为0的时候就删掉

其实也很好理解,第一个范围是程序员经常用的范围,字符串系列嘛就更正常了,老外肯定不管中文什么的,要是中国人发明的可以常用汉字常驻内存^_^ 然后一篇文章里面单词出现频率肯定比词组和句子高,所以都能解释通了

来简单验证一下:

澳门新浦京娱乐场网站 25

In [1]:

# 257的时候就取不到了,这时候都是不同的ID
# 这个就是所谓的大整数了(每一个大整数,均创建一个新的对象)
a=257
b=257
c=257
print(id(a))
print(id(b))
print(id(c))

 

140602139583728
140602139584112
140602139583792

In [2]:

# 单个字符
d='a'
e='a'
f='a'
print(id(d))
print(id(e))
print(id(f))

 

140602366927792
140602366927792
140602366927792

In [3]:

# 英文单词
str1 = "dog"
str2 = "dog"
str3 = "dog"
print(id(str1))
print(id(str2))
print(id(str3))

 

140602139175376
140602139175376
140602139175376

In [4]:

# 英文中有空格(句子,词组)
str4 = "big dog"
str5 = "big dog"
str6 = "big dog"
print(id(str4))
print(id(str5))
print(id(str6))

 

140602139174984
140602139174816
140602139175544

In [5]:

# 不共享对象,计数为0就删除
str7 = "明"
str8 = "明"
str9 = "明"
print(id(str7))
print(id(str8))
print(id(str9))

 

140602139296272
140602139296352
140602139296192

In [6]:

str10 = "小明"
str11 = "小明"
str12 = "小明"
print(id(str10))
print(id(str11))
print(id(str12))

 

140602139147320
140602139146616
140602139146792

In [7]:

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主要的一种垃圾回收方式(计数引用),看看源码:

参考链接:

// 实际上没有任何东西被声明为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;

In [1]:

# 引用计数
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)

来看看阀值情况:

In [1]:

import gc

print(gc.get_count())
print(gc.get_threshold())

 

(234, 8, 1)
(700, 10, 10)

 

比如你新创建了1000个对象,才释放20个,就已经超过默认的700阀值,Python第一代检测就上场了(以此类推)

一般能活到最后的都不大可能是垃圾了,比如配置文件之类的,基本上不太改动的(越老越成精嘛)

小张若有所思的说道:

  1. 当计数器从(699,3,0)增加到(700,3,0),gc模块就会执行gc.collect(0),即检查一代对象的垃圾,并重置计数器为(0,4,0)
  2. 当计数器从(699,9,0)增加到(700,9,0),gc模块就会执行gc.collect(1),即检查一、二代对象的垃圾,并重置计数器为(0,0,1)
  3. 当计数器从(699,9,9)增加到(700,9,9),gc模块就会执行gc.collect(2),即检查一、二、三代对象的垃圾,并重置计数器为(0,0,0)

小明左右端详小张,终于忍不住说出了那句话:“小张,你能不能..."

话没说完就被小张打断了:”我是男的,不搞基!就是搞基也只喜欢我们班的培哥!“

小明吃惊的说道:”你想啥呢?我只是看你骨骼清奇,想要收你为徒罢了...“

(完)


经典引用:(参考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 内存泄露与循环引用

Out[35]: [44,55,66]

In [36]: c

Out[36]: ([11,22,33,77,88], [44,55,66])

In [37]: e

Out[37]: ([11,22,33], [44,55,66])

In [38]: f

Out[38]: ([11,22,33,77,88], [44,55,66])

1.2.3拷贝的其他方式

浅拷贝对不可变类型和可变类型的copy不同

In [88]: a = [11,22,33]

In [89]: b =copy.copy(a)

In [90]: id(a)

Out[90]:59275144

In [91]: id(b)

Out[91]:59525600

In [92]:

a.append(44)

In [93]: a

Out[93]: [11,22,33,44]

In [94]: b

Out[94]: [11,22,33]

In [95]:

In [95]:

In [95]: a = (11,22,33)

In [96]: b =copy.copy(a)

In [97]: id(a)

Out[97]:58890680

In [98]: id(b)

Out[98]:58890680

·分片表达式可以赋值一个序列

a ="abc"

b = a[:]

·字典的copy方法可以拷贝一个字典

d = dict(name="zhangsan", age=27)

co = d.copy()

·有些内置函数可以生成拷贝(list)

a = list(range(10))

b = list(a)

·copy模块中的copy函数

importcopy

a = (1,2,3)

b = copy.copy(a)

1.3属性property

1.3.1私有属性添加getter和setter方法

classMoney(object):

def__init__(self):

self.__money =0

defgetMoney(self):

returnself.__money

defsetMoney(self, value):

ifisinstance(value, int):

self.__money = value

else:

print("error:不是整型数字")

1.3.2使用property升级getter和setter方法

classMoney(object):

def__init__(self):

self.__money =0

defgetMoney(self):

returnself.__money

defsetMoney(self, value):

ifisinstance(value, int):

self.__money = value

else:

print("error:不是整型数字")

money = property(getMoney, setMoney)

运行结果:

In [1]:fromget_setimportMoney

In [2]:

In [2]: a = Money()

In [3]:

In [3]: a.money

Out[3]:0

In [4]: a.money =100

In [5]: a.money

Out[5]:100

In [6]: a.getMoney()

Out[6]:100

1.3.3使用property取代getter和setter方法

@property成为属性函数,可以对属性赋值时做必要的检查,并保证代码的清晰短小,主要有2个作用:

·将方法转换为只读

·重新实现一个属性的设置和读取方法,可做边界判定

classMoney(object):

def__init__(self):

self.__money =0

@property

defmoney(self):

returnself.__money

@money.setter

defmoney(self, value):

ifisinstance(value, int):

self.__money = value

else:

print("error:不是整型数字")

运行结果

In [3]: a =Money()

In [4]:

In [4]:

In [4]: a.money

Out[4]:0

In [5]: a.money

=100

In [6]: a.money

Out[6]:100

1.4生成器

1.4.1什么是生成器

一边循环一边计算的机制,称为生成器:generator。

1.4.2创建生成器方法1

第一种方法很简单,只要把一个列表生成式的[ ]改成( )

In [15]: L = [ x*2forxinrange(5)]

In [16]: L

Out[16]: [0,2,4,6,8]

In [17]: G = ( x*2forxinrange(5))

In [18]: G

Out[18]: at0x7f626c132db0>

In [19]:

创建L和G的区别仅在于最外层的[ ]和( ),L是一个列表,而G是一个生成器。可以直接打印出L的每一个元素,但如果要一个一个打印出G的元素,可以通过next()函数获得生成器的下一个返回值:

In [19]: next(G)

Out[19]: 0

In [20]: next(G)

Out[20]: 2

In [21]: next(G)

Out[21]: 4

In [22]: next(G)

Out[22]: 6

In [23]: next(G)

Out[23]: 8

In [24]: next(G)


StopIterationTraceback (most recent call last)

in ()

----> 1 next(G)

StopIteration:

In [25]:

In [26]: G = ( x*2forxinrange(5))

In [27]:forxinG:

....:print(x)

....:

0

2

4

6

8

In [28]:

生成器保存的是算法,每次调用next(G),就计算出G的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的异常。当然,正确的方法是使用for循环,因为生成器也是可迭代对象。所以,创建了一个生成器后,基本上永远不会调用next(),而是通过for循环来迭代它,并且不需要关心StopIteration异常。

1.4.3创建生成器方法2

generator还可以用函数来实现。

比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:

1, 1, 2, 3, 5, 8, 13, 21, 34, ...

斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:

[In []():

....:n =0

....:a,b =0,1

....:whilen

....:print(b)

....:a,b = b,a b

....:n =1

....:return'done'

....:

In [29]: fib(5)

1

1

2

3

5

Out[29]:'done'

可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。

上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改为yield b就可以了:

In [30]: def fib(times):

....:n = 0

....:a,b = 0,1

....:while n

....:yield b

....:a,b = b,a b

....:n =1

....:return 'done'

....:

In [31]: F = fib(5)

In [32]: next(F)

Out[32]: 1

In [33]: next(F)

Out[33]: 1

In [34]: next(F)

Out[34]: 2

In [35]: next(F)

Out[35]: 3

In [36]: next(F)

Out[36]: 5

In [37]: next(F)


StopIterationTraceback (most recent call last)

in ()

----> 1 next(F)

StopIteration: done

上面fib的例子中,在循环过程中不断调用yield,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。同样的,把函数改成generator后,基本上从来不会用next()来获取下一个返回值,而是直接使用for循环来迭代:

In [38]:forninfib(5):

....:print(n)

....:

1

1

2

3

5

In [39]:

用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:

In [39]: g = fib(5)

In [40]:whileTrue:

....:try:

....:x = next(g)

....:print("value:%d"%x)

....:exceptStopIterationase:

....:print("生成器返回值:%s"%e.value)

....:break

....:

value:1

value:1

value:2

value:3

value:5

生成器返回值:done

In [41]:

1.4.4send

例子:执行到yield时,gen函数作用暂时保存,返回i的值;temp接收下次c.send("python"),send发送过来的值,c.next()等价c.send(None)

In [10]:defgen():

....:i =0

....:whilei<5:

....:temp =yieldi

....:print(temp)

....:i =1

....:

使用next函数

In [11]: f = gen()

In [12]: next(f)

Out[12]: 0

In [13]: next(f)

None

Out[13]: 1

In [14]: next(f)

None

Out[14]: 2

In [15]: next(f)

None

Out[15]: 3

In [16]: next(f)

None

Out[16]: 4

In [17]: next(f)

None


StopIterationTraceback (most recent call last)

in ()

----> 1 next(f)

StopIteration:

使用__next__()方法

In [18]: f = gen()

In [19]: f.__next__()

Out[19]: 0

In [20]: f.__next__()

None

Out[20]: 1

In [21]: f.__next__()

None

Out[21]: 2

In [22]: f.__next__()

None

Out[22]: 3

In [23]: f.__next__()

None

Out[23]: 4

In [24]: f.__next__()

None


StopIterationTraceback (most recent call last)

in ()

----> 1 f.__next__()

StopIteration:

使用send

In [43]: f = gen()

In [44]: f.__next__()

Out[44]:0

In [45]: f.send('haha')

haha

Out[45]:1

In [46]: f.__next__()

None

Out[46]:2

In [47]: f.send('haha')

haha

Out[47]:3

In [48]:

1.4.5实现多任务

模拟多任务实现方式之一:协程

def test1():

while True:

print("--1--")

yield None

def test2():

while True:

print("--2--")

yield None

t1 = test1()

t2 = test2()

while True:

t1.__next__()

t2.__next__()

总结

生成器是这样一个函数,它记住上一次返回时在函数体中的位置。对生成器函数的第二次(或第n次)调用跳转至该函数中间,而上次调用的所有局部变量都保持不变。

生成器不仅“记住”了它数据状态;生成器还“记住”了它在流控制构造(在命令式编程中,这种构造不只是数据值)中的位置。

生成器的特点:

1.节约内存

2.迭代到下一次的调用时,所使用的参数都是第一次所保留下的,即是说,在整个所有函数调用的参数都是第一次所调用时保留的,而不是新创建的

1.5迭代器

迭代是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。

1.5.1可迭代对象

以直接作用于for循环的数据类型有以下几种:

一类是集合数据类型,如list、tuple、dict、set、str等;

一类是generator,包括生成器和带yield的generator function。

这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。

1.5.2判断是否可以迭代

可以使用isinstance()判断一个对象是否是Iterable对象:

In [50]:fromcollectionsimportIterable

In [51]: isinstance([], Iterable)

Out[51]:True

In [52]: isinstance({}, Iterable)

Out[52]:True

In [53]: isinstance('abc', Iterable)

Out[53]:True

In [54]: isinstance((xforxinrange(10)), Iterable)

Out[54]:True

In [55]: isinstance(100, Iterable)

Out[55]:False

生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。

1.5.3迭代器

可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。

可以使用isinstance()判断一个对象是否是Iterator对象:

In [56]:fromcollectionsimportIterator

In [57]: isinstance((xforxinrange(10)), Iterator)

Out[57]:True

In [58]: isinstance([], Iterator)

Out[58]:False

In [59]: isinstance({}, Iterator)

Out[59]:False

In [60]: isinstance('abc', Iterator)

Out[60]:False

In [61]: isinstance(100, Iterator)

Out[61]:False

1.5.4iter()函数

生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。

把list、dict、str等Iterable变成Iterator可以使用iter()函数:

In [62]: isinstance(iter([]), Iterator)

Out[62]:True

In [63]: isinstance(iter('abc'), Iterator)

Out[63]:True

总结

·凡是可作用于for循环的对象都是Iterable类型;

·凡是可作用于next()函数的对象都是Iterator类型

·集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。

·目的是在使用集合的时候,减少占用的内容。

1.6闭包

1.6.1函数引用

deftest1():

print("--- in test1 func----")

#调用函数

test1()

#澳门新浦京娱乐场网站:高档编制程序④元类,幼功实行。引用函数

ret = test1

print(id(ret))

print(id(test1))

#通过引用调用函数

ret()

运行结果:

---intest1 func----

140212571149040

140212571149040

---intest1 func----

1.6.2什么是闭包

#定义一个函数

deftest(number):

#在函数内部再定义一个函数,并且这个函数用到了外边函数的变量,那么将这个函数以及用到的一些变量称之为闭包

deftest_in(number_in):

print("in test_in函数, number_in is %d"%number_in)

returnnumber number_in

#其实这里返回的就是闭包的结果

returntest_in

#给test函数赋值,这个20就是给参数number

ret = test(20)

#注意这里的100其实给参数number_in

print(ret(100))

#注意这里的200其实给参数number_in

print(ret(200))

运行结果:

intest_in函数, number_inis100

120

intest_in函数, number_inis200

220

1.6.3看一个闭包的实际例子:

defline_conf(a, b):

defline(x):

returna*x b

returnline

line1 = line_conf(1,1)

line2 = line_conf(4,5)

print(line1(5))

print(line2(5))

例子中,函数line与变量a,b构成闭包。在创建闭包的时候,通过line_conf的参数a,b说明了这两个变量的取值,这样,就确定了函数的最终形式(y = x 1和y = 4x 5)。只需要变换参数a,b,就可以获得不同的直线表达函数。由此可以看到,闭包也具有提高代码可复用性的作用。

如果没有闭包,就需要每次创建直线函数的时候同时说明a,b,x。这样,就需要更多的参数传递,也减少了代码的可移植性。

注意:

1.闭包优化了变量,原来需要类对象完成的工作,闭包也可以完成

2.由于闭包引用了外部函数的局部变量,则外部函数的局部变量没有及时释放,消耗内存

1.7装饰器

装饰器,功能就是在运行原来功能基础上,加上一些其它功能,比如权限的验证,比如日志的记录等等。不修改原来的代码,进行功能的扩展。

1.7.1装饰器的理解

装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

1.7.2多个装饰器

#定义函数:完成包裹数据

defmakeBold(fn):

澳门新浦京娱乐场网站:高档编制程序④元类,幼功实行。defwrapped():

return"" fn() ""

returnwrapped

#定义函数:完成包裹数据

defmakeItalic(fn):

defwrapped():

return"" fn() ""

returnwrapped

@makeBold

deftest1():

return"hello world-1"

@makeItalic

deftest2():

return"hello world-2"

@makeBold

@makeItalic

deftest3():

return"hello world-3"

print(test1()))

print(test2()))

print(test3()))

运行结果:

hello world-1

hello world-2

hello world-3

1.7.3装饰器(decorator)功能

1.引入日志

2.函数执行时间统计

3.执行函数前预备处理

4.执行函数后清理功能

5.权限校验等场景

6.缓存

1.7.4装饰器示例

1.7.4.1例1:无参数的函数

fromtimeimportctime, sleep

deftimefun(func):

defwrappedfunc():

print("%s called at %s"%(func.__name__, ctime()))

func()

returnwrappedfunc

@timefun

deffoo():

print("I am foo")

foo()

sleep(2)

foo()

上面代码理解装饰器执行行为可理解成

foo = timefun(foo)

#foo先作为参数赋值给func后,foo接收指向timefun返回的wrappedfunc

foo()

#调用foo(),即等价调用wrappedfunc()

#内部函数wrappedfunc被引用,所以外部函数的func变量(自由变量)并没有释放

#func里保存的是原foo函数对象

1.7.4.2例2:被装饰的函数有参数

fromtimeimportctime, sleep

deftimefun(func):

defwrappedfunc(a, b):

print("%s called at %s"%(func.__name__, ctime()))

print(a, b)

func(a, b)

returnwrappedfunc

@timefun

deffoo(a, b):

print(a b)

foo(3,5)

sleep(2)

foo(2,4)

1.7.4.3例3:被装饰的函数有不定长参数

fromtimeimportctime, sleep

deftimefun(func):

defwrappedfunc(*args, **kwargs):

print("%s called at %s"%(func.__name__, ctime()))

func(*args, **kwargs)

returnwrappedfunc

@timefun

deffoo(a, b, c):

print(a b c)

foo(3,5,7)

sleep(2)

foo(2,4,9)

1.7.4.4例4:装饰器中的return

fromtimeimportctime, sleep

deftimefun(func):

defwrappedfunc():

print("%s called at %s"%(func.__name__, ctime()))

func()

returnwrappedfunc

@timefun

deffoo():

print("I am foo")

@timefun

defgetInfo():

return'----hahah---'

foo()

sleep(2)

foo()

print(getInfo())

执行结果:

foo called at Fri Nov421:55:352016

I am foo

foo called at Fri Nov421:55:372016

I am foo

getInfo called at Fri Nov421:55:372016

None

如果修改装饰器为return

func(),则运行结果:

foo called at Fri Nov421:55:572016

I am foo

foo called at Fri Nov421:55:592016

I am foo

getInfo called at Fri Nov421:55:592016

----hahah---

总结:

·一般情况下为了让装饰器更通用,可以有return

1.7.4.5例5:装饰器带参数,在原有装饰器的基础上,设置外部变量

#decorator2.py

fromtimeimportctime, sleep

deftimefun_arg(pre="hello"):

deftimefun(func):

defwrappedfunc():

print("%s called at %s %s"%(func.__name__, ctime(), pre))

returnfunc()

returnwrappedfunc

returntimefun

@timefun_arg("wangcai")

deffoo():

print("I am foo")

@timefun_arg("python")

deftoo():

print("I am too")

foo()

sleep(2)

foo()

too()

sleep(2)

too()

可以理解为

foo()==timefun_arg("wangcai")(foo)()

1.7.4.6例6:类装饰器(扩展,非重点)

装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。在Python中一般callable对象都是函数,但也有例外。只要某个对象重写了__call__()方法,那么这个对象就是callable的。

classTest():

def__call__(self):

print('call me!')

t = Test()

t()# call me

类装饰器demo

classTest(object):

def__init__(self, func):

print("---初始化---")

print("func name is %s"%func.__name__)

self.__func = func

def__call__(self):

print("---装饰器中的功能---")

self.__func()

#说明:

#1.当用Test来装作装饰器对test函数进行装饰的时候,首先会创建Test的实例对象

#并且会把test这个函数名当做参数传递到__init__方法中

#即在__init__方法中的func变量指向了test函数体

#

#2. test函数相当于指向了用Test创建出来的实例对象

#

#3.当在使用test()进行调用时,就相当于让这个对象(),因此会调用这个对象的__call__方法

#

#4.为了能够在__call__方法中调用原来test指向的函数体,所以在__init__方法中就需要一个实例属性来保存这个函数体的引用

#所以才有了self.__func = func这句代码,从而在调用__call__方法中能够调用到test之前的函数体

@Test

deftest():

print("----test---")

test()

showpy()#如果把这句话注释,重新运行程序,依然会看到"--初始化--"

运行结果如下:

---初始化---

func nameistest

---装饰器中的功能---

----test---

1.8python是动态语言

1.8.1动态语言的定义

动态编程语言是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。

1.8.2运行的过程中给对象绑定(添加)属性

>>>classPerson(object):

def__init__(self, name = None, age = None):

self.name = name

self.age = age

>>>P = Person("小明","24")

>>>

上面定义了1个类Person,在这个类里,定义了两个初始属性name和age,

>>>P.sex ="male"

>>>P.sex

'male'

>>>

动态给实例绑定sex属性。

1.8.3运行的过程中给类绑定(添加)属性

>>>P1 = Person("小丽","25")

>>>P1.sex

Traceback (most recent call last):

File"", line1,in

P1.sex

AttributeError: Person instance has no attribute'sex'

>>>

尝试打印P1.sex,发现报错,P1没有sex这个属性。可以直接给Person绑定属性。

>>>> Person.sex =None#给类Person添加一个属性

>>>P1 = Person("小丽","25")

>>>print(P1.sex)#如果P1这个实例对象中没有sex属性的话,那么就会访问它的类属性

None#可以看到没有出现异常

>>>

1.8.4运行的过程中给类绑定(添加)方法

functio绑定:

>>>classPerson(object):

def__init__(self, name = None, age = None):

self.name = name

self.age = age

defeat(self):

print("eat food")

>>>defrun(self, speed):

print("%s在移动,速度是%d km/h"%(self.name, speed))

>>>P = Person("老王",24)

>>>P.eat()

eat food

>>>

>>>P.run()

Traceback (most recent call last):

File"", line1,in

P.run()

AttributeError: Person instance has no attribute'run'

>>>

>>>

>>>importtypes

>>>P.run = types.MethodType(run, P)

>>>P.run(180)

老王在移动,速度是180km/h

给对象添加一个方法是对象.方法名= xxxx

完整的代码如下:

importtypes

#定义了一个类

classPerson(object):

num =0

def__init__(self, name = None, age = None):

self.name = name

self.age = age

defeat(self):

print("eat food")

#定义一个实例方法

defrun(self, speed):

print("%s在移动,速度是%d km/h"%(self.name, speed))

#定义一个类方法

@classmethod

deftestClass(cls):

cls.num =100

#定义一个静态方法

@staticmethod

deftestStatic():

print("---static method----")

#创建一个实例对象

P = Person("老王",24)

#调用在class中的方法

P.eat()

#给这个对象添加实例方法

P.run = types.MethodType(run, P)

#调用实例方法

P.run(180)

#给Person类绑定类方法

Person.testClass = testClass

#调用类方法

print(Person.num)

Person.testClass()

print(Person.num)

#给Person类绑定静态方法

Person.testStatic = testStatic

#调用静态方法

Person.testStatic()

1.8.5运行的过程中删除属性、方法

删除的方法:

1.del对象.属性名

2.delattr(对象, "属性名")

1.8.6__slots__

动态语言与静态语言的不同:

动态语言:可以在运行的过程中,修改代码

静态语言:编译时已经确定好代码,运行过程中不能修改

Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性:

>>>classPerson(object):

__slots__ = ("name","age")

>>>P = Person()

>>>P.name ="老王"

>>>P.age =20

>>>P.score =100

Traceback (most recent call last):

File"", line1,in

AttributeError: Person instance has no attribute'score'

>>>

注意:

·使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的

In [67]:classTest(Person):

...:pass

...:

In [68]: t = Test()

In [69]: t.score =100

1.9元类

1.9.1类也是对象

类就是一组用来描述如何生成一个对象的代码段:

>>>classObjectCreator(object):

…pass

>>>my_object = ObjectCreator()

>>>printmy_object

<__main__.ObjectCreator object at0x8974f2c>

类同样也是一种对象。

Python解释器在执行class的时候会创建一个对象。

下面的代码段:

>>>classObjectCreator(object):

…pass

将在内存中创建一个对象,名字就是ObjectCreator。这个对象(类对象ObjectCreator)拥有创建对象(实例对象)的能力。但是,它的本质仍然是一个对象,可以进行如下的操作:

1.可以将它赋值给一个变量

2.可以拷贝它

3.可以为它增加属性

4.可以将它作为函数参数进行传递

下面是示例:

>>> print ObjectCreator#可以打印一个类,因为它其实也是一个对象

>>> def echo(o):

…print o

>>> echo(ObjectCreator)#可以将类做为参数传给函数

>>> print hasattr(ObjectCreator, 'new_attribute')

Fasle

>>> ObjectCreator.new_attribute = 'foo' #可以为类增加属性

>>> print hasattr(ObjectCreator, 'new_attribute')

True

>>> print ObjectCreator.new_attribute

foo

>>> ObjectCreatorMirror = ObjectCreator #可以将类赋值给一个变量

>>> print ObjectCreatorMirror()

<__main__.ObjectCreator object at 0x8997b4c>

1.9.2动态地创建类

类也是对象,可以在运行时动态的创建它们,就像其他任何对象一样。

可以在函数中创建类,使用class关键字:

>>> def choose_class(name):

…if name == 'foo':

…class Foo(object):

…pass

…return Foo#返回的是类,不是类的实例

…else:

…class Bar(object):

…pass

…return Bar

>>> MyClass = choose_class('foo')

>>> print MyClass#函数返回的是类,不是类的实例

>>> print MyClass()#可以通过这个类创建类实例,也就是对象

<__main__.Foo object at 0x89c6d4c>

内建函数type查看对象的类型:

>>> print type(1) #数值的类型

>>> print type("1") #字符串的类型

>>> print type(ObjectCreator()) #实例对象的类型

>>> print type(ObjectCreator) #类的类型

1.9.3使用type创建类

type可以动态的创建类。

type可以接受一个类的描述作为参数,然后返回一个类。

type可以像这样工作:

type(类名,由父类名称组成的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))

比如下面的代码:

In [2]:classTest:#定义了一个Test类

...:pass

...:

In [3]: Test()#创建了一个Test类的实例对象

Out[3]: <__main__.Test at0x10d3f8438>

可以手动像这样创建:

Test2 = type("Test2",(),{})#定了一个Test2类

In [5]: Test2()#创建了一个Test2类的实例对象

Out[5]: <__main__.Test2 at0x10d406b38>

使用"Test2"作为类名,并且也可以把它当做一个变量来作为类的引用。类和变量是不同的,这里没有任何理由把事情弄的复杂。即type函数中第1个实参,也可以叫做其他的名字,这个名字表示类的名字

In [23]: MyDogClass = type('MyDog', (), {})

In [24]: print MyDogClass

使用help来测试这2个类

In [10]: help(Test) #用help查看Test类

Help on class Test in module __main__:

class Test(builtins.object)

|Data descriptors defined here:

|

|__dict__

|dictionary for instance variables (if defined)

|

|__weakref__

|list of weak references to the object (if defined)

In [8]: help(Test2) #用help查看Test2类

Help on class Test2 in module __main__:

class Test2(builtins.object)

|Data descriptors defined here:

|

|__dict__

|dictionary for instance variables (if defined)

|

|__weakref__

|list of weak references to the object (if defined)

1.9.4使用type创建带有属性的类

type接受一个字典来为类定义属性,因此

>>>Foo = type('Foo', (), {'bar':True})

可以翻译为:

>>>classFoo(object):

…bar =True

并且可以将Foo当成一个普通的类一样使用:

>>> print Foo

>>> print Foo.bar

True

>>> f = Foo()

>>> print f

<__main__.Foo object at 0x8a9b84c>

>>> print f.bar

True

可以向这个类继承,所以,如下的代码:

>>>classFooChild(Foo):

…pass

就可以写成:

>>> FooChild = type('FooChild', (Foo,),{})

>>> print FooChild

>>> print FooChild.bar# bar属性是由Foo继承而来

True

注意:

·type的第2个参数,元组中是父类的名字,而不是字符串

·添加的属性是类属性,并不是实例属性

1.9.5使用type创建带有方法的类

添加实例方法

In [46]:defecho_bar(self):#定义了一个普通的函数

...:print(self.bar)

...:

In [47]: FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})#让FooChild类中的echo_bar属性,指向了上面定义的函数

In [48]: hasattr(Foo,'echo_bar')#判断Foo类中,是否有echo_bar这个属性

Out[48]:False

In [49]:

In [49]: hasattr(FooChild,'echo_bar')#判断FooChild类中,是否有echo_bar这个属性

Out[49]:True

In [50]: my_foo = FooChild()

In [51]: my_foo.echo_bar()

True

添加静态方法

In [36]: @staticmethod

...:deftestStatic():

...:print("static method ....")

...:

In [37]: Foochild = type('Foochild', (Foo,), {"echo_bar":echo_bar,"testStatic":

...: testStatic})

In [38]: fooclid = Foochild()

In [39]: fooclid.testStatic

Out[39]:

In [40]: fooclid.testStatic()

static method ....

In [41]: fooclid.echo_bar()

True

添加类方法

In [42]: @classmethod

...:deftestClass(cls):

...:print(cls.bar)

...:

In [43]:

In [43]: Foochild = type('Foochild', (Foo,), {"echo_bar":echo_bar,"testStatic":

...: testStatic,"testClass":testClass})

In [44]:

In [44]: fooclid = Foochild()

In [45]: fooclid.testClass()

True

在Python中,类也是对象,可以动态的创建类。

1.9.6到底什么是元类

元类就是用来创建类的“东西”。

元类就是用来创建类(对象)的,元类就是类的类:

MyClass = MetaClass()#使用元类创建出一个对象,这个对象称为“类”

MyObject = MyClass()#使用“类”来创建出实例对象

元类就是创建类这种对象的东西。type就是Python的内建元类。

1.9.7__metaclass__属性

可以在定义一个类的时候添加__metaclass__属性。

classFoo(object):

__metaclass__ = something…

...省略...

Python会用元类来创建类Foo。先写下class Foo(object),但是类Foo还没有在内存中创建。Python会在类的定义中寻找__metaclass__属性,如果找到了,Python就会用它来创建类Foo,如果没有找到,就会用内建的type来创建这个类。如下代码:

classFoo(Bar):

pass

Python做了如下的操作:

1.Foo中有__metaclass__这个属性吗?如果是,Python会通过__metaclass__创建一个名字为Foo的类(对象)

2.如果Python没有找到__metaclass__,它会继续在Bar(父类)中寻找__metaclass__属性,并尝试做和前面同样的操作。

3.如果Python在任何父类中都找不到__metaclass__,它就会在模块层次中去寻找__metaclass__,并尝试做同样的操作。

4.如果还是找不到__metaclass__,Python就会用内置的type来创建这个类对象。

1.9.8自定义元类

__metaclass__可以被任意调用,它并不需要是一个正式的类。

1.拦截类的创建

2.修改类

3.返回修改之后的类

1.10垃圾回收

1.10.1小整数对象池

整数在程序中的使用非常广泛,Python为了优化速度,使用了小整数对象池,避免为整数频繁申请和销毁内存空间。

Python对小整数的定义是[-5, 257)这些整数对象是提前建立好的,不会被垃圾回收。在一个Python的程序中,所有位于这个范围内的整数使用的都是同一个对象.

同理,单个字母也是这样的。

但是当定义2个相同的字符串时,引用计数为0,触发垃圾回收

1.10.2大整数对象池

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

1.10.3intern机制

·小整数[-5,257)共用对象,常驻内存

·单个字符共用对象,常驻内存

·单个单词,不可修改,默认开启intern机制,共用对象,引用计数为0,则销毁

字符串(含有空格),不可修改,没开启intern机制,不共用对象,引用计数为0,销毁

大整数不共用内存,引用计数为0,销毁

数值类型和字符串类型在Python中都是不可变的,这意味着每次对变量的修改实际上是创建一个新的对象

1.10.4Garbage collection(GC垃圾回收)

python采用的是引用计数机制为主,标记-清除和分代收集两种机制为辅的策略

引用计数机制:

python里每一个东西都是对象,它们的核心就是一个结构体:PyObject

typedefstruct_object {

intob_refcnt;

struct_typeobject *ob_type;

} PyObject;

PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少

#definePy_INCREF(op)((op)->ob_refcnt )//增加计数

#definePy_DECREF(op) //减少计数

if(--(op)->ob_refcnt !=0)

;

else

__Py_Dealloc((PyObject *)(op))

当引用计数为0时,该对象生命就结束了。

引用计数机制的优点:

·简单

·实时性:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时。

引用计数机制的缺点:

·维护引用计数消耗资源

·循环引用

list1 = []

list2 = []

list1.append(list2)

list2.append(list1)

list1与list2相互引用,如果不存在其他对象对它们的引用,list1与list2的引用计数也仍然为1,所占用的内存永远无法被回收,这将是致命的。对于如今的强大硬件,缺点1尚可接受,但是循环引用导致内存泄露,注定python还将引入新的回收机制。(标记清除和分代收集)

1.10.5gc模块

1.10.5.1垃圾回收机制

Python中的垃圾回收是以引用计数为主,分代收集为辅。

1.10.5.1.1导致引用计数 1的情况

·对象被创建,例如a=23

·对象被引用,例如b=a

·对象被作为参数,传入到一个函数中,例如func(a)

·对象作为一个元素,存储在容器中,例如list1=[a,a]

1.10.5.1.2导致引用计数-1的情况

·对象的别名被显式销毁,例如del a

·对象的别名被赋予新的对象,例如a=24

·一个对象离开它的作用域,例如f函数执行完毕时,func函数中的局部变量(全局变量不会)

·对象所在的容器被销毁,或从容器中删除对象

1.10.5.1.3查看一个对象的引用计数

importsys

a ="hello world"

sys.getrefcount(a)

可以查看a对象的引用计数,但是比正常计数大1,因为调用函数的时候传入a,这会让a的引用计数 1

1.10.5.2循环引用导致内存泄露

引用计数的缺陷是循环引用的问题

importgc

classClassA():

def__init__(self):

print('object born,id:%s'%str(hex(id(self))))

deff2():

whileTrue:

c1 = ClassA()

c2 = ClassA()

c1.t = c2

c2.t = c1

delc1

delc2

#把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的对象,内存2的对象的引用数都是1。

·虽然它们两个的对象都是可以被销毁的,但是由于循环引用,导致垃圾回收器都不会回收它们,所以就会导致内存泄露。

1.10.5.3垃圾回收

#coding=utf-8

importgc

classClassA():

def__init__(self):

print('object born,id:%s'%str(hex(id(self))))

# def __del__(self):

#print('object del,id:%s'%str(hex(id(self))))

deff3():

print("-----0------")

# print(gc.collect())

c1 = ClassA()

c2 = ClassA()

c1.t = c2

c2.t = c1

print("-----1------")

delc1

delc2

print("-----2------")

print(gc.garbage)

print("-----3------")

print(gc.collect())#显式执行垃圾回收

print("-----4------")

print(gc.garbage)

print("-----5------")

if__name__ =='__main__':

gc.set_debug(gc.DEBUG_LEAK)#设置gc模块的日志

f3()

python2运行结果:

-----0------

object born,id:0x724b20

object born,id:0x724b48

-----1------

-----2------

[]

-----3------

gc: collectable

gc: collectable

gc: collectable

gc: collectable

4

-----4------

[<__main__.ClassA instance at0x724b20>, <__main__.ClassA instance at0x724b48>, {'t': <__main__.ClassA instance at0x724b48>}, {'t': <__main__.ClassA instance at0x724b20>}]

-----5------

说明:

·垃圾回收后的对象会放在gc.garbage列表里面

·gc.collect()会返回不可达的对象数目,4等于两个对象以及它们对应的dict

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

1.调用gc.collect(),

2.当gc模块的计数器达到阀值的时候。

3.程序退出的时候

1.10.5.4gc模块常用功能解析

gc模块提供一个接口给开发者设置垃圾回收的选项。上面说到,采用引用计数的方法管理内存的一个缺陷是循环引用,而gc模块的一个主要功能就是解决循环引用的问题。

1.10.5.4.1常用函数:

1、gc.set_debug(flags)设置gc的debug日志,一般设置为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的列表

1.10.5.4.2gc模块的自动垃圾回收机制

必须要import gc模块,并且is_enable()=True才会启动自动垃圾回收。

这个机制的主要作用就是发现并处理不可达的垃圾对象。

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

在Python中,采用分代收集的方法。把对象分为三代,一开始,对象在创建的时候,放在一代中,如果在一次一代的垃圾检查中,改对象存活下来,就会被放到二代中,同理在一次二代的垃圾检查中,该对象存活下来,就会被放到三代中。

gc模块里面会有一个长度为3的列表的计数器,可以通过gc.get_count()获取。

例如(488,3,0),其中488是指距离上一次一代垃圾检查,Python分配内存的数目减去释放内存的数目,注意是内存分配,而不是引用计数的增加。例如:

printgc.get_count()# (590, 8, 0)

a = ClassA()

printgc.get_count()# (591, 8, 0)

dela

printgc.get_count()# (590, 8, 0)

3是指距离上一次二代垃圾检查,一代垃圾检查的次数,同理,0是指距离上一次三代垃圾检查,二代垃圾检查的次数。

gc模快有一个自动垃圾回收的阀值,即通过gc.get_threshold函数获取到的长度为3的元组,例如(700,10,10)每一次计数器的增加,gc模块就会检查增加后的计数是否达到阀值的数目,如果是,就会执行对应的代数的垃圾检查,然后重置计数器

例如,假设阀值是(700,10,10):

当计数器从(699,3,0)增加到(700,3,0),gc模块就会执行gc.collect(0),即检查一代对象的垃圾,并重置计数器为(0,4,0)

当计数器从(699,9,0)增加到(700,9,0),gc模块就会执行gc.collect(1),即检查一、二代对象的垃圾,并重置计数器为(0,0,1)

当计数器从(699,9,9)增加到(700,9,9),gc模块就会执行gc.collect(2),即检查一、二、三代对象的垃圾,并重置计数器为(0,0,0)

注意点

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

importgc

classClassA():

pass

# def __del__(self):

#print('object born,id:%s'%str(hex(id(self))))

gc.set_debug(gc.DEBUG_LEAK)

a = ClassA()

b = ClassA()

a.next = b

b.prev = a

print"--1--"

printgc.collect()

print"--2--"

dela

print"--3--"

delb

print"--3-1--"

printgc.collect()

print"--4--"

运行结果:

--1--

0

--2--

--3--

--3-1--

gc: collectable

gc: collectable

gc: collectable

gc: collectable

4

--4--

如果把del打开,运行结果为:

--1--

0

--2--

--3--

--3-1--

gc: uncollectable

gc: uncollectable

gc: uncollectable

gc: uncollectable

4

--4--

1.11内建属性

子类没有实现__init__方法时,默认自动调用父类的。如定义__init__方法时,需自己手动调用父类的__init__方法

__getattribute__例子:

classTest(object):

def__init__(self,subject1):

self.subject1 = subject1

self.subject2 ='cpp'

#属性访问时拦截器,打log

def__getattribute__(self,obj):

ifobj =='subject1':

print('log subject1')

return'redirect python'

else:#测试时注释掉这2行,将找不到subject2

returnobject.__getattribute__(self,obj)

defshow(self):

print('this is Test')

s = Test("python")

print(s.subject1)

print(s.subject2)

运行结果:

log subject1

redirect python

cpp

__getattribute__的坑

classPerson(object):

def__getattribute__(self,obj):

print("---test---")

ifobj.startswith("a"):

return"hahha"

else:

returnself.test

deftest(self):

print("heihei")

t = Person()

t.a#返回hahha

t.b#会让程序死掉

#原因是:当t.b执行时,会调用Person类中定义的__getattribute__方法,但是在这个方法的执行过程中

#if条件不满足,所以程序执行else里面的代码,即return self.test问题就在这,因为return需要把

#self.test的值返回,那么首先要获取self.test的值,因为self此时就是t这个对象,所以self.test就是

#t.test此时要获取t这个对象的test属性,那么就会跳转到__getattribute__方法去执行,即此时产

#生了递归调用,由于这个递归过程中没有判断什么时候推出,所以这个程序会永无休止的运行下去,又因为

#每次调用函数,就需要保存一些数据,那么随着调用的次数越来越多,最终内存吃光,所以程序崩溃

#

#注意:以后不要在__getattribute__方法中调用self.xxxx

1.12内建函数

Build-in Function,启动python解释器,输入dir(__builtins__),可以看到很多python解释器启动后默认加载的属性和函数,这些函数称之为内建函数,这些函数因为在编程时使用较多,cpython解释器用c语言实现了这些函数,启动解释器时默认加载。

1.12.1range

range(stop) -> list of integers

range(start, stop[, step]) -> list of integers

·start:计数从start开始。默认是从0开始。例如range(5)等价于range(0,5);

·stop:到stop结束,但不包括stop.例如:range(0,5)是[0,

1, 2, 3, 4]没有5

·step:每次跳跃的间距,默认为1。例如:range(0,5)等价于range(0, 5, 1)

range返回一个迭代值。如果想得到列表,可通过list函数

a = range(5)

list(a)

创建列表的另外一种方法

In [21]: testList = [x 2forxinrange(5)]

In [22]: testList

Out[22]: [2,3,4,5,6]

1.12.2map函数

map函数会根据提供的函数对指定序列做映射

map(...)

map(function, sequence[, sequence, ...]) -> list

·function:是一个函数

·sequence:是一个或多个序列,取决于function需要几个参数

·返回值是一个map

参数序列中的每一个元素分别调用function函数,返回包含每次function函数返回值的list。

#函数需要一个参数

map(lambdax: x*x, [1,2,3])

#结果为:[1, 4, 9]

#函数需要两个参数

map(lambdax, y: x y, [1,2,3], [4,5,6])

#结果为:[5, 7, 9]

deff1( x, y ):

return(x,y)

l1 = [0,1,2,3,4,5,6]

l2 = ['Sun','M','T','W','T','F','S']

l3 = map( f1, l1, l2 )

print(list(l3))

#结果为:[(0, 'Sun'), (1, 'M'), (2, 'T'), (3, 'W'), (4, 'T'), (5, 'F'), (6, 'S')]

1.12.3filter函数

filter函数会对指定序列执行过滤操作

filter(...)

filter(function or None, sequence) -> list, tuple, or string

Return those items of sequence for which function(item) is true.If

function is None, return the items that are true.If sequence is a tuple

or string, return the same type, else return a list.

·function:接受一个参数,返回布尔值True或False

·sequence:序列可以是str,tuple,list

filter函数会对序列参数sequence中的每个元素调用function函数,最后返回的结果包含调用结果为True的元素。

返回值的类型和参数sequence的类型相同

filter(lambdax: x%2, [1,2,3,4])

[1,3]

filter(None,"she")

'she'

1.12.4reduce函数

reduce函数,reduce函数会对参数序列中元素进行累积

reduce(...)

reduce(function, sequence[, initial]) -> value

Apply a function of two arguments cumulatively to the items of a sequence,

from left to right, so as to reduce the sequence to a single value.

For example, reduce(lambda x, y: x y, [1, 2, 3, 4, 5]) calculates

((((1 2) 3) 4) 5).If initial is present, it is placed before the items

of the sequence in the calculation, and serves as a default when the

sequence is empty.

·function:该函数有两个参数

·sequence:序列可以是str,tuple,list

·initial:固定初始值

reduce依次从sequence中取一个元素,和上一次调用function的结果做参数再次调用function。第一次调用function时,如果提供initial参数,会以sequence中的第一个元素和initial作为参数调用function,否则会以序列sequence中的前两个元素做参数调用function。注意function函数不能为None。

reduce(lambdax, y: x y, [1,2,3,4])

10

reduce(lambdax, y: x y, [1,2,3,4],5)

15

reduce(lambdax, y: x y, ['aa','bb','cc'],'dd')

'ddaabbcc'

在Python3里,reduce函数已经被从全局名字空间里移除了,它现在被放置在fucntools模块里用的话要先引入:from functools import

reduce

1.12.5sorted函数

sorted(...)

sorted(iterable, key=None, reverse=False) --> new sorted list

1.13functools

一些工具函数放在此functools里。

importfunctools

dir(functools)

运行结果:

['MappingProxyType',

'RLock',

'WRAPPER_ASSIGNMENTS',

'WRAPPER_UPDATES',

'WeakKeyDictionary',

'_CacheInfo',

'_HashedSeq',

'__all__',

'__builtins__',

'__cached__',

'__doc__',

'__file__',

'__loader__',

'__name__',

'__package__',

'__spec__',

'_c3_merge',

'_c3_mro',

'_compose_mro',

'_convert',

'_find_impl',

'_ge_from_gt',

'_ge_from_le',

'_ge_from_lt',

'_gt_from_ge',

'_gt_from_le',

'_gt_from_lt',

'_le_from_ge',

'_le_from_gt',

'_le_from_lt',

'_lru_cache_wrapper',

'_lt_from_ge',

'_lt_from_gt',

'_lt_from_le',

'_make_key',

'cmp_to_key',

'get_cache_token',

'lru_cache',

'namedtuple',

'partial',

'partialmethod',

'reduce',

'singledispatch',

'total_ordering',

'update_wrapper',

'wraps']

1.13.1partial函数(偏函数)

把一个函数的某些参数设置默认值,返回一个新的函数,调用这个新函数会更简单。

importfunctools

defshowarg(*args, **kw):

print(args)

print(kw)

p1=functools.partial(showarg,1,2,3)

p1()

p1(4,5,6)

p1(a='python', b='test')

p2=functools.partial(showarg, a=3,b='linux')

p2()

p2(1,2)

p2(a='python', b='test')

1.13.2wraps函数

使用装饰器时,有一些细节需要被注意。例如,被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变)。

添加后由于函数名和函数的doc发生了改变,对测试结果有一些影响,例如:

defnote(func):

"note function"

defwrapper():

"wrapper function"

print('note something')

returnfunc()

returnwrapper

@note

deftest():

"test function"

print('I am test')

test()

print(test.__doc__)

运行结果

note something

I am test

wrapper function

所以,Python的functools包中提供了一个叫wraps的装饰器来消除这样的副作用。例如:

importfunctools

defnote(func):

"note function"

@functools.wraps(func)

defwrapper():

"wrapper function"

print('note something')

returnfunc()

returnwrapper

@note

deftest():

"test function"

print('I am test')

test()

print(test.__doc__)

运行结果

note something

I am test

test function

1.14模块进阶

Python有一套很有用的标准库(standard library)。标准库会随着Python解释器一起安装。它是Python的一个组成部分。

1.14.1常用标准库

1.14.1.1time

1、time有2种时间表示形式:

1、时间戳表示法,即以整型或浮点型表示的是一个以秒为单位的时间间隔。这个时间的基础值是从1970年的1月1号零点开始算起。

2、元组格式表示法,即一种Python的数据结构表示。这个元组有9个整型内容。分别表示不同的时间含义。

2、名词解释:

UTC(Coordinated Universal Time,世界协调时)亦即格林威治天文时间,世界标准时间。在中国为UTC 8。

DST(Daylight Saving Time)即夏令时。是一种为节约能源而人为规定地方时间的制度,一般在天亮早的夏季人为将时间提前一小时。

3、包含的变量:

timezone --当地时间与标准UTC时间的误差,以秒计

altzone --当地夏令时时间与标准UTC时间的误差,以秒计

daylight --当地时间是否反映夏令时,默认为0

zname --关于(标准时区名称,夏令时时区名称)的元组

4、包含的函数:

time() --返回当前时间戳,浮点数形式。不接受参数

clock() --返回当前程序的cpu执行时间。unix系统始终返回全部运行时间;而windows从第二次开始都是以第一次调用此函数时的时间戳作为基准,而不是程序开始时间为基准。不接受参数。

sleep() --延迟一个时间段,接受整型、浮点型。

gmtime() --将时间戳转换为UTC时间元组格式。接受一个浮点型时间戳参数,其默认值为当前时间戳。

localtime()

--将时间戳转换为本地时间元组格式。接受一个浮点型时间戳参数,其默认值为当前时间戳。

asctime() --将时间元组格式转换为字符串形式。接受一个时间元组,其默认值为localtime()返回值

ctime() --将时间戳转换为字符串。接受一个时间戳,其默认值为当前时间戳。等价于asctime(localtime(seconds))

mktime() --将本地时间元组转换为时间戳。接受一个时间元组,必选。

strftime() --将时间元组以指定的格式转换为字符串形式。接受字符串格式化串、时间元组。时间元组为可选,默认为localtime()

strptime() --将指定格式的时间字符串解析为时间元组,strftime()的逆向过程。接受字符串,时间格式2个参数,都是必选。

tzset() --改变本地时区。

5、时间字符串支持的格式符号:

格式含义备注

%a本地(locale)简化星期名称

%A本地完整星期名称

%b本地简化月份名称

%B本地完整月份名称

%c本地相应的日期和时间表示

%d一个月中的第几天(01 - 31)

%H一天中的第几个小时(24小时制,00 - 23)

%I第几个小时(12小时制,01 - 12)

%j一年中的第几天(001 - 366)

%m月份(01 - 12)

%M分钟数(00 - 59)

%p本地am或者pm的相应符

%S秒(01 - 61)

%U一年中的星期数。(00 - 53星期天是一个星期的开始。)第一个星期天之前的所有天数都放在第0周。

%w一个星期中的第几天(0 - 6,0是星期天)

%W和%U基本相同,不同的是%W以星期一为一个星期的开始。

%x本地相应日期

%X本地相应时间

%y去掉世纪的年份(00 - 99)

%Y完整的年份

%Z时区的名字(如果不存在为空字符)

%%‘%’字符

1.14.1.2hashlib

importhashlib

m = hashlib.md5()#创建hash对象,md5:(message-Digest Algorithm 5)消息摘要算法,得出一个128位的密文

printm#

m.update('test')#更新哈希对象以字符串参数

printm.hexdigest()#返回十六进制数字字符串

应用实例

用于注册、登录....

importhashlib

importdatetime

KEY_VALUE ='test'

now = datetime.datetime.now()

m = hashlib.md5()

str ='%s%s'% (KEY_VALUE,now.strftime("%Y%m%d"))

m.update(str.encode('utf-8'))

value = m.hexdigest()

print(value)

运行结果:

8ad2d682e3529dac50e586fee8dc05c0

1.14.2常用扩展库

1.14.2.1SimpleHTTPServer

可以运行静态服务。

在终端中输入命令:

python -m http.server PORT

1.15编码风格

错误认知

·这很浪费时间

·我是个艺术家

·所有人都能穿的鞋不会合任何人的脚

·我善长制定编码规范

正确认知

·促进团队合作

·减少bug处理

·提高可读性,降低维护成本

·有助于代码审查

·养成习惯,有助于程序员自身的成长

pep8编码规范

Python Enhancement Proposals:python改进方案

Guido的关键点之一是:代码更多是用来读而不是写。编码规范旨在改善Python代码的可读性。

风格指南强调一致性。项目、模块或函数保持一致都很重要。

1.15.1每级缩进用4个空格。

括号中使用垂直隐式缩进或使用悬挂缩进。后者应该注意第一行要没有参数,后续行要有缩进。

·Yes

#对准左括号

foo = long_function_name(var_one, var_two,

var_three, var_four)

#不对准左括号,但加多一层缩进,以和后面内容区别。

deflong_function_name(

var_one, var_two, var_three,

var_four):

print(var_one)

#悬挂缩进必须加多一层缩进.

foo = long_function_name(

var_one, var_two,

var_three, var_four)

·No

#不使用垂直对齐时,第一行不能有参数。

foo = long_function_name(var_one, var_two,

var_three, var_four)

#参数的缩进和后续内容缩进不能区别。

deflong_function_name(

var_one, var_two, var_three,

var_four):

print(var_one)

4个空格的规则是对续行可选的。

#悬挂缩进不一定是4个空格

foo = long_function_name(

var_one, var_two,

var_three, var_four)

if语句跨行时,两个字符关键字(比如if)加上一个空格,再加上左括号构成了很好的缩进。后续行暂时没有规定,至少有如下三种格式,建议使用第3种。

#没有额外缩进,不是很好看,个人不推荐.

if(this_is_one_thingand

that_is_another_thing):

do_something()

#添加注释

if(this_is_one_thingand

that_is_another_thing):

# Since both conditions are true, we can frobnicate.

do_something()

#额外添加缩进,推荐。

# Add some extra indentation on the conditional continuation line.

if(this_is_one_thing

andthat_is_another_thing):

do_something()

1.15.2右边括号也可以另起一行。有两种格式,建议第2种。

#右括号不回退,个人不推荐

my_list = [

1,2,3,

4,5,6,

]

result = some_function_that_takes_arguments(

'a','b','c',

'd','e','f',

)

#右括号回退

my_list = [

1,2,3,

4,5,6,

]

result = some_function_that_takes_arguments(

'a','b','c',

'd','e','f',

)

1.15.3空格或Tab?

·空格是首选的缩进方法。

·Tab仅仅在已经使用tab缩进的代码中为了保持一致性而使用。

1.15.4最大行宽

·限制所有行的最大行宽为79字符。

·文本长块,比如文档字符串或注释,行长度应限制为72个字符。

1.15.5空行

·两行空行分割顶层函数和类的定义。

·类的方法定义用单个空行分割。

·额外的空行可以必要的时候用于分割不同的函数组,但是要尽量节约使用。

·额外的空行可以必要的时候在函数中用于分割不同的逻辑块,但是要尽量节约使用。

1.15.6源文件编码

·在核心Python发布的代码应该总是使用UTF-8。

·Python 3(默认UTF-8)不应有编码声明。

1.15.7导入在单独行

·Yes:

importos

importsys

fromsubprocessimportPopen, PIPE

·No:

importsys, os

·导入始终在文件的顶部,在模块注释和文档字符串之后,在模块全局变量和常量之前。

·导入顺序如下:标准库进口,相关的第三方库,本地库。各组的导入之间要有空行。

1.15.8禁止使用通配符导入。

通配符导入(fromimport*)应该避免,因为它不清楚命名空间有哪些名称存,混淆读者和许多自动化的工具。

1.15.9字符串引用

·Python中单引号字符串和双引号字符串都是相同的。注意尽量避免在字符串中的反斜杠以提高可读性。

·根据PEP 257,三个引号都使用双引号。

1.15.10括号里边避免空格

#括号里边避免空格

# Yes

spam(ham[1], {eggs:2})

# No

spam( ham[1], { eggs:2} )

1.15.11逗号,冒号,分号之前避免空格

#逗号,冒号,分号之前避免空格

# Yes

ifx ==4:printx, y; x, y = y, x

# No

ifx ==4:printx , y ; x , y = y , x

1.15.12索引操作中的冒号

当作操作符处理前后要有同样的空格(一个空格或者没有空格,个人建议是没有。

# Yes

ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:]

ham[lower:upper], ham[lower:upper:], ham[lower::step]

ham[lower offset : upper offset]

ham[: upper_fn(x) : step_fn(x)], ham[:: step_fn(x)]

ham[lower offset : upper offset]

# No

ham[lower offset:upper offset]

ham[1:9], ham[1:9], ham[1:9:3]

ham[lower : : upper]

ham[ : upper]

1.15.13函数调用的左括号之前不能有空格

# Yes

spam(1)

dct['key'] = lst[index]

# No

spam (1)

dct ['key'] = lst [index]

1.15.14赋值等操作符前后

赋值等操作符前后不能因为对齐而添加多个空格

# Yes

x =1

y =2

long_variable =3

# No

x=1

y=2

long_variable =3

1.15.15二元运算符两边放置一个空格

涉及=、符合操作符( = , -=等)、比较( == , < , > , != , <> , <= , >= , in , not in , is ,

is not )、布尔( and , or , not )。

优先级高的运算符或操作符的前后不建议有空格。

# Yes

i = i 1

submitted =1

x = x*2-1

hypot2 = x*x y*y

c = (a b) * (a-b)

# No

i=i 1

submitted =1

x = x *2-1

hypot2 = x * x y * y

c = (a b) * (a - b)

1.15.16关键字参数和默认值参数的前后不要加空格

# Yes

defcomplex(real, imag=0.0):

returnmagic(r=real, i=imag)

# No

defcomplex(real, imag =0.0):

returnmagic(r = real, i = imag)

1.15.17通常不推荐复合语句

Compound statements:多条语句写在同一行

# Yes

iffoo =='blah':

do_blah_thing()

do_one()

do_two()

do_three()

# No

iffoo =='blah': do_blah_thing()

do_one(); do_two(); do_three()

尽管有时可以在if/for/while的同一行跟一小段代码,但绝不要跟多个子句,并尽量避免换行。

# No

iffoo =='blah': do_blah_thing()

forxinlst: total = x

whilet <10: t = delay()

更不是:

# No

iffoo =='blah': do_blah_thing()

else: do_non_blah_thing()

try: something()

finally: cleanup()

do_one(); do_two(); do_three(long, argument,

list, like, this)

iffoo =='blah': one(); two(); three()

1.15.18避免采用的名字

决不要用字符'l'(小写字母el),'O'(大写字母oh),或'I'(大写字母eye)作为单个字符的变量名。一些字体中,这些字符不能与数字1和0区别。用'L'代替'l'时。

1.15.19包和模块名

模块名要简短,全部用小写字母,可使用下划线以提高可读性。包名和模块名类似,但不推荐使用下划线。

1.16代码调试

1.16.1.1pycharm

步骤:

1、设置断点

2、shift f9开始调试

3、光标就在断点处停了。这一行没有运行的

4、下一行:f8

5、进入方法:f7

6、跳到下一个断点:alt f9

7、进入方法,跳出这一步,shift f8

本文由澳门新浦京娱乐场网站发布于澳门新浦京娱乐场网站,转载请注明出处:澳门新浦京娱乐场网站:高档编制程序④元类,