最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

一文帶你深度解密Python的字節(jié)碼

 更新時間:2022年12月14日 11:06:18   作者:古明地覺  
當我們想要執(zhí)行一個?py?文件的時候,只需要在命令行中輸入?python?xxx.py?即可,但你有沒有想過這背后的流程是怎樣的呢?本文主要賀和大家來聊聊Python中的字節(jié)碼,感興趣的可以了解一下

楔子

當我們想要執(zhí)行一個 py 文件的時候,只需要在命令行中輸入 python xxx.py 即可,但你有沒有想過這背后的流程是怎樣的呢?

首先 py 文件不是一上來就直接執(zhí)行的,而是會先有一個編譯的過程,整個步驟如下:

這里我們看到了 Python 編譯器、Python 虛擬機,而且我們平常還會說 Python 解釋器,那么三者之間有什么區(qū)別呢?

Python 編譯器負責將 Python 源代碼編譯成 PyCodeObject 對象,然后交給 Python 虛擬機來執(zhí)行。

那么 Python 編譯器和 Python 虛擬機都在什么地方呢?如果打開 Python 的安裝目錄,會發(fā)現(xiàn)有一個 python.exe,點擊的時候會通過它來啟動一個終端。

但問題是這個文件大小還不到 100K,不可能容納一個編譯器加一個虛擬機,所以下面還有一個 python38.dll。沒錯,編譯器、虛擬機都藏身于 python38.dll 當中。

因此 Python 雖然是解釋型語言,但也有編譯的過程。源代碼會被編譯器編譯成 PyCodeObject 對象,然后再交給虛擬機來執(zhí)行。而之所以要存在編譯,是為了讓虛擬機能更快速地執(zhí)行,比如在編譯階段常量都會提前分配好,而且還可以盡早檢測出語法上的錯誤。

pyc 文件是什么

在 Python 開發(fā)時,我們肯定都見過這個 pyc 文件,它一般位于 __pycache__ 目錄中,那么 pyc 文件和 PyCodeObject 之間有什么關(guān)系呢?

首先我們都知道字節(jié)碼,虛擬機的執(zhí)行實際上就是對字節(jié)碼不斷解析的一個過程。然而除了字節(jié)碼之外,還應該包含一些其它的信息,這些信息也是 Python 運行的時候所必需的,比如常量、變量名等等。

我們常聽到 py 文件被編譯成字節(jié)碼,這句話其實不太嚴謹,因為字節(jié)碼只是一個 PyBytesObject 對象、或者說一段字節(jié)序列。但很明顯,光有字節(jié)碼是不夠的,還有很多的靜態(tài)信息也需要被收集起來,它們整體被稱為 PyCodeObject。

而 PyCodeObject 對象中有一個成員 co_code,它是一個指針,指向了這段字節(jié)序列。但是這個對象除了有 co_code 指向的字節(jié)碼之外,還有很多其它成員,負責保存代碼涉及到的常量、變量(名字、符號)等等。

但是問題來了,難道每一次執(zhí)行都要將源文件編譯一遍嗎?如果沒有對源文件進行修改的話,那么完全可以使用上一次的編譯結(jié)果。相信此時你能猜到 pyc 文件是干什么的了,它就是負責保存編譯之后的 PyCodeObject 對象。

所以我們知道了,pyc 文件里面的內(nèi)容是 PyCodeObject 對象。對于 Python 編譯器來說,PyCodeObject 對象是對源代碼編譯之后的結(jié)果,而 pyc 文件則是這個對象在硬盤上的表現(xiàn)形式。

當下一次運行的時候,Python 會根據(jù) pyc 文件中記錄的編譯結(jié)果直接建立內(nèi)存中的 PyCodeObject 對象,而不需要再重新編譯了,當然前提是沒有對源文件進行修改。

PyCodeObject 底層結(jié)構(gòu)

我們來看一下這個結(jié)構(gòu)體長什么樣子,它的定義位于 Include/code.h 中。

