第六章 与对象深入交往

6.1 一切皆对象

实际上,许多语法如运算符、元素引用、内置函数中,其实都来自一些特殊的对象。

1.运算符

运算符+、-、* 、/、>、<、or、and都是通过特殊方法实现的。
比如,list是列表类,用dir(list)查看列表的属性,会发现有一个特殊方法__add__(),它就特殊在定义了两个列表的相加,即 +。对于其他对象,只要定义了特殊方法,也就可以进行相应的运算了。

print([1,5,6] + [2,3,4])
结果:
[1, 5, 6, 2, 3, 4]

字符串也有+
print("abc"+"xyz")
结果:
abcxyz

上面两个例子实际上调用了__add__()方法

print([1,5,6].__add__([2,3,4]))
print("abc".__add__("xyz"))

结果:
[1, 5, 6, 2, 3, 4]
abcxyz

其他特殊方法

print(1.8.__mul__(2))  # 1.8 * 2
print(True.__or__(False)) # True or False
print(5.0.__floordiv__(2)) # 5.0 // 2

结果:
3.6
True
2.0

这些运算相关的特殊方法还能改变执行运算的方式。
比如,列表中是不允许相减运算的,我们可以创建一个子类,通过增加__sub__()方法来实现减法操作。

无法进行减法操作

#创建列表的子类,定义__sub__()方法
class SuperList(list):
    def __sub__(self,b):
        a = self[:]  #由于继承list,self可以利用[:]的引用来表示整个列表
        b = b[:]  # 用b来接收列表b
        
        while(len(b) > 0): # 当b的长度不为0,
            element_b = b.pop()  #弹出b最后一个元素,赋值给element_b
            if element_b in a: # 如果element_b在a列表中
                a.remove(element_b)   # 就删元素element_b
        return a # 最后返回a列表

print(SuperList([1,2,3]) - SuperList([2,3,4]))   #创建两个SuperList对象做减法

结果:
[1]

上面例子用内置函数__sub__()方法定义了-操作。即使__sub__()方法在父类中已经定义过,我们可以进行重写覆盖。

2.元素引用

我们想要获取列表某一个元素时,通常如下

li = [1,2,3,4,5]
print(li[3]) # 找到列表索引为3的元素

结果:
4

实际上,这个引用调用了__getitem__()特殊方法
print(li.__getitem__(3))

结果:
4

__getitem__()方法,那么就有__setitem__()方法

li = [1,2,3,4,5]
li.__setitem__(3,0)  # 将索引为3的元素设置为0
print(li)

结果:
[1,2,3,0,5]

字典也有相应的特殊方法,如__delitem__()方法

dict1 = {"a":1,"b":2}
dict1.__delitem__("a")  # 删除键为"a"的键值对
print(dict1)

结果:
{"b":2}

3.内置函数的实现

许多内置函数也是调用对象的特殊方法。比如len()方法、abs()方法、int()方法。

print([1,2,3].__len__())  # 显示列表的长度
print((-1).__abs__()) # 取绝对值
print(2.3.__int__()) # 转型

结果:
3
1
2

6.2 属性管理

1.属性覆盖的背后

为了了解属性覆盖,首先得了解__dict__属性。
当我们调用对象的属性时,这个属性可能不仅来自于自身对象属性和类属性,还可以来自于父类甚至租父类。一个类或对象拥有的属性,会记录在__dict__中。这是一个词典,键为属性名,值为某个属性。
Python在寻找对象的属性时,会按照继承关系依次寻找__dict__
举例:People类继承object类,Asian类继承People类,而People1是Asian类的对象。

class People(object):
    Name = True
    def eat(self):
        print("吃东西")

class Asian(People):
    Sex = True
    def __init__(self,Age):
        self.Age = Age
    def eat(self):
        print("吃粥")
        
People1 = Asian(100) # 创建Asian类的对象 People1

print("People1的__dict__")
print(People1.__dict__)
print()
print("Asian的__dict__")
print(Asian.__dict__)
print()
print("People的__dict__")
print(People.__dict__)
print()
print("object的__dict__")
print(object.__dict__)

结果如下:
People1的__dict__
{'Age': 100}

Asian的__dict__
{'__module__': '__main__', 
'Sex': True,
 '__init__': <function Asian.__init__ at 0x0000018B015039D8>, 
'eat': <function Asian.eat at 0x0000018B01503378>,
 '__doc__': None}

