Python 高级 11

IO密集型程序、深拷贝和浅拷贝、模块导入、with 语句

1.1 GIL

学习目标

  1. 能够说出 GIL 是什么

  2. 能够说出 GIL 和 线程互斥锁的区别

  3. 能够说出什么是计算密集型程序

  4. 能够说出什么是IO密集型程序

  5. 能够说出 GIL 对计算密集型程序程序有什么影响

  6. 能够说出 GIL 对IO密集型程序程序有什么影响

  7. 能够说出如何改善 GIL 对程序产生的影响

--------------------------------------------------------------------------------

Python语言和GIL没有半毛钱关系。仅仅是由于历史原因在Cpython虚拟机(解释器),难以移除GIL。

GIL:全局解释器锁。每个线程在执行的过程都需要先获取GIL,保证同一时刻只有一个线程可以执行代码。

线程释放GIL锁的情况: 在IO操作等可能会引起阻塞的system call之前,可以暂时释放GIL,但在执行完毕后,

必须重新获取GIL Python 3.x使用计时器(执行时间达到阈值后,当前线程释放GIL)或Python 2.x,tickets计数

达到100

1.1.1 GIL 概述

GIL ( Global Interperter Lock ) 称作全局解释器锁。首先需要明确一点,我们所讲的 GIL 并不是 Python 语言的特性,它是在实现 Python 解释器时引用的一个概念。GIL 只在CPython 解释器上存在。

1.1.2 回顾线程互斥锁

由于多线程非同步竞争共享数据资源时,导致问题产生。可以使用线程互斥锁来解决。

通过回顾互斥锁,我们知道使用锁的目的是为了解决多线程竞争共享资源的问题。

1.1.3 互斥锁和 GIL 的区别

上面多线程程序的执行流程如下图:

由上图分析得到结论如下:

  1. Python 解释器也是一个应用程序

  2. GIL 只在 CPython 解释器中存在

  3. 线程互斥锁是 Python 代码层面的锁,解决 Python 程序中多线程共享资源的问题

  4. GIL 是 Python 解释层面的锁,解决解释器中多个线程的竞争资源问题。

1.1.4 GIL 对程序的影响

下面对上图做进一步的分析,从CPU的角度来分析 GIL 锁对程序产生什么影响

计算密集型程序

通过分析得到结论如下:

  1. 在 Python 中同一时刻有且只有一个线程会执行。

  2. Python 中的多线程由于 GIL 锁的存在无法利用多核 CPU

  3. Python 中的多线程不适合计算密集型的程序。

  4. 如果程序需要大量的计算,利用多核CPU资源,可以使用多进程来解决

IO密集型程序(IO, input写入,output读取)

大部分的程序在运行时,都需要大量IO操作,比如网络数据的收发,大文件的读写,(磁盘读取,web服务)这样的程序称为IO密集型程序。

Python 3.x使用计时器(执行时间达到阈值后,当前线程释放GIL)或Python 2.x,tickets计数达到100,这样对CPU密集型程序更加友好

IO密集型程序在运行时,需要大量的时间进行等待,那么这时如果IO操作不完成,程序就无法执行后面的操作,导致CPU空闲。

在Python解释器执行程序时,由于GIL的存在,导致同一时刻只能有一个线程执行,那么程序执行效率非常低,那么在程序进行IO读取时,CPU实际并没有做任何工作,为了提高CPU的使用率,那么Python解释在程序执行IO等待时,会释放 GIL 锁,让其它线程执行,提高Python程序的执行效率。

1.1.5 如何改善 GIL 产生的问题

因为 GIL 锁是解释器层面的锁,无法去除 GIL 锁在执行程序时带来的问题。只能去改善。

  1. 更换更高版本的解释器,比如3.6,从3.2版本开始,Python对解释做了优化,但并不理想

  2. 更换解释器,比如 Jython,但是由于比较小众,支持的模块较少,导致开发效率降低

  3. Python为了解决程序使用多核的问题,使用多进程替代多线程

