Python两个案例练习

项目介绍

Python官方LOGO

项目是一个python入门的案例代码,可以帮助你通过实践来更好的学习python

项目地址:https://gitlab.com/lxqxsyu/PythonDemos

Tkinter是什么

Tkinter是python自带的一个图形开发介面库
python提供了多个图形开发界面的库,几个常用Python GUI库如下:

  • Tkinter: Tkinter模块(“Tk 接口”)是Python的标准Tk GUI工具包的接口.Tk和Tkinter可以在大多数的Unix平台下使用,同样可以应用在Windows和Macintosh系统里.,Tk8.0的后续版本可以实现本地窗口风格,并良好地运行在绝大多数平台中。

  • wxPython:wxPython 是一款开源软件,是 Python 语言的一套优秀的 GUI 图形库,允许 Python 程序员很方便的创建完整的、功能键全的 GUI 用户界面。

  • Jython:Jython程序可以和Java无缝集成。除了一些标准模块,Jython使用Java的模块。Jython几乎拥有标准的Python中不依赖于C语言的全部模块。比如,Jython的用户界面将使用Swing,AWT或者SWT。Jython可以被动态或静态地编译成Java字节码。

最简单 的Tkinter实例

tkinter显示一个窗体

我们从导入Tkinter模块开始。它包含了与Tk工具包一起工作所需的所有类,函数和其他东西。在大多数情况下,你可以简单地将所有东西从Tkinter导入你的模块名字空间:

1
from Tkinter import *

要初始化Tkinter,我们必须创建一个Tk根部件。这是一个普通的窗口,窗口管理器提供标题栏和其他装饰。您应该只为每个程序创建一个根窗口小部件,并且必须在任何其他窗口部件之前创建它。

1
root = Tk()

程序将保持在事件循环中,直到关闭窗口。事件循环不仅处理来自用户的事件(如鼠标点击和按键)还处理窗口系统(如重绘事件和窗口配置消息)事件。

1
root.mainloop()

相关文档地址:http://effbot.org/tkinterbook/tkinter-whats-tkinter.htm

python的模块

模块可以让你有逻辑的组织代码,可以把相关的代码分配到一个模块里。简单的说模块就是一个python文件,模块可以定义函数,类和变量.

import 语句

想使用Python源文件,只需在另一个源文件里执行import语句,语法如下:

1
import module1[, module2[,... moduleN]

当解释器遇到import语句,如果模块在当前的搜索路径就会被导入。
搜索路径是一个解释器会先进行搜索的所有目录的列表。如想要导入模块hello.py,需要把命令放在脚本的顶端:

1
2
3
4
5
6
7
8
#coding=utf-8
#!/usr/bin/python

# 导入模块
import support

# 现在可以调用模块里包含的函数了
support.print_func("Zara")

From…import 语句

Python的from语句让你从模块中导入一个指定的部分到当前命名空间中。语法如下:

1
from modname import name1[, name2[, ... nameN]]

例如,要导入模块fib的fibonacci函数,使用如下语句:

1
from fib import fibonacci

From…import* 语句

把一个模块的所有内容全都导入到当前的命名空间也是可行的,只需使用如下声明:

1
from modname import *

这提供了一个简单的方法来导入一个模块中的所有项目。然而这种声明不该被过多地使用。

定位模块

当你导入一个模块,Python解析器对模块位置的搜索顺序是:

  1. 当前目录
  2. 如果不在当前目录,Python则搜索在shell变量PYTHONPATH下的每个目录。
  3. 如果都找不到,Python会察看默认路径。UNIX下,默认路径一般为/usr/local/lib/python/

模块搜索路径存储在system模块的sys.path变量中。变量里包含当前目录,PYTHONPATH和由安装过程决定的默认目录。

python内置了很多常用的模块

dir()函数

dir()函数一个排好序的字符串列表,内容是一个模块里定义过的名字。

返回的列表容纳了在一个模块里定义的所有模块,变量和函数。如下一个简单的实例:

1
2
3
4
5
6
7
8
9
#coding=utf-8
#!/usr/bin/python

# 导入内置math模块
import math

content = dir(math)

print(content)

dir()函数执行结果

python面向对象

创建类

1
2
3
4
5
6
7
8
9
10
class Person:

#特殊方法,构造函数,创建这个类的时候会首先调用
def __init__(self, name, age):
self.name = name
self.age = age

#定义一个方法,显示人的信息
def displyPersonInfor(self):
print("name:", self.name, ", age:", self.age)

创建对象(对象就是类的具体实例)

1
person = Person("xiaoming", 22)

访问属性及方法

1
2
3
4
person.displyPersonInfor()  #访问类的成员方法

print(person.name) #访问类的属性
print(person.age)

python对象销毁(垃圾回收)

析构函数 __del____del__在对象销毁的时候被调用,当对象不再被使用时,__del__方法运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Person:

#特殊方法,构造函数,创建这个类的时候会首先调用
def __init__(self, name, age):
self.name = name
self.age = age

#定义一个方法,显示人的信息
def displyPersonInfor(self):
print("name:", self.name, ", age:", self.age)

#析构函数,对象销毁的时候调用
def __del__(self):
print(self.__class__.__name__ + "销毁")


xiaoming = Person("xiaoming", 22)
xiaoming.displyPersonInfor() #访问类的成员方法

xiaoqiang = Person("xiaoqiang", 25)
xiaoqiang.displyPersonInfor()

del xiaoming
del xiaoqiang

类的继承

面向对象的编程带来的主要好处之一是代码的重用,实现这种重用的方法之一是通过继承机制。继承完全可以理解成类之间的类型和子类型关系。

继承语法 class 派生类名(基类名):基类名写作括号里,基本类是在类定义的时候,在元组之中指明的

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
class Person:

#特殊方法,构造函数,创建这个类的时候会首先调用
def __init__(self, name, age):
self.name = name
self.age = age

#定义一个方法,显示人的信息
def displyPersonInfor(self):
print("name:", self.name, ", age:", self.age)

#析构函数,对象销毁的时候调用
def __del__(self):
print(self.__class__.__name__ + "销毁")


class Student(Person):

def __init__(self, name, age, grade):
Person.__init__(self, name, age)
self.grade = grade

def displyPersonInfor(self):
print("i am a student, my name is " + self.name)


def __del__(self):
print("student析构函数调用")


xiaoming = Person("xiaoming", 22)
xiaoming.displyPersonInfor() #访问类的成员方法

xiaoqiang = Student("xiaoqiang", 25, 3)
xiaoqiang.displyPersonInfor()

del xiaoming
del xiaoqiang

Tkinter面向对象写法

创建一个Frame窗体,然后给里面添加了一个关闭按钮

Tkinter面向对象写法

相关代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/python3.6.3
#-*-coding:utf-8-*-
import tkinter

class App:
def __init__(self, master):
frame = tkinter.Frame(master)
frame.pack()

self.button = tkinter.Button(
frame, text="关闭", fg="red", command=frame.quit)
self.button.pack()

root = tkinter.Tk(None, None)
app = App(root)
root.mainloop()

细心的朋友会发现上面每次创建一个窗体对象就会调用一次pack()方法,这个pack()方法是用来确定窗体大小然后重绘窗体的。

案例一:计算器界面

我们先写出计算器界面的最上面一行和输入框

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
#!/usr/bin/python3
#-*-coding:utf-8-*-
import tkinter
import tkinter.font

class Calculate():
def __init__(self, master):
master.title("Calculate")
master.resizable(0, 0)

self.showfont = tkinter.font.Font(master, size=26)
self.sysfont = tkinter.font.Font(master, size=16)

self.entry = tkinter.Entry(master, width=20, font=self.showfont, background="#ffffff")
self.entry.grid(row=0, column=0, columnspan=4, pady=10)

self.btn1 = tkinter.Button(master, text="1", font=self.sysfont)
self.btn1.grid(row=1, column=0, sticky = tkinter.N + tkinter.S + tkinter.W+tkinter.E)

self.btn2 = tkinter.Button(master, text="2", font=self.sysfont)
self.btn2.grid(row=1, column=1, sticky = tkinter.N + tkinter.S + tkinter.W+tkinter.E)

self.btn3 = tkinter.Button(master, text="3", font = self.sysfont)
self.btn3.grid(row=1, column=2, sticky = tkinter.N + tkinter.S + tkinter.W + tkinter.E)

self.btn_divide = tkinter.Button(master, text = "÷", font=self.sysfont)
self.btn_divide.grid(row=1, column = 3, sticky = tkinter.N + tkinter.S + tkinter.W + tkinter.E)

root = tkinter.Tk(None, None)
cal=Calculate(root)
root.mainloop()

tkinter实现计算器按钮过程示例

修改框体的名字,也可在创建时使用className参数来命名;

root.title(‘标题名’)

框体大小可调性,分别表示x,y方向的可变性;

root.resizable(0,0)

tkinter有那些UI控件呢?

Tkinter supports 15 core widgets

Button、Cavans、Checkbutton、Entry、Frame、Label、Listbox、Menu、Menubutton、Message、Radiobutton、Scale、Scrollbar、Text、Toplevel

计算器全部代码

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
#!/usr/bin/python3
#-*-coding:utf-8-*-
import tkinter
import tkinter.font


class Calculate():
def __init__(self, master):
master.title("Calculate")
master.resizable(0, 0)
self.showfont = tkinter.font.Font(master, size=26)
self.sysfont = tkinter.font.Font(master, size=16)
self.entry = tkinter.Entry(
master, width=20, font=self.showfont, background="#ffffff")
self.entry.grid(row=0, column=0, columnspan=4, pady=10)
self.btn1 = tkinter.Button(master, text="1", font=self.sysfont)
self.btn1.grid(row=1, column=0, sticky=tkinter.N +
tkinter.S + tkinter.W + tkinter.E)
self.btn2 = tkinter.Button(master, text="2", font=self.sysfont)
self.btn2.grid(row=1, column=1, sticky=tkinter.N +
tkinter.S + tkinter.W + tkinter.E)
self.btn3 = tkinter.Button(master, text="3", font=self.sysfont)
self.btn3.grid(row=1, column=2, sticky=tkinter.N +
tkinter.S + tkinter.W + tkinter.E)
self.btn_divide = tkinter.Button(master, text="÷", font=self.sysfont)
self.btn_divide.grid(
row=1, column=3, sticky=tkinter.N + tkinter.S + tkinter.W + tkinter.E)
self.btn4 = tkinter.Button(master, text="4", font=self.sysfont)
self.btn4.grid(row=2, column=0, sticky=tkinter.N +
tkinter.S + tkinter.W + tkinter.E)
self.btn5 = tkinter.Button(master, text="5", font=self.sysfont)
self.btn5.grid(row=2, column=1, sticky=tkinter.N +
tkinter.S + tkinter.W + tkinter.E)
self.btn6 = tkinter.Button(master, text="6", font=self.sysfont)
self.btn6.grid(row=2, column=2, sticky=tkinter.N +
tkinter.S + tkinter.W + tkinter.E)
self.btn_mult = tkinter.Button(master, text="×", font=self.sysfont)
self.btn_mult.grid(row=2, column=3, sticky=tkinter.N +
tkinter.S + tkinter.W + tkinter.E)
self.btn7 = tkinter.Button(master, text="7", font=self.sysfont)
self.btn7.grid(row=3, column=0, sticky=tkinter.N +
tkinter.S + tkinter.W + tkinter.E)
self.btn8 = tkinter.Button(master, text="8", font=self.sysfont)
self.btn8.grid(row=3, column=1, sticky=tkinter.N +
tkinter.S + tkinter.W + tkinter.E)
self.btn9 = tkinter.Button(master, text="9", font=self.sysfont)
self.btn9.grid(row=3, column=2, sticky=tkinter.N +
tkinter.S + tkinter.W + tkinter.E)
self.btn_minus = tkinter.Button(master, text="-", font=self.sysfont)
self.btn_minus.grid(row=3, column=3, sticky=tkinter.N +
tkinter.S + tkinter.W + tkinter.E)
self.btn0 = tkinter.Button(master, text="0", font=self.sysfont)
self.btn0.grid(row=4, column=0, sticky=tkinter.N +
tkinter.S + tkinter.W + tkinter.E)
self.btn_point = tkinter.Button(master, text=".", font=self.sysfont)
self.btn_point.grid(row=4, column=1, sticky=tkinter.N +
tkinter.S + tkinter.W + tkinter.E)
self.btn_es = tkinter.Button(master, text="=", font=self.sysfont)
self.btn_es.grid(row=4, column=2, sticky=tkinter.N +
tkinter.S + tkinter.W + tkinter.E)
self.btn_add = tkinter.Button(master, text="+", font=self.sysfont)
self.btn_add.grid(row=4, column=3, sticky=tkinter.N +
tkinter.S + tkinter.W + tkinter.E)


root = tkinter.Tk(None, None)
cal = Calculate(root)
root.mainloop()

tkinter实现的计算器界面

Python中的包

包是一个分层次的文件目录结构,它定义了一个由模块及子包,和子包下的子包等组成的Python的应用环境。
考虑一个在Phone目录下的pots.py文件。这个文件有如下源代码:

1
2
3
4
5
#coding=utf-8
#!/usr/bin/python

def Pots():
print "I'm Pots Phone"

同样地,我们有另外两个保存了不同函数的文件:

  • Phone/Isdn.py 含有函数Isdn()
  • Phone/G3.py 含有函数G3()

现在,在Phone目录下创建file __init__.py

  • Phone/__init__.py

当你导入Phone时,为了能够使用所有函数,你需要在__init__.py里使用显式的导入语句,如下:

1
2
3
from Pots import Pots
from Isdn import Isdn
from G3 import G3

当你把这些代码添加到__init__.py之后,导入Phone包的时候这些类就全都是可用的了。

packagetest.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#coding=utf-8
#!/usr/bin/python

# 导入内置math模块
#import math

#导入我们创建的Phone包
import Phone

#content = dir(math)
#print(content)

Phone.Pots()
Phone.Isdn()
Phone.G3()

python工程目录结构

案例二(android多渠道打包)

1
2
3
4
5
6
7
8
#!/usr/bin/python
# coding=utf-8

# 空文件 便于写入此空文件到apk包中作为channel文件
src_empty_file = 'info/czt.txt'
# 创建一个空文件(不存在则创建)
f = open(src_empty_file, 'w')
f.close()

open函数

python中必须先用Python内置的open()函数打开一个文件,创建一个file对象,相关的辅助方法才可以调用它进行读写。

1
file object = open(file_name [, access_mode][, buffering])

各个参数的细节如下:

  • file_name:file_name变量是一个包含了你要访问的文件名称的字符串值。
  • access_mode:access_mode决定了打开文件的模式:只读,写入,追加等。所有可取值见如下的完全列表。这个参数是非强制的,默认文件访问模式为只读(r)。
  • buffering:如果buffering的值被设为0,就不会有寄存。如果buffering的值取1,访问文件时会寄存行。如果将buffering的值设为大于1的整数,表明了这就是的寄存区的缓冲大小。如果取负值,寄存区的缓冲大小则为系统默认。

不同模式打开文件的完全列表:

不同模式打开文件的完全列表

File对象的属性

一个文件被打开后,你有一个file对象,你可以得到有关该文件的各种信息。
以下是和file对象相关的所有属性的列表:

File对象的属性

1
2
3
4
5
6
7
8
9
#!/usr/bin/python
# -*- coding: UTF-8 -*-

# 打开一个文件
fo = open("foo.txt", "wb")
print "文件名: ", fo.name
print "是否已关闭 : ", fo.closed
print "访问模式 : ", fo.mode
print "末尾是否强制加空格 : ", fo.softspace

close()方法

File对象的close()方法刷新缓冲区里任何还没写入的信息,并关闭该文件,这之后便不能再进行写入。
当一个文件对象的引用被重新指定给另一个文件时,Python会关闭之前的文件。用close()方法关闭文件是一个很好的习惯。

1
fileObject.close();

更多关于读写的方法请参考:https://www.w3cschool.cn/python/python-files-io.html

筛选apk文件

1
2
3
4
5
6
7
8
9
10
11
12
13
# 获取当前目录中所有的apk源包
src_apks = []
# python3 : os.listdir()即可,这里使用兼容Python2的os.listdir('.')
apk_path = 'E:/AndroidStudio/work/xxx/app/build/outputs/apk/'
for file in os.listdir(apk_path):
fulldirfile = os.path.join(apk_path, file)
if os.path.isfile(fulldirfile):
extension = os.path.splitext(file)[1][1:]
if extension in 'apk':
if file.find('unaligned') == -1:
src_apks.append(fulldirfile)

print(src_apks)

Python中有很多标准库,我们来看看这个操作系统接口库os

注意:确保使用import os而不是from os import *。这样可以防止函数os.open()覆盖内建函数open(),两者之间的操作是很不同的。

我们可以通过dir()和help()函数来帮助我们了解这些库是怎么使用的

1
2
3
4
5
6
7
import os

dir = dir(os)

print(dir)

help(os)

dir()和help()函数的输出结果

os.listdir(path=’.’)

返回一个list,包含给定path 目录下所有条目的名字。该list是任意顺序,不包括特殊条目’.’以及’..’,即使它们存在于目录中。

path可以是str类型或bytes类型。如果path的类型为bytes,则返回的文件名也将为bytes类型;否则,它们的类型为str。

os.path.join(path, *paths)
将一个或多个路径正确地连接起来。返回值是路径和*路径的任何成员与每个非空的后面紧跟一个目录分隔符(os.sep)的连接部分

os.path.isfile(path)
如果路径是现有的常规文件,则返回True。

os.path.splitext(path)
将从文件路径的后缀名分割字符串

关于更多module-os的函数请参考:http://python.usyiyi.cn/translate/python_352/library/os.html#module-os

os.path相关文档:http://python.usyiyi.cn/translate/python_352/library/os.path.html#module-os.path

完整代码

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
#!/usr/bin/python
# coding=utf-8

import os
import zipfile
import shutil

# 空文件 便于写入此空文件到apk包中作为channel文件
src_empty_file = 'info/czt.txt'
# 创建一个空文件(不存在则创建)
f = open(src_empty_file, 'w')
f.close()

# 获取当前目录中所有的apk源包
src_apks = []
# python3 : os.listdir()即可,这里使用兼容Python2的os.listdir('.')
apk_path = 'E:/AndroidStudio/work/xxx/app/build/outputs/apk/'
for file in os.listdir(apk_path):
fulldirfile = os.path.join(apk_path, file)
if os.path.isfile(fulldirfile):
extension = os.path.splitext(file)[1][1:]
if extension in 'apk':
if file.find('unaligned') == -1:
src_apks.append(fulldirfile)

print(src_apks)


# 获取渠道列表
channel_file = 'info/channel.txt'
f = open(channel_file)
lines = f.readlines()
f.close()

for src_apk in src_apks:
# file name (with extension)
src_apk_file_name = os.path.basename(src_apk)
# 分割文件名与后缀
temp_list = os.path.splitext(src_apk_file_name)
# name without extension
src_apk_name = temp_list[0]
# 后缀名,包含. 例如: ".apk "
src_apk_extension = temp_list[1]

# 创建生成目录,与文件名相关
package_name = src_apk_name[0:src_apk_name.index('-')]
output_dir = 'G:/XXX打包/' + package_name +'/output_' + src_apk_name + '/'
# 目录不存在则创建
if not os.path.exists(output_dir):
os.mkdir(output_dir)

# 遍历渠道号并创建对应渠道号的apk文件
for line in lines:
# 获取当前渠道号,因为从渠道文件中获得带有\n,所有strip一下
target_channel = line.strip()
# 拼接对应渠道号的apk
target_apk = output_dir + src_apk_name + "-" + target_channel + src_apk_extension
# 拷贝建立新apk
shutil.copy(src_apk, target_apk)
# zip获取新建立的apk文件
zipped = zipfile.ZipFile(target_apk, 'a', zipfile.ZIP_DEFLATED)
# 初始化渠道信息
empty_channel_file = "META-INF/cztchannel_{channel}".format(channel = target_channel)
# 写入渠道信息
zipped.write(src_empty_file, empty_channel_file)
# 关闭zip流
zipped.close()

打包思路参考请参考:https://segmentfault.com/a/1190000003763833

评论

Ajax Android AndroidStudio Animation Anroid Studio AppBarLayout Babel Banner Buffer Bulma ByteBuffer C++ C11 C89 C99 CDN CMYK COM1 COM2 CSS Camera Raw, 直方图 Chrome Class ContentProvider CoordinatorLayout C语言 DML DOM Dagger Dagger2 Darktable Demo Document DownloadManage ES2015 ESLint Element Error Exception Extensions File FileProvider Flow Fresco GCC Git GitHub GitLab Gradle Groovy HTML5 Handler HandlerThread Hexo Hybrid I/O IDEA IO ImageMagick IntelliJ Intellij Interpolator JCenter JNI JS Java JavaScript JsBridge Kotlin Lab Lambda Lifecycle Lint Linux Looper MQTT MVC MVP Maven MessageQueue Modbus Momentum MySQL NDK NIO NexT Next Nodejs ObjectAnimator Oracle VM Permission PhotoShop Physics Python RGB RS-232 RTU Remote-SSH Retrofit Runnable RxAndroid RxJava SE0 SSH Spring SpringBoot Statubar Style Task Theme Thread Tkinter UI UIKit UML VM virtualBox VS Code VUE ValueAnimator ViewPropertyAnimator Vue Vue.js Web Web前端 Workbench api apk bookmark by关键字 cli compileOnly computed css c语言 databases demo hexo hotfix html iOS icarus implementation init jQuery javascript launchModel logo merge methods mvp offset photos pug query rxjava2 scss servlet shell svg tkinter tomcat transition unicode utf-8 vector virtual box vscode watch webpack 七牛 下载 中介者模式 串口 临潼石榴 主题 书签 事件 享元模式 仓库 代理模式 位运算 依赖注入 修改,tables 光和色 内存 内核 内部分享 函数 函数式编程 分支 分析 创建 删除 动画 单例模式 压缩图片 发布 可空性 合并 同向性 后期 启动模式 命令 命令模式 响应式 响应式编程 图层 图床 图片压缩 图片处理 图片轮播 地球 域名 基础 增加 备忘录模式 外观模式 多线程 大爆炸 天气APP 太白山 头文件 奇点 字符串 字符集 存储引擎 宇宙 宏定义 实践 属性 属性动画 岐山擀面皮 岐山肉臊子 岐山香醋 工具 工厂模式 年终总结 开发技巧 异常 弱引用 恒星 打包 技巧 指令 指针 插件 插值 摄影 操作系统 攻略 故事 数据库 数据类型 数组 文件 新功能 旅行 旋转木马 时序图 时空 时间简史 曲线 杂谈 权限 枚举 架构 查询 标准库 标签选择器 样式 核心 框架 案例 桥接模式 检测工具 模块化 模板 模板引擎 模板方法模式 油泼辣子 泛型 洛川苹果 浅色状态栏 渲染 源码 源码分析 瀑布流 热修复 版本 版本控制 状态栏 状态模式 生活 留言板 相册 相对论 眉县猕猴桃 知识点 码云 磁盘 科学 笔记 策略模式 类图 系统,发行版, GNU 索引 组件 组合模式 绑定 结构 结构体 编码 网易云信 网格布局 网站广播 网站通知 网络 美化 联合 脚手架 膨胀的宇宙 自定义 自定义View 自定义插件 蒙版 虚拟 虚拟机 补码 补齐 表单 表达式 装饰模式 西安 观察者模式 规范 视图 视频 解耦器模式 设计 设计原则 设计模式 访问者模式 语法 责任链模式 贪吃蛇 转换 软件工程 软引用 运算符 迭代子模式 适配器模式 选择器 通信 通道 配置 链表 锐化 错误 键盘 闭包 降噪 陕西地方特产 面向对象 项目优化 项目构建 黑洞
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×