People的__dict__
{'__module__': '__main__', 
'Name': True,
 'eat': <function People.eat at 0x0000018B01503BF8>, 
'__dict__': <attribute '__dict__' of 'People' objects>, 
'__weakref__': <attribute '__weakref__' of 'People' objects>, 
'__doc__': None}

object的__dict__
{'__repr__': <slot wrapper '__repr__' of 'object' objects>, 
'__hash__': <slot wrapper '__hash__' of 'object' objects>, 
'__str__': <slot wrapper '__str__' of 'object' objects>, 
'__getattribute__': <slot wrapper '__getattribute__' of 'object' objects>, 
'__setattr__': <slot wrapper '__setattr__' of 'object' objects>, 
'__delattr__': <slot wrapper '__delattr__' of 'object' objects>, 
'__lt__': <slot wrapper '__lt__' of 'object' objects>, '__le__':
 <slot wrapper '__le__' of 'object' objects>,
 '__eq__': <slot wrapper '__eq__' of 'object' objects>,
 '__ne__': <slot wrapper '__ne__' of 'object' objects>,
 '__gt__': <slot wrapper '__gt__' of 'object' objects>,
 '__ge__': <slot wrapper '__ge__' of 'object' objects>, 
'__init__': <slot wrapper '__init__' of 'object' objects>,
 '__new__': <built-in method __new__ of type object at 0x00007FFADC3B6D30>,
 '__reduce_ex__': <method '__reduce_ex__' of 'object' objects>, 
'__reduce__': <method '__reduce__' of 'object' objects>, 
'__subclasshook__': <method '__subclasshook__' of 'object' objects>,
 '__init_subclass__': <method '__init_subclass__' of 'object' objects>,
 '__format__': <method '__format__' of 'object' objects>, 
'__sizeof__': <method '__sizeof__' of 'object' objects>, 
'__dir__': <method '__dir__' of 'object' objects>,
 '__class__': <attribute '__class__' of 'object' objects>,
 '__doc__': 'The most base type'}

第一部分是People1对象自身的属性,即Age。
第二部分是Asian类的属性,如Sex和__init__()方法。
第三部分是People类的属性,如Name。
最后一个是object类的属性,如__doc__属性。

用dir()来查看People1对象时,可以看到People1对象包含了这四部分的属性。这说明了,对象的属性是分层管理的。当我们调用对象的属性时,Python会一层一层的寻找,直到找到最先遇见的那个。这就是属性覆盖的原理所在。如,People类和Asian类都有eat()方法,如果对象调用eat()方法,首先调用的是Asian类中的eat()方法。

People1.eat()
结果:
吃粥

子类的属性比父类的同名属性具有优先权,这就是覆盖的关键。

上面是对属性的调用,下面来看看属性的赋值。创建People2对象,给Name属性赋值,看看会不会影响People类的Name属性。

class People(object):
    Name = True
    def eat(self):
        print("吃东西")

class Asian(People):
    Sex = True
    def __init__(self,Age):
        self.Age = Age
    def eat(self):
        print("吃粥")
        
People1 = Asian(100)

People2 = Asian(200)

print("未修改Name属性之前,People2的__dict__")
print(People2.__dict__)
print()
People2.Name = "盘古"
print("给People2的Name赋值后,看看People1的Name会不会被影响:",People1.Name)
print("再看看People类中的Name会不会改变:",People.Name)
print()
print("修改Name属性之后,People2的__dict__")
print(People2.__dict__)

结果:
未修改Name属性之前,People2的__dict__
{'Age': 100}

给People2的Name赋值后,看看People1的Name会不会被影响: True
再看看People类中的Name会不会改变: True

修改Name属性之后,People2的__dict__
{'Age': 100, 'Name': '盘古'}

从上面的结果可以看出,即使修改了People2的Name属性,也不会影响父类的Name属性,反而是在People2中,新创建了一个名为Name的属性。
因此,Python在对对象进行赋值时,会搜索对象本身的__dict__,如果找不到对应的属性,就会在__dict__中新建一个属性。
对于方法,如果用self引用对象,也会遵守相同的规则。
如果想要修改父类中的某个属性,可以直接按照下面的操作。如

People.Name = "亚当"

这相当于修改了People类的__dict__
People.__dict__["Name"] = "亚当"  # 词典的赋值操作

2.特性