1.1.6 小结

  1. GIL ( Global Interpreter Lock ) 全局解释器锁。

  2. GIL 不是 Python 语言的特性,是CPython中的一个概念。

  3. Python 解释器也是一个应用程序

  4. 线程互斥锁是 Python 代码层面的锁,解决 Python 程序中多线程共享资源的问题

  5. GIL 是 Python 解释器层面的锁,解决解释器中多个线程的竞争资源问题。

  6. 由于 GIL 的存在, Python程序中同一时刻有且只有一个线程会执行。

  7. Python 中的多线程由于 GIL 锁的存在无法利用多核 CPU

  8. Python 中的多线程不适合计算密集型的程序。

  9. GIL 锁在遇到IO等待时,会释放 GIL 锁,可以提高Python中IO密集型程序的效率

  10. 如果程序需要大量的计算,利用多核CPU资源,可以使用多进程来解决

1.2 深拷贝和浅拷贝

学习目标

  1. 能够说出什么是对象引用

  2. 能够说出什么是不可变对象

  3. 能够说出什么是可变对象

  4. 能够说出什么是引用赋值

  5. 能够说出什么是浅拷贝

  6. 能够说出什么是深拷贝

  7. 能够说出浅拷贝对可变对象有什么影响

  8. 能够说出深拷贝对可变对象有什么影响

  9. 能够说出浅拷贝的几种实现方式

  10. 能够说出浅拷贝的优点

--------------------------------------------------------------------------------

1.2.1 深拷贝和浅拷贝概述

在程序开发过程中,经常涉及到数据的传递,在数据传递使用过程中,可能会发生数据被修改的问题。为了防止数据被修改,就需要在传递一个副本,即使副本被修改,也不会影响原数据的使用。为了生成这个副本,就产生了拷贝。

1.2.2 技术点回顾

  一切皆对象

在 Python 中,所有的数据都被当成对象来处理,无论是数字,字符串,还是函数,甚至是模块。

  不可变对象

在 Python 中,int, str, tuple 等类型的数据都是不可变对象,不可变对象的特性是数字不可被修改。

  可变对象

在 Python 中,list, set,dict 等类型的数据都是可变对象,相对于不可变对象而言,可变对象的数据可以被修改

  引用

在 Python 程序中,每个对象都会在内存中申请开辟一块空间来保存该对象,该对象在内存中所在位置的地址被称为引用

在开发程序时,所定义的变量名实际就对象的地址引用

引用实际就是内存中的一个数字地址编号,在使用对象时,只要知道这个对象的地址,就可以操作这个对象,但是因为这个数字地址不方便在开发时使用和记忆,所以使用变量名的形式来代替对象的数字地址。 在 Python 中,变量就是地址的一种表示形式,并不开辟开辟存储空间。

就像 IP 地址,在访问网站时,实际都是通过 IP 地址来确定主机,而 IP 地址不方便记忆,所以使用域名来代替 IP 地址,在使用域名访问网站时,域名被解析成 IP 地址来使用。

# 使用 id() 函数可以查看对象的引用

1.2.2 引用赋值

赋值的本质就是让多个变量同时引用同一个对象的地址。  那么在对数据修改时会发生什么问题呢?

  不可变对象的引用赋值

在不可变对象赋值时,不可变对象不会被修改,而是会新开辟一个空间

程序原理图:

  可变对象的引用赋值

在可变对象中,保存的并不真正的对象数据,而对象的引用。 当对可变对象进行修改时,只是将可变对象中保存的引用进行更

程序原理图:

函数在传递参数时,实际上就是实参对形参的赋值,如果实参是可变对象,那么就可以在函数的内部修改传入的数据。

1.2.3 浅拷贝

为了解决函数传递后被修改的问题,就需要拷贝一份副本,将副本传递给函数使用,就算是副本被修改,也不会影响原始数据 。

拷贝对象需要导入 copy 模块。

import copy

使用 copy 模块中的 copy 方法就可以拷贝对象了。

  不可变对象的拷贝

因为不可变对象只有在修改时才会开辟新空间,所以拷贝也相当于让多个引用同时引用了一个数据,所以不可变对象的浅拷贝和赋值没有区别

  可变对象的拷贝

程序原理图:

程序原理图:

