Python3 CookBook学习笔记 -- 类与对象

1. 改变对象的字符串显示

你想改变对象实例的打印或显示输出,让它们更具可读性。

需要我们重新定义它的 __str__()__repr__() 方法。

  • __repr__() 方法返回一个实例的代码表示形式。

  • __str__() 方法将实例转换为一个字符串,使用 str()print()函数会输出这个字符串。

>>> class Pair:
...     def __init__(self, x, y):
...         self.x = x
...         self.y = y
... 
...     def __repr__(self):
...         return 'Pair({0.x!r}, {0.y!r})'.format(self)
... 
...     def __str__(self):
...         return 'Pair({0.x!s}, {0.y!s})'.format(self)
... 
>>> pair=Pair('1', '2')
>>> 
>>> pair
Pair('1', '2')
>>> print(pair)
Pair(1, 2)
  • !r 表示以 __repr__()方式表示。

  • !s 表示以 __str__()方式表示。

自定义 __repr__()__str__() 通常是很好的习惯,因为它能简化调试和实例输出。

规则:

  • __repr__() 生成的文本字符串标准做法是需要让 eval(repr(x)) == x 为真。 如果实在不能这样子做,应该创建一个有用的文本表示,并使用 <> 括起来。
>>> f = open('file.dat')
>>> f
<_io.TextIOWrapper name='file.dat' mode='r' encoding='UTF-8'>
  • 如果 __str__() 没有被定义,那么就会使用 __repr__()来代替输出。

2. 自定义字符串的格式化

你想通过 format() 函数和字符串方法使得一个对象能支持自定义的格式化。

>>> class Date:
...     _formats={
...         'ymd': '{d.year}-{d.month}-{d.day}',
...         'mdy': '{d.month}/{d.day}/{d.year}',
...         'dmy': '{d.day}/{d.month}/{d.year}'
...     }
... 
...     def __init__(self, year, month, day):
...         self.year=year
...         self.month=month
...         self.day=day
... 
...     def __format__(self, format_spec):
...         if format_spec is None or format_spec == '':
...             format_spec='ymd'
...         fmt = Date._formats[format_spec]
...         return fmt.format(d=self)
... 
>>> d=Date(2018,12,12)
>>> format(d)
'2018-12-12'
>>> 
>>> format(d, 'mdy')
'12/12/2018'
>>> 
>>> 'The date is {:ymd}'.format(d)
'The date is 2018-12-12'

__format__() 方法给 Python 的字符串格式化功能提供了一个钩子。

3. 让对象支持上下文管理协议

我们都知道 with 在文件处理中,提供了很好的上下文管理。

为了让一个对象兼容 with 语句,你需要实现 __enter__()__exit__() 方法。

>>> class FileOperator:
...     def __init__(self, path):
...         self.path=path
... 
...     def __enter__(self):
...         print('__enter__')
...         self.file=open(self.path, 'rt')
...         return self.file
... 
...     def __exit__(self, exc_type, exc_val, exc_tb):
...         print('__exit__')
...         self.file.close()
...         self.file=None
... 
>>> operator= FileOperator('/Users/faris/Desktop/splider.py')
>>> 
>>> with operator as f:
...     print(f)
... 
__enter__
<_io.TextIOWrapper name='/Users/faris/Desktop/splider.py' mode='rt' encoding='UTF-8'>
__exit__

4. 在类中封装私有属性名

Python 语言并没有访问控制,而是通过遵循一定的属性和方法命名规约来达到这个效果。

  • 任何以单下划线 _ 开头的名字都应该是内部实现

  • 任何双下划线 __开头的名字都是为了防止被继承后覆盖。

class B:
    def __init__(self):
        self.__private = 0

    def __private_method(self):
        pass

    def public_method(self):
        pass

在前面的类B中,私有属性会被分别重命名为 _B__private_B__private_method

class C(B):
    def __init__(self):
        super().__init__()
        self.__private = 1 # Does not override B.__private

    # Does not override B.__private_method()
    def __private_method(self):
        pass

而当 C 继承 B 时,C中的 __private__private_method 被重命名为 _C__private_C__private_method ,这个跟父类 B 中的名称是完全不同的。

规则:

你应该让你的非公共名称以单下划线开头。但是,如果你清楚你的代码会涉及到子类, 并且有些内部属性应该在子类中隐藏起来,那么才考虑使用双下划线方案。

有时候你定义的一个变量和某个保留关键字冲突,这时候可以使用单下划线作为后缀。这里我们并不使用单下划线前缀的原因是它避免误解它的使用初衷 (如果这里使用单下划线前缀的目的是为了防止命名冲突而不是指明这个属性是私有的)。 通过使用单下划线后缀可以解决这个问题。

5. 创建可管理的属性

我们可以将某个属性定义为一个property,就可以给这个属性增加除访问与修改之外的其他处理逻辑。

比如类型检查或合法性验证:

class Person:
    def __init__(self, first_name):
        self._first_name = first_name

    @property
    def first_name(self):
        return self._first_name

    @first_name.setter
    def first_name(self, value):
        if not isinstance(value, str):
            raise TypeError('Expected a string')
        self._first_name = value

    @first_name.deleter
    def first_name(self):
        raise AttributeError("can't delete attribute")


if  __name__ == "__main__":
    person = Person(1)
    print(person.first_name)

比如动态计算:

import math


class Circle:

    def __int__(self, radius):
        self.radius = radius

    @property
    def area(self):
        return math.pi * (self.radius ** 2)

    @property
    def diameter(self):
        return self.radius * 2

    @property
    def perimeter(self):
        return 2 * self.radius * math.pi

Python 不需要像 Java 那样的gettersetter方法。那样只会让代码很臃肿,违背了 Python 简洁的原则。

6. 调用父类方法

class A:

    def __init__(self, name):
        self.name = name
        self._age = 1
        self.__sex = 'female'

    def get_name(self):
        return self.name


class B(A):

    def __init__(self, name):
        super().__init__(name)

    def age1(self):
        return self._age

    def name1(self):
        return self.name

    def sex1(self):
        return super().__sex

b = B('faris')
print('b.name: ',  b.name)
print('b.name1(): ',  b.name1())
print('b.get_name(): ',  b.get_name())

print('b._age: ',  b._age)
print('b.age1(): ', b.age1())

print('b.sex1(): ',  b.sex1())
print('A.__sex:',  A.__sex)

那些会报错?

由于 nameget_nameA 的公共属性和公共方法,因此会被继承到 B上,可以随意使用。

b.name:  faris
b.name1():  faris
b.get_name():  faris

由于_ageA的私有属性,Python仅仅靠命名约束私有属性,但是不会强制约束,因此编译器会有警告,但是仍然可以被 B正常使用。

b._age:  1
b.age1():  1

由于 __sex 前面为双下划线,本来就是为了防止被继承后覆盖的,且在编译时被编译为 _A__sex, 这样B肯定无法调用。

>>> b.sex1()
AttributeError: 'super' object has no attribute '_B__sex'
>>> A.__sex
AttributeError: type object 'A' has no attribute '__sex'

初始化顺序:

class Base:
    def __init__(self):
        print('Base.__init__')


class A(Base):
    def __init__(self):
        super().__init__()
        print('A.__init__')


class B(Base):
    def __init__(self):
        super().__init__()
        print('B.__init__')


class C(A, B):
    def __init__(self):
        super().__init__()
        print('C.__init__')

执行结果:

>>> c = C()
Base.__init__
B.__init__
A.__init__
C.__init__
>>> 

我们会发现 Base.__init__ 只被调用了 一次。Python是如何实现继承关系的?

Python 会计算出一个所谓的方法解析顺序( MRO )列表。 这个 MRO 列表就是一个简单的所有基类的线性顺序表。

>>>  C.__mro__
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>,
<class '__main__.Base'>, <class 'object'>)
>>> 

为了实现继承,Python 会在 MRO 列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。

而这个 MRO 列表的构造是通过一个C3线性化算法来实现的。 我们不去深究这个算法的数学原理,它实际上就是合并所有父类的 MRO 列表并遵循如下三条准则:

  • 子类会先于父类被检查
  • 多个父类会根据它们在列表中的顺序被检查
  • 如果对下一个类存在两个合法的选择,选择第一个父类

