Tony小窝。

python迭代协议

Iterator & Iterable

迭代器是访问集合内元素的一种方式, 一般用来遍历数据
迭代器Iterator和以下标的访问方式不一样, 迭代器是不能返回的, 迭代器提供了一种惰性方式数据的方式.
list内部只实现了__iter__,而没有实现__next__,list是iterable,但不是迭代器。

自己定义可迭代对象的时候,注意设计模式,不要把可迭代对象和迭代器混在一个class里,用单独的迭代器去管理index,设置__next__,在可迭代对象里只要__iter__返回一个迭代器就好

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
from collections.abc import Iterator

class Company(object):
def __init__(self, employee_list):
self.employee = employee_list

def __iter__(self):
return MyItera·tor(self.employee)

# def __getitem__(self, item):
# return self.employee[item]


class MyIterator(Iterator):
def __init__(self, employee_list):
self.iter_list = employee_list
self.index = 0

def __next__(self):
#真正返回迭代值的逻辑
try:
word = self.iter_list[self.index]
except IndexError:
raise StopIteration
self.index += 1
return word

if __name__ == "__main__":
company = Company(["tom", "bob", "jane"])
my_itor = iter(company)
# while True:
# try:
# print (next(my_itor))
# except StopIteration:
# pass

# next(my_itor)
for item in company:
print (item)

python元编程

getattr

getattr‘ 就是在查找不到属性的时候调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#__getattr__, __getattribute__

from datetime import date
class User:
def __init__(self,info={}):
self.info = info

def __getattr__(self, item):
return self.info[item]

# def __getattribute__(self, item):
# return "bobby"

if __name__ == "__main__":
user = User(info={"company_name":"imooc", "name":"bobby"})
print(user.test)

getattribute

所有属性访问入口,控制整个类实例过程。

descriptor

执行顺序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
如果user是某个类的实例,那么user.age(以及等价的getattr(user,’age’))
首先调用__getattribute__。如果类定义了__getattr__方法,
那么在__getattribute__抛出 AttributeError 的时候就会调用到__getattr__,
而对于描述符(__get__)的调用,则是发生在__getattribute__内部的。
user = User(), 那么user.age 顺序如下:

(1)如果“age”是出现在User或其基类的__dict__中, 且age是data descriptor, 那么调用其__get__方法, 否则

(2)如果“age”出现在user的__dict__中, 那么直接返回 obj.__dict__[‘age’], 否则

(3)如果“age”出现在User或其基类的__dict__中

(3.1)如果age是non-data descriptor,那么调用其__get__方法, 否则

(3.2)返回 __dict__[‘age’]

(4)如果User有__getattr__方法,调用__getattr__方法,否则

(5)抛出AttributeError

new vs init

new 是用来控制对象的生成过程, 在对象生成之前
init是用来完善对象的

type动态创建类

type: 创建类的类
第一个参数,继承自哪里,若继承obj则()
第二个参数,传入的方法或者属性。
User = type(“User”, (), {})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MetaClass(type):
def __new__(cls, *args, **kwargs):
return super().__new__(cls, *args, **kwargs)

from collections.abc import *

#什么是元类, 元类是创建类的类 对象<-class(对象)<-type
class User(metaclass=MetaClass):
def __init__(self, name):
self.name = name
def __str__(self):
return "user"

if __name__ == "__main__":
User = type("User", (BaseClass, ), {"name":"user", "say":say})

simple orm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# 需求
import numbers


class Field:
pass

class IntField(Field):
# 数据描述符
def __init__(self, db_column, min_value=None, max_value=None):
self._value = None
self.min_value = min_value
self.max_value = max_value
self.db_column = db_column
if min_value is not None:
if not isinstance(min_value, numbers.Integral):
raise ValueError("min_value must be int")
elif min_value < 0:
raise ValueError("min_value must be positive int")
if max_value is not None:
if not isinstance(max_value, numbers.Integral):
raise ValueError("max_value must be int")
elif max_value < 0:
raise ValueError("max_value must be positive int")
if min_value is not None and max_value is not None:
if min_value > max_value:
raise ValueError("min_value must be smaller than max_value")

def __get__(self, instance, owner):
return self._value

def __set__(self, instance, value):
if not isinstance(value, numbers.Integral):
raise ValueError("int value need")
if value < self.min_value or value > self.max_value:
raise ValueError("value must between min_value and max_value")
self._value = value


class CharField(Field):
def __init__(self, db_column, max_length=None):
self._value = None
self.db_column = db_column
if max_length is None:
raise ValueError("you must spcify max_lenth for charfiled")
self.max_length = max_length