通过上图发现,copy() 函数在拷贝对象时,只是将指定对象中的所有引用拷贝了一份,如果这些引用当中包含了一个可变对象的话,那么数据还是会被改变。 这种拷贝方式,称为浅拷贝。

1.2.4 深拷贝

相对于浅拷贝只拷贝顶层的引用外,copy模块还提供了另外一个拷贝方法 deepcopy() 函数,这个函数可以逐层进行拷贝引用,直到所有的引用都是不可变引用为止。

程序原理图:

但是大多数的情况下,我们并不希望这样,反而希望数据可以被修改,以达在函数间共享数据的目的。

1.2.5 浅拷贝的几种方式

  copy 模块的 copy 方法

import copy

a = [1, 2]

b = [3, 4, a]

c = copy.copy(b)

  对象本身的 copy 方法

a = [1, 2]

b = [3, 4, a]

c = b.copy()

  工厂方法

      通过类创建对象

a = [1, 2]

b = [3, 4, a]

c = list(b)

  切片

a = [1, 2]

b = [3, 4, a]

c = b[0:]

1.2.6 浅拷贝的优势

  时间角度,浅拷贝花费时间更少

  空间角度,浅拷贝花费内存更少

  效率角度,浅拷贝只拷贝顶层数据,一般情况下比深拷贝效率高。

1.2.7 小结

  不可变对象在赋值时会开辟新空间

  可变对象在赋值时,修改一个的值,另一个也会发生改变

  深浅拷贝对不可变对象拷贝时,不开辟新空间,相当于赋值操作

  浅拷贝在拷贝时,只拷贝第一层中的引用,如果元素是可变对象,并且被修改,那么拷贝的对象也会发生变化

  深拷贝在拷贝时,会逐层进行拷贝,直到所有的引用都是不可变对象为止。

  Python 中有多种方式实现浅拷贝,copy模块的 copy 函数 ,对象的 copy 函数 ,工厂方法,切片等。

  大多数情况下,编写程序时,都是使用浅拷贝,除非有特定的需求

  浅拷贝的优点:拷贝速度快,占用空间少,拷贝效率高

1.3 模块导入

学习目标

  1. 能够说出模块在加载时的搜索过程

  2. 能够说出如何添加模块搜索路径

  3. 能够说出如何动态加载模块

  4. 能够说出 import 和 from-import 两种导入模块的区别

  5. 能够说出循环导入会出现什么问题

--------------------------------------------------------------------------------

1.3.1 模块导入概述

在 Python 开发过程中,需要使用大量的系统模块,第三方模块,自定义模块。这些模块以 Python 文件的形式进行组织。

当需要使用模块中提供的功能时,只需要将模块导入到当前文件中即可。

如果有多个模块可以将这些模块放在一个文件中,并创建一个 __init__.py 的文件,这个文件夹称为 package。

1.3.3 模块导入方式

现有如图中的模块组织方式

  import module

import BB

BB.show()

  import package.module

import MyModules.AA

MyModules.AA.show()

  from module import 成员

from BB import show

show()

  from package import module

from MyModules import AA

AA.show()

  from package.module import 成员

from MyModules.AA import show

show()

1.3.4 模块别名 as

在导入模块时,特别是在从包中导入模块时,如果包名和模块名都特别长,在使用时,非常不方便。

可以使用 as 给 模块起一个别名,编写代码时就可以直接使用别名代替。

import MyModules.AA as MMAA

MMAA.show()

1.3.5 模块搜索路径

在导入模块时,程序是依据什么找到这些模块的呢?

在 sys 模块中有一个 path 变量,记录了程序在导入模块时的查找位置,返回的是一个列表类型。

import sys

path_list = sys.path

print(path_list)

模块的搜索顺序是:

  当前程序所在目录

  当前程序根目录

  PYTHONPATH

  标准库目录

  第三方库目录site-packages目录

如果导入的模块不在 path 保存的路径中,那么导入模块时就会报错

ModuleNotFoundError: No module named 'CC'

可以在程序中向 path 变量中添加模块所在的路径。

假定在路径 /Users/KG/Desktop 下有一个 CC.py 模块