7. 定义接口或者抽象基类

你想定义一个接口或抽象类,并且通过执行类型检查来确保子类实现了某些特定的方法

使用 abc 模块可以很轻松的定义抽象基类:

from abc import ABCMeta, abstractmethod


class IStream(metaclass=ABCMeta):

    @abstractmethod
    def read(self, max_bytes=-1):
        pass

    @abstractmethod
    def write(self, data):
        pass


class SocketStream(IStream):
    def read(self, max_bytes=-1):
        pass

    def write(self, data):
        pass

@abstractmethod 还能注解 静态方法类方法properties 。 你只需保证这个注解紧靠在函数定义前即可,这对Java程序员来说是毁三观的定义:

from abc import ABCMeta, abstractmethod

class A(metaclass=ABCMeta):

    @property
    @abstractmethod
    def name(self):
        pass

    @name.setter
    @abstractmethod
    def name(self, value):
        pass

    @classmethod
    @abstractmethod
    def method1(cls):
        pass

    @staticmethod
    @abstractmethod
    def method2():
        pass
   
    @classmethod
    def method3(cls):
        print(cls)

    @staticmethod
    def method4():
        print(A)

class B(A):

    def __init__(self, name):
        self._name = name

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

    @name.setter
    def name(self, value):
        if not isinstance(value, str):
            raise TypeError('Expected string')

    @classmethod
    def method1(cls):
        print(cls)

    @staticmethod
    def method2():
        print(B)

b = B('1')
b.name = 'faris'
print(b.name)
B.method1()
B.method2()
B.method3()
B.method4()

执行结果:

faris
<class '__main__.B'>
<class '__main__.B'>
<class '__main__.B'>
<class '__main__.A'>

我们可以看到,B 仍然可以调用 method3method4。但是method3中打印的却是 B类。

8. 属性的代理访问

代理是一种编程模式,它将某个操作转移给另外一个对象来实现。

这个对于 Java程序员来说,应该已经习以为常了。

class Proxy:
    def __init__(self, obj):
        self._obj = obj

    # Delegate attribute lookup to internal obj
    def __getattr__(self, name):
        print('getattr:', name)
        return getattr(self._obj, name)

    # Delegate attribute assignment
    def __setattr__(self, name, value):
        if name.startswith('_'):
            super().__setattr__(name, value)
        else:
            print('setattr:', name, value)
            setattr(self._obj, name, value)

    # Delegate attribute deletion
    def __delattr__(self, name):
        if name.startswith('_'):
            super().__delattr__(name)
        else:
            print('delattr:', name)
            delattr(self._obj, name)


class Spam:
    def __init__(self, x):
        self.x = x

    def bar(self, y):
        print('Spam.bar:', self.x, y)

s =Spam(4)
p = Proxy(s)
p.bar(3)

需要注意:

  • __getattr__() 实际是一个后备方法,只有在属性不存在时才会调用。 因此,如果代理类实例本身有这个属性的话,那么不会触发这个方法的。

  • __setattr__()__delattr__() 只代理那些不以下划线 _开头的属性(代理类只暴露被代理类的公共属性)。

  • __getattr__() 对于大部分以双下划线(__)开始和结尾的属性并不适用。

Java中的思路一样,我们会使用 代理组合 来替代 继承

例如:我们实现一个列表,我们不会使用继承 :

>>> class ListLike:
...     def __init__(self):
...         self._items = []
... 
...     def __getattr__(self, name):
...         print("method name:", name)
...         return getattr(self._items, name)
>>> l=ListLike()
>>> 
>>> 
>>> l.insert(1,1,)
method name: insert
>>> l.sort()
method name: sort
>>> l.append(2)
method name: append

但是,当我们调用 len()元素查找、以及 打印则报错,或者没有调用__getattr__

原因在于上面需要注意的第三点:

__getattr__() 对于大部分以双下划线(__)开始和结尾的属性并不适用

  • len()需要对象实现 __len__
  • 元素查找需要对象 __getitem____setitem____delitem__
  • 打印则需要实现 __str____repr__