typedef?struct?{
????//頭部信息,我們看到真的一切皆對象,字節(jié)碼也是個對象
????PyObject_HEAD????
????//可以通過位置參數(shù)傳遞的參數(shù)個數(shù)
????int?co_argcount;????????????
????//只能通過位置參數(shù)傳遞的參數(shù)個數(shù),Python3.8新增
????int?co_posonlyargcount;?????
????//只能通過關(guān)鍵字參數(shù)傳遞的參數(shù)個數(shù)
????int?co_kwonlyargcount;?????
????//代碼塊中局部變量的個數(shù),也包括參數(shù)
????int?co_nlocals;?????????????
????//執(zhí)行該段代碼塊需要的棧空間
????int?co_stacksize;??????
????//參數(shù)類型標識????
????int?co_flags;???????????
????//代碼塊在文件中的行號??
????int?co_firstlineno;?????????
????//指令集,也就是字節(jié)碼,它是一個bytes對象?
????PyObject?*co_code;?????????
????//常量池,一個元組,保存代碼塊中的所有常量?
????PyObject?*co_consts;???????
????//一個元組,保存代碼塊中引用的其它作用域的變量
????PyObject?*co_names;??????
????//一個元組,保存當前作用域中的變量???
????PyObject?*co_varnames;????
????//內(nèi)層函數(shù)引用的外層函數(shù)的作用域中的變量
????PyObject?*co_freevars;??????
????//外層函數(shù)的作用域中被內(nèi)層函數(shù)引用的變量
????//本質(zhì)上和co_freevars是一樣的
????PyObject?*co_cellvars;??????
????//無需關(guān)注
????Py_ssize_t?*co_cell2arg;????
????//代碼塊所在的文件名
????PyObject?*co_filename;????
????//代碼塊的名字,通常是函數(shù)名、類名,或者文件名
????PyObject?*co_name;?????????
????//字節(jié)碼指令與python源代碼的行號之間的對應關(guān)系
????//以PyByteObject的形式存在?
????PyObject?*co_lnotab;????????
}?PyCodeObject;

這里面的每一個成員,我們一會兒都會逐一演示進行說明??傊?Python 編譯器在對源代碼進行編譯的時候,對于代碼中的每一個 block,都會創(chuàng)建一個 PyCodeObject 與之對應。

但多少代碼才算得上是一個 block 呢?事實上,Python 有一個簡單而清晰的規(guī)則:當進入一個新的名字空間,或者說作用域時,就算是進入了一個新的 block 了。舉個例子:

class?A:
????a?=?123

def?foo():
????a?=?[]

我們仔細觀察一下上面這個文件,它在編譯完之后會有三個 PyCodeObject 對象,一個是對應整個 py 文件(模塊)的,一個是對應 class A 的,一個是對應 def foo 的。因為這是三個不同的作用域,所以會有三個 PyCodeObject 對象。

所以一個 code block 對應一個作用域、同時也對應一個 PyCodeObject 對象。Python 的類、函數(shù)、模塊都有自己獨立的作用域,因此在編譯時也都會有一個 PyCodeObject 對象與之對應。

PyCodeObject 代碼演示

PyCodeObject 我們知道它是干什么的了,那如何才能拿到這個對象呢?首先該對象在 Python 里面的類型是 <class 'code'>,但是底層沒有將這個類暴露給我們,因此 code 這個名字在 Python 里面只是一個沒有定義的變量罷了。

但是我們可以通過其它的方式進行獲取,比如函數(shù)。

def?func():
????pass

print(func.__code__)??#?<code?object?......
print(type(func.__code__))??#?<class?'code'>

我們可以通過函數(shù)的 __code__ 屬性拿到底層對應的PyCodeObject對象,當然也可以獲取里面的成員,我們來演示一下。

co_argcount:可以通過位置參數(shù)傳遞的參數(shù)個數(shù)

def?foo(a,?b,?c=3):
????pass
print(foo.__code__.co_argcount)??#?3

def?bar(a,?b,?*args):
????pass
print(bar.__code__.co_argcount)??#?2

def?func(a,?b,?*args,?c):
????pass
print(func.__code__.co_argcount)??#?2

foo 中的參數(shù) a、b、c 都可以通過位置參數(shù)傳遞,所以結(jié)果是 3;對于 bar,則是兩個,這里不包括 *args;而函數(shù) func,顯然也是兩個,因為參數(shù) c 只能通過關(guān)鍵字參數(shù)傳遞。

