Python中應(yīng)用protobuf的示例詳解
楔子
本次我們來聊一聊 protobuf,它是一個數(shù)據(jù)序列化和反序列化協(xié)議,因此它和 json 的定位是一樣的。當(dāng)客戶端需要傳遞數(shù)據(jù)給服務(wù)端時,會將內(nèi)存中的對象序列化成一個可以在網(wǎng)絡(luò)中傳輸?shù)亩M制流,服務(wù)端收到之后再反序列化,得到內(nèi)存中的對象。
不過既然都有 json 了,還會出現(xiàn) protobuf,那就說明 protobuf 相較于 json 有著很大的優(yōu)勢。來看一下優(yōu)缺點:
總結(jié)一下,protobuf 全稱為 Protocol Buffer,它是 Google 開發(fā)的一種輕量并且高效的結(jié)構(gòu)化數(shù)據(jù)存儲格式,性能要遠遠優(yōu)于 json 和 xml。另外 protobuf 經(jīng)歷了兩個版本,分別是 protobuf2 和 protobuf3,目前主流的版本是 3,因為更加易用。
下面就來開始學(xué)習(xí) protobuf 吧。
但是別忘記安裝,直接 pip3 install grpcio grpcio-tools protobuf 即可
編寫一個簡單的 protobuf 文件
protobuf 文件有自己的語法格式,所以相比 json 它的門檻要高一些。我們創(chuàng)建一個文件,文件名為 girl.proto。
protobuf 文件的后綴是 .proto
//?syntax?負責(zé)指定使用哪一種?protobuf?服務(wù) //?注意:syntax 必須寫在非注釋的第一行 syntax?=?"proto3"; //?包名,?這個目前不是很重要,?你刪掉也是無所謂的 package?girl; //?把 UserInfo 當(dāng)成 Python 中的類 //?name 和 age 當(dāng)成綁定在實例上的兩個屬性 message?UserInfo?{ ??string?name?=?1;??//?=?1表示第1個參數(shù) ??int32?age?=?2; }
protobuf 文件編寫完成,然后我們要用它生成相應(yīng)的 Python 文件,命令如下:
我們要用 protobuf 文件生成 Python 文件,所以 --python_out 負責(zé)指定 Python 文件的輸出路徑,這里是當(dāng)前目錄;-I 表示從哪里尋找 protobuf 文件,這里也是當(dāng)前目錄;最后的 girl.proto 就是指定的 protobuf 文件了。
我們執(zhí)行該命令,會發(fā)現(xiàn)執(zhí)行完之后多了一個 girl_pb2.py,我們直接用即可。注意:這是基于 protobuf 自動生成的 Python 文件,我們不要修改它。如果參數(shù)或返回值需要改變,那么應(yīng)該修改 protobuf 文件,然后重新生成 Python 文件。
然后我們來看看采用 protobuf 協(xié)議序列化之后的結(jié)果是什么,不是說它比較高效嗎?那么怎能不看看它序列化之后的結(jié)果呢,以及它和 json 又有什么不一樣呢?
import?orjson import?girl_pb2 #?在?protobuf?文件中定義了?message?UserInfo #?那么我們可以直接實例化它,而參數(shù)則是?name?和?age #?因為在?message?UserInfo?里面指定的字段是?name?和?age user_info?=?girl_pb2.UserInfo(name="satori",?age=17) #?如果不使用?protobuf,那么我們會選擇創(chuàng)建一個字典 user_info2?=?{"name":?"satori",?"age":?17} #?然后來看看序列化之后的結(jié)果 #?調(diào)用?SerializeToString?方法會得到序列化之后的字節(jié)串 print(user_info.SerializeToString()) """ b'\n\x06satori\x10\x11' """ #?如果是?json?的話 print(orjson.dumps(user_info2)) """ b'{"name":"satori","age":17}' """
可以看到使用 protobuf 協(xié)議序列化之后的結(jié)果要比 json 短,平均能得到一倍的壓縮。序列化我們知道了,那么如何反序列化呢?
import?orjson import?girl_pb2 #?依舊是實例化一個對象,但是不需要傳參 user_info?=?girl_pb2.UserInfo() #?傳入序列化之后的字節(jié)串,進行解析(反序列化) user_info.ParseFromString(b'\n\x06satori\x10\x11') print(user_info.name)??#?satori print(user_info.age)??#?17 #?json?也是同理,通過 loads 方法反序列化 user_info2?=?orjson.loads(b'{"name":"satori","age":17}') print(user_info2["name"])??#?satori print(user_info2["age"])??#?17
所以無論是 protobuf 還是 json,都是將一個對象序列化成二進制字節(jié)串。然后根據(jù)序列化之后的字節(jié)串,再反序列出原來的對象。只不過采用 protobuf 協(xié)議進行序列化和反序列化,速度會更快,并且序列化之后的數(shù)據(jù)壓縮比更高,在傳輸?shù)臅r候耗時也會更少。
然后還有一個關(guān)鍵地方的就是,json 這種數(shù)據(jù)結(jié)構(gòu)比較松散。你在返回 json 的時候,需要告訴調(diào)用你接口的人,返回的 json 里面都包含哪些字段,以及類型是什么。但 protobuf 則不需要,因為字段有哪些、以及相應(yīng)的類型,都必須在文件里面定義好。別人只要拿到 .proto 文件,就知道你要返回什么樣的數(shù)據(jù)了,一目了然。
在服務(wù)端之間傳輸 protobuf
如果兩個服務(wù)需要彼此訪問,那么最簡單的方式就是暴露一個 HTTP 接口,服務(wù)之間發(fā)送 HTTP 請求即可彼此訪問,至于請求數(shù)據(jù)和響應(yīng)數(shù)據(jù),則使用 JSON。
所以通過 HTTP + JSON 是最簡單的方式,也是業(yè)界使用最多的方式。但這種方式的性能不夠好,如果是同一個內(nèi)網(wǎng)的多個服務(wù),那么更推薦使用 gRPC + protobuf。關(guān)于 gRPC 以后再聊,我們來看看 protobuf 數(shù)據(jù)在 HTTP 請求中是如何傳遞的。
首先還是編寫 .proto 文件。
//?文件名:girl.proto syntax?=?"proto3"; package?girl; message?Request?{ ????string?name?=?1;?? ????int32?age?=?2; } message?Response?{ ????string?info?=?1; }
一個 protobuf 文件中可以定義任意個 message,在生成 Python 文件之后每個 message 會對應(yīng)一個同名的類。然后我們執(zhí)行之前的命令,生成 Python 文件。
接下來使用 Tornado 編寫一個服務(wù):
from?abc?import?ABC from?tornado?import?web,?ioloop import?girl_pb2 class?GetInfoHandler(web.RequestHandler,?ABC): ????async?def?post(self): ????????#?拿到客戶端傳遞的字節(jié)流 ????????#?這個字節(jié)流應(yīng)該是由?girl_pb2.Request()?序列化得到的 ????????content?=?self.request.body ????????#?下面進行反序列化 ????????request?=?girl_pb2.Request() ????????request.ParseFromString(content) ????????#?獲取里面的?name?和?age?字段的值 ????????name?=?request.name ????????age?=?request.age ????????#?生成?Response?對象 ????????response?=?girl_pb2.Response( ????????????info=f"name:?{name},?age:?{age}" ????????) ????????#?但?Response?對象不能直接返回,需要序列化 ????????return?await?self.finish(response.SerializeToString()) app?=?web.Application( ????[("/get_info",?GetInfoHandler)] ) app.listen(9000) ioloop.IOLoop.current().start()
整個過程很簡單,和 JSON 是一樣的。然后我們來訪問一下:
import?requests import?girl_pb2 #?往?localhost:9000?發(fā)請求 #?參數(shù)是?girl_pb2.Request()?序列化后的字節(jié)流 payload?=?girl_pb2.Request( ????name="古明地覺",?age=17 ).SerializeToString() #?發(fā)送?HTTP?請求,返回?girl_pb2.Response()?序列化后的字節(jié)流 content?=?requests.post("http://localhost:9000/get_info", ????????????????????????data=payload).content #?然后我們反序列化 response?=?girl_pb2.Response() response.ParseFromString(content) print(response.info) """ name:?古明地覺,?age:?17 """
所以 protobuf 本質(zhì)上也是一個序列化和反序列化協(xié)議,在使用上和 JSON 沒有太大區(qū)別。只不過 JSON 對應(yīng)的 Python 對象是字典,而 protobuf 則是單獨生成的對象。
protobuf 的基礎(chǔ)數(shù)據(jù)類型
在不涉及 gRPC 的時候,protobuf 文件是非常簡單的,你需要返回啥結(jié)構(gòu),那么直接在 .proto 文件里面使用標識符 message 定義即可。
message 消息名稱 {
類型 字段名 = 1;
類型 字段名 = 2;
類型 字段名 = 3;
}
但是類型我們需要說一下,之前用到了兩個基礎(chǔ)類型,分別是 string 和 int32,那么除了這兩個還有哪些類型呢?
以上是基礎(chǔ)類型,當(dāng)然還有復(fù)合類型,我們一會單獨說,先來演示一下基礎(chǔ)類型。編寫 .proto 文件:
//?文件名:basic_type.proto syntax?=?"proto3"; package?basic_type; message?BasicType?{ ????//?字段的名稱可以和類型名稱一致,這里為了清晰 ????//?我們就直接將類型的名稱用作字段名 ????int32?int32?=?1; ????sint32?sint32?=?2; ????uint32?uint32?=?3; ????fixed32?fixed32?=?4; ????sfixed32?sfixed32?=?5; ????int64?int64?=?6; ????sint64?sint64?=?7; ????uint64?uint64?=?8; ????fixed64?fixed64?=?9; ????sfixed64?sfixed64?=?10; ????double?double?=?11; ????float?float?=?12; ????bool?bool?=?13; ????string?string?=?14; ????bytes?bytes?=?15; }
然后我們來生成 Python 文件,命令如下:
python3 -m grpc_tools.protoc --python_out=. -I=. basic_type.proto
執(zhí)行之后,會生成 basic_type_pb2.py 文件,我們測試一下:
import?basic_type_pb2 basic_type?=?basic_type_pb2.BasicType( ????int32=123, ????sint32=234, ????uint32=345, ????fixed32=456, ????sfixed32=789, ????int64=1230, ????sint64=2340, ????uint64=3450, ????fixed64=4560, ????sfixed64=7890, ????double=3.1415926, ????float=2.71, ????bool=True, ????string="古明地覺", ????bytes=b"satori", ) #?定義一個函數(shù),接收序列化之后的字節(jié)流 def?parse(content:?bytes): ????obj?=?basic_type_pb2.BasicType() ????#?反序列化 ????obj.ParseFromString(content) ????print(obj.int32) ????print(obj.sfixed64) ????print(obj.string) ????print(obj.bytes) ????print(obj.bool) parse(basic_type.SerializeToString()) """ 123 7890 古明地覺 b'satori' True """
很簡單,沒有任何問題,以上就是 protobuf 的基礎(chǔ)類型。然后再來看看符合類型,以及一些特殊類型。
repeat 和 map
repeat 和 map 是一種復(fù)合類型,可以把它們當(dāng)成 Python 的列表和字典。
//?文件名:girl.proto syntax?=?"proto3"; package?girl; message?UserInfo?{ ????//?對于?Python?而言 ????//?repeated?表示?hobby?字段的類型是列表 ????//?string?則表示列表里面的元素必須都是字符串 ????repeated?string?hobby?=?1;??? ????//?map<string,?string>?表示?info?字段的類型是字典 ????//?字典的鍵值對必須都是字符串 ????map<string,?string>?info?=?2; }
我們執(zhí)行命令,生成 Python 文件,然后導(dǎo)入測試一下。
import?girl_pb2 user_info?=?girl_pb2.UserInfo( ????hobby=["唱",?"跳",?"rap",?"??"], ????info={"name":?"古明地覺",?"age":?"17"} ) print(user_info.hobby) print(user_info.info) """ ['唱',?'跳',?'rap',?'??'] {'name':?'古明地覺',?'age':?'17'} """
結(jié)果正常,沒有問題。但需要注意:對于復(fù)合類型而言,在使用的時候有一個坑。
import?girl_pb2 #?如果我們沒有給字段傳值,那么會有一個默認的零值 user_info?=?girl_pb2.UserInfo() print(user_info.hobby)??#?[] print(user_info.info)??#?{} #?對于復(fù)合類型的字段來說,我們不能單獨賦值 try: ????user_info.hobby?=?["唱",?"跳",?"rap",?"??"] except?AttributeError?as?e: ????print(e) """ Assignment?not?allowed?to?repeated?field?"hobby"?in?protocol?message?object. """ #?先實例化,然后單獨給字段賦值,只適用于基礎(chǔ)類型 #?因此我們需要這么做 user_info.hobby.extend(["唱",?"跳",?"rap",?"??"]) user_info.info.update({"name":?"古明地覺",?"age":?"17"}) print(user_info.hobby) print(user_info.info) """ ['唱',?'跳',?'rap',?'??'] {'name':?'古明地覺',?'age':?'17'} """
所以這算是一個需要注意的點,也不能叫坑吧,總之注意一下即可。
message 的嵌套
通過標識符 message 即可定義一個消息體,大括號里面的則是參數(shù),但參數(shù)的類型也可以是另一個 message。換句話說,message 是可以嵌套的。
//?文件名:girl.proto syntax?=?"proto3"; package?girl; message?UserInfo?{ ????repeated?string?hobby?=?1; ????//?BasicInfo?定義在外面也是可以的 ????message?BasicInfo?{ ????????string?name?=?1; ????????int32?age?=?2; ????????string?address?=?3; ????} ????BasicInfo?basic_info?=?2; }
生成 Python 文件,導(dǎo)入測試一下。
import?girl_pb2 #?在?protobuf?文件中,BasicInfo?定義在?UserInfo?里面 #?所以?BasicInfo?在這里對應(yīng)?UserInfo?的一個類屬性 #?如果定義在全局,那么直接通過?girl_pb2?獲取即可 basic_info?=?girl_pb2.UserInfo.BasicInfo( ????name="古明地覺",?age=17,?address="地靈殿") user_info?=?girl_pb2.UserInfo( ????hobby=['唱',?'跳',?'rap',?'??'], ????basic_info=basic_info ) print(user_info.hobby) """ ['唱',?'跳',?'rap',?'??'] """ print(user_info.basic_info.name) print(user_info.basic_info.age) print(user_info.basic_info.address) """ 古明地覺 17 地靈殿 """
以上是 message 的嵌套,或者說通過 message 定義的消息體,也可以作為字段的類型。
枚舉類型
再來聊一聊枚舉類型,它通過 enum 標識符定義。
//?里面定義了兩個成員,分別是?MALE?和?FEMALE enum?Gender?{ ????MALE?=?0; ????FEMALE?=?1; }
這里需要說明的是,對于枚舉來說,等號后面的值表示成員的值。比如一個字段的類型是 Gender,那么在給該字段賦值的時候,要么傳 0 要么傳 1。因為枚舉 Gender 里面只有兩個成員,分別代表 0 和 1。
而我們前面使用 message 定義消息體的時候,每個字段后面跟著的值則代表序號,從 1 開始,依次遞增。至于為什么要有這個序號,是因為我們在實例化的時候,可以只給指定的部分字段賦值,沒有賦值的字段則使用對應(yīng)類型的零值。那么另一端在拿到字節(jié)流的時候,怎么知道哪些字段被賦了值,哪些字段沒有被賦值呢?顯然要通過序號來進行判斷。
下面來編寫 .proto 文件。
//?文件名:girl.proto syntax?=?"proto3"; package?girl; //?枚舉成員的值必須是整數(shù) enum?Gender?{ ????MALE?=?0; ????FEMALE?=?1; } message?UserInfo?{ ????string?name?=?1; ????int32?age?=?2; ????Gender?gender?=?3; } message?Girls?{ ????//?列表里面的類型也可以是?message?定義的消息體 ????repeated?UserInfo?girls?=?1; }
輸入命令生成 Python 文件,然后導(dǎo)入測試:
import?girl_pb2 user_info1?=?girl_pb2.UserInfo( ????name="古明地覺",?age=17, ????gender=girl_pb2.Gender.Value("FEMALE")) user_info2?=?girl_pb2.UserInfo( ????name="芙蘭朵露",?age=400, ????#?傳入一個具體的值也是可以的 ????gender=1) girls?=?girl_pb2.Girls(girls=[user_info1,?user_info2]) print(girls.girls[0].name,?girls.girls[1].name) print(girls.girls[0].age,?girls.girls[1].age) print(girls.girls[0].gender,?girls.girls[1].gender) """ 古明地覺?芙蘭朵露 17?400 1?1 """
枚舉既可以定義在全局,也可以定義在某個 message 里面。
.proto 文件的導(dǎo)入
.proto 文件也可以互相導(dǎo)入,我們舉個例子。下面定義兩個文件,一個是 people.proto,另一個是 girl.proto,然后在 girl.proto 里面導(dǎo)入 people.proto。
/*?文件名:people.proto */ syntax?=?"proto3"; //?此時的包名就很重要了,當(dāng)該文件被其它文件導(dǎo)入時 //?需要通過這里的包名,來獲取內(nèi)部的消息體、枚舉等數(shù)據(jù) package?people; message?BasicInfo?{ ????string?name?=?1; ????int32?age?=?2; } /*?文件名:girl.proto */ syntax?=?"proto3"; //?導(dǎo)入?people.proto, import?"people.proto"; message?PersonalInfo?{ ????string?phone?=?1; ????string?address?=?2; } message?Girl?{ ????//?這里的?BasicInfo?是在?people.proto?里面定義的 ????//?people.proto?里面的?package?指定的包名為?people ????//?所以這里需要通過?people.?的方式獲取 ????people.BasicInfo?basic_info?=?1; ????PersonalInfo?personal_info?=?2; }
然后執(zhí)行命令,基于 proto 文件生成 Python 文件,顯然此時會有兩個 Python 文件。
python3 -m grpc_tools.protoc --python_out=. -I=. girl.proto
python3 -m grpc_tools.protoc --python_out=. -I=. people.proto
import?girl_pb2 import?people_pb2 basic_info?=?people_pb2.BasicInfo(name="古明地覺",?age=17) personal_info?=?girl_pb2.PersonalInfo(phone="18838888888", ??????????????????????????????????????address="地靈殿") girl?=?girl_pb2.Girl(basic_info=basic_info, ?????????????????????personal_info=personal_info) print(girl.basic_info.name)??#?古明地覺 print(girl.basic_info.age)??#?17 print(girl.personal_info.phone)??#?18838888888 print(girl.personal_info.address)??#?地靈殿
以上就是 proto 文件的導(dǎo)入,不復(fù)雜。
一些常用的方法
.proto 文件在生成 .py 文件之后,里面的一個消息體對應(yīng)一個類,我們可以對類進行實例化。而這些實例化的對象都有哪些方法呢?我們總結(jié)一下常用的。
首先重新編寫 girl.proto,然后生成 Python 文件。
syntax?=?"proto3"; message?People?{ ????string?name?=?1; ????int32?age?=?2; } message?Girl?{ ????People?people?=?1; ????string?address?=?2; ????int32?length?=?3; }
內(nèi)容很簡單,我們測試一下。
import?girl_pb2 girl?=?girl_pb2.Girl( ????people=girl_pb2.People(name="古明地覺",?age=17), ????address="地靈殿", ????length=152 ) # SerializeToString:將對象序列化成二進制字節(jié)串 content?=?girl.SerializeToString() # ParseFromString:將二進制字節(jié)串反序列化成對象 girl2?=?girl_pb2.Girl() girl2.ParseFromString(content) print( ????girl2.people.name, ????girl2.people.age, ????girl2.address, ????girl2.length )??#?古明地覺?17?地靈殿?152 #?以上兩個是最常用的方法 # MergeFrom:將一個對象合并到另一個對象上面 girl?=?girl_pb2.Girl(address="紅魔館",?length=145) #?我們先實例化了?Girl,后實例化?People #?接下來要將它綁定到?girl?的?people?字段上 people?=?girl_pb2.People(name="芙蘭朵露",?age=400) #?但?girl.people?=?people?是會報錯的,因為只有標量才能這么做 #?所以我們可以通過?girl.people.xxx?=?people.xxx?進行綁定 #?但如果?people?的字段非常多,那么會很麻煩 #?因此這個時候可以使用?MergeFrom girl.people.MergeFrom(people) print( ????girl.people.name,?girl.people.age )??#?芙蘭朵露?400 #?同理還有?MergeFromString,接收的是序列化之后的字節(jié)串 people.name,?people.age?=?"魔理沙",?15 girl.people.MergeFromString(people.SerializeToString()) print( ????girl.people.name,?girl.people.age )??#?魔理沙?15
非常簡單,但我們發(fā)現(xiàn)還少了點什么,就是它和 Python 的字典能不能互相轉(zhuǎn)化呢?答案是可以的,但需要導(dǎo)入專門的函數(shù)。
from?google.protobuf.json_format?import?( ????MessageToJson, ????MessageToDict ) import?girl_pb2 girl?=?girl_pb2.Girl( ????people=girl_pb2.People(name="古明地覺",?age=17), ????address="地靈殿", ????length=152 ) #?轉(zhuǎn)成?JSON print(MessageToJson(girl)) """ { ??"people":?{ ????"name":?"\u53e4\u660e\u5730\u89c9", ????"age":?17 ??}, ??"address":?"\u5730\u7075\u6bbf", ??"length":?152 } """ #?轉(zhuǎn)成字典 print(MessageToDict(girl)) """ {'people':?{'name':?'古明地覺',?'age':?17},? ?'address':?'地靈殿',?'length':?152} """
同理,如果我們有一個字典,也可以轉(zhuǎn)成相應(yīng)的對象。
from?google.protobuf.json_format?import?( ????ParseDict ) import?girl_pb2 data?=?{'people':?{'name':?'魔理沙',?'age':?16}, ????????'address':?'魔法森林',?'length':?156} girl?=?girl_pb2.Girl() #?基于字典進行解析 ParseDict(data,?girl) print(girl.people.name)??#?魔理沙 print(girl.people.age)??#?16 print(girl.address)??#?魔法森林 print(girl.length)??#?156
以上就是工作中的一些常用的方法。
小結(jié)
以上就是 protobuf 相關(guān)的內(nèi)容,核心就是編寫 .proto 文件,然后生成 Python 文件。它在業(yè)務(wù)中發(fā)揮的作用,和 json 是類似的,都是將對象轉(zhuǎn)成二進制之后再通過網(wǎng)絡(luò)進行傳輸。接收方在收到字節(jié)流之后,將其反序列化成內(nèi)存中的對象,然后獲取內(nèi)部的字段。
但是 protobuf 比 json 的性能要優(yōu)秀很多,并且通過 .proto 文件定義好結(jié)構(gòu),約束性也要更強一些。
最后補充一點,.proto 文件里面還可以定義很多和 gRPC 相關(guān)的內(nèi)容,關(guān)于 gRPC 我們以后再聊。
到此這篇關(guān)于Python中應(yīng)用protobuf的示例詳解的文章就介紹到這了,更多相關(guān)Python protobuf內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python集成測試提高軟件質(zhì)量關(guān)鍵步驟探究
Python是一門強大的編程語言,提供了眾多工具和庫,用于執(zhí)行高效的集成測試,本文將深入介紹Python集成測試的概念、方法和最佳實踐,并通過豐富的示例代碼演示如何提高軟件質(zhì)量和減少潛在的缺陷2024-01-01Pytorch轉(zhuǎn)onnx、torchscript方式
這篇文章主要介紹了Pytorch轉(zhuǎn)onnx、torchscript方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-05-05python通過pil模塊將raw圖片轉(zhuǎn)換成png圖片的方法
這篇文章主要介紹了python通過pil模塊將raw圖片轉(zhuǎn)換成png圖片的方法,實例分析了Python中pil模塊的使用技巧,并Image.fromstring函數(shù)進行了較為詳盡的分析說明,需要的朋友可以參考下2015-03-03Python二進制文件讀取并轉(zhuǎn)換為浮點數(shù)詳解
這篇文章主要介紹了Python二進制文件讀取并轉(zhuǎn)換為浮點數(shù)詳解,用python讀取二進制文件,這里主要用到struct包,而這個包里面的方法主要是unpack、pack、calcsize。,需要的朋友可以參考下2019-06-06使用Python matplotlib作圖時,設(shè)置橫縱坐標軸數(shù)值以百分比(%)顯示
這篇文章主要介紹了使用Python matplotlib作圖時,設(shè)置橫縱坐標軸數(shù)值以百分比(%)顯示,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-05-05Pytorch中的torch.nn.Linear()方法用法解讀
這篇文章主要介紹了Pytorch中的torch.nn.Linear()方法用法,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-02-02