Lua में table और metatable क्या हैं?

API7.ai

October 11, 2022

OpenResty (NGINX + Lua)

आज हम LuaJIT में एकमात्र डेटा संरचना table के बारे में सीखेंगे।

अन्य स्क्रिप्टिंग भाषाओं के विपरीत जिनमें समृद्ध डेटा संरचनाएं होती हैं, LuaJIT में केवल एक डेटा संरचना होती है, table, जो arrays, hashes, collections आदि से अलग नहीं होती है, बल्कि कुछ हद तक मिश्रित होती है। आइए पहले बताए गए उदाहरणों में से एक को फिर से देखें।

local color = {first = "red", "blue", third = "green", "yellow"} print(color["first"]) --> output: red print(color[1]) --> output: blue print(color["third"]) --> output: green print(color[2]) --> output: yellow print(color[3]) --> output: nil

इस उदाहरण में, color टेबल में एक array और एक hash शामिल है और इसे एक-दूसरे के साथ हस्तक्षेप किए बिना एक्सेस किया जा सकता है। उदाहरण के लिए, आप टेबल के केवल array भाग को पुनरावृत्त करने के लिए ipairs फ़ंक्शन का उपयोग कर सकते हैं।

$ resty -e 'local color = {first = "red", "blue", third = "green", "yellow"} for k, v in ipairs(color) do print(k) end '

table ऑपरेशन इतने महत्वपूर्ण हैं कि LuaJIT ने मानक Lua 5.1 टेबल लाइब्रेरी का विस्तार किया है, और OpenResty ने LuaJIT की टेबल लाइब्रेरी को और भी आगे बढ़ाया है। आइए इन लाइब्रेरी फ़ंक्शन्स में से प्रत्येक को देखें।

टेबल लाइब्रेरी फ़ंक्शन्स

आइए मानक टेबल लाइब्रेरी फ़ंक्शन्स से शुरू करें। Lua 5.1 में बहुत सारे टेबल लाइब्रेरी फ़ंक्शन्स नहीं हैं, इसलिए हम उन्हें जल्दी से देख सकते हैं।

table.getn तत्वों की संख्या प्राप्त करें

जैसा कि हमने मानक Lua और LuaJIT अध्याय में बताया था, LuaJIT में सभी टेबल तत्वों की सही संख्या प्राप्त करना एक बड़ी समस्या है।

सीक्वेंस के लिए, आप table.getn या यूनरी ऑपरेटर # का उपयोग करके सही संख्या में तत्व वापस कर सकते हैं। निम्नलिखित उदाहरण 3 की संख्या वापस करता है जिसकी हम उम्मीद करते हैं।

$ resty -e 'local t = { 1, 2, 3 } print(table.getn(t))

गैर-सीक्वेंशियल टेबल्स के लिए सही मान वापस नहीं किया जा सकता है। दूसरे उदाहरण में, वापस किया गया मान 1 है।

$ resty -e 'local t = { 1, a = 2 } print(#t) '

सौभाग्य से, ऐसे समझने में कठिन फ़ंक्शन्स को LuaJIT के एक्सटेंशन्स द्वारा प्रतिस्थापित किया गया है, जिनका हम बाद में उल्लेख करेंगे। इसलिए OpenResty संदर्भ में, table.getn फ़ंक्शन और यूनरी ऑपरेटर # का उपयोग न करें जब तक कि आप स्पष्ट रूप से न जानते हों कि आप सीक्वेंस लंबाई प्राप्त कर रहे हैं।

इसके अलावा, table.getn और यूनरी ऑपरेटर # O(1) समय जटिलता नहीं हैं बल्कि O(n) हैं, जो उन्हें यथासंभव टालने का एक और कारण है।

table.remove निर्दिष्ट तत्व को हटाएं

दूसरा है table.remove फ़ंक्शन, जो टेबल में सबस्क्रिप्ट के आधार पर तत्वों को हटाता है, यानी केवल टेबल के array भाग के तत्वों को हटाया जा सकता है। आइए color उदाहरण को फिर से देखें।