co_posonlyargcount:只能通過位置參數(shù)傳遞的參數(shù)個數(shù),Python3.8 新增

def?foo(a,?b,?c):
????pass

print(foo.__code__.co_posonlyargcount)??#?0

def?bar(a,?b,?/,?c):
????pass

print(bar.__code__.co_posonlyargcount)??#?2

注意:這里是只能通過位置參數(shù)傳遞的參數(shù)個數(shù)。對于 foo 而言,里面的三個參數(shù)既可以通過位置參數(shù)、也可以通過關(guān)鍵字參數(shù)傳遞;而函數(shù) bar,里面的 a、b 只能通過位置參數(shù)傳遞。

co_kwonlyargcount:只能通過關(guān)鍵字參數(shù)傳遞的參數(shù)個數(shù)

def?foo(a,?b=1,?c=2,?*,?d,?e):
????pass
print(foo.__code__.co_kwonlyargcount)??#?2

這里是 d 和 e,它們必須通過關(guān)鍵字參數(shù)傳遞。

co_nlocals:代碼塊中局部變量的個數(shù),也包括參數(shù)

def?foo(a,?b,?*,?c):
????name?=?"xxx"
????age?=?16
????gender?=?"f"
????c?=?33

print(foo.__code__.co_nlocals)??#?6

局部變量有 a、b、c、name、age、gender,所以我們看到在編譯之后,函數(shù)的局部變量就已經(jīng)確定了,因為它們是靜態(tài)存儲的。

co_stacksize:執(zhí)行該段代碼塊需要的??臻g

def?foo(a,?b,?*,?c):
????name?=?"xxx"
????age?=?16
????gender?=?"f"
????c?=?33

print(foo.__code__.co_stacksize)??#?1

這個暫時不需要太關(guān)注。

co_flags:參數(shù)類型標識

標識函數(shù)的參數(shù)類型,如果一個函數(shù)的參數(shù)出現(xiàn)了 *args,那么 co_flags & 0x04 為真;如果一個函數(shù)的參數(shù)出現(xiàn)了 **kwargs,那么 co_flags & 0x08 為真;

def?foo1():
????pass
#?結(jié)果全部為假
print(foo1.__code__.co_flags?&?0x04)??#?0
print(foo1.__code__.co_flags?&?0x08)??#?0

def?foo2(*args):
????pass
#?co_flags?&?0x04?為真,因為出現(xiàn)了?*args
print(foo2.__code__.co_flags?&?0x04)??#?4
print(foo2.__code__.co_flags?&?0x08)??#?0

def?foo3(*args,?**kwargs):
????pass
#?顯然?co_flags?&?0x04?和?co_flags?&?0x08?均為真
print(foo3.__code__.co_flags?&?0x04)??#?4
print(foo3.__code__.co_flags?&?0x08)??#?8

當然啦,co_flags 可以做的事情并不止這么簡單,它還能檢測一個函數(shù)的類型。比如函數(shù)內(nèi)部出現(xiàn)了 yield,那么它就是一個生成器函數(shù),調(diào)用之后可以得到一個生成器;使用 async def 定義,那么它就是一個協(xié)程函數(shù),調(diào)用之后可以得到一個協(xié)程。

這些在詞法分析的時候就可以檢測出來,編譯之后會體現(xiàn)在 co_flags 這個成員中。

#?如果是生成器函數(shù)
#?那么?co_flags?&?0x20?為真
def?foo1():
????yield
print(foo1.__code__.co_flags?&?0x20)??#?32

#?如果是協(xié)程函數(shù)
#?那么?co_flags?&?0x80?為真
async?def?foo2():
????pass
print(foo2.__code__.co_flags?&?0x80)??#?128
#?顯然?foo2?不是生成器函數(shù)
#?所以?co_flags?&?0x20?為假
print(foo2.__code__.co_flags?&?0x20)??#?0

#?如果是異步生成器函數(shù)
#?那么?co_flags?&?0x200?為真
async?def?foo3():
????yield
print(foo3.__code__.co_flags?&?0x200)??#?512
#?顯然它不是生成器函數(shù)、也不是協(xié)程函數(shù)
#?因此和?0x20、0x80?按位與之后,結(jié)果都為假
print(foo3.__code__.co_flags?&?0x20)??#?0
print(foo3.__code__.co_flags?&?0x80)??#?0