在程序中将 /Users/KG/Desktop 路径加到 path中去

import sys

sys.path.append('/Users/KG/Desktop')

print(sys.path)

import CC

CC.show()

程序运行结果:

['/Users/KG/PycharmProjects/TestDay12', '/Users/KG/PycharmProjects/TestDay12', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python36.zip', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/lib-dynload', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages', '/Users/KG/Desktop']

CC-Show Run ...

path 变量本质就是一个列表,使用 append() 方法可以将新路径加入到 path 中

也可以使用 insert 方式添加

1.3.6 重新加载模块

模块导入成功后,在使用模块过程中,如果被导入的模块对数据进行了修改,那么正在使用该模块的程序并不会修改。

因为PyCharm不能保持程序运行,所以使用命令行验证

就算重新导入修改后的模块也不行

因为 程序在导入模块时,会将模块创建一个对象保存到一个字典中,如果之前导入过一次,再次导入时就不会再创建这个对象。(后面有图示)

可以通过 sys 模块下的 modules 属性来查看一个文件中导入过的模块。sys.modules 返回一个字典

如果需要使用修改后的数据 ,需要重新加载模块。

重新加载模块需要使用 imp 模块下的 reload 函数(已经被废弃了)

重新加载模块需要使用 importlib 模块下的 reload 函数

from importlib import reload

1.3.7 import 和 from-import 导入的使用区别

  import 方式

import 方式导入时,只是在当前文件中建立了一个模块的引用,通过模块的引用来使用模块内的数据 。

使用这种方式导入时,访问控制权限对文件内级别的数据不起作用,通过模块名都可以进行访问。

相当于将一个模块中所有的内容都导入到当前文件中使用。

AA.py

x = 1

_y = 2

__z = 3

BB.py

import AA

print(AA.x)

print(AA._y)

print(AA.__z) # 虽然不提示,但是依然可以用

程序运行结果

1

2

3

可以将 import 导入方式理解成浅拷贝,只是拷贝了模块的一个引用。通过引用可以使用所有的数据 。

  from-import 方式

from-import 方式在导入数据时,会将导入模块中数据复制一份到当前文件中,所以可以直接使用模块中的变量,函数,类等内容。

在使用 from-import 方式导入时,文件内私有属性 _xxx 形式的数据不会被导入。

在使用 from-import 方式导入时,如果模块内和当前文件中有标识符命名重名,会引用命名冲突,当前文件中的内容会覆盖模块的数据

BB.py

from AA import *

print(x)

# print(_y) # 禁止导入,不能使用

# print(_AA.__z) #因为在文件内,也不能导入

# 定义了一个和AA模块中的x同名的函数

# 在当前文件中 x 就不在代表 x 变量了,而是函数了

def x():

    print('x is function now!')

print(x)

x()

程序运行结果:

1

<function x at 0x101f62e18>

x is function now!

from-import 方式可以理解成深拷贝,被导入模块中的数据被拷贝了一份放在当前文件中。

__all__ 魔法变量 在 Python 中还提供种方式来隐藏或公开数据 ,就是使用 __all__

__all__ 本质是一个列表,在列表中以字符串形式加入要公开的数据

在使用 from-import 导入模块时,如果模块中存在这个变量,那么就按这个变量里的内容进行导入,没有包含的不导入。 AA.py

__all__ = ['_y','__z']

x = 1

_y = 2

__z = 3

BB.py

from AA import *

print(_y)    #虽然是私有的,但是在 __all__中公开了就可以导入

print(__z)

# print(x)    # 没有公开,不能使用

# show()

程序运行结果

2

3

小结

从使用便利的角度,使用from-import

从命名冲突的角度,使用 import

1.3.8 循环导入问题

在开发过程中,可能会遇到这种情况。两个模块相互之间进行导入。这样的话,会造成程序出现死循环。程序运行时就会报错。

AA.py

from BB import *

def ashow():

    print('A - show')

bshow()

BB.py

from AA import *

def bshow():

    print('A - show')

ashow()

程序运行结果:

NameError: name 'ashow' is not defined

这是因为模块在导入时要经过这么几步:

  在sys.modules 中去搜索导入的模块对象

  如果没有找到就创建一个空模块并加入到sys.modules中,如果找到就不在创建

  然后读取模块中的数据对空模块初始化

  对存在的模块直接建立引用在当前文件中使用

循环引用出错的原因是创建完空模块后,对模块初始化时,又遇到了另外一个模块的导入。这时重复执行创建空模块初始化操作。 但是在第二个模块中发现又是在导入模块。但是这时会发现,这个模块以第一次的时候已经创建过了,就不在创建。但是模块并未初始化成功。 在使用时对一个空的模块内容进行调用。最后报错。

可以通过下图来理解循环导入出错的过程

下面的代码尝试去解决循环引用问题: AA.py

def ashow():

    print('A - show')

import BB

BB.bshow()

BB.py

def bshow():

    print('B - show')

import AA

AA.ashow()

程序输出结果:

A - show

B - show

A - show

结果还是有问题

代码中使用 import 替代了 from-impot 。 程序在执行 BB.py ,由于要创建两次相互导入时的模块到 sys.modules 中,在初始化模块过程中会执行模块内的语句,所以输出结果 多了前两次。

循环导入不是语法知识,也不止在 Python 中出现。这是在程序设计时的逻辑出现了问题。 不要想出现逻辑错误的时候怎么修改。而是要从根本上去避免不能出现这种设计逻辑。就像函数调用死循环一样。 切记切记!!!

1.3.9 总结

  在Python中,一个文件就是一个模块

  使用模块时,可以使用 import 或 from-import 来将模块导入

  导入模块时,程序到 sys.path 路径中去搜索,如果路径中没有指定的模块会报错

  可以向 sys.path 中去添加搜索路径

  模块导入后,在执行过程中是不可更新的,如果模块发生了变化,需要使用 imp 模块中的reload 函数重新导入

  import 导入类似浅拷贝,使用模块的引用操作模块中的数据

  from-import 导入类似深拷贝,相当于复制了一份模块中的数据到当前文件中,可能会命名冲突

  循环导入模块会出错,这不是语法,是思想逻辑错误,不要想着怎么改,要想怎么避免发生

1.4 with 语句

学习目标

  1. 能够说出with的执行过程

  2. 能够说出with的作用

  3. 能够说出为什么使用 with

--------------------------------------------------------------------------------

1.4.1 with 概述

with 语句是 Pyhton 提供的一种简化语法,with 语句是从 Python 2.5 开始引入的一种与异常处理相关的功能。

with 语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源。

比如文件使用后自动关闭、数据库的打开和自动关闭等。

1.4.3 with 语句的使用

with open('test', 'w') as f:

    f.write('Python好')

通过 with 语句在编写代码时,会使代码变得更加简洁。

在编写代码时,不用再显示的去关闭文件。

1.4.4 with 语句的执行过程

  在执行 with 语句时,首先执行 with 后面的 open 代码

  执行完代码后,会将代码的结果通过 as 保存到 f 中

  然后在下面实现真正要执行的操作

  在操作后面,并不需要写文件的关闭操作,文件会在使用完后自动关闭

1.4.5 with 语句的执行原理

实际上,在文件操作时,并不是不需要写文件的关闭,而是文件的关闭操作在 with 的上下文管理器中的协议方法里已经写好了。

当文件操作执行完成后, with语句会自动调用上下文管理器里的关闭语句来关闭文件资源。

上下文(环境)管理器

ContextManager ,上下文是 context(环境)直译的叫法,在程序中用来表示代码执行过程中所处的前后环境。  简单理解,在文件操作时,需要打开,关闭文件,而在文件在进行读写操作时,就是处在文件操作的上下文中,也就是文件操作环境中。

说明

很多计算机术语在由英文翻译成中文的过程中,因为语境或翻译人的各人理解的关系,导致一些中文术语都晦涩难懂,大家只需要记住这个术语,理解这个术语表示的意义即可。不需要在此纠结。 比如 file 大陆地区直接翻译成文件,而台湾地址则会翻译成文档或档案。 个人理解:context 翻译成环境可能会更贴切

with 语句在执行时,需要调用上下文管理器中的 __enter__ 和 __exit__ 两个方法。

__enter__ 方法会在执行 with 后面的语句时执行,一般用来处理操作前的内容。比如一些创建对象,初始化等。

__exit__ 方法会在 with 内的代码执行完毕后执行,一般用来处理一些善后收尾工作,比如文件的关闭,数据库的关闭等。

1.4.6 自定义上下文管理器

在自定义上下文管理器时,只需要在类中实现 __enter__ 和 __exit__ 两个方法即可。

模拟文件打开过程:

import time

class MyOpen(object):

    def __init__(self,file, mode):

        self.__file = file

        self.__mode = mode

    def __enter__(self):

        print('__enter__ run ... 打开文件')

        self.__handle = open(self.__file, self.__mode)

        return self.__handle

    def __exit__(self, exc_type, exc_val, exc_tb):

        print('__exit__... run ... 关闭文件')

        self.__handle.close()

with MyOpen('test','w') as f:

    f.write('Python 大法好')

    time.sleep(3)

print('over')

程序执行结果:

__enter__ run ... 打开文件

__exit__ run ... 关闭文件

over

1.4.8 __exit__ 方法的参数

__exit__ 方法中有三个参数,用来接收处理异常,如果代码在运行时发生异常,异常会被保存到这里。

  exc_type : 异常类型

  exc_val : 异常值

  exc_tb : 异常回溯追踪

class MyCount(object):

    def __init__(self,x, y):

        self.__x = x

        self.__y = y

    def __enter__(self):

        return self

    def __exit__(self, exc_type, exc_val, exc_tb):

        print('Type: ', exc_type)

        print('Value:', exc_val)

        print('TreacBack:', exc_tb)

    def div(self):

        return self.__x / self.__y

with MyCount(1, 0) as mc:

    ret = mc.div()

    print('ret = ', ret)

程序运行结果:

Type:  <class 'ZeroDivisionError'>

Traceback (most recent call last):

Value: division by zero

TreacBack: <traceback object at 0x10410de08>

  File "/Users/KG/PycharmProjects/TestDay12/AA.py", line 18, in <module>

    ret = mc.div()

  File "/Users/KG/PycharmProjects/TestDay12/AA.py", line 14, in div

    return self.__x / self.__y

ZeroDivisionError: division by zero

因为程序发生了除零错误,所以出现异常,异常信息被保存到这三个变量中。

Type:  <class 'ZeroDivisionError'>              # 异常类型

Value: division by zero                        # 异常值

TreacBack: <traceback object at 0x10410de08>    # 异常追踪对象

  异常信息的处理

当with中执行的语句发生异常时,异常信息会被发送到 __exit__ 方法的参数中,这时可以根据情况选择如何处理异常。

class MyCount(object):

    def __init__(self, x, y):

        self.__x = x

        self.__y = y

    def __enter__(self):

        return self

    def __exit__(self, exc_type, exc_val, exc_tb):

        # 通过 参数接收到的值,来判断程序执行是否出现异常

        # 如果是 None ,说明没有异常

        if exc_type == None:

            print('计算正确执行')

        else:

            # 否则出现异常,可以选择怎么处理异常

            print(exc_type,exc_val)

        # 返回值决定了捕获的异常是否继续向外抛出

        # 如果是 False 那么就会继续向外抛出,程序会看到系统提示的异常信息

        # 如果是 True 不会向外抛出,程序看不到系统提示信息,只能看到else中的输出

        return True

    def div(self):

        print(self.__x / self.__y)

with MyCount(6, 0) as mc:

    mc.div()

在 __exit__函数执行异常处理时,会根据函数的返回值决定是否将系统抛出的异常继续向外抛出。

如果返回值为 False 就会向外抛出,用户就会看到。 如果返回值为 True 不会向外抛出,可以将异常显示为更加友好的提示信息。

1.4.9 总结

  with 语句主要是为了简化代码操作。

  with 在执行过程中,会自动调用上下文管理器中的 __enter__ 和 __exit__ 方法

  __enter__ 方法主要用来做一些准备操作

  __exit__ 方法主要用来做一些善后工作

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容