>>> class ListLike:
...     def __init__(self):
...         self._items = []
...
...     def __getattr__(self, name):
...         print("method name:", name)
...         return getattr(self._items, name)
...
...     def __repr__(self):
...         return self._items.__repr__()
...
...     def __str__(self):
...         return self._items.__str__()
...
...     def __len__(self):
...         return len(self._items)
...
...     def __setitem__(self, index, value):
...          self._items[index] = value
...
...     def __getitem__(self, index):
...         return self._items[index]
...
...     def __delitem__(self, index):
...         del self._items[index]

9. 在类中定义多个构造器

你想实现一个类,除了使用 __init__() 方法外,还有其他方式可以初始化它。

为了实现多个构造器,你需要使用到类方法。例如:

import time

class Date:
  
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    @classmethod
    def today(cls):
        t = time.localtime()
        return cls(t.tm_year, t.tm_mon, t.tm_mday)

类方法的一个主要用途就是定义多个构造器。它接受一个 class 作为第一个参数(cls)。 你应该注意到了这个类被用来创建并返回最终的实例。在继承时也能工作的很好:

class NewDate(Date):
    pass

c = Date.today() # Creates an instance of Date (cls=Date)
d = NewDate.today() # Creates an instance of NewDate (cls=NewDate)

10. 实现数据模型的类型约束

你想定义某些在属性赋值上面有限制的数据结构,

10.1 描述器类

class Integer:
    def __init__(self, name):
        self.name = name

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if not isinstance(value, int):
            raise TypeError('Expected an int')
        instance.__dict__[self.name] = value

    def __delete__(self, instance):
        del instance.__dict__[self.name]

一个描述器就是一个实现了三个核心的属性访问操作(get, set, delete)的类, 分别为 __get__()__set__()__delete__() 这三个特殊的方法。 这些方法接受一个实例作为输入,之后相应的操作实例底层的字典。

所有对描述器属性的访问会被 __get__()__set__()__delete__() 方法捕获到。

描述器只能在类级别被定义,而不能为每个实例单独定义。

class Point:
    x = Integer('x')
    y = Integer('y')

    def __init__(self, x, y):
        self.x = x
        self.y = y

执行结果:

>>> p = Point(2, 3)
>>> p.x  # Calls Point.x.__get__(p,Point)
2
>>> p.y = 5  # Calls Point.y.__set__(p, 5)
>>> p.x = 2.3  # Calls Point.x.__set__(p, 2.3)
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "descrip.py", line 12, in __set__
        raise TypeError('Expected an int')
TypeError: Expected an int
>>>

__get__() 看上去有点复杂的原因归结于实例变量和类变量的不同。 如果一个描述器被当做一个类变量来访问,那么 instance 参数被设置成 None

>>> p = Point(2,3)
>>> p.x # Calls Point.x.__get__(p, Point)
2
>>> Point.x # Calls Point.x.__get__(None, Point)
<__main__.Integer object at 0x100671890>
>>>

10.2 类装饰器

我们发现描述器类需要在每一个类中定义类属性,会侵入代码。

而类装饰器则会更加的简洁与灵活

class Typed:

    def __init__(self, name, expected_type):
        self.name = name
        self.expected_type = expected_type

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if not isinstance(value, self.expected_type):
            raise TypeError('Expected ' + str(self.expected_type))
        instance.__dict__[self.name] = value

    def __delete__(self, instance):
        del instance.__dict__[self.name]


# Class decorator that applies it to selected attributes
def typeassert(**kwargs):

    def decorate(cls):
        for name, expected_type in kwargs.items():
            # Attach a Typed descriptor to the class
            setattr(cls, name, Typed(name, expected_type))
        return cls
    return decorate


# Example use
@typeassert(name=str, shares=int, price=float)
class Stock:
    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

stock = Stock('1', 1, 1)

这样利用类装饰器将描述器隐藏在身后,进而展示出装饰器的灵活性。

10.3 元类

class Integer:
    def __init__(self, name):
        self.name = name

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if not isinstance(value, int):
            raise TypeError('Expected an int')
        instance.__dict__[self.name] = value

    def __delete__(self, instance):
        del instance.__dict__[self.name]

class checked_meta(type):

    def __new__(cls, clsname, bases, methods):
        print(cls, clsname, bases, methods)
        for key, value in methods.items():
            if isinstance(value, Integer):
                value.name = key
        return type.__new__(cls,  clsname, bases, methods)