在判斷函數(shù)種類時,這種方式是最優(yōu)雅的。

co_firstlineno:代碼塊在對應文件的起始行

def?foo(a,?b,?*,?c):
????pass

#?顯然是文件的第一行
#?或者理解為?def?所在的行
print(foo.__code__.co_firstlineno)??#?1

如果函數(shù)出現(xiàn)了調(diào)用呢?

def?foo():
????return?bar

def?bar():
????pass

print(foo().__code__.co_firstlineno)??#?4

如果執(zhí)行 foo,那么會返回函數(shù) bar,最終得到的就是 bar 的字節(jié)碼,因此最終結(jié)果是 def bar(): 所在的行數(shù)。所以每個函數(shù)都有自己的作用域,以及 PyCodeObject 對象。

co_names:符號表,一個元組,保存代碼塊中引用的其它作用域的變量

c?=?1

def?foo(a,?b):
????print(a,?b,?c)
????d?=?(list,?int,?str)

print(
????foo.__code__.co_names
)??#?('print',?'c',?'list',?'int',?'str')

一切皆對象,但看到的都是指向?qū)ο蟮淖兞?,所?print, c, list, int, str 都是變量,它們都不在當前 foo 函數(shù)的作用域中。

co_varnames:符號表,一個元組,保存在當前作用域中的變量

c?=?1

def?foo(a,?b):
????print(a,?b,?c)
????d?=?(list,?int,?str)
print(foo.__code__.co_varnames)??#?('a',?'b',?'d')

a、b、d 是位于當前 foo 函數(shù)的作用域當中的,所以編譯階段便確定了局部變量是什么。

co_consts:常量池,一個元組,保存代碼塊中的所有常量

x?=?123

def?foo(a,?b):
????c?=?"abc"
????print(x)
????print(True,?False,?list,?[1,?2,?3],?{"a":?1})
????return?">>>"

print(
????foo.__code__.co_consts
)??#?(None,?'abc',?True,?False,?1,?2,?3,?'a',?'>>>')

co_consts 里面出現(xiàn)的都是常量,但 [1, 2, 3] 和 {"a": 1} 卻沒有出現(xiàn),由此我們可以得出,列表和字典絕不是在編譯階段構(gòu)建的。編譯時,只是收集了里面的元素,然后等到運行時再去動態(tài)構(gòu)建。

不過問題來了,在構(gòu)建的時候解釋器怎么知道是要構(gòu)建列表、還是字典、亦或是其它的什么對象呢?所以這就依賴于字節(jié)碼了,解釋字節(jié)碼的時候,會判斷到底要構(gòu)建什么樣的對象。

因此解釋器執(zhí)行的是字節(jié)碼,核心邏輯都體現(xiàn)在字節(jié)碼中。但是光有字節(jié)碼還不夠,它包含的只是程序的主干邏輯,至于變量、常量,則從符號表和常量池里面獲取。

co_name:代碼塊的名字

def?foo():
????pass
#?這里就是函數(shù)名
print(foo.__code__.co_name)??#?foo

co_code:字節(jié)碼

def?foo(a,?b,?/,?c,?*,?d,?e):
????f?=?123
????g?=?list()
????g.extend([tuple,?getattr,?print])

print(foo.__code__.co_code)
#b'd\x01}\x05t\x00\x83\x00}\x06|\x06......'

這便是字節(jié)碼,它只保存了要操作的指令,因此光有字節(jié)碼是肯定不夠的,還需要其它的靜態(tài)信息。顯然這些信息連同字節(jié)碼一樣,都位于 PyCodeObject 中。

字節(jié)碼與反編譯

Python 執(zhí)行源代碼之前會先編譯得到 PyCodeObject 對象,里面的 co_code 指向了字節(jié)碼序列。

虛擬機會根據(jù)這些字節(jié)碼序列來進行一系列的操作(當然也依賴其它的靜態(tài)信息),從而完成對程序的執(zhí)行。