同一个对象的不同属性之间存在依赖关系。当某个属性被修改时,我们希望依赖于该属性的其他属性也同时变化。比如,是否成年(isAdult)这个属性依赖于年龄(Age),当Age≥18岁,isAdult要变为True,Age<18岁,isAdult要变为False。这时,我们不能通过__dict__的静态方式来存储属性了。
Python提供了特性(property)来实现。特性就是特殊的属性。
如我们为人类添加一个是否成年(isAdult)的特性。

class People(object):
    Name = True
    Sex = True
    def __init__(self,Age):
        self.Age = Age
        
    def get_adult(self):
        if self.Age >= 18:
            return True
        else:
            return False
    
    isAdult = property(get_adult) # isAdult特性
    
People1 = People(10)
print(People1.isAdult)
People2 = People(100)
print(People2.isAdult)

结果:
False
True

特性用内置函数property()来创建。property()有四个参数。
前三个参数分别用于获取特性、修改特性和删除特性。
最后一个参数是对特性的说明,可以是一个简单的字符串。

特性的四个参数

class Num(object):
    def __init__(self,value):
        self.value = value
    
    def get_neg(self):
        return -self.value
    
    def set_neg(self,value):
        self.value = value
    
    def del_neg(self):
        print("删除属性value")
        del self.value
        
    neg = property(get_neg,set_neg,del_neg,"我是负数")
    
x = Num(9)
print("我是",x.value,"的负数:",x.neg)
x.neg = 10 # 修改neg的值
print("我已经不是原来的x了:",x.value)
print("我是新x的负数",x.neg)
print("我是neg特性的描述",Num.neg.__doc__)
del x.neg

结果:
我是 9 的负数: -9
我已经不是原来的x了: 10
我是新x的负数 -10
我是neg特性的描述 我是负数
删除属性value

3.getattr()方法

class People(object):
    Name = True
    Sex = True
    def __init__(self,Age):
        self.Age = Age
        
    def __getattr__(self,name):
        if name == "isAdult":
            if self.Age >= 18:
                return True
            else:
                return False
        else:
            raise AttributeError(name)

People1 = People(19)
print(People1.isAdult)

People1.Age = 10
print(People1.isAdult)

print(People1.male)

结果:
True
False
AttributeError: male

6.3 我是风儿,我是沙

1.动态类型

Python中的变量不需要说明。在赋值时,变量可以重新复制为任意其他值。这种一会是沙一会是风的能力,就是动态类型的体现。如

a = 1

在这里,整数"1"是一个整数对象,对象的名字是a。也就是说1这个对象存储在引用a里面,我们不能直接接触1,而是通过引用a来引用对象。
对象名是指向对象的引用
这就像住酒店。对象1是入住客人,引用a就是房间号。
我们可以通过id()内置函数来查看对象,该函数能返回对象的编号。这就像入住酒店,需要客人的身份证,通过身份证查看客人的身份证号。

a = 1
print(id(1))
print(id(a))

结果:
140715413902144
140715413902144

在Python中,赋值其实就是不同的客人(对象)入住到房间(引用)中。引用能随时指向一个对象。

a = 3
print(id(a))

a = "another"
print(id(a))

结果:
140715413902208
1696533891760

上面的引用a从指向对象3到指向对象"another",而且id()返回的值不一样,因此引用的指向发生了变化。也就是说,变量名是随时可以改变的引用,那么它的类型就可以动态变化。因此,Python是一门动态类型的语言。
除了通过id比较两个引用是否指向同一个对象,我们还可以有is来进行判断。

a = 3
b = 3
print(a is b)

结果:
True

2.可变与不可变对象

一个对象可以有多个引用。

a = 5  # 让a指向对象5
b = a # 让b指向引用a所指向的对象5
c = b # 让c指向引用b所指向的引用所指向的对象5
print(id(a))
print(id(b))
print(id(c))
print()
a = a +2 # a指向(a原来指向的对象5 加上 对象2 得到的对象7)
print(id(a))
print(id(7))
print(id(b)) # 看b是指向原来的a所指向的对象5,还是新指向的对象7

结果:
140715413902272
140715413902272
140715413902272

140715413902336
140715413902336
140715413902272

第一个例子可以看出,a指向5,b指向a,c指向b,都是指向了对象5。
第二个例子,让a增加2,再赋值给a,可以看到a指向了对象7,而b仍然指向对象5。这就好比,对象5用自己的id开了两个房间a和b,但是a房间不是给对象5住,而是给7住。改变一个引用的指向,并不会影响其他引用的指向。也即是各个房间互不影响。
像这种就被称为不可变对象。常见的有整数、浮点数以及字符串和元组。