def __get__(self, instance, owner):
return self._value

def __set__(self, instance, value):
if not isinstance(value, str):
raise ValueError("string value need")
if len(value) > self.max_length:
raise ValueError("value len excess len of max_length")
self._value = value


class ModelMetaClass(type):
def __new__(cls, name, bases, attrs, **kwargs):
if name == "BaseModel":
return super().__new__(cls, name, bases, attrs, **kwargs)
fields = {}
for key, value in attrs.items():
if isinstance(value, Field):
fields[key] = value
attrs_meta = attrs.get("Meta", None)
_meta = {}
db_table = name.lower()
if attrs_meta is not None:
table = getattr(attrs_meta, "db_table", None)
if table is not None:
db_table = table
_meta["db_table"] = db_table
attrs["_meta"] = _meta
attrs["fields"] = fields
del attrs["Meta"]
return super().__new__(cls, name, bases, attrs, **kwargs)


class BaseModel(metaclass=ModelMetaClass):
def __init__(self, *args, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
return super().__init__()

def save(self):
fields = []
values = []
for key, value in self.fields.items():
db_column = value.db_column
if db_column is None:
db_column = key.lower()
fields.append(db_column)
value = getattr(self, key)
values.append(str(value))

sql = "insert {db_table}({fields}) value({values})".format(db_table=self._meta["db_table"],
fields=",".join(fields), values=",".join(values))
pass

class User(BaseModel):
name = CharField(db_column="name", max_length=10)
age = IntField(db_column="age", min_value=1, max_value=100)

class Meta:
db_table = "user"


if __name__ == "__main__":
user = User(name="bobby", age=28)
# user.name = "bobby"
# user.age = 28
user.save()

python dict & set

dict 属于 mapping 类型

1
2
3
4
5
from collections.abc import Mapping, MutableMapping
#dict属于mapping类型

a = {}
print (isinstance(a, MutableMapping))

dict 抽象基类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
class MutableMapping(Mapping):

__slots__ = ()

"""A MutableMapping is a generic container for associating
key/value pairs.

This class provides concrete generic implementations of all
methods except for __getitem__, __setitem__, __delitem__,
__iter__, and __len__.

"""

@abstractmethod
def __setitem__(self, key, value):
raise KeyError

@abstractmethod
def __delitem__(self, key):
raise KeyError

__marker = object()

def pop(self, key, default=__marker):
'''D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
If key is not found, d is returned if given, otherwise KeyError is raised.
'''
try:
value = self[key]
except KeyError:
if default is self.__marker:
raise
return default
else:
del self[key]
return value

def popitem(self):
'''D.popitem() -> (k, v), remove and return some (key, value) pair
as a 2-tuple; but raise KeyError if D is empty.
'''
try:
key = next(iter(self))
except StopIteration:
raise KeyError from None
value = self[key]
del self[key]
return key, value

def clear(self):
'D.clear() -> None. Remove all items from D.'
try:
while True:
self.popitem()
except KeyError:
pass

def update(self, other=(), /, **kwds):
''' D.update([E, ]**F) -> None. Update D from mapping/iterable E and F.
If E present and has a .keys() method, does: for k in E: D[k] = E[k]
If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v
In either case, this is followed by: for k, v in F.items(): D[k] = v
'''
if isinstance(other, Mapping):
for key in other:
self[key] = other[key]
elif hasattr(other, "keys"):
for key in other.keys():
self[key] = other[key]
else:
for key, value in other:
self[key] = value
for key, value in kwds.items():
self[key] = value

def setdefault(self, key, default=None):
'D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D'
try:
return self[key]
except KeyError:
self[key] = default
return default

class Mapping(Collection):

__slots__ = ()

"""A Mapping is a generic container for associating key/value
pairs.

This class provides concrete generic implementations of all
methods except for __getitem__, __iter__, and __len__.

"""

@abstractmethod
def __getitem__(self, key):
raise KeyError

def get(self, key, default=None):
'D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None.'
try:
return self[key]
except KeyError:
return default

def __contains__(self, key):
try:
self[key]
except KeyError:
return False
else:
return True

def keys(self):
"D.keys() -> a set-like object providing a view on D's keys"
return KeysView(self)

def items(self):
"D.items() -> a set-like object providing a view on D's items"
return ItemsView(self)

def values(self):
"D.values() -> an object providing a view on D's values"
return ValuesView(self)

def __eq__(self, other):
if not isinstance(other, Mapping):
return NotImplemented
return dict(self.items()) == dict(other.items())

__reversed__ = None

dict 实现的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class dict(MutableMapping[_KT, _VT], Generic[_KT, _VT]):
@overload
def __init__(self: dict[_KT, _VT]) -> None: ...
@overload
def __init__(self: dict[str, _VT], **kwargs: _VT) -> None: ...
@overload
def __init__(self, map: SupportsKeysAndGetItem[_KT, _VT], **kwargs: _VT) -> None: ...
@overload
def __init__(self, iterable: Iterable[Tuple[_KT, _VT]], **kwargs: _VT) -> None: ...
def __new__(cls: Type[_T1], *args: Any, **kwargs: Any) -> _T1: ...
def clear(self) -> None: ...
def copy(self) -> dict[_KT, _VT]: ...
def popitem(self) -> Tuple[_KT, _VT]: ...
def setdefault(self, __key: _KT, __default: _VT = ...) -> _VT: ...
@overload
def update(self, __m: Mapping[_KT, _VT], **kwargs: _VT) -> None: ...
@overload
def update(self, __m: Iterable[Tuple[_KT, _VT]], **kwargs: _VT) -> None: ...
@overload
def update(self, **kwargs: _VT) -> None: ...
def keys(self) -> KeysView[_KT]: ...
def values(self) -> ValuesView[_VT]: ...
def items(self) -> ItemsView[_KT, _VT]: ...
@classmethod
@overload
def fromkeys(cls, __iterable: Iterable[_T], __value: None = ...) -> dict[_T, Any | None]: ...
@classmethod
@overload
def fromkeys(cls, __iterable: Iterable[_T], __value: _S) -> dict[_T, _S]: ...
def __len__(self) -> int: ...
def __getitem__(self, k: _KT) -> _VT: ...
def __setitem__(self, k: _KT, v: _VT) -> None: ...
def __delitem__(self, v: _KT) -> None: ...
def __iter__(self) -> Iterator[_KT]: ...
if sys.version_info >= (3, 8):
def __reversed__(self) -> Iterator[_KT]: ...
def __str__(self) -> str: ...
__hash__: None # type: ignore
if sys.version_info >= (3, 9):
def __class_getitem__(cls, item: Any) -> GenericAlias: ...
def __or__(self, __value: Mapping[_T1, _T2]) -> dict[_KT | _T1, _VT | _T2]: ...
def __ror__(self, __value: Mapping[_T1, _T2]) -> dict[_KT | _T1, _VT | _T2]: ...
def __ior__(self, __value: Mapping[_KT, _VT]) -> dict[_KT, _VT]: ... # type: ignore

不建议直接继承 dict,如果有必要继承 collections 里的 user dict

python序列类

序列

可以通过 collections.abc 观察序列相关的协议和其要实现的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
class Sequence(Reversible, Collection):

"""All the operations on a read-only sequence.

Concrete subclasses must override __new__ or __init__,
__getitem__, and __len__.
"""

__slots__ = ()

@abstractmethod
def __getitem__(self, index):
raise IndexError

def __iter__(self):
i = 0
try:
while True:
v = self[i]
yield v
i += 1
except IndexError:
return

def __contains__(self, value):
for v in self:
if v is value or v == value:
return True
return False

def __reversed__(self):
for i in reversed(range(len(self))):
yield self[i]

def index(self, value, start=0, stop=None):
'''S.index(value, [start, [stop]]) -> integer -- return first index of value.
Raises ValueError if the value is not present.

Supporting start and stop arguments is optional, but
recommended.
'''
if start is not None and start < 0:
start = max(len(self) + start, 0)
if stop is not None and stop < 0:
stop += len(self)

i = start
while stop is None or i < stop:
try:
v = self[i]
if v is value or v == value:
return i
except IndexError:
break
i += 1
raise ValueError

def count(self, value):
'S.count(value) -> integer -- return number of occurrences of value'
return sum(1 for v in self if v is value or v == value)

class Reversible(Iterable):

__slots__ = ()

@abstractmethod
def __reversed__(self):
while False:
yield None

@classmethod
def __subclasshook__(cls, C):
if cls is Reversible:
return _check_methods(C, "__reversed__", "__iter__")
return NotImplemented

class Collection(Sized, Iterable, Container):

__slots__ = ()

@classmethod
def __subclasshook__(cls, C):
if cls is Collection:
return _check_methods(C, "__len__", "__iter__", "__contains__")
return NotImplemented

class Sized(metaclass=ABCMeta):

__slots__ = ()

@abstractmethod
def __len__(self):
return 0

@classmethod
def __subclasshook__(cls, C):
if cls is Sized:
return _check_methods(C, "__len__")
return NotImplemented

class Iterable(metaclass=ABCMeta):

__slots__ = ()

@abstractmethod
def __iter__(self):
while False:
yield None

@classmethod
def __subclasshook__(cls, C):
if cls is Iterable:
return _check_methods(C, "__iter__")
return NotImplemented

class Container(metaclass=ABCMeta):

__slots__ = ()

@abstractmethod
def __contains__(self, x):
return False

@classmethod
def __subclasshook__(cls, C):
if cls is Container:
return _check_methods(C, "__contains__")
return NotImplemented

如果要实现一个不可变序列的切片,按照上面的协议实现对应魔法函数即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import numbers
class Group:
#支持切片操作
def __init__(self, group_name, company_name, staffs):
self.group_name = group_name
self.company_name = company_name
self.staffs = staffs

def __reversed__(self):
self.staffs.reverse()

def __getitem__(self, item):
cls = type(self)
if isinstance(item, slice):
return cls(group_name=self.group_name, company_name=self.company_name, staffs=self.staffs[item])
elif isinstance(item, numbers.Integral):
return cls(group_name=self.group_name, company_name=self.company_name, staffs=[self.staffs[item]])

def __len__(self):
return len(self.staffs)

def __iter__(self):
return iter(self.staffs)

def __contains__(self, item):
if item in self.staffs:
return True
else:
return False

staffs = ["bobby1", "imooc", "bobby2", "bobby3"]
group = Group(company_name="imooc", group_name="user", staffs=staffs)
reversed(group)
for user in group:
print(user)

python类型和对象

多态

python 的多态不同于 java,并不需要继承,只要实现同一个方法名就可以。语言层面上的多态,看起来像鸭子它就是鸭子,不需要特别申明变量类型,只需要给对象实现特定的接口或者魔法函数它就有特定的特性。

抽象基类(Abstracted Base Class)

相当于 java 里的 interface 接口,不能被实例化。

使用场景

  • 判定对象类型
1
2
from collections.abc import Sized
isinstance(x, Sized)
  • 强制子类必须实现某些方法

对象引用

python 的对象本质上是一个指针,可以理解为一个便利贴,可以贴在任何变量上。变量创建时先在堆里生成对象,然后变量指向对象。指针大小是固定的,变量没有类型这个概念。

del 并不是垃圾回收,python 中的垃圾回收是在计数器到 0 时回收(引用计数)

Xpath学习笔记

XPATH -> XML path language

Node

In XPath, there are seven kinds of nodes: element, attribute, text, namespace, processing-instruction, comment, and document nodes.

XML documents are treated as trees of nodes. The topmost element of the tree is called the root element.

Relationship of Nodes

  • Parent

Each element and attribute has one parent.

  • Children

Element nodes may have zero, one or more children.

  • Siblings

Nodes that have the same parent.

  • Ancestors

A node’s parent, parent’s parent, etc.

  • Descendants

A node’s children, children’s children, etc.

Selecting nodes

XPath uses path expressions to select nodes in an XML document. The node is selected by following a path or steps. The most useful path expressions are listed below:

1
2
3
4
5
6
7
Expression	        Description
nodename Selects all nodes with the name "nodename"
/ Selects from the root node
// Selects nodes in the document from the current node that match the selection no matter where they are
. Selects the current node
.. Selects the parent of the current node
@ Selects attributes

Examples

1
2
3
4
5
6
bookstore	        Selects all nodes with the name "bookstore"
/bookstore Selects the root element bookstore
bookstore/book Selects all book elements that are children of bookstore
//book Selects all book elements no matter where they are in the document
bookstore//book Selects all book elements that are descendant of the bookstore element, no matter where they are under the bookstore element
//@lang Selects all attributes that are named lang

Xpath Axes

https://www.w3schools.com/xml/xpath_syntax.asp

Contains, Siblings

https://www.guru99.com/using-contains-sbiling-ancestor-to-find-element-in-selenium.html

Simple online Xpath test

http://www.xpathtester.com/xpath/76bb0bca-1896-43b7-8312-54f924a98a89

Louvre

18 年 11 月 2 日第一次参观卢浮宫,带给自己的绝不仅仅是震撼,法国人民保护历史的观念可能早从拿破仑时代就有开始,短短几百年,卢浮宫已经收猎了来自法国本土,欧洲,埃及,希腊,近东等地的珍贵文物,这些文物没有因为战争而被毁坏,也没有因为中国文化大革命这般把自己的历史全盘抹去,讽刺的是中国三十年河东,三十年又河西,一会要全面反传统,一面又要文化传播弘扬主旋律,一味建什么孔子学院,试问一个民族如果连自己都不坚持或信任自己的体制或者文化,又谈怎么宣传,皇帝的新装在中国活生生上演。

这个时代活脱脱是一个闹剧,一切历史都可以被赢家粉饰。我在意识到卢浮宫到底是多么宏伟的一霎那泪水差点蹦出来,之前对法国人不甚了解,他们其实很会自嘲,也有很强的精神领域,浪漫或者艺术只是表面,他们有着对人类文明的捍卫观念,从某个角度上来说,他们的年轻人相比其他国家的年轻人更可能发现人为什么活着。毕竟在欧洲有太多机会接触到不同的文化和事物,不容易对世界有那种非黑即白的认知,世界太大,荣华富贵都是过往云烟,一味的物质其实真的只是浅薄和无知,当我看到卢浮宫有着世界文明那般程度的浓缩之后,一霎那我用一天的时间走完了世界几千年。也罢,对酒当歌,人生几何,作为一个人更需要明白你自己为什么活着,能不能做出一些贡献,有自己的精神场,才是我现期的要务了罢。

断舍离

生活在一个物质亢余的年代是辛苦的,所有人都拼命得到物质,人与人评价的标准无非是金钱,女人,地位,这些标准面目可憎,令人厌恶,每次我回到家姥姥都滔滔不绝地说我太瘦要多吃饭,穿的太少赶紧买衣服,我知道她本质上是为我好;可这些东西与她有什么关系?吃多少穿什么是我自己的事情,与她有什么关系?难道我不知道自己喜欢什么生活方式?中国人的家庭最令我讨厌的是总有人喜欢对他人生活指手画脚,这些点让我拼命的想逃离家庭的束缚。

现代社会难能可贵的是断舍离,抛弃一切不是必须的东西,享受极简主义的生活方式,落实到家庭生活中 80%都是些鸡毛蒜皮的琐事,而我们却被这些琐事占据了大量的精力心神,要享受纯粹的生活必须放弃亢余的物质,扔掉 80%不常用的衣服,物件。

终身学习读书笔记

教育模式

教育制度分为三种,第一种所谓应试教育,运用考试的力量迫使学生学习,这种方式学生的参与性较低,但却是最公平的方式,无论是寒门还是贵子都要参与统一的考试,这种方式着重把学生培育成一件工具,或是一台机器,即培养所谓对社会有用的人,或有用的工具。

第二种为素质教育,这其实是着重培养中产阶级的教育方式,除学习之外会要求学生有很多的兴趣爱好,美国主要倡导的即这种方式,常青藤常常要求学生积极参与社会活动,体育运动,这些贵族运动其实无形之中把贫穷的学习拒之门外;比较与中国阶级流动的减缓停滞,美国形成了阶级的固化,如果从小没有相应的环境培养各类兴趣爱好很好进入好的大学。

素质教育的核心是成为一个更好的人,本质上成为更好的自己是为了被利用,而第三种,也是最高层的教育模式却对此不屑一顾,他们只需要利用管理他人,相比前两种模式第三种教育只注重培养学生做决策的能力,他们培养出的学生可以不会做 ppt 也可以不会 excel,他们只需要在正确的时机做出正确的决定。在这种教育模式下,教授会对学生比较宽松,他们会要求学生无论是好是坏,为自己所做出的决定负责。

知行合一

在互联网时代下,地位背景金钱等等都变得不再重要,唯一重要的东西是认知,有钱并不能保证你赚更多的钱,有高人一等的认知会在这个世纪胜出,一个行业的更迭可能只有十几年,旧的知识体系在新的时代下经受不住挑战,要胜出就要不断改进自己的认知结构,终身学习,先提升认识确定超人一等的目标,再用行动和执行力妥善跟上自己思想的步伐,言必至,行必果。

认知升级的途径:

1) 承认自己的无知,承认自己不知道 (万物皆有裂痕,那是光照进来的地方)
2) 采取行动,把知道变成行动
3) 永远和比你知道的多的人在一起

一只眼死两只眼活

培养两种以上的禀赋,在一个领域做到前 1%是非常困难的,可是在两个不同的领域做到前 10%就不是那么难,考虑到知识的更新换代,有两种不同的禀赋相长相承有更有助于职业的发展和个人的进步。

在查理芒格的穷查理宝典中更强调了这一点,查理甚至建议一百种左右的跨学科学习。

  • Copyrights © 2019-2022 Chenhao Li

请我喝杯咖啡吧~

支付宝
微信