当前位置:   article > 正文

Python-VBA函数之旅-property函数_vbaproperty

vbaproperty

目录

一、property函数的常见应用场景

二、property函数使用注意事项

三、如何用好property函数?

1、property函数:

1-1、Python:

1-2、VBA:

2、推荐阅读:

个人主页:神奇夜光杯-CSDN博客 

  

一、property函数的常见应用场景

        在Python中,property()函数(或更常见的是@property装饰器)的实际应用场景非常广泛,主要用于封装类的内部状态并提供对外的访问接口,常见的应用场景有:

1、隐藏内部实现细节:通过getter和setter方法,我们可以隐藏对象的内部状态,只暴露必要的接口给外部调用者,这有助于保护对象的状态不被随意修改,同时也使得代码更加易于理解和维护。

2、添加属性验证:在setter方法中,我们可以添加对属性值的验证逻辑。例如,我们可以确保一个年龄属性总是大于0,或者一个邮箱地址属性总是符合某种格式。

3、计算属性:有些属性可能不是直接存储在对象的状态中的,而是需要基于其他属性计算得出,通过使用property,我们可以将这样的属性表示为对象的一个“正常”属性,而无需调用一个方法来获取它。

4、延迟计算:对于某些计算成本较高的属性,我们可能希望在第一次访问时才进行计算,并将结果缓存起来,通过使用property和内部变量,我们可以实现这样的延迟计算。

5、访问控制: property()函数允许你控制对属性的访问权限。例如,你可以使属性只读,或者只允许在特定条件下修改属性。

6、简化接口:在某些情况下,你可能想要隐藏对象的内部复杂性,只向外部提供一个简洁的接口,使用property可以将内部方法(如getter、setter)暴露为看起来像普通属性的接口。

7、实现数据绑定:在更复杂的场景中,property可以与观察者模式(Observer Pattern)或数据绑定库结合使用,以实现当属性发生变化时自动触发某些操作或更新UI等效果。

8、线程安全:在多线程环境中,对属性的直接访问可能会导致数据竞争或其他并发问题,使用property可以确保在访问或修改属性时执行必要的同步操作,从而保证线程安全。

9、日志记录或监控:在获取或设置属性时,你可能想要记录一些信息(如访问时间、访问者等),或者触发某些监控事件,通过property可以轻松实现这些功能。

10、惰性加载:对于某些需要大量计算或需要从外部资源(如数据库或网络)加载的属性,可以使用property实现惰性加载,这意味着属性只有在首次被访问时才会进行计算或加载,从而节省资源。

二、property函数使用注意事项

        在Python中,property()函数是一个内置函数,用于在新式类中返回属性值property()函数允许你定义getter,setter和deleter方法,这些方法分别用于获取、设置和删除对象的属性。以下是在使用property()函数时需要注意的一些事项:

1、定义getter/setter/deleter方法:getter方法通常不需要任何参数,并返回属性的值;setter方法需要接收两个参数:self和要设置的值value;deleter方法只需要self参数。   

  1. 示例:
  2.    class Circle:
  3.        def __init__(self, radius=1.0):
  4.            self._radius = radius
  5.        @property
  6.        def radius(self):
  7.            return self._radius
  8.        @radius.setter
  9.        def radius(self, value):
  10.            if value < 0:
  11.                raise ValueError("Radius cannot be negative")
  12.            self._radius = value
  13.        @radius.deleter
  14.        def radius(self):
  15.            del self._radius

2、私有属性:当你使用@property装饰器时,通常建议将实际存储数据的属性设为私有(例如使用下划线前缀),以避免直接访问和修改它们。

3、装饰器语法:你可以使用装饰器语法来定义getter,setter和deleter方法;另一种语法是使用property()函数直接返回getter,setter和deleter方法作为元组,但这种方法较少使用,因为它不够直观。

4、性能:虽然property()通常不会成为性能瓶颈,但在需要处理大量对象的场合中,频繁地调用getter或setter可能会产生一些开销,在这种情况下,你可能需要考虑其他方法,如使用@cached_property装饰器(来自cached_property库或类似库)来缓存getter方法的返回值。

5、线程安全:如果你在多线程环境中使用setter或deleter方法,并且这些方法可能会修改共享状态,那么你需要确保这些方法是线程安全的。