对于不可变对象,有如下情况

li1 = [1,3,5,7]
li2 = li1
print(li1)
li2[0] = 9
print(li1)

结果:
[1,3,5,7]
[9,3,5,7]

从上述的例子可以看出改变了li2的内容,li1也改变了。
实际上是因为引用li1和引用li2指向的是整个同一个列表对象,但是一个li1和li2包含了多个引用,每一个元素就是一个指向具体对象的引用,比如li1[0]、li2[0]指向的对象是1。而li2[0]=9这一赋值操作,使得li2[0]指向的对象变为9,但不改变li2的指向,即li2仍然指向整个列表对象。也就是说,列表对象的一部分,即列表中一个引用的指向改变,所有指向该列表对象的引用也随之改变。如下图

常见的可变对象有列表和词典。

3.从动态类型看函数的参数传递

函数的参数传递,本质上传递的是引用

  • 可变对象传参
def f(x):
   x = 100
   print(id(x))
    
a = 1
print(id(a))
f(a)
print(id(a))

结果:
140715413902144
140715413905312
140715413902144

参数x是一个新引用。当我们调用f时,a传递给参数x,因此x会指向a所指的对象即1,也就是将1赋给了x,然后在f内,x又指向100,此时打印id(x),发现id(x)改变了,然而函数外面的id(a)不会被影响。这也体现了,不可变对象的引用不会相互影响。

  • 不可变对象传参
    下面介绍不可变对象的参数传递。
def f(x):
   print(x)
   x[0] = 100
   print(x)
    
a = [1,3,5,7]
print(a)
f(a)
print(a)

结果:
[1, 3, 5, 7]
[1, 3, 5, 7]
[100, 3, 5, 7]
[100, 3, 5, 7]

从上面我们可以看出,a指向了一个可变的列表,之后调用f,a给x,a和x就指向了同一个列表[1,3,5,7]。然后,x中的一个引用x[0]的指向变为了对象100,接着打印x,可以肯定引用x的内容肯定改变。接着再打印引用a,发现a的引用也改变了。也就是说,函数内部的对引用x的改变,也影响了函数外部的引用a。这就是说明了,通过一个引用操作可变对象,其他指向同一个可变对象的引用也会被改变。

6.4 内存管理

1.引用管理

对象内存的管理,是基于对引用的管理。在Python中,引用与对象分离。一个对象可以有多个引用,而对于每个对象都存在一个指向该对象的引用总数,即对引用计数。
我们可以通过sys包中的getrefcount(),来查看对象的引用计数。
注意,由于当引用作为参数传递给getrefcount()方法时,该参数本身也是一个引用。因此,getrefcount()所得到的结果比预想的多1个。

from sys import getrefcount 

a = [1,2,3,4]
print(getrefcount(a))

b = a
print(getrefcount(b))

结果:
2
3

2.对象引用对象

列表和词典。都是容器对象,可以包含多个对象,但实际上,他们包含的不是对象本身,而是对象的引用。如上面提过的例子
列表包含的是对象的引用,并不是对象本身

我们也可以自己定义一个对象,来引用其他对象。如下

# 创建一个自定义类
class from_obj(object):
    def __init__(self,to_obj):
        self.to_obj = to_obj
        
a = [1,23,4] # a是一个列表对象

b = from_obj(a)  # b是自定义对象,且b引用了a

print(id(b.to_obj)) # b的to_obj是a指向的列表对象
print(id(a)) # a指向的列表对象

结果:
1696533754568
1696533754568

可以看到自定义对象b引用了列表对象a,即对象引用对象。
再如,a=1时,Python会把引用关系存入到词典中。该词典对象用于记录所有的全局引用。可以通过globals()方法查看该词典。

print(globals())

当一个对象a被另一个对象b引用时,a的引用次数将加1

from sys import getrefcount 

a = [1,2,3,4]
print(getrefcount(a))

b = [a,a]
print(getrefcount(a))

结果:
2
4

由于对象b引用了两次a,因此再打印a的引用计数时,变成了4。


容器对象的引用可能会形成复杂的拓扑结构。我们可以用objgraph包来进行绘制引用关系。objgraph是一个第三方包,可以通过pip安装。除了安装objgraph,还需要按照graphviz软件。
安装教程如下https://blog.csdn.net/HNUCSEE_LJK/article/details/86772806

import objgraph

x = [1,2,3]
y = [x,dict(key1 = x)] # 第二个元素意思为:以key1为键,列表x为值构建字典
z = [y,(x,y)]