$ resty -e 'local color = {first = "red", "blue", third = "green", "yellow"} table.remove(color, 1) for k, v in pairs(color) do print(v) end'

यह कोड सबस्क्रिप्ट 1 के साथ blue को हटा देगा। आप पूछ सकते हैं, टेबल के hash भाग को कैसे हटाएं? यह उतना ही सरल है जितना कि key के अनुरूप मान को nil पर सेट करना। इस प्रकार, color उदाहरण में, third के अनुरूप green हटा दिया जाता है।

$ resty -e 'local color = {first = "red", "blue", third = "green", "yellow"} color.third = nil for k, v in pairs(color) do print(v) end'

table.concat तत्व जोड़ फ़ंक्शन

तीसरा है table.concat तत्व जोड़ फ़ंक्शन। यह टेबल के तत्वों को सबस्क्रिप्ट के अनुसार जोड़ता है। चूंकि यह फिर से सबस्क्रिप्ट पर आधारित है, यह टेबल के array भाग के लिए है। फिर से color उदाहरण के साथ।

$ resty -e 'local color = {first = "red", "blue", third = "green", "yellow"} print(table.concat(color, ", "))'

table.concat फ़ंक्शन का उपयोग करने के बाद, यह blue, yellow आउटपुट करता है और hash भाग को छोड़ दिया जाता है।

इसके अलावा, यह फ़ंक्शन सबस्क्रिप्ट की शुरुआती स्थिति को जोड़ने के लिए भी निर्दिष्ट कर सकता है; उदाहरण के लिए, इसे निम्नानुसार लिखा जाता है

$ resty -e 'local color = {first = "red", "blue", third = "green", "yellow", "orange"} print(table.concat(color, ", ", 2, 3))'

इस बार आउटपुट yellow, orange है, blue को छोड़कर।

कृपया इस प्रतीत होने वाले बेकार फ़ंक्शन को कम न समझें, लेकिन यह प्रदर्शन अनुकूलन में अप्रत्याशित प्रभाव डाल सकता है और हमारे बाद के प्रदर्शन अनुकूलन अध्यायों में मुख्य पात्रों में से एक है।

table.insert एक तत्व डालें

अंत में, आइए table.insert फ़ंक्शन को देखें। यह निर्दिष्ट सबस्क्रिप्ट में एक नया तत्व डालता है, जो टेबल के array भाग को प्रभावित करता है। उदाहरण के लिए, फिर से color उदाहरण का उपयोग करते हुए।

$ resty -e 'local color = {first = "red", "blue", third = "green", "yellow"} table.insert(color, 1, "orange") print(color[1]) '

आप देख सकते हैं कि color का पहला तत्व orange हो जाता है, लेकिन निश्चित रूप से, आप सबस्क्रिप्ट को अनिर्दिष्ट छोड़ सकते हैं ताकि यह डिफ़ॉल्ट रूप से कतार के अंत में डाला जाए।

मुझे यह नोट करना चाहिए कि table.insert एक व्यापक ऑपरेशन है, लेकिन प्रदर्शन अच्छा नहीं है। यदि आप निर्दिष्ट स्क्रिप्ट के आधार पर तत्व नहीं डाल रहे हैं, तो आपको हर बार LuaJIT के lj_tab_len को कॉल करने की आवश्यकता होगी ताकि कतार के अंत में डाला जा सके। जैसा कि table.getn, टेबल लंबाई प्राप्त करने की समय जटिलता O(n) है।

इसलिए, table.insert ऑपरेशन के लिए; हमें हॉट कोड में इसका उपयोग करने से बचना चाहिए। उदाहरण के लिए:

local t = {} for i = 1, 10000 do table.insert(t, i) end

LuaJIT का टेबल एक्सटेंशन फ़ंक्शन

अगला, आइए LuaJIT के टेबल एक्सटेंशन फ़ंक्शन्स को देखें। LuaJIT ने मानक Lua को दो उपयोगी टेबल फ़ंक्शन्स के साथ विस्तारित किया है जो एक टेबल बनाने और खाली करने के लिए हैं, जिन्हें मैं नीचे वर्णित करूंगा।