class Stock2(metaclass=checked_meta):

    shares = Integer('shares')

    def __init__(self, shares):
        self.shares = shares

s=Stock2(1)

有时我们需要通过多个描述器才能完成一个限制,这样我们可以通过多继承的方式来实现。

10.4 混入

class Descriptor:
    def __init__(self, name, **opts):
        self.name = name
        for key, value in opts.items():
            setattr(self, key, value)

    def __set__(self, instance, value):
        instance.__dict__[self.name] = value

class Typed(Descriptor):

    expected_type = type(None)

    def __set__(self, instance, value):
        if not isinstance(value, self.expected_type):
            raise TypeError('expected ' + str(self.expected_type))
        super().__set__(instance, value)

class String(Typed):
    expected_type = str

class Sized(Descriptor):

    def __init__(self, name, **opts):
        if 'min_size' not in opts and 'max_size' not in opts:
            raise TypeError('missing min_size or max_size option')
        super().__init__(name, **opts)

    def __set__(self, instance, value):
        length = len(value)
        if self.min_size and self.min_size is not None and self.min_size > length:
            raise ValueError('size must be > ' + str(self.min_size))

        if self.max_size and self.max_size is not None and self.max_size < length:
            raise ValueError('size must be < ' + str(self.max_size))

        super().__set__(instance, value)

class SizedString(String, Sized):
    pass


class Stock:
    name = SizedString('name', min_size=8, max_size=10)

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

s=Stock('a')

混入类的一个比较难理解的地方是,调用 super() 函数时,你并不知道究竟要调用哪个具体类。 你需要跟其他类结合后才能正确的使用,也就是必须合作才能产生效果。

类装饰器方案应该是最灵活和最高明的。

  • 首先,它并不依赖任何其他新的技术,比如元类。

  • 其次,装饰器可以很容易的添加或删除。

  • 最后,也是最重要的,装饰器是最快的。

11. 利用Mixins扩展类功能

想扩展映射对象,给它们添加日志功能。

11.1 混入类

class LoggedMappingMixin:
 
    def __getitem__(self, key):
        print('Getting ' + str(key))
        return super().__getitem__(key)

    def __setitem__(self, key, value):
        print('Setting {} = {!r}'.format(key, value))
        return super().__setitem__(key, value)

    def __delitem__(self, key):
        print('Deleting ' + str(key))
        return super().__delitem__(key)

class LoggedDict(LoggedMappingMixin, dict):
    pass

d = LoggedDict()
d['x'] = 23

11.2 装饰器

def LoggedMapping(cls):
    """第二种方式:使用类装饰器"""
    cls_getitem = cls.__getitem__
    cls_setitem = cls.__setitem__
    cls_delitem = cls.__delitem__

    def __getitem__(self, key):
        print('Getting ' + str(key))
        return cls_getitem(self, key)

    def __setitem__(self, key, value):
        print('Setting {} = {!r}'.format(key, value))
        return cls_setitem(self, key, value)

    def __delitem__(self, key):
        print('Deleting ' + str(key))
        return cls_delitem(self, key)

    cls.__getitem__ = __getitem__
    cls.__setitem__ = __setitem__
    cls.__delitem__ = __delitem__
    return cls


@LoggedMapping
class LoggedDict(dict):
    pass

l=LoggedDict()
l['x'] = 1
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • 本文为《爬着学Python》系列第十四篇文章。 中间因为工作原因隔了好久没有更新,现在稳定很多,会陆续开始更新。不...
    SyPy阅读 12,081评论 1 10
  • 要点: 函数式编程:注意不是“函数编程”,多了一个“式” 模块:如何使用模块 面向对象编程:面向对象的概念、属性、...
    victorsungo阅读 1,459评论 0 6
  • 昨天晚上我和妈妈,姐姐一起看了《》
    张世强阅读 152评论 0 0
  • 有时候不知不觉会感慨一下,那曾经幼稚的少年,终于在多年以后 长大了,可是长大的伤感又怎么能避免呢。 陪你聊几天你就...
    叫我阿根阅读 1,506评论 0 1