objgraph.show_refs([z],filename="sample.jpg")

生成的图片如下

两个对象相互引用,从而构成引用环,如

a = [1,2,3]
b = [a]
a.append(a)
print(a)
print(b)

结果:
[1, 2, 3, [...]]
[[1, 2, 3, [...]]]

单个对象,自己调用自己也能够形成引用环

from sys import getrefcount
a = [1,2,3]
a.append(a)
print(a)
print(getrefcount(a))

结果:
[1, 2, 3, [...]]
3

引用环的形成会浪费空间,使垃圾回收很麻烦。


某个对象的引用计数可能会减少。比如,使用del关键字删除某个引用:

from sys import getrefcount
a = [1,2,3] # 对象[1,2,3]使用了引用a
b = a # b引用了a
print(getrefcount(b)) #该参数又引用了一次
del a #删除引用a
print(getrefcount(b))

结果:
3
2

前面我们说到,列表容器对象里面包含的其实也是引用,
所有也可以用del删除列表里面的引用
a = [1,2,3]
print(a)
del a[0]
print(a)

结果:
[1,2,3]
[2,3]

除了上述情况会是引用计数减少,另一种减少情况如下:
如果某个引用指向对象a,当这个引用被重新指向其他对象b时,对象a的引用计数也会减少。

from sys import getrefcount
a = [1,2,3]
b = a
print(getrefcount(b))
a = 1
print(getrefcount(b))

结果:
3
2

3.垃圾回收

当对象越来越多,占据的内存越来越大,Python自动地启动垃圾回收,将没用的对象清除。
原理上,当某个对象的引用次数为0时,即没有任何引用指向该对象,该对象就会成为回收的垃圾。如

a = [1,2,3] # 列表对象[1,2,3]被引用a引用,增加了1个引用
del a # 删除a引用,列表对象[1,2,3]被回收。

然而,频繁地回收垃圾,会使得Python的工作效率降低。如果内存中的对象不多,那么Python不会频繁的回收垃圾。所有,Python只会在特定条件下进行垃圾回收。
当Python运行时,会记录其中分配对象取消分配对象的次数。当两者差值高于某个阈值时,垃圾回收才启动。
我们可以通过gc模块的get_threshold()方法,查看该阈值:

import gc
print(gc.get_threshold())

结果:
(700, 10, 10)

700就是启动垃圾回收的阈值,后面两个10是与分代回收相关的阈值,后文说明。启动垃圾回收的阈值可以通过set_threshold()方法进行重新设定。我们也可以手动回收垃圾,用collect()方法实现。

除了上述基础的垃圾回收方法,Python同时还采用了分代回收的策略。给策略的假设是,存活越久的对象,越不可能在后面的程序变为垃圾。程序中通常产生大量的对象,有的对象很快产生且消失,而有的对象就长期保留在程序中被使用。出于信任和效率,对于这些长期对象,我们认为其有用,所有减少他们在垃圾回收中被扫描的频率。如何划分对象是长期的呢?
Python将所有对象分为0、1、2代三代对象。当某一代对象经历垃圾回收而没有被清除,依旧存在于程序中,它将被归入下一代对象。所有新建对象都是0代对象。垃圾回收启动时,一定会扫描所有的0代对象,如果0代对象经历一定次数的垃圾回收,那么就启动对0代和1代对象的扫描清理,当1代对象经历到一定次数垃圾回收,就会启动0、1、2代的扫描,对所有对象进行扫描。

上面两个10的意思就是,每10次0代回收,会配合1次1代回收;每10次1代回收,会进行1次2代回收。我们也可以通过set_threshold()方法对回收次数更改。

4.孤立的引用环

引用环的存在让上面的垃圾回收机制造成很大的困难。
这些引用环可能构成无法使用,但是引用次数又不为0的一些对象。

a = []
b = []
a.append(b)
del a
del b

我们创建了两个列表,并相互引用,构成一个引用环。删除了引用a和b之后,这两个对象无法再从程序中调用,没什么用处,但是由于环的存在,这两个对象的引用次数不为0,不会被垃圾回收。
其他对象的引用与自身的引用

为了回收这样的孤立环,Python会复制每个对象的引用计数,可以记为gc_ref。假设,每个对象i,该计数为gc_ref_i。Python会遍历所有的对象i,然后对于对象i引用的对象j,将相应的gc_ref_j减少1,遍历后如下

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

推荐阅读更多精彩内容