Python中的元类
@[toc]
一、类也是一种对象
1.类是什么?
在大多数语言当中,类的出现体现出了面向对象的封装性,是把一些属性及其方法封装在一起,就构成了类,而类只是一个框架、图纸,真正使用类的方式是用类创建一个实例对象。
在大多数语言中,类只是描述如何产生对象的代码段
。在Python中也是如此:
>>> class ObjectCreator(object):
... pass
>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>
2.Python中的类
Python中类也是对象。
是的,对象。
一旦使用关键字class,Python就会执行它并创建一个对象。指令
>>> class ObjectCreator(object):
... pass
在内存中创建一个名称为“ ObjectCreator”的对象。
这个对象(类)本身具有创建对象(实例)的能力,这就是为什么它是一个类。
但是,它仍然是一个对象
,因此:
您可以将其分配给变量
你可以复制它
您可以为其添加属性
您可以将其作为函数参数传递
例:
>>> print(ObjectCreator) # 你可以打印一个类,因为它其实也是一个对象
<class '__main__.ObjectCreator'>
>>> def echo(o):
… print(o)
…
>>> echo(ObjectCreator) # 你可以将类做为参数传给函数
<class '__main__.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>
3.动态的创建类
由于类是对象,因此您可以像创建任何对象一样即时创建它们。
首先,您可以使用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) # 函数返回的是类,不是类的实例
<class '__main__'.Foo>
>>> print(MyClass()) # 你可以通过这个类创建类实例,也就是对象
<__main__.Foo object at 0x89c6d4c>
但这并不是那么动态,因为您仍然必须自己编写整个类
。
由于类是对象,因此它们必须由某种东西生成。
使用class关键字时,Python会自动创建此对象。但是,与Python中的大多数事情一样,它为您提供了一种手动进行操作的方法
。
4.type函数
还记得type的功能吗?type函数可以让您知道对象的类型:
>>> print(type(1)) # 数值的类型
<type 'int'>
>>> print(type("1")) # 字符串的类型
<type 'str'>
>>> print(type(ObjectCreator())) # 实例对象的类型
<class '__main__.ObjectCreator'>
>>> print(type(ObjectCreator)) # 类的类型
<type 'type'>
嗯,type具有完全不同的功能,它也可以动态创建类。type可以将类的描述作为参数,并返回一个类。
(我知道,根据传递给它的参数,同一个函数可以有两种完全不同的用法是很愚蠢的。由于Python向后兼容,这是一个问题)
5.type创建类
type 这样工作:
type(name, bases, attrs)
-
name:类的名称
-
bases:继承父类的元组(对于继承,可以为空)
-
attrs:包含属性名称和值的字典
即:
type(类名, 由父类名称组成的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))
例:
In [2]: class Test: #定义了一个Test类
...: pass
...:
In [3]: Test() # 创建了一个Test类的实例对象
Out[3]: <__main__.Test at 0x10d3f8438>
可以手动像这样创建:
Test2 = type("Test2", (), {}) # 定了一个Test2类
In [5]: Test2() # 创建了一个Test2类的实例对象
Out[5]: <__main__.Test2 at 0x10d406b38>
- 我们使用"Test2"作为类名,并且
也可以把它当做一个变量来作为类的引用
。 - 类和变量是不同的,这里没有任何理由把事情弄的复杂。
- 即type函数中第1个实参,也可以叫做其他的名字,这个名字表示类的名字
In [23]: MyDogClass = type('MyDog', (), {})
In [24]: print(MyDogClass)
<class '__main__.MyDog'>
使用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)
5.使用type创建带有属性的类
type 接受一个字典来为类定义属性,因此
>>> Foo = type('Foo', (), {'bar': True})
可以翻译为:
>>> class Foo(object):
… bar = True
并且可以将Foo当成一个普通的类一样使用:
>>> print(Foo)
<class '__main__.Foo'>
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar)
True
当然,你可以继承这个类,代码如下:
>>> class FooChild(Foo):
… pass
就可以写成:
>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # bar属性是由Foo继承而来
True
注意:
- type的第2个参数,元组中是父类的名字,而不是字符串
-
添加的属性是类属性,并不是实例属性
6. 使用type创建带有方法的类
最终你会希望为你的类增加方法。只需要定义一个有着恰当签名的函数并将其作为属性赋值就可以了
。
6.1添加实例方法
In [46]: def echo_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
6.2添加静态方法
In [36]: @staticmethod
...: def test_static():
...: print("static method ....")
...:
In [37]: Foochild = type('Foochild', (Foo,), {"echo_bar": echo_bar, "test_static": test_static})
In [38]: fooclid = Foochild()
In [39]: fooclid.test_static
Out[39]: <function __main__.test_static>
In [40]: fooclid.test_static()
static method ....
In [41]: fooclid.echo_bar()
True
6.3添加类方法
In [42]: @classmethod
...: def test_class(cls):
...: print(cls.bar)
...:
In [43]:
In [43]: Foochild = type('Foochild', (Foo,), {"echo_bar":echo_bar, "test_static": test_static, "test_class": test_class})
In [44]:
In [44]: fooclid = Foochild()
In [45]: fooclid.test_class()
True
你可以看到,在Python中,类也是对象,你可以动态的创建类。这就是当你使用关键字class时Python在幕后做的事情,而这就是通过元类来实现的。
6.4.较为完整的使用type创建类的方式:
class A(object):
num = 100
def print_b(self):
print(self.num)
@staticmethod
def print_static():
print("----haha-----")
@classmethod
def print_class(cls):
print(cls.num)
B = type("B", (A,), {"print_b": print_b, "print_static": print_static, "print_class": print_class})
b = B()
b.print_b()
b.print_static()
b.print_class()
# 结果
# 100
# ----haha-----
# 100
二、元类
元类是创建类的“东西”。您定义类是为了创建对象,对吗?但是我们了解到Python类是对象。
好吧,元类是创建这些对象的原因。它们是班级的班级,您可以通过以下方式描绘它们:
MyClass = MetaClass() # 使用元类创建出一个对象,这个对象称为“类”
my_object = MyClass() # 使用“类”来创建出实例对象
您已经看到,type您可以执行以下操作:
MyClass = type('MyClass', (), {})
-
这是因为该函数type实际上是一个元类。type是Python用于在幕后创建所有类的元类。
- 现在您想知道为什么用小写而不是小写Type?
- 好吧,我想这与str创建字符串对象int的类和创建整数对象的类的一致性有关。type只是创建类对象的类。
- 您可以通过检查
__class__属性
来看到。 - 一切,我的意思是,
一切都是Python中的对象。其中包括整数,字符串,函数和类。它们都是对象
。
所有这些都是从一个类创建的:
>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>
现在,对于任何一个__class__的__class__属性又是什么呢
?
>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>
因此,元类只是创建类对象的东西
。
- 如果愿意,可以将其称为“班级工厂”。
- type 是Python使用的内置元类,但是您当然可以创建自己的元类。
三、__metaclass__属性
1.在Python 2中
您可以__metaclass__在编写类时添加属性
(有关Python 3语法,请参见下一部分):
class Foo(object):
__metaclass__ = something...
[...]
- 如果这样做,Python将使用元类来创建class Foo。小心点,这很棘手。您class Foo(object)先编写,但Foo尚未在内存中创建类对象。
- Python将__metaclass__在类定义中查找。如果找到它,它将使用它来创建对象类Foo。如果没有,它将type用于创建类。
当您这样做时:
class Foo(Bar):
pass
Python执行以下操作:
- Foo中有__metaclass__这个属性吗?如果是,Python会通过__metaclass__创建一个名字为Foo的类(对象)
-
如果Python没有找到__metaclass__,它会继续在Bar(父类)中寻找__metaclass__属性,并尝试做和前面同样的操作。
- 如果Python在任何父类中都找不到__metaclass__,它就会在模块层次中去寻找__metaclass__,并尝试做同样的操作。
-
如果还是找不到__metaclass__,Python就会用内置的type来创建这个类对象
- 请注意,该__metaclass__属性将不会被继承,父(Bar.class)的元类将被继承。如果Bar使用的__metaclass__是创建的属性Bar与type()(不是type.new()),子类不会继承该行为。
现在的问题就是,你可以在__metaclass__中放置些什么代码呢
?
答案就是:可以创建一个类的东西。
那么什么可以用来创建一个类呢
?
type,或者任何使用到type或者子类化type的东东都可以。
2.Python 3中的元类
设置元类的语法在Python 3中已更改:
class Foo(object, metaclass=something):
...
即__metaclass__不再使用该属性,而在基类列表中使用关键字参数。
但是,元类的行为基本保持不变。
在python 3中添加到元类的一件事是,您还可以将属性作为关键字参数传递给元类,
如下所示:
class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2):
...
四、自定义元类
元类的主要目的是在创建类时自动更改它。
- 通常,您要针对要在其中创建与当前上下文匹配的类的API进行此操作。
- 想象一个愚蠢的示例,在该示例中,您决定模块中的所有类的属性都应以大写形式编写。有多种方法可以执行此操作,但是一种方法是__metaclass__在模块级别进行设置。
- 这样,将使用此元类创建该模块的所有类,而我们只需要告诉元类将所有属性都转换为大写即可。
- 幸运的是,__metaclass__实际上可以是任何可调用的,它不必是正式的类(我知道,名称中带有“ class”的东西不必是类,请弄清楚……但这很有用)。
因此,我们将从使用一个简单示例开始,使用一个函数。
# the metaclass will automatically get passed the same argument
# that you usually pass to `type`
def upper_attr(future_class_name, future_class_parents, future_class_attrs):
"""
Return a class object, with the list of its attribute turned
into uppercase.
"""
# pick up any attribute that doesn't start with '__' and uppercase it
uppercase_attrs = {
attr if attr.startswith("__") else attr.upper(): v
for attr, v in future_class_attrs.items()
}
# let `type` do the class creation
return type(future_class_name, future_class_parents, uppercase_attrs)
__metaclass__ = upper_attr # this will affect all classes in the module
class Foo(): # global __metaclass__ won't work with "object" though
# but we can define __metaclass__ here instead to affect only this class
# and this will work with "object" children
bar = 'bip'
# 让我们检查:
>>> hasattr(Foo, 'bar')
False
>>> hasattr(Foo, 'BAR')
True
>>> Foo.BAR
'bip'
现在,让我们做完全一样的操作,但是对元类使用真实的类:
# remember that `type` is actually a class like `str` and `int`
# so you can inherit from it
class UpperAttrMetaclass(type):
# __new__ is the method called before __init__
# it's the method that creates the object and returns it
# while __init__ just initializes the object passed as parameter
# you rarely use __new__, except when you want to control how the object
# is created.
# here the created object is the class, and we want to customize it
# so we override __new__
# you can do some stuff in __init__ too if you wish
# some advanced use involves overriding __call__ as well, but we won't
# see this
def __new__(upperattr_metaclass, future_class_name,
future_class_parents, future_class_attrs):
uppercase_attrs = {
attr if attr.startswith("__") else attr.upper(): v
for attr, v in future_class_attrs.items()
}
return type(future_class_name, future_class_parents, uppercase_attrs)
让我们重写上面的内容,但是现在有了更短,更实际的变量名,我们知道它们的含义了:
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, attrs):
uppercase_attrs = {
attr if attr.startswith("__") else attr.upper(): v
for attr, v in attrs.items()
}
return type(clsname, bases, uppercase_attrs)
- 您可能已经注意到了额外的争论cls。
- 它没有什么特别的:__new__始终将其定义的类作为第一个参数。
- 就像您有self将实例作为第一个参数接收的普通方法一样,还是为类方法定义了类。
- 但这不是适当的OOP。我们正在type直接致电,而不是覆盖或致电父母的__new__。让我们改为:
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, attrs):
uppercase_attrs = {
attr if attr.startswith("__") else attr.upper(): v
for attr, v in attrs.items()
}
return type.__new__(cls, clsname, bases, uppercase_attrs)
通过使用super,我们可以使其更加整洁,这将简化继承(因为是的,您可以具有元类,从元类继承,从类型继承):
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, attrs):
uppercase_attrs = {
attr if attr.startswith("__") else attr.upper(): v
for attr, v in attrs.items()
}
return super(UpperAttrMetaclass, cls).__new__(
cls, clsname, bases, uppercase_attrs)
哦,在python 3中,如果您使用关键字参数进行此调用,例如:
class Foo(object, metaclass=MyMetaclass, kwarg1=value1):
...
它将在元类中转换为使用它:
class MyMetaclass(type):
def __new__(cls, clsname, bases, dct, kwargs1=default):
...
而已。实际上,关于元类的更多信息了。
- 使用元类编写代码的复杂性背后的原因不是因为元类,而是因为您通常使用元类依靠自省,操纵继承以及诸如var之类的变量来做扭曲的事情__dict__。
就是这样,除此之外,关于元类真的没有别的可说的了。但就元类本身而言,它们其实是很简单的:
- 拦截类的创建
- 修改类
- 返回修改之后的类
五、为什么要使用元类类而不是函数?
既然__metaclass__可以接受任何可调用对象,那么为什么要使用一个类,因为它显然更复杂?
这样做有几个原因:
- 意图很明确。阅读时UpperAttrMetaclass(type),您会知道接下来会发生什么 您可以使用OOP。
- 元类可以继承元类,重写父方法。元类甚至可以使用元类。
- 如果您指定了元类类,但没有元类函数,则该类的子类将是其元类的实例。
- 您可以更好地构建代码。绝对不要像上面的示例那样将元类用于琐碎的事情
- 通常用于复杂的事情。能够制作几种方法并将它们分组在一个类中的能力对于使代码更易于阅读非常有用。
- 您可以勾上__new__,init__和__call。这将使您可以做不同的事情。即使通常可以全部使用__new__,有些人也更习惯使用__init__。
元类是更深层次的魔术,99%的用户永远不必担心。如果您想知道是否需要它们,则不需要(实际上需要它们的人肯定会知道他们需要它们,并且不需要解释原因)。
--------- Python大师Tim Peters
元类的主要用例是创建一个API。一个典型的例子是Django ORM。它允许您定义如下内容:
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()
但是,如果您这样做:
person = Person(name='bob', age='35')
print(person.age)
- 它不会返回IntegerField对象。它将返回一个int,甚至可以直接从数据库中获取它。
- 这是可能的,因为models.Modeldefine
__metaclass__并使用了一些魔术,这些魔术将使Person您使用简单语句定义的对象变成与数据库字段的复杂挂钩。 - Django通过公开一个简单的API并使用元类,从该API重新创建代码来完成幕后的实际工作,从而使看起来复杂的事情变得简单。
总结:可以认为元类就是Python中的‘女娲’
六、元类实现ORM
1. ORM是什么
ORM 是 python编程语言后端web框架 Django的核心思想,“Object Relational Mapping”,即对象-关系映射,简称ORM
。
一个句话理解就是:创建一个实例对象,用创建它的类名当做数据表名,用创建它的类属性对应数据表的字段,当对这个实例对象操作时,能够对应MySQL语句
demo
:
class User(父类省略):
uid = ('uid', "int unsigned")
name = ('username', "varchar(30)")
email = ('email', "varchar(30)")
password = ('password', "varchar(30)")
...省略...
u = User(uid=12345, name='Michael', email='test@orm.org', password='my-pwd')
u.save()
# 对应如下sql语句
# insert into User (username,email,password,uid)
# values ('Michael','test@orm.org','my-pwd',12345)
说明
- 所谓的ORM就是让开发者在操作数据库的时候,能够像操作对象时通过xxxx.属性=yyyy一样简单,这是开发ORM的初衷
- 只不过ORM的实现较为复杂,Django中已经实现了 很复杂的操作,本例主要通过完成一个insert相类似的ORM,理解其中的道理就就可以了
2. 通过元类简单实现ORM中的insert功能
class ModelMetaclass(type):
def __new__(cls, name, bases, attrs):
mappings = dict()
# 判断是否需要保存
for k, v in attrs.items():
# 判断是否是指定的StringField或者IntegerField的实例对象
if isinstance(v, tuple):
print('Found mapping: %s ==> %s' % (k, v))
mappings[k] = v
# 删除这些已经在字典中存储的属性
for k in mappings.keys():
attrs.pop(k)
# 将之前的uid/name/email/password以及对应的对象引用、类名字
attrs['__mappings__'] = mappings # 保存属性和列的映射关系
attrs['__table__'] = name # 假设表名和类名一致
return type.__new__(cls, name, bases, attrs)
class User(metaclass=ModelMetaclass):
uid = ('uid', "int unsigned")
name = ('username', "varchar(30)")
email = ('email', "varchar(30)")
password = ('password', "varchar(30)")
# 当指定元类之后,以上的类属性将不在类中,而是在__mappings__属性指定的字典中存储
# 以上User类中有
# __mappings__ = {
# "uid": ('uid', "int unsigned")
# "name": ('username', "varchar(30)")
# "email": ('email', "varchar(30)")
# "password": ('password', "varchar(30)")
# }
# __table__ = "User"
def __init__(self, **kwargs):
for name, value in kwargs.items():
setattr(self, name, value)
def save(self):
fields = []
args = []
for k, v in self.__mappings__.items():
fields.append(v[0])
args.append(getattr(self, k, None))
sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join([str(i) for i in args]))
print('SQL: %s' % sql)
u = User(uid=12345, name='Michael', email='test@orm.org', password='my-pwd')
# print(u.__dict__)
u.save()
#执行的效果:
Found mapping: password ==> ('password', 'varchar(30)')
Found mapping: email ==> ('email', 'varchar(30)')
Found mapping: uid ==> ('uid', 'int unsigned')
Found mapping: name ==> ('username', 'varchar(30)')
SQL: insert into User (uid,password,username,email) values (12345,my-pwd,Michael,test@orm.
3. 完善对数据类型的检测
class ModelMetaclass(type):
def __new__(cls, name, bases, attrs):
mappings = dict()
# 判断是否需要保存
for k, v in attrs.items():
# 判断是否是指定的StringField或者IntegerField的实例对象
if isinstance(v, tuple):
print('Found mapping: %s ==> %s' % (k, v))
mappings[k] = v
# 删除这些已经在字典中存储的属性
for k in mappings.keys():
attrs.pop(k)
# 将之前的uid/name/email/password以及对应的对象引用、类名字
attrs['__mappings__'] = mappings # 保存属性和列的映射关系
attrs['__table__'] = name # 假设表名和类名一致
return type.__new__(cls, name, bases, attrs)
class User(metaclass=ModelMetaclass):
uid = ('uid', "int unsigned")
name = ('username', "varchar(30)")
email = ('email', "varchar(30)")
password = ('password', "varchar(30)")
# 当指定元类之后,以上的类属性将不在类中,而是在__mappings__属性指定的字典中存储
# 以上User类中有
# __mappings__ = {
# "uid": ('uid', "int unsigned")
# "name": ('username', "varchar(30)")
# "email": ('email', "varchar(30)")
# "password": ('password', "varchar(30)")
# }
# __table__ = "User"
def __init__(self, **kwargs):
for name, value in kwargs.items():
setattr(self, name, value)
def save(self):
fields = []
args = []
for k, v in self.__mappings__.items():
fields.append(v[0])
args.append(getattr(self, k, None))
args_temp = list()
for temp in args:
# 判断入如果是数字类型
if isinstance(temp, int):
args_temp.append(str(temp))
elif isinstance(temp, str):
args_temp.append("""'%s'""" % temp)
sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(args_temp))
print('SQL: %s' % sql)
u = User(uid=12345, name='Michael', email='test@orm.org', password='my-pwd')
# print(u.__dict__)
u.save()
# 运行效果如下:
Found mapping: uid ==> ('uid', 'int unsigned')
Found mapping: password ==> ('password', 'varchar(30)')
Found mapping: name ==> ('username', 'varchar(30)')
Found mapping: email ==> ('email', 'varchar(30)')
SQL: insert into User (email,uid,password,username) values ('test@orm.org',12345,'my-pwd','Michael')
4. 抽取到基类中
class ModelMetaclass(type):
def __new__(cls, name, bases, attrs):
mappings = dict()
# 判断是否需要保存
for k, v in attrs.items():
# 判断是否是指定的StringField或者IntegerField的实例对象
if isinstance(v, tuple):
print('Found mapping: %s ==> %s' % (k, v))
mappings[k] = v
# 删除这些已经在字典中存储的属性
for k in mappings.keys():
attrs.pop(k)
# 将之前的uid/name/email/password以及对应的对象引用、类名字
attrs['__mappings__'] = mappings # 保存属性和列的映射关系
attrs['__table__'] = name # 假设表名和类名一致
return type.__new__(cls, name, bases, attrs)
class Model(object, metaclass=ModelMetaclass):
def __init__(self, **kwargs):
for name, value in kwargs.items():
setattr(self, name, value)
def save(self):
fields = []
args = []
for k, v in self.__mappings__.items():
fields.append(v[0])
args.append(getattr(self, k, None))
args_temp = list()
for temp in args:
# 判断入如果是数字类型
if isinstance(temp, int):
args_temp.append(str(temp))
elif isinstance(temp, str):
args_temp.append("""'%s'""" % temp)
sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(args_temp))
print('SQL: %s' % sql)
class User(Model):
uid = ('uid', "int unsigned")
name = ('username', "varchar(30)")
email = ('email', "varchar(30)")
password = ('password', "varchar(30)")
u = User(uid=12345, name='Michael', email='test@orm.org', password='my-pwd')
# print(u.__dict__)
u.save()
评论区