table.new(narray, nhash) एक नई टेबल बनाएं

पहला है table.new(narray, nhash) फ़ंक्शन। तत्व डालते समय खुद को बढ़ाने के बजाय, यह फ़ंक्शन निर्दिष्ट array और hash के स्थान आकार को पूर्व-आवंटित करेगा, जो इसके दो पैरामीटर narray और nhash का अर्थ है। स्व-वृद्धि एक महंगा ऑपरेशन है जिसमें स्थान आवंटन, resize और rehash शामिल हैं, और इसे हर कीमत पर टाला जाना चाहिए।

यहां ध्यान दें कि table.new का दस्तावेज़ीकरण LuaJIT वेबसाइट पर नहीं है बल्कि GitHub प्रोजेक्ट के विस्तारित दस्तावेज़ीकरण में गहराई से है, इसलिए इसे Google करने पर भी इसे ढूंढना मुश्किल है, इसलिए बहुत कम इंजीनियर इसके बारे में जानते हैं।

यहां एक सरल उदाहरण है, और मैं आपको दिखाऊंगा कि यह कैसे काम करता है। सबसे पहले, यह फ़ंक्शन एक्सटेंडेड है, इसलिए इसका उपयोग करने से पहले आपको इसे require करना होगा।

local new_tab = require "table.new" local t = new_tab(100, 0) for i = 1, 100 do t[i] = i end

जैसा कि आप देख सकते हैं, यह कोड 100 array तत्वों और 0 hash तत्वों के साथ एक नई टेबल बनाता है। निश्चित रूप से, आप 100 array तत्वों और 50 hash तत्वों के साथ एक नई टेबल बना सकते हैं जैसा कि आवश्यक हो, जो कानूनी है।

local t = new_tab(100, 50)

वैकल्पिक रूप से, यदि आप पूर्व-सेट स्थान आकार से आगे जाते हैं, तो आप इसे सामान्य रूप से उपयोग कर सकते हैं, लेकिन प्रदर्शन कम हो जाएगा, और table.new का उपयोग करने का मतलब खो जाएगा।

निम्नलिखित उदाहरण में, हमारे पास 100 का पूर्व-सेट आकार है, लेकिन हम 200 का उपयोग कर रहे हैं।

local new_tab = require "table.new" local t = new_tab(100, 0) for i = 1, 200 do t[i] = i end

आपको table.new में array और hash स्थान के आकार को वास्तविक परिदृश्य के अनुसार पूर्व-सेट करना होगा ताकि आप प्रदर्शन और मेमोरी उपयोग के बीच संतुलन पा सकें।

table.clear() टेबल को साफ़ करें

दूसरा है साफ़ करने वाला फ़ंक्शन table.clear()। यह टेबल में सभी डेटा को साफ़ करता है लेकिन array और hash भागों द्वारा कब्जा की गई मेमोरी को मुक्त नहीं करता है। इसलिए, यह Lua टेबल्स को रीसायकल करते समय फायदेमंद है ताकि टेबल्स को बार-बार बनाने और नष्ट करने के ओवरहेड से बचा जा सके।

$ resty -e 'local clear_tab =require "table.clear" local color = {first = "red", "blue", third = "green", "yellow"} clear_tab(color) for k, v in pairs(color) do print(k) end'

हालांकि, इस फ़ंक्शन का उपयोग करने के लिए बहुत कम परिदृश्य हैं, और अधिकांश मामलों में, हमें इस कार्य को LuaJIT GC पर छोड़ देना चाहिए।

OpenResty का टेबल एक्सटेंशन फ़ंक्शन

जैसा कि मैंने शुरुआत में बताया, OpenResty अपनी LuaJIT शाखा को बनाए रखता है, जो टेबल को भी विस्तारित करता है, कई नए API के साथ: table.isempty, table. isarray, table.nkeys और table.clone

इन नए API का उपयोग करने से पहले, कृपया OpenResty के संस्करण की जांच करें, क्योंकि इनमें से अधिकांश API का उपयोग OpenResty के 1.15.8.1 के बाद के संस्करणों में ही किया जा सकता है। ऐसा इसलिए है क्योंकि OpenResty ने संस्करण 1.15.8.1 से पहले लगभग एक साल तक कोई नया रिलीज़ नहीं किया था, और इन API को उस रिलीज़ अंतराल में जोड़ा गया था।

मैंने लेख का लिंक शामिल किया है, इसलिए मैं table.nkeys को एक उदाहरण के रूप में उपयोग करूंगा। अन्य तीन API नामकरण के दृष्टिकोण से समझने में बहुत सरल हैं, इसलिए GitHub दस्तावेज़ीकरण को देखें, और आप समझ जाएंगे। मुझे कहना है कि OpenResty का दस्तावेज़ीकरण बहुत उच्च गुणवत्ता का है, जिसमें कोड उदाहरण शामिल हैं, चाहे वह JIT हो सकता है, क्या देखना है, आदि। Lua और LuaJIT के दस्तावेज़ीकरण से कई गुना बेहतर है।

ठीक है, table.nkeys फ़ंक्शन पर वापस आते हैं। इसका नाम आपको भ्रमित कर सकता है, लेकिन यह टेबल की लंबाई प्राप्त करने वाला एक फ़ंक्शन है और टेबल के तत्वों की संख्या वापस करता है, जिसमें array और hash भाग के तत्व शामिल हैं। इसलिए, हम इसका उपयोग table.getn के बजाय कर सकते हैं, उदाहरण के लिए, निम्नानुसार।

local nkeys = require "table.nkeys" print(nkeys({})) -- 0 print(nkeys({ "a", nil, "b" })) -- 2 print(nkeys({ dog = 3, cat = 4, bird = nil })) -- 2 print(nkeys({ "a", dog = 3, cat = 4 })) -- 3

मेटाटेबल

टेबल फ़ंक्शन के बारे में बात करने के बाद, आइए table से प्राप्त metatable को देखें। मेटाटेबल Lua में एक अनूठी अवधारणा है, और वास्तविक परियोजनाओं में व्यापक रूप से उपयोग किया जाता है। यह कहना अतिशयोक्ति नहीं होगा कि आप इसे लगभग किसी भी lua-resty-* लाइब्रेरी में पा सकते हैं।

Metatable ऑपरेटर ओवरलोड की तरह व्यवहार करता है; उदाहरण के लिए, हम दो Lua arrays के संयोजन की गणना करने के लिए __add को ओवरलोड कर सकते हैं या __tostring को स्ट्रिंग में परिवर्तित करने वाले फ़ंक्शन को परिभाषित कर सकते हैं।

Lua, दूसरी ओर, मेटाटेबल को संभालने के लिए दो फ़ंक्शन प्रदान करता है।

  • पहला है setmetatable(table, metatable), जो एक टेबल के लिए मेटाटेबल सेट करता है।
  • दूसरा है getmetatable(table), जो टेबल का मेटाटेबल प्राप्त करता है।

इस सब के बाद, आप इसके बारे में अधिक जानने में रुचि रख सकते हैं, इसलिए आइए देखें कि मेटाटेबल का उपयोग किस लिए किया जाता है। यहां एक वास्तविक परियोजना से एक कोड का टुकड़ा है।

$ resty -e ' local version = { major = 1, minor = 1, patch = 1 } version = setmetatable(version, { __tostring = function(t) return string.format("%d.%d.%d", t.major, t.minor, t.patch) end }) print(tostring(version)) '

हम पहले version नामक एक टेबल को परिभाषित करते हैं, और जैसा कि आप देख सकते हैं, इस कोड का उद्देश्य version में संस्करण संख्या को प्रिंट करना है। हालांकि, हम version को सीधे प्रिंट नहीं कर सकते। आप इसे करने का प्रयास कर सकते हैं और देख सकते हैं कि सीधे प्रिंट करने पर केवल टेबल का पता आउटपुट होगा।

print(tostring(version))

इसलिए, हमें इस टेबल के लिए स्ट्रिंग रूपांतरण फ़ंक्शन को कस्टमाइज़ करने की आवश्यकता है, जो __tostring है, और यहीं पर मेटाटेबल आता है। हम setmetatable का उपयोग करके टेबल version के __tostring मेथड को रीसेट करते हैं ताकि संस्करण संख्या प्रिंट हो: 1.1.1.

__tostring के अलावा, हम वास्तविक परियोजनाओं में मेटाटेबल में निम्नलिखित दो मेटामेथड को ओवरराइड करते हैं।

उनमें से एक है __index। जब हम एक टेबल में एक तत्व को खोजते हैं, तो हम पहले सीधे टेबल से इसे खोजते हैं, और यदि हम इसे नहीं पाते हैं, तो हम मेटा टेबल के __index पर जाते हैं।

हम निम्नलिखित उदाहरण में version टेबल से patch को हटाते हैं।

$ resty -e ' local version = { major = 1, minor = 1 } version = setmetatable(version, { __index = function(t, key) if key == "patch" then return 2 end end, __tostring = function(t) return string.format("%d.%d.%d", t.major, t.minor, t.patch) end }) print(tostring(version)) '

इस मामले में, t.patch मान प्राप्त नहीं करता है, इसलिए यह __index फ़ंक्शन पर जाता है, जो 1.1.2 प्रिंट करता है।

__index न केवल एक फ़ंक्शन हो सकता है बल्कि एक टेबल भी हो सकता है, और यदि आप निम्नलिखित कोड को चलाने का प्रयास करते हैं, तो आप देखेंगे कि वे समान परिणाम प्राप्त करते हैं।

$ resty -e ' local version = { major = 1, minor = 1 } version = setmetatable(version, { __index = {patch = 2}, __tostring = function(t) return string.format("%d.%d.%d", t.major, t.minor, t.patch) end }) print(tostring(version)) '

एक और मेटामेथड है __call। यह एक फ़ंक्टर के समान है जो एक टेबल को कॉल करने की अनुमति देता है।

आइए संस्करण संख्या प्रिंट करने वाले कोड पर बनाएं और देखें कि एक टेबल को कैसे कॉल किया जाए।

$ resty -e ' local version = { major = 1, minor = 1, patch = 1 } local function print_version(t) print(string.format("%d.%d.%d", t.major, t.minor, t.patch)) end version = setmetatable(version, {__call = print_version}) version() '

इस कोड में, हम setmetatable का उपयोग करके टेबल version में एक मेटाटेबल जोड़ते हैं, और इसके अंदर __call मेटामेथड फ़ंक्शन print_version को इंगित करता है। इसलिए, यदि हम version को एक फ़ंक्शन के रूप में कॉल करने का प्रयास करते हैं, तो यहां फ़ंक्शन print_version निष्पादित होगा।

और getmetatable setmetatable के साथ जोड़ी गई ऑपरेशन है जो सेट किए गए मेटाटेबल को प्राप्त करता है, जैसे निम्नलिखित कोड।

$ resty -e ' local version = { major = 1, minor = 1 } version = setmetatable(version, { __index = {patch = 2}, __tostring = function(t) return string.format("%d.%d.%d", t.major, t.minor, t.patch) end }) print(getmetatable(version).__index.patch) '

आज हमने जिन तीन मेटामेथड्स के बारे में बात की है, उनके अलावा कुछ कम उपयोग किए जाने वाले मेटामेथड्स हैं जिनके बारे में आप दस्तावेज़ीकरण से अधिक जान सकते हैं जब आप उनका सामना करते हैं।

ऑब्जेक्ट-ओरिएंटेड

अंत में, आइए ऑब्जेक्ट ओरिएंटेशन के बारे में बात करते हैं। जैसा कि आप जानते हैं, Lua एक ऑब्जेक्ट ओरिएंटेशन भाषा नहीं है, लेकिन हम मेटाटेबल का उपयोग करके OO को लागू कर सकते हैं।

आइए एक व्यावहारिक उदाहरण देखें। lua-resty-mysql OpenResty का आधिकारिक MySQL क्लाइंट है, और यह मेटाटेबल्स का उपयोग करके सिमुलेशन क्लास और क्लास मेथड्स का उपयोग करता है, जिनका उपयोग निम्नानुसार किया जाता है।

$ resty -e 'local mysql = require "resty.mysql" -- पहले lua-resty लाइब्रेरी को संदर्भित करें local db, err = mysql:new() -- क्लास का एक नया उदाहरण बनाएं db:set_timeout(1000) -- क्लास के मेथड को कॉल करें

आप उपरोक्त कोड को सीधे resty कमांड लाइन के साथ निष्पादित कर सकते हैं। ये कोड लाइनें समझने में आसान हैं; केवल एक चीज जो आपको परेशान कर सकती है वह है।

क्लास मेथड को कॉल करते समय, यह कोलन के बजाय डॉट क्यों है?

वास्तव में, यहां कोलन और डॉट दोनों ठीक हैं, और db:set_timeout(1000) और db.set_timeout(db, 1000) बिल्कुल समान हैं। कोलन Lua में एक सिंटैक्टिक शुगर है जो एक फ़ंक्शन के पहले तर्क self को छोड़ने की अनुमति देता है।

जैसा कि हम सभी जानते हैं, स्रोत कोड के सामने कोई रहस्य नहीं होता है, इसलिए आइए उपरोक्त कोड लाइनों के अनुरूप ठोस कार्यान्वयन को देखें ताकि आप मेटा-टेबल्स के साथ ऑब्जेक्ट-ओरिएंटेड को मॉडल करने को बेहतर ढंग से समझ सकें।

local _M = { _VERSION = '0.21' } -- टेबल सिमुलेशन क्लास का उपयोग करें local mt = { __index = _M } -- mt मेटाटेबल के लिए संक्षिप्त है, __index क्लास को इंगित करता है -- क्लास का कंस्ट्रक्टर function _M.new(self) local sock, err = tcp() if not sock then return nil, err end return setmetatable({ sock = sock }, mt) -- टेबल और मेटाटेबल का उपयोग करके क्लास का सिमुलेशन end -- क्लास के सदस्य फ़ंक्शन function _M.set_timeout(self, timeout) -- self तर्क का उपयोग करके उस क्लास के उदाहरण को प्राप्त करें जिस पर आप कार्य करना चाहते हैं local sock = self.sock if not sock then return nil, "not initialized" end return sock:settimeout(timeout) end

टेबल _M एक क्लास को सिमुलेट करता है जिसे एकल सदस्य चर _VERSION के साथ आरंभ किया जाता है और बाद में _M.set_timeout जैसे सदस्य फ़ंक्शन को परिभाषित करता है। कंस्ट्रक्टर _M.new(self) में, हम एक टेबल लौटाते हैं जिसका मेटाटेबल mt है, और mt का __index मेटामेथड _M को इंगित करता है ताकि लौटाया गया टेबल क्लास _M के उदाहरण को सिमुलेट करे।

सारांश

खैर, आज के लिए मुख्य सामग्री यहीं समाप्त होती है। टेबल और मेटाटेबल OpenResty के lua-resty-* लाइब्रेरी और OpenResty-आधारित ओपन सोर्स परियोजनाओं में भारी मात्रा में उपयोग किए जाते हैं। मुझे आशा है कि यह पाठ आपके लिए स्रोत कोड को पढ़ने और समझने में आसान बना देगा।

Lua में टेबल के अलावा अन्य मानक फ़ंक्शन भी हैं, जिन्हें हम अगले पाठ में एक साथ सीखेंगे।

अंत में, मैं आपको एक विचारोत्तेजक प्रश्न छोड़ना चाहता हूं। lua-resty-mysql लाइब्रेरी OO को एक परत के रूप में क्य