6、错误处理:在setter方法中,你可能需要添加错误处理逻辑来确保设置的值是有效的。

7、文档字符串:为getter,setter和deleter方法提供文档字符串是一个好习惯,这有助于其他开发者理解这些方法的作用和用法。

8、可读性:当使用property()函数时,确保你的getter,setter和deleter方法的命名清晰易懂,以便其他开发者能够轻松理解你的代码。

9、继承:虽然property()函数提供了更好的封装和抽象,但它也带来了一定的开销,因为每次访问属性时都会调用一个方法,然而,在大多数情况下,这种开销是可以接受的,因为Python方法调用的开销相对较低。

10、向后兼容性:如果你的类需要向后兼容Python 2,请注意property()函数在Python 2和Python 3中的行为可能略有不同:在Python 2中,property()是一个内置的工厂函数;而在Python 3中,它不仅是一个内置函数,也可以用作装饰器。

三、如何用好property函数?

        在Python中,property()函数是一个内置函数,用于创建只读的、可写的或可删除的属性,这提供了一种将方法作为属性访问的方式,从而简化了对对象属性的访问和修改。

property()函数的基本用法有三种:

1、只读属性:只需要定义一个getter方法。

  1. class MyClass:
  2.     def __init__(self, value):
  3.         self._value = value
  4.     @property
  5.     def value(self):
  6.         return self._value
  7. obj = MyClass(10)
  8. print(obj.value)  # 输出: 10

在这个例子中,`value` 是一个只读属性,只能通过 `obj.value` 来访问,但不能直接赋值。

2、可读写属性:除了getter方法,还需要定义一个setter方法。

  1. class MyClass:
  2.     def __init__(self, value):
  3.         self._value = value
  4.     @property
  5.     def value(self):
  6.         return self._value
  7.     @value.setter
  8.     def value(self, new_value):
  9.         if new_value < 0:
  10.             raise ValueError("Value must be non-negative")
  11.         self._value = new_value
  12. obj = MyClass(10)
  13. print(obj.value)  # 输出: 10
  14. obj.value = 20
  15. print(obj.value)  # 输出: 20
  16. obj.value = -1   # 抛出 ValueError

在这个例子中,`value` 是一个可读写属性,可以通过 `obj.value` 来访问,也可以通过 `obj.value = new_value` 来修改,在setter方法中,我们还添加了一个简单的验证逻辑。

3、可删除属性:除了getter和setter方法,还需要定义一个deleter方法。

  1. class MyClass:
  2.     def __init__(self, value):
  3.         self._value = value
  4.     @property
  5.     def value(self):
  6.         return self._value
  7.     @value.setter
  8.     def value(self, new_value):
  9.         self._value = new_value
  10.     @value.deleter
  11.     def value(self):
  12.         del self._value
  13. obj = MyClass(10)
  14. print(obj.value)  # 输出: 10
  15. obj.value = 20
  16. print(obj.value)  # 输出: 20
  17. del obj.value
  18. # 尝试访问 obj.value 将引发 AttributeError

在这个例子中,`value` 是一个可删除的属性,可以使用 `del obj.value` 来删除它。

注意:虽然property()函数可以直接使用,但通常建议使用@property、@value.setter和 @value.deleter装饰器来定义属性,因为它们更加简洁和易读。

