摘要:類似消息傳遞中的分發(fā)字典,對象響應行為請求。消息傳遞和點表達式方法定義在類中,而實例屬性通常在構造器中賦值,二者都是面向對象編程的基本元素。使用帶有內建對象系統語言的優(yōu)點是,消息傳遞能夠和其它語言特性,例如賦值語句無縫對接。
2.5 面向對象編程
來源:2.5 Object-Oriented Programming
譯者:飛龍
協議:CC BY-NC-SA 4.0
面向對象編程(OOP)是一種用于組織程序的方法,它組合了這一章引入的許多概念。就像抽象數據類型那樣,對象創(chuàng)建了數據使用和實現之間的抽象界限。類似消息傳遞中的分發(fā)字典,對象響應行為請求。就像可變的數據結構,對象擁有局部狀態(tài),并且不能直接從全局環(huán)境訪問。Python 對象系統提供了新的語法,更易于為組織程序實現所有這些實用的技巧。
但是對象系統不僅僅提供了便利;它也為程序設計添加了新的隱喻,其中程序中的幾個部分彼此交互。每個對象將局部狀態(tài)和行為綁定,以一種方式在數據抽象背后隱藏二者的復雜性。我們的約束程序的例子通過在約束和連接器之前傳遞消息,產生了這種隱喻。Python 對象系統使用新的途徑擴展了這種隱喻,來表達程序的不同部分如何互相關聯,以及互相通信。對象不僅僅會傳遞消息,還會和其它相同類型的對象共享行為,以及從相關的類型那里繼承特性。
面向對象編程的范式使用自己的詞匯來強化對象隱喻。我們已經看到了,對象是擁有方法和屬性的數據值,可以通過點運算符來訪問。每個對象都擁有一個類型,叫做類。Python 中可以定義新的類,就像定義函數那樣。
2.5.1 對象和類類可以用作所有類型為該類的對象的模板。每個對象都是某個特定類的實例。我們目前使用的對象都擁有內建類型,但是我們可以定義新的類,就像定義函數那樣。類的定義規(guī)定了在該類的對象之間共享的屬性和方法。我們會通過重新觀察銀行賬戶的例子,來介紹類的語句。
在介紹局部狀態(tài)時,我們看到,銀行賬戶可以自然地建模為擁有balance的可變值。銀行賬戶對象應該擁有withdraw方法,在可用的情況下,它會更新賬戶余額,并返回所請求的金額。我們希望添加一些額外的行為來完善賬戶抽象:銀行賬戶應該能夠返回它的當前余額,返回賬戶持有者的名稱,以及接受存款。
Account類允許我們創(chuàng)建銀行賬戶的多個實例。創(chuàng)建新對象實例的動作被稱為實例化該類。Python 中實例化某個類的語法類似于函數的調用語句。這里,我們使用參數"Jim"(賬戶持有者的名稱)來調用Account。
>>> a = Account("Jim")
對象的屬性是和對象關聯的名值對,它可以通過點運算符來訪問。屬性特定于具體的對象,而不是類的所有對象,也叫做實例屬性。每個Account對象都擁有自己的余額和賬戶持有者名稱,它們是實例屬性的一個例子。在更寬泛的編程社群中,實例屬性可能也叫做字段、屬性或者實例變量。
>>> a.holder "Jim" >>> a.balance 0
操作對象或執(zhí)行對象特定計算的函數叫做方法。方法的副作用和返回值可能依賴或改變對象的其它屬性。例如,deposit是Account對象a上的方法。它接受一個參數,即需要存入的金額,修改對象的balance屬性,并返回產生的余額。
>>> a.deposit(15) 15
在 OOP 中,我們說方法可以在特定對象上調用。作為調用withdraw方法的結果,要么取錢成功,余額減少并返回,要么請求被拒絕,賬戶打印出錯誤信息。
>>> a.withdraw(10) # The withdraw method returns the balance after withdrawal 5 >>> a.balance # The balance attribute has changed 5 >>> a.withdraw(10) "Insufficient funds"
像上面展示的那樣,方法的行為取決于對象屬性的改變。兩次以相同參數對withdraw的調用返回了不同的結果。
2.5.2 類的定義用戶定義的類由class語句創(chuàng)建,它只包含單個子句。類的語句定義了類的名稱和基類(會在繼承那一節(jié)討論),之后包含了定義類屬性的語句組:
class( ):
當類的語句被執(zhí)行時,新的類會被創(chuàng)建,并且在當前環(huán)境第一幀綁定到
類通常圍繞實例屬性來組織,實例屬性是名值對,不和類本身關聯但和類的每個對象關聯。通過為實例化新對象定義方法,類規(guī)定了它的對象的實例屬性。
class語句的
>>> class Account(object): def __init__(self, account_holder): self.balance = 0 self.holder = account_holder
Account的__init__方法有兩個形參。第一個是self,綁定到新創(chuàng)建的Account對象上。第二個參數,account_holder,在被調用來實例化的時候,綁定到傳給該類的參數上。
構造器將實例屬性名稱balance與0綁定。它也將屬性名稱holder綁定到account_holder上。形參account_holder是__init__方法的局部名稱。另一方面,通過最后一個賦值語句綁定的名稱holder是一直存在的,因為它使用點運算符被存儲為self的屬性。
定義了Account類之后,我們就可以實例化它:
>>> a = Account("Jim")
這個對Account類的“調用”創(chuàng)建了新的對象,它是Account的實例,之后以兩個參數調用了構造函數__init__:新創(chuàng)建的對象和字符串"Jim"。按照慣例,我們使用名稱self來命名構造器的第一個參數,因為它綁定到了被實例化的對象上。這個慣例在幾乎所有 Python 代碼中都適用。
現在,我們可以使用點運算符來訪問對象的balance和holder。
>>> a.balance 0 >>> a.holder "Jim"
身份。每個新的賬戶實例都有自己的余額屬性,它的值獨立于相同類的其它對象。
>>> b = Account("Jack") >>> b.balance = 200 >>> [acc.balance for acc in (a, b)] [0, 200]
為了強化這種隔離,每個用戶定義類的實例對象都有個獨特的身份。對象身份使用is和is not運算符來比較。
>>> a is a True >>> a is not b True
雖然由同一個調用來構造,綁定到a和b的對象并不相同。通常,使用賦值將對象綁定到新名稱并不會創(chuàng)建新的對象。
>>> c = a >>> c is a True
用戶定義類的新對象只在類(比如Account)使用調用表達式被實例化的時候創(chuàng)建。
方法。對象方法也由class語句組中的def語句定義。下面,deposit和withdraw都被定義為Account類的對象上的方法:
>>> class Account(object): def __init__(self, account_holder): self.balance = 0 self.holder = account_holder def deposit(self, amount): self.balance = self.balance + amount return self.balance def withdraw(self, amount): if amount > self.balance: return "Insufficient funds" self.balance = self.balance - amount return self.balance
雖然方法定義和函數定義在聲明方式上并沒有區(qū)別,方法定義有不同的效果。由class語句中的def語句創(chuàng)建的函數值綁定到了聲明的名稱上,但是只在類的局部綁定為一個屬性。這個值可以使用點運算符在類的實例上作為方法來調用。
每個方法定義同樣包含特殊的首個參數self,它綁定到方法所調用的對象上。例如,讓我們假設deposit在特定的Account對象上調用,并且傳遞了一個對象值:要存入的金額。對象本身綁定到了self上,而參數綁定到了amount上。所有被調用的方法能夠通過self參數來訪問對象,所以它們可以訪問并操作對象的狀態(tài)。
為了調用這些方法,我們再次使用點運算符,就像下面這樣:
>>> tom_account = Account("Tom") >>> tom_account.deposit(100) 100 >>> tom_account.withdraw(90) 10 >>> tom_account.withdraw(90) "Insufficient funds" >>> tom_account.holder "Tom"
當一個方法通過點運算符調用時,對象本身(這個例子中綁定到了tom_account)起到了雙重作用。首先,它決定了withdraw意味著哪個名稱;withdraw并不是環(huán)境中的名稱,而是Account類局部的名稱。其次,當withdraw方法調用時,它綁定到了第一個參數self上。求解點運算符的詳細過程會在下一節(jié)中展示。
2.5.3 消息傳遞和點表達式方法定義在類中,而實例屬性通常在構造器中賦值,二者都是面向對象編程的基本元素。這兩個概念很大程度上類似于數據值的消息傳遞實現中的分發(fā)字典。對象使用點運算符接受消息,但是消息并不是任意的、值為字符串的鍵,而是類的局部名稱。對象也擁有具名的局部狀態(tài)值(實例屬性),但是這個狀態(tài)可以使用點運算符訪問和操作,并不需要在實現中使用nonlocal語句。
消息傳遞的核心概念,就是數據值應該通過響應消息而擁有行為,這些消息和它們所表示的抽象類型相關。點運算符是 Python 的語法特征,它形成了消息傳遞的隱喻。使用帶有內建對象系統語言的優(yōu)點是,消息傳遞能夠和其它語言特性,例如賦值語句無縫對接。我們并不需要不同的消息來“獲取”和“設置”關聯到局部屬性名稱的值;語言的語法允許我們直接使用消息名稱。
點表達式。類似tom_account.deposit的代碼片段叫做點表達式。點表達式包含一個表達式,一個點和一個名稱:
.
內建的函數getattr也會按名稱返回對象的屬性。它是等價于點運算符的函數。使用getattr,我們就能使用字符串來查找某個屬性,就像分發(fā)字典那樣:
>>> getattr(tom_account, "balance") 10
我們也可以使用hasattr測試對象是否擁有某個具名屬性:
>>> hasattr(tom_account, "deposit") True
對象的屬性包含所有實例屬性,以及所有定義在類中的屬性(包括方法)。方法是需要特別處理的類的屬性。
方法和函數。當一個方法在對象上調用時,對象隱式地作為第一個參數傳遞給方法。也就是說,點運算符左邊值為
為了自動實現self的綁定,Python 區(qū)分函數和綁定方法。我們已經在這門課的開始創(chuàng)建了前者,而后者在方法調用時將對象和函數組合到一起。綁定方法的值已經將第一個函數關聯到所調用的實例,當方法調用時實例會被命名為self。
通過在點運算符的返回值上調用type,我們可以在交互式解釋器中看到它們的差異。作為類的屬性,方法只是個函數,但是作為實例屬性,它是綁定方法:
>>> type(Account.deposit)>>> type(tom_account.deposit)
這兩個結果的唯一不同點是,前者是個標準的二元函數,帶有參數self和amount。后者是一元方法,當方法被調用時,名稱self自動綁定到了名為tom_account的對象上,而名稱amount會被綁定到傳遞給方法的參數上。這兩個值,無論函數值或綁定方法的值,都和相同的deposit函數體所關聯。
我們可以以兩種方式調用deposit:作為函數或作為綁定方法。在前者的例子中,我們必須為self參數顯式提供實參。而對于后者,self參數已經自動綁定了。
>>> Account.deposit(tom_account, 1001) # The deposit function requires 2 arguments 1011 >>> tom_account.deposit(1000) # The deposit method takes 1 argument 2011
函數getattr的表現就像運算符那樣:它的第一個參數是對象,而第二個參數(名稱)是定義在類中的方法。之后,getattr返回綁定方法的值。另一方面,如果第一個參數是個類,getattr會直接返回屬性值,它僅僅是個函數。
實踐指南:命名慣例。類名稱通常以首字母大寫來編寫(也叫作駝峰拼寫法,因為名稱中間的大寫字母像駝峰)。方法名稱遵循函數命名的慣例,使用以下劃線分隔的小寫字母。
有的時候,有些實例變量和方法的維護和對象的一致性相關,我們不想讓用戶看到或使用它們。它們并不是由類定義的一部分抽象,而是一部分實現。Python 的慣例規(guī)定,如果屬性名稱以下劃線開始,它只能在方法或類中訪問,而不能被類的用戶訪問。
2.5.4 類屬性有些屬性值在特定類的所有對象之間共享。這樣的屬性關聯到類本身,而不是類的任何獨立實例。例如,讓我們假設銀行以固定的利率對余額支付利息。這個利率可能會改變,但是它是在所有賬戶中共享的單一值。
類屬性由class語句組中的賦值語句創(chuàng)建,位于任何方法定義之外。在更寬泛的開發(fā)者社群中,類屬性也被叫做類變量或靜態(tài)變量。下面的類語句以名稱interest為Account創(chuàng)建了類屬性。
>>> class Account(object): interest = 0.02 # A class attribute def __init__(self, account_holder): self.balance = 0 self.holder = account_holder # Additional methods would be defined here
這個屬性仍舊可以通過類的任何實例來訪問。
>>> tom_account = Account("Tom") >>> jim_account = Account("Jim") >>> tom_account.interest 0.02 >>> jim_account.interest 0.02
但是,對類屬性的單一賦值語句會改變所有該類實例上的屬性值。
>>> Account.interest = 0.04 >>> tom_account.interest 0.04 >>> jim_account.interest 0.04
屬性名稱。我們已經在我們的對象系統中引入了足夠的復雜性,我們需要規(guī)定名稱如何解析為特定的屬性。畢竟,我們可以輕易擁有同名的類屬性和實例屬性。
像我們看到的那樣,點運算符由表達式、點和名稱組成:
.
為了求解點表達式:
求出點左邊的
如果
這個值會被返回,如果它是個函數,則會返回綁定方法。
在這個求值過程中,實例屬性在類的屬性之前查找,就像局部名稱具有高于全局的優(yōu)先級。定義在類中的方法,在求值過程的第三步綁定到了點運算符的對象上。在類中查找名稱的過程有額外的差異,在我們引入類繼承的時候就會出現。
賦值。所有包含點運算符的賦值語句都會作用于右邊的對象。如果對象是個實例,那么賦值就會設置實例屬性。如果對象是個類,那么賦值會設置類屬性。作為這條規(guī)則的結果,對對象屬性的賦值不能影響類的屬性。下面的例子展示了這個區(qū)別。
如果我們向賬戶實例的具名屬性interest賦值,我們會創(chuàng)建屬性的新實例,它和現有的類屬性具有相同名稱。
>>> jim_account.interest = 0.08
這個屬性值會通過點運算符返回:
>>> jim_account.interest 0.08
但是,類屬性interest會保持為原始值,它可以通過所有其他賬戶返回。
>>> tom_account.interest 0.04
類屬性interest的改動會影響tom_account,但是jim_account的實例屬性不受影響。
>>> Account.interest = 0.05 # changing the class attribute >>> tom_account.interest # changes instances without like-named instance attributes 0.05 >>> jim_account.interest # but the existing instance attribute is unaffected 0.082.5.5 繼承
在使用 OOP 范式時,我們通常會發(fā)現,不同的抽象數據結構是相關的。特別是,我們發(fā)現相似的類在特化的程度上有區(qū)別。兩個類可能擁有相似的屬性,但是一個表示另一個的特殊情況。
例如,我們可能希望實現一個活期賬戶,它不同于標準的賬戶。活期賬戶對每筆取款都收取額外的 $1,并且具有較低的利率。這里,我們演示上述行為:
>>> ch = CheckingAccount("Tom") >>> ch.interest # Lower interest rate for checking accounts 0.01 >>> ch.deposit(20) # Deposits are the same 20 >>> ch.withdraw(5) # withdrawals decrease balance by an extra charge 14
CheckingAccount是Account的特化。在 OOP 的術語中,通用的賬戶會作為CheckingAccount的基類,而CheckingAccount是Account的子類(術語“父類”和“超類”通常等同于“基類”,而“派生類”通常等同于“子類”)。
子類繼承了基類的屬性,但是可能覆蓋特定屬性,包括特定的方法。使用繼承,我們只需要關注基類和子類之間有什么不同。任何我們在子類未指定的東西會自動假設和基類中相同。
繼承也在對象隱喻中有重要作用,不僅僅是一種實用的組織方式。繼承意味著在類之間表達“is-a”關系,它和“has-a”關系相反。活期賬戶是(is-a)一種特殊類型的賬戶,所以讓CheckingAccount繼承Account是繼承的合理使用。另一方面,銀行擁有(has-a)所管理的銀行賬戶的列表,所以二者都不應繼承另一個。反之,賬戶對象的列表應該自然地表現為銀行賬戶的實例屬性。
2.5.6 使用繼承我們通過將基類放置到類名稱后面的圓括號內來指定繼承。首先,我們提供Account類的完整實現,也包含類和方法的文檔字符串。
>>> class Account(object): """A bank account that has a non-negative balance.""" interest = 0.02 def __init__(self, account_holder): self.balance = 0 self.holder = account_holder def deposit(self, amount): """Increase the account balance by amount and return the new balance.""" self.balance = self.balance + amount return self.balance def withdraw(self, amount): """Decrease the account balance by amount and return the new balance.""" if amount > self.balance: return "Insufficient funds" self.balance = self.balance - amount return self.balance
CheckingAccount的完整實現在下面:
>>> class CheckingAccount(Account): """A bank account that charges for withdrawals.""" withdraw_charge = 1 interest = 0.01 def withdraw(self, amount): return Account.withdraw(self, amount + self.withdraw_charge)
這里,我們引入了類屬性withdraw_charge,它特定于CheckingAccount類。我們將一個更低的值賦給interest屬性。我們也定義了新的withdraw方法來覆蓋定義在Account對象中的行為。類語句組中沒有更多的語句,所有其它行為都從基類Account中繼承。
>>> checking = CheckingAccount("Sam") >>> checking.deposit(10) 10 >>> checking.withdraw(5) 4 >>> checking.interest 0.01
checking.deposit表達式是用于存款的綁定方法,它定義在Account類中,當 Python 解析點表達式中的名稱時,實例上并沒有這個屬性,它會在類中查找該名稱。實際上,在類中“查找名稱”的行為會在原始對象的類的繼承鏈中的每個基類中查找。我們可以遞歸定義這個過程,為了在類中查找名稱:
如果類中有帶有這個名稱的屬性,返回屬性值。
否則,如果有基類的話,在基類中查找該名稱。
在deposit中,Python 會首先在實例中查找名稱,之后在CheckingAccount類中。最后,它會在Account中查找,這里是deposit定義的地方。根據我們對點運算符的求值規(guī)則,由于deposit是在checking實例的類中查找到的函數,點運算符求值為綁定方法。這個方法以參數10調用,這會以綁定到checking對象的self和綁定到10的amount調用deposit方法。
對象的類會始終保持不變。即使deposit方法在Account類中找到,deposit以綁定到CheckingAccount實例的self調用,而不是Account的實例。
譯者注:CheckingAccount的實例也是Account的實例,這個說法是有問題的。
調用祖先。被覆蓋的屬性仍然可以通過類對象來訪問。例如,我們可以通過以包含withdraw_charge的參數調用Account的withdraw方法,來實現CheckingAccount的withdraw方法。
要注意我們調用self.withdraw_charge而不是等價的CheckingAccount.withdraw_charge。前者的好處就是繼承自CheckingAccount的類可能會覆蓋支取費用。如果是這樣的話,我們希望我們的withdraw實現使用新的值而不是舊的值。
2.5.7 多重繼承Python 支持子類從多個基類繼承屬性的概念,這是一種叫做多重繼承的語言特性。
假設我們從Account繼承了SavingsAccount,每次存錢的時候向客戶收取一筆小費用。
>>> class SavingsAccount(Account): deposit_charge = 2 def deposit(self, amount): return Account.deposit(self, amount - self.deposit_charge)
之后,一個聰明的總經理設想了AsSeenOnTVAccount,它擁有CheckingAccount和SavingsAccount的最佳特性:支取和存入的費用,以及較低的利率。它將儲蓄賬戶和活期存款賬戶合二為一!“如果我們構建了它”,總經理解釋道,“一些人會注冊并支付所有這些費用。甚至我們會給他們一美元。”
>>> class AsSeenOnTVAccount(CheckingAccount, SavingsAccount): def __init__(self, account_holder): self.holder = account_holder self.balance = 1 # A free dollar!
實際上,這個實現就完整了。存款和取款都需要費用,使用了定義在CheckingAccount和SavingsAccount中的相應函數。
>>> such_a_deal = AsSeenOnTVAccount("John") >>> such_a_deal.balance 1 >>> such_a_deal.deposit(20) # $2 fee from SavingsAccount.deposit 19 >>> such_a_deal.withdraw(5) # $1 fee from CheckingAccount.withdraw 13
就像預期那樣,沒有歧義的引用會正確解析:
>>> such_a_deal.deposit_charge 2 >>> such_a_deal.withdraw_charge 1
但是如果引用有歧義呢,比如withdraw方法的引用,它定義在Account和CheckingAccount中?下面的圖展示了AsSeenOnTVAccount類的繼承圖。每個箭頭都從子類指向基類。
對于像這樣的簡單“菱形”,Python 從左到右解析名稱,之后向上。這個例子中,Python 按下列順序檢查名稱,直到找到了具有該名稱的屬性:
AsSeenOnTVAccount, CheckingAccount, SavingsAccount, Account, object
繼承順序的問題沒有正確的解法,因為我們可能會給某個派生類高于其他類的優(yōu)先級。但是,任何支持多重繼承的編程語言必須始終選擇同一個順序,便于語言的用戶預測程序的行為。
擴展閱讀。Python 使用一種叫做 C3 Method Resolution Ordering 的遞歸算法來解析名稱。任何類的方法解析順序都使用所有類上的mro方法來查詢。
>>> [c.__name__ for c in AsSeenOnTVAccount.mro()] ["AsSeenOnTVAccount", "CheckingAccount", "SavingsAccount", "Account", "object"]
這個用于查詢方法解析順序的算法并不是這門課的主題,但是 Python 的原作者使用一篇原文章的引用來描述它。
2.5.8 對象的作用Python 對象系統為使數據抽象和消息傳遞更加便捷和靈活而設計。類、方法、繼承和點運算符的特化語法都可以讓我們在程序中形成對象隱喻,它能夠提升我們組織大型程序的能力。
特別是,我們希望我們的對象系統在不同層面上促進關注分離。每個程序中的對象都封裝和管理程序狀態(tài)的一部分,每個類語句都定義了一些函數,它們實現了程序總體邏輯的一部分。抽象界限強制了大型程序不同層面之間的邊界。
面向對象編程適合于對系統建模,這些系統擁有相互分離并交互的部分。例如,不同用戶在社交網絡中互動,不同角色在游戲中互動,以及不同圖形在物理模擬中互動。在表現這種系統的時候,程序中的對象通常自然地映射為被建模系統中的對象,類用于表現它們的類型和關系。
另一方面,類可能不會提供用于實現特定的抽象的最佳機制。函數式抽象提供了更加自然的隱喻,用于表現輸入和輸出的關系。一個人不應該強迫自己把程序中的每個細微的邏輯都塞到類里面,尤其是當定義獨立函數來操作數據變得十分自然的時候。函數也強制了關注分離。
類似 Python 的多范式語言允許程序員為合適的問題匹配合適的范式。為了簡化程序,或使程序模塊化,確定何時引入新的類,而不是新的函數,是軟件工程中的重要設計技巧,這需要仔細關注。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/38139.html
摘要:對象表示信息,但是同時和它們所表示的抽象概念行為一致。通過綁定行為和信息,對象提供了可靠獨立的日期抽象。名稱來源于實數在中表示的方式浮點表示。另一方面,對象可以表示很大范圍內的分數,但是不能表示所有有理數。 2.1 引言 來源:2.1 Introduction 譯者:飛龍 協議:CC BY-NC-SA 4.0 在第一章中,我們專注于計算過程,以及程序設計中函數的作用。我們看到了...
摘要:以這種方式實現對象系統的目的是展示使用對象隱喻并不需要特殊的編程語言。我們的實現并不遵循類型系統的明確規(guī)定。反之,它為實現對象隱喻的核心功能而設計。是分發(fā)字典,它響應消息和。 2.6 實現類和對象 來源:2.6 Implementing Classes and Objects 譯者:飛龍 協議:CC BY-NC-SA 4.0 在使用面向對象編程范式時,我們使用對象隱喻來指導程序...
摘要:為通用語言設計解釋器的想法可能令人畏懼。但是,典型的解釋器擁有簡潔的通用結構兩個可變的遞歸函數,第一個求解環(huán)境中的表達式,第二個在參數上調用函數。這一章接下來的兩節(jié)專注于遞歸函數和數據結構,它們是理解解釋器設計的基礎。 3.1 引言 來源:3.1 Introduction 譯者:飛龍 協議:CC BY-NC-SA 4.0 第一章和第二章描述了編程的兩個基本元素:數據和函數之間的...
摘要:我們是一個大型開源社區(qū),旗下群共余人,數量超過個,網站日超過,擁有博客專家和簡書程序員優(yōu)秀作者認證。我們組織公益性的翻譯活動學習活動和比賽組隊活動,并和等國內著名開源組織保持良好的合作關系。 Special Sponsors showImg(https://segmentfault.com/img/remote/1460000018907426?w=1760&h=200); 我們是一個...
閱讀 1523·2021-09-22 15:35
閱讀 2005·2021-09-14 18:04
閱讀 876·2019-08-30 15:55
閱讀 2449·2019-08-30 15:53
閱讀 2680·2019-08-30 12:45
閱讀 1203·2019-08-29 17:01
閱讀 2577·2019-08-29 15:30
閱讀 3514·2019-08-29 15:09