每個操作都對應一個操作指令、也叫操作碼,總共有120多種,定義在 Include/opcode.h 中。

#define?POP_TOP???????????????????1
#define?ROT_TWO???????????????????2
#define?ROT_THREE?????????????????3
#define?DUP_TOP???????????????????4
#define?DUP_TOP_TWO???????????????5
#define?NOP???????????????????????9
#define?UNARY_POSITIVE???????????10
#define?UNARY_NEGATIVE???????????11
#define?UNARY_NOT????????????????12
#define?UNARY_INVERT?????????????15
#define?BINARY_MATRIX_MULTIPLY???16
#define?INPLACE_MATRIX_MULTIPLY??17
#define?BINARY_POWER?????????????19
#define?BINARY_MULTIPLY??????????20
#define?BINARY_MODULO????????????22
#define?BINARY_ADD???????????????23
#define?BINARY_SUBTRACT??????????24
#define?BINARY_SUBSCR????????????25
#define?BINARY_FLOOR_DIVIDE??????26
#define?BINARY_TRUE_DIVIDE???????27
#define?INPLACE_FLOOR_DIVIDE?????28
//?...
//?...

操作指令只是一個整數(shù),然后我們可以通過反編譯的方式查看每行 Python 代碼都對應哪些操作指令:

#?Python中的dis模塊專門負責干這件事情
import?dis

def?foo(a,?b):
????c?=?a?+?b
????return?c

#?里面接收一個字節(jié)碼
#?當然函數(shù)也是可以的,會自動獲取co_code
dis.dis(foo)
"""
??5???????????0?LOAD_FAST????????????????0?(a)
??????????????2?LOAD_FAST????????????????1?(b)
??????????????4?BINARY_ADD
??????????????6?STORE_FAST???????????????2?(c)

??6???????????8?LOAD_FAST????????????????2?(c)
?????????????10?RETURN_VALUE
"""

字節(jié)碼反編譯后的結(jié)果多么像匯編語言,其中第一列是源代碼行號,第二列是字節(jié)碼偏移量,第三列是操作指令(也叫操作碼),第四列是指令參數(shù)(也叫操作數(shù))。Python 的字節(jié)碼指令都是成對出現(xiàn)的,每個指令都會帶有一個指令參數(shù)。

查看字節(jié)碼也可以使用 opcode 模塊:

from?opcode?import?opmap

opmap?=?{v:?k?for?k,?v?in?opmap.items()}

def?foo(a,?b):
????c?=?a?+?b
????return?c

code?=?foo.__code__.co_code
for?i?in?range(0,?len(code),?2):
????print("操作碼:?{:<12}?操作數(shù):?{}".format(
????????opmap[code[i]],?code[i+1]
????))
"""
操作碼:?LOAD_FAST????操作數(shù):?0
操作碼:?LOAD_FAST????操作數(shù):?1
操作碼:?BINARY_ADD???操作數(shù):?0
操作碼:?STORE_FAST???操作數(shù):?2
操作碼:?LOAD_FAST????操作數(shù):?2
操作碼:?RETURN_VALUE?操作數(shù):?0
"""

總之字節(jié)碼就是一段字節(jié)序列,轉(zhuǎn)成列表之后就是一堆數(shù)字。偶數(shù)位置表示指令本身,而每個指令后面都會跟一個指令參數(shù),也就是奇數(shù)位置表示指令參數(shù)。

所以指令本質(zhì)上只是一個整數(shù):

虛擬機會根據(jù)不同的指令執(zhí)行不同的邏輯,說白了 Python 虛擬機執(zhí)行字節(jié)碼的邏輯就是把自己想象成一顆 CPU,并內(nèi)置了一個巨型的 switch case 語句,其中每個指令都對應一個 case 分支。

然后遍歷整條字節(jié)碼,拿到每一個指令和指令參數(shù)。然后對指令進行判斷,不同的指令進入不同的 case 分支,執(zhí)行不同的處理邏輯,直到字節(jié)碼全部執(zhí)行完畢或者程序出錯。

到此這篇關(guān)于一文帶你深度解密Python的字節(jié)碼的文章就介紹到這了,更多相關(guān)Python字節(jié)碼內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • python實現(xiàn)掃雷小游戲

    python實現(xiàn)掃雷小游戲

    這篇文章主要為大家詳細介紹了python實現(xiàn)掃雷小游戲,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-04-04
  • python web自制框架之接受url傳遞過來的參數(shù)實例

    python web自制框架之接受url傳遞過來的參數(shù)實例

    今天小編就為大家分享一篇python web自制框架之接受url傳遞過來的參數(shù)實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-12-12
  • 五個簡單有效的Python清理數(shù)據(jù)腳本分享

    五個簡單有效的Python清理數(shù)據(jù)腳本分享

    通常情況下,在機器學習中的數(shù)據(jù)清理往往是一件令人頭疼的事情,本文整理了一份清單,列出了5個常用的Python腳本,用于自動化數(shù)據(jù)清理,需要的可以參考一下
    2022-09-09
  • Python操作PostgreSQL數(shù)據(jù)庫的基本方法(增刪改查)

    Python操作PostgreSQL數(shù)據(jù)庫的基本方法(增刪改查)

    PostgreSQL數(shù)據(jù)庫是最常用的關(guān)系型數(shù)據(jù)庫之一,最吸引人的一點是它作為開源數(shù)據(jù)庫且具有可拓展性,能夠提供豐富的應用,這篇文章主要給大家介紹了關(guān)于Python操作PostgreSQL數(shù)據(jù)庫的基本方法,文中介紹了連接PostgreSQL數(shù)據(jù)庫,以及增刪改查,需要的朋友可以參考下
    2023-09-09
  • python3格式化字符串 f-string的高級用法(推薦)

    python3格式化字符串 f-string的高級用法(推薦)

    從Python 3.6開始,f-string是格式化字符串的一種很好的新方法。與其他格式化方式相比,它們不僅更易讀,更簡潔,不易出錯,而且速度更快!本文重點給大家介紹python3格式化字符串 f-string的高級用法,一起看看吧
    2020-03-03
  • Python列表推導式、字典推導式與集合推導式用法實例分析

    Python列表推導式、字典推導式與集合推導式用法實例分析

    這篇文章主要介紹了Python列表推導式、字典推導式與集合推導式用法,結(jié)合實例形式分析了Python三種推導式的概念、使用方法及相關(guān)注意事項,需要的朋友可以參考下
    2018-02-02
  • jupyter notebook遠程訪問不了的問題解決方法

    jupyter notebook遠程訪問不了的問題解決方法

    這篇文章主要介紹了jupyter notebook遠程訪問不了的問題解決方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-01-01
  • Python中進行表單處理的方法詳解

    Python中進行表單處理的方法詳解

    這篇文章主要為大家詳細介紹了Python中進行表單處理的方法,分別是利用Flask和Flask-WTF處理表單,文中的示例代碼講解詳細,感興趣的小伙伴可以了解一下
    2023-12-12
  • Python7個爬蟲小案例詳解(附源碼)上篇

    Python7個爬蟲小案例詳解(附源碼)上篇

    這篇文章主要介紹了Python7個爬蟲小案例詳解(附源碼)上篇,本文章內(nèi)容詳細,通過案例可以更好的理解爬蟲的相關(guān)知識,七個例子分為了三部分,本次為上篇,共有二道題,需要的朋友可以參考下
    2023-01-01
  • python實現(xiàn)調(diào)用其他python腳本的方法

    python實現(xiàn)調(diào)用其他python腳本的方法

    python實現(xiàn)調(diào)用其他python腳本的方法,是一個比較實用的技巧,需要的朋友可以參考下
    2014-10-10

最新評論

泾阳县| 札达县| 疏勒县| 湟源县| 平遥县| 眉山市| 工布江达县| 岑巩县| 广丰县| 砚山县| 百色市| 额尔古纳市| 东阳市| 阜南县| 包头市| 双江| 深州市| 中牟县| 南澳县| 绥棱县| 赤峰市| 栾城县| 黄陵县| 玉环县| 舟曲县| 龙泉市| 金乡县| 剑阁县| 新建县| 富民县| 阳朔县| 江津市| 兰坪| 渝中区| 东阳市| 建阳市| 惠水县| 白银市| 克东县| 武宁县| 重庆市|