1、property函数:
1-1、Python:
  1. # 1.函数:property
  2. # 2.功能:用于创建属性实例,这些实例可以绑定到类的方法上,从而允许我们像访问数据属性一样访问类的方法
  3. # 3.语法:property([fget=None[, fset=None[, fdel=None[, doc=None]]]])
  4. # 4.参数:
  5. # 4-1、fget:用于获取属性值的方法(getter),它应该是一个不带参数的方法,并且返回一个值;如果省略,则属性为只读
  6. # 4-2、fset:用于设置属性值的方法(setter),它应该是一个接受一个参数(新值)的方法,并且不返回任何内容(或者返回None);如果省略,则属性为只读
  7. # 4-3、fdel:用于删除属性的方法(deleter),它应该是一个不带参数的方法,并且不返回任何内容(或者返回None);如果省略,则不能删除属性
  8. # 4-4、doc:一个可选的字符串,作为属性的文档字符串,如果省略,则使用`fget`、`fset`或`fdel`方法中的文档字符串(如果存在的话)
  9. # 5.返回值:返回类的属性
  10. # 6.说明:
  11. # 6-1、如果给出doc参数,doc将成为该property属性的文档字符串;否则该property将复制fget/fset/fdel等方法中的文档字符串(如果存在的话)
  12. # 7.示例:
  13. # 用dir()函数获取该函数内置的属性和方法
  14. print(dir(property))
  15. # ['__class__', '__delattr__', '__delete__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__',
  16. # '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__isabstractmethod__', '__le__', '__lt__',
  17. # '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__set__', '__set_name__', '__setattr__', '__sizeof__',
  18. # '__str__', '__subclasshook__', 'deleter', 'fdel', 'fget', 'fset', 'getter', 'setter']
  19. # 用help()函数获取该函数的文档信息
  20. help(property)
  21. # 应用一:隐藏内部实现细节
  22. class Circle:
  23. def __init__(self, radius):
  24. self._radius = radius
  25. @property
  26. def radius(self):
  27. """Getter for radius."""
  28. return self._radius
  29. @radius.setter
  30. def radius(self, value):
  31. """Setter for radius, checks if value is positive."""
  32. if value < 0:
  33. raise ValueError("Radius cannot be negative.")
  34. self._radius = value
  35. @property
  36. def diameter(self):
  37. """Calculate and return the diameter."""
  38. return 2 * self._radius
  39. @property
  40. def area(self):
  41. """Calculate and return the area."""
  42. import math
  43. return math.pi * (self._radius ** 2)
  44. # 注意:diameter和area是只读的,因为它们没有setter方法
  45. # 使用Circle类
  46. c = Circle(5)
  47. print(c.radius) # 访问radius属性
  48. print(c.diameter) # 访问diameter属性(只读)
  49. print(c.area) # 访问area属性(只读)
  50. # 5
  51. # 10
  52. # 78.53981633974483
  53. c.radius = 10 # 修改radius属性
  54. print(c.radius) # 再次访问radius属性,确认已修改
  55. print(c.diameter) # 再次访问diameter属性,确认已根据新的radius更新
  56. print(c.area) # 再次访问area属性,确认已根据新的radius更新
  57. # 10
  58. # 20
  59. # 314.1592653589793
  60. # 尝试设置直径或面积(这将失败,因为它们没有setter方法)
  61. # c.diameter = 15 # 这会抛出一个 AttributeError
  62. # c.area = 50 # 这也会抛出一个 AttributeError
  63. # AttributeError: property 'diameter' of 'Circle' object has no setter
  64. # 应用二:添加属性验证
  65. class Person:
  66. def __init__(self, name, age):
  67. self._name = name
  68. self._age = None
  69. self.age = age # 使用setter方法进行初始化
  70. @property
  71. def name(self):
  72. return self._name
  73. @property
  74. def age(self):
  75. """Getter for age."""
  76. return self._age
  77. @age.setter
  78. def age(self, value):
  79. """Setter for age, checks if value is a positive integer."""
  80. if not isinstance(value, int) or value < 0:
  81. raise ValueError("Age must be a positive integer.")
  82. self._age = value
  83. # 如果你想要一个deleter方法,可以添加如下:
  84. @age.deleter
  85. def age(self):
  86. raise AttributeError("Cannot delete the age attribute.")
  87. # 使用 Person 类
  88. try:
  89. p = Person("Myelsa", 18)
  90. print(p.name) # 输出: Myelsa
  91. print(p.age) # 输出: 18
  92. p.age = 42 # 修改age属性
  93. print(p.age) # 输出: 42
  94. # 尝试设置不合法的age值
  95. p.age = -1 # 抛出 ValueError
  96. # p.age = "fourty two" # 也会抛出ValueError
  97. except ValueError as e:
  98. print(e)
  99. # Myelsa
  100. # 18
  101. # 42
  102. # Age must be a positive integer.
  103. # 尝试删除age属性(如果已定义deleter)
  104. del p.age # 如果 deleter 存在且未抛出AttributeError,则会执行
  105. # 否则,上面的代码会按原样运行,因为deleter抛出了AttributeError
  106. # AttributeError: Cannot delete the age attribute.
  107. # 应用三:计算属性
  108. class Rectangle:
  109. def __init__(self, width, height):
  110. self._width = width
  111. self._height = height
  112. @property
  113. def width(self):
  114. """Getter for width."""
  115. return self._width
  116. @width.setter
  117. def width(self, value):
  118. """Setter for width."""
  119. if value < 0:
  120. raise ValueError("Width cannot be negative.")
  121. self._width = value
  122. @property
  123. def height(self):
  124. """Getter for height."""
  125. return self._height
  126. @height.setter
  127. def height(self, value):
  128. """Setter for height."""
  129. if value < 0:
  130. raise ValueError("Height cannot be negative.")
  131. self._height = value
  132. @property
  133. def area(self):
  134. """Calculate and return the area."""
  135. return self._width * self._height
  136. @property
  137. def perimeter(self):
  138. """Calculate and return the perimeter."""
  139. return 2 * (self._width + self._height)
  140. # 使用Rectangle类
  141. rect = Rectangle(5, 10)
  142. print(rect.area) # 输出: 50 (宽度5乘以高度10)
  143. print(rect.perimeter) # 输出: 30 (两倍的(宽度5加高度10))
  144. rect.width = 7
  145. print(rect.area) # 输出: 70 (新的宽度7乘以高度10)
  146. print(rect.perimeter) # 输出: 34 (两倍的(新的宽度7加高度10))
  147. # 50
  148. # 30
  149. # 70
  150. # 34
  151. # 应用四:延迟计算
  152. class Circle:
  153. def __init__(self, radius):
  154. self._radius = radius
  155. self._area = None
  156. self._circumference = None
  157. @property
  158. def radius(self):
  159. """Getter for radius."""
  160. return self._radius
  161. @radius.setter
  162. def radius(self, value):
  163. """Setter for radius."""
  164. if value < 0:
  165. raise ValueError("Radius cannot be negative.")
  166. self._radius = value
  167. # 当半径改变时,重置缓存的area和circumference
  168. self._area = None
  169. self._circumference = None
  170. @property
  171. def area(self):
  172. """Calculate and cache the area."""
  173. if self._area is None:
  174. self._area = 3.14159 * self._radius ** 2
  175. return self._area
  176. @property
  177. def circumference(self):
  178. """Calculate and cache the circumference."""
  179. if self._circumference is None:
  180. self._circumference = 2 * 3.14159 * self._radius
  181. return self._circumference
  182. # 使用 Circle 类
  183. circle = Circle(5)
  184. print(circle.area) # 首次访问,进行计算并缓存结果
  185. print(circle.circumference) # 首次访问,进行计算并缓存结果
  186. # 再次访问,直接从缓存中获取结果
  187. print(circle.area) # 输出与之前相同,因为结果是缓存的
  188. print(circle.circumference) # 输出与之前相同,因为结果是缓存的
  189. circle.radius = 7
  190. print(circle.area) # 半径改变,重新计算并缓存新的area
  191. print(circle.circumference) # 半径改变,重新计算并缓存新的circumference
  192. # 78.53975
  193. # 31.4159
  194. # 78.53975
  195. # 31.4159
  196. # 153.93791
  197. # 43.98226
  198. # 应用五:访问控制
  199. class AccessControlled:
  200. def __init__(self, value):
  201. self._value = value
  202. def get_value(self):
  203. """Getter for value."""
  204. # 在这里可以添加访问前的验证或逻辑
  205. print("Reading value...")
  206. return self._value
  207. def set_value(self, value):
  208. """Setter for value."""
  209. # 在这里可以添加设置前的验证或逻辑
  210. if value < 0:
  211. raise ValueError("Value cannot be negative.")
  212. print("Setting value...")
  213. self._value = value
  214. def del_value(self):
  215. """Deleter for value."""
  216. # 在这里可以添加删除前的验证或逻辑
  217. print("Deleting value...")
  218. del self._value
  219. # 使用property()将方法绑定到属性
  220. value = property(get_value, set_value, del_value, "This is a controlled access value.")
  221. # 使用AccessControlled类
  222. obj = AccessControlled(10)
  223. # 读取属性值
  224. print(obj.value) # 输出: Reading value... 10
  225. # 设置属性值
  226. obj.value = 20
  227. # 输出: Setting value...
  228. # 尝试设置不合法的属性值
  229. try:
  230. obj.value = -5
  231. except ValueError as e:
  232. print(e) # 输出: Value cannot be negative.
  233. # 删除属性值
  234. del obj.value
  235. # 输出: Deleting value...
  236. # 尝试访问已删除的属性值
  237. try:
  238. print(obj.value)
  239. except AttributeError:
  240. print("Value has been deleted.") # 输出: Value has been deleted.
  241. # Reading value...
  242. # 10
  243. # Setting value...
  244. # Value cannot be negative.
  245. # Deleting value...
  246. # Reading value...
  247. # Value has been deleted.
  248. # 应用六:简化接口
  249. class Rectangle:
  250. def __init__(self, width, height):
  251. self._width = width
  252. self._height = height
  253. @property
  254. def width(self):
  255. """Getter for width."""
  256. return self._width
  257. @width.setter
  258. def width(self, value):
  259. """Setter for width."""
  260. if value < 0:
  261. raise ValueError("Width cannot be negative.")
  262. self._width = value
  263. @property
  264. def height(self):
  265. """Getter for height."""
  266. return self._height
  267. @height.setter
  268. def height(self, value):
  269. """Setter for height."""
  270. if value < 0:
  271. raise ValueError("Height cannot be negative.")
  272. self._height = value
  273. @property
  274. def area(self):
  275. """Calculate and return the area of the rectangle."""
  276. return self._width * self._height
  277. @property
  278. def perimeter(self):
  279. """Calculate and return the perimeter of the rectangle."""
  280. return 2 * (self._width + self._height)
  281. # 使用 Rectangle 类
  282. rect = Rectangle(5, 10)
  283. # 访问属性,无需关心内部计算
  284. print(rect.area) # 输出: 50
  285. print(rect.perimeter) # 输出: 30
  286. # 设置属性
  287. rect.width = 7
  288. rect.height = 8
  289. # 再次访问属性
  290. print(rect.area) # 输出: 56
  291. print(rect.perimeter) # 输出: 30
  292. # 尝试设置不合法的属性值
  293. try:
  294. rect.width = -5
  295. except ValueError as e:
  296. print(e) # 输出: Width cannot be negative.
  297. # 50
  298. # 30
  299. # 56
  300. # 30
  301. # Width cannot be negative.
  302. # 应用七:实现数据绑定
  303. class DataBinder:
  304. def __init__(self, initial_value, callback=None):
  305. self._value = initial_value
  306. self._callback = callback
  307. @property
  308. def value(self):
  309. """Getter for value."""
  310. return self._value
  311. @value.setter
  312. def value(self, new_value):
  313. """Setter for value. Triggers the callback if provided."""
  314. self._value = new_value
  315. if self._callback:
  316. self._callback(new_value)
  317. # 定义一个回调函数来模拟数据绑定的效果
  318. def on_value_change(new_value):
  319. print(f"Value has changed to: {new_value}")
  320. # 使用 DataBinder 类并设置回调函数
  321. binder = DataBinder(10, on_value_change)
  322. # 访问属性值
  323. print(binder.value) # 输出: 10
  324. # 修改属性值,这会触发回调函数
  325. binder.value = 20
  326. # 输出: Value has changed to: 20
  327. # 再次修改属性值
  328. binder.value = 30
  329. # 输出: Value has changed to: 30
  330. # 应用八:线程安全
  331. import threading
  332. class ThreadSafeClass:
  333. def __init__(self):
  334. self._value = 0
  335. self._lock = threading.Lock()
  336. @property
  337. def value(self):
  338. """线程安全的getter方法"""
  339. with self._lock:
  340. return self._value
  341. @value.setter
  342. def value(self, new_value):
  343. """线程安全的setter方法"""
  344. with self._lock:
  345. self._value = new_value
  346. # 使用示例
  347. if __name__ == "__main__":
  348. # 创建一个ThreadSafeClass的实例
  349. obj = ThreadSafeClass()
  350. # 假设我们有两个线程,它们将尝试同时访问和修改obj.value
  351. def worker(obj, name, increment):
  352. for _ in range(100000): # 假设我们进行大量操作以突出线程安全问题
  353. with obj._lock: # 在这里,我们只是为了演示而手动加锁,但在实际setter/getter中不需要
  354. old_value = obj.value
  355. new_value = old_value + increment
  356. obj.value = new_value
  357. print(f"{name}: Value after increment is {obj.value}")
  358. # 创建两个线程
  359. t1 = threading.Thread(target=worker, args=(obj, "Thread 1", 1))
  360. t2 = threading.Thread(target=worker, args=(obj, "Thread 2", -1))
  361. # 启动线程
  362. t1.start()
  363. t2.start()
  364. # 等待线程完成
  365. t1.join()
  366. t2.join()
  367. # 输出最终值(可能不是0,因为线程调度是随机的,但每次操作都是线程安全的)
  368. print(f"Final value: {obj.value}")
  369. # 应用九:日志记录或监控
  370. import logging
  371. class LoggedPropertyClass:
  372. def __init__(self):
  373. self._value = None
  374. self._logger = logging.getLogger(__name__)
  375. self._logger.setLevel(logging.INFO)
  376. # 创建一个handler,用于写入日志文件
  377. fh = logging.FileHandler('property_log.log')
  378. fh.setLevel(logging.INFO)
  379. # 再创建一个handler,用于输出到控制台
  380. ch = logging.StreamHandler()
  381. ch.setLevel(logging.INFO)
  382. # 定义handler的输出格式
  383. formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
  384. fh.setFormatter(formatter)
  385. ch.setFormatter(formatter)
  386. # 给logger添加handler
  387. self._logger.addHandler(fh)
  388. self._logger.addHandler(ch)
  389. @property
  390. def value(self):
  391. """Getter方法,用于访问_value属性,并添加日志记录"""
  392. self._logger.info("Accessing value: %s", self._value)
  393. return self._value
  394. @value.setter
  395. def value(self, new_value):
  396. """Setter方法,用于设置_value属性,并添加日志记录"""
  397. self._logger.info("Setting value to: %s", new_value)
  398. self._value = new_value
  399. # 使用示例
  400. if __name__ == "__main__":
  401. obj = LoggedPropertyClass()
  402. obj.value = 42 # 这将触发setter方法并记录日志
  403. print(obj.value) # 这将触发getter方法并记录日志
  404. # 42
  405. # 2024-05-07 18:46:57,919 - __main__ - INFO - Setting value to: 42
  406. # 2024-05-07 18:46:57,919 - __main__ - INFO - Accessing value: 42
  407. # 应用十:惰性加载
  408. class LazyLoadedClass:
  409. def __init__(self):
  410. # 初始化时,我们不立即加载数据,而是设置一个标记
  411. self._data = None
  412. self._loaded = False
  413. @property
  414. def data(self):
  415. """惰性加载的getter方法"""
  416. if not self._loaded:
  417. # 模拟数据加载过程,这里可以替换为实际的数据加载逻辑
  418. self._data = self._load_data()
  419. self._loaded = True
  420. return self._data
  421. def _load_data(self):
  422. """模拟的数据加载函数"""
  423. # 这里可以放置实际的加载逻辑,比如从数据库、文件或网络请求中获取数据
  424. # 这里我们只是简单地返回一个固定的值作为示例
  425. print("Loading data...")
  426. return "Loaded data"
  427. # 使用示例
  428. if __name__ == "__main__":
  429. obj = LazyLoadedClass()
  430. print(obj.data) # 第一次访问,会加载数据并打印"Loading data...",然后返回"Loaded data"
  431. print(obj.data) # 第二次访问,因为数据已经被加载和缓存,所以不会再次加载,直接返回"Loaded data"
  432. # Loading data...
  433. # Loaded data
  434. # Loaded data
1-2、VBA
略,待后补。
2、推荐阅读:

2-1、Python-VBA函数之旅-open()函数

Python算法之旅:Algorithm

Python函数之旅:Functions

个人主页:神奇夜光杯-CSDN博客 
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/668988
推荐阅读
相关标签
  

闽ICP备14008679号