国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

理解Ruby中的類

tanglijun / 802人閱讀

摘要:序言源起于開發者公眾號轉載的深刻理解中的元類一文回憶著自己看過的元編程一書參照寫個相應的版和在很多方面都非常相像特別是的設計部分參考了但在很多方面它倆的很多概念并非一一對應的在這里的元類在中并沒有相應的概念如果理解為創建類的類最相近的應該是

live with scope

序言

源起于Python開發者"公眾號轉載的深刻理解Python中的元類一文, 回憶著自己看過的 Ruby元編程 一書, 參照寫個相應的Ruby版.

Python和Ruby在很多方面都非常相像, 特別是Ruby的設計部分參考了Python. 但在很多方面, 它倆的很多概念并非一一對應的. 在這里的 元類 在Ruby 中并沒有相應的概念, 如果理解為創建類的類, 最相近的應該是Class .

這里不會將那篇文章的內容都復制過來, 只是挑選不一樣的地方寫一寫, 因此, 你最好已經讀過那篇文章了. 讀這篇時, 最好對照著讀.

類也是對象

相比Python, Ruby語言有著最純粹的面向對象編程的設計. 同樣的,Ruby的類的概念也是借鑒于Smalltalk. 關于什么是類, 我更傾向于理解為, 描述一個對象的狀態(實例變量)和操作(方法)的代碼段.

class ObjectCreator < Object; end  #=> nil
my_object = ObjectCreator.new  #=>#
print my_object  #=>nil

說明:

類默認繼承自Object, 因此< Object非必要. 原文的Python代碼也是.

在Ruby 中, 在不引起歧義的前提下, 函數調用的()可以省略. 這里同原文的Python 代碼雖然看起來相同, 但原理完全不同.Python2.7中, print實現為語句, 但在Python 3.x中, 實現為全局函數, 則必須加()表示調用.

這里的#=>表示輸出的結果, print無輸出, 即nil來表示無

同Python, Ruby的類同樣也是對象. 不同于Python, Ruby中的class實際是打開一個類, 如果類不存在則創建它. 換句話說, 在Python中重復class定義同一類, 后者會覆蓋前者, 而在Ruby中, 類是同一個, 后者只是給這個類添加了新的方法或變量.

Python:

class N1:
  def __init__(self, name):
    self.name = name
  def hello(self, s):
    return self.name + s
N1("lzp").hello(" is good man")  #=> "lzp is good man"
class N1:
  def __init__(self, name):
    self.name = name
  def world(self, s):
    return self.name + s
N1("lzp").hello(" is good man")  #=> AttributeError, 無屬性
N1("lzp").world(" is good man")  #=> "lzp is good man"

Ruby:

class N1
  def initialize(name)
    @name = name
  end
  def hello(s)
    @name + s
  end
end
N1.new("lzp").hello(" is good man")  #=> "lzp is good man"
class N1
  def initialize(name)
    @name = name
  end
  def world(s) @name + s end
end
N1("lzp").hello(" is good man")  #=> "lzp is good man"
N1("lzp").world(" is good man")  #=> "lzp is good man"

Ruby少了無語的self, 但多了無語的end.

Ruby的函數默認返回最后一個表達式的值, 但在Python中則必須顯示地return.

Ruby的方法定義可以寫成一行,Python來咬我啊

很多語言都聲稱 _xx語言中一切都是對象_, 包括Java. 很明顯, 不同語言中的對象概念應該是有區別的, 那么如何來理解對象呢. 這里我基本同意原文中所說, 可賦值, 可拷貝, 可增加屬性, 可作參傳遞.

注意, 不要將對象和對象的引用混淆, 對象的引用往往表現為常見的各種標識符.

Rb: ObjectCreator.to_s  #=> "ObjectCreator"
Py: str(ObjectCreator)  #=> 

由于print函數實際是調用對象轉字符串后輸出, 并無特殊意義. 下面的例子更好地展示了, 作參傳遞.

Python:

def new(o):  return o()
oc1 = new(ObjectCreator)  #=><__main__.ObjectCreator at 0x...>, 新的實例對象

Ruby:

def new(o) o.new end
oc1 = new(ObjectCreator)  #=>#, 新的實例對象
Python屬性操作

Python 中有3個全局函數, 用于對象的屬性操作.

hasattr(obj, "attr_name")判斷對象是否有此屬性,

getattr(obj, "attr_name")獲取對象指定屬性,

setattr(obj, "attr_name", attr_value)則是設置指定屬性

obj.new_attr = attr_value設置屬性.

delattr(obj, "attr_name")刪除屬性.

Python中的屬性是一個寬泛的概念, 包括類變量, 實例變量, 類方法和實例方法. 這其中的區別是非常經典的, 且在不同語言中有不同的名稱, 有不同的書面寫法.

類變量, 通常指依附于類本身而非類的實例的變量, 表述的是類的狀態

實例變量, 類的每個實例有獨立的變量, 來表述實例對象的狀態

類方法, 通過類名調用的方法

實例方法, 通過類的實例對象調用的方法

在Python中, 通過給self.var_name賦值創建實例變量, 類定義中方法外賦值的非self變量都是類變量. 定義方法時, 傳遞有self參數的是實例方法, 否則為類方法.

Python:

class N2:
  class_var = 3  # 類變量, 也能通過實例對象訪問
  def __init__(self, name):
    self.name = name  #實例變量
  def hello(self, s):
    return "hello " + self.name + s
  def world(s):
    return "world " + N2.c_var + s
n2 = N2("lzp")
n2.hello(" is good man")  #=> "hello lzp is good man"
N2.hello(n2, " is good man")  #=> "hello lzp is good man"
n2.world(" lzp")  #=> 函數只要參數, 但參數多余
N2.world(" lzp")  #=> "world lzp"

Python在類的方法設計上很取巧. 就如之后所說, Python其實是沒有類方法一說的, 全部都是函數. 類的方法第一個參數是self, 像在world方法定義中, 沒有self, 方法內是不能引用實例變量的. 且此處是不是self也無所謂, 任意標識符都可以, 基于慣例使用self. 且在對象上調用方法, 本質上只是將對象作為接收者, 作為第一參數傳遞給函數. 若函數的第一參數不是self, 則在對象上調用方法會提示多余參數.

在Python中, 函數是對象, 同其他所有對象一樣. 因此大一統的去理解Python的類概念就是: 類是對象, 對象有屬性, 屬性即變量名和其對應的對象. 若對應的對象是函數對象, 則對應的變量是函數名, 其中第一參數為self的為類實例方法.

從屬性的角度重新定義N2, Python:

class N2: pass
N2.c_var = 3
def init(self, name):  self.name = name
N2.__init__ = init
N2.hello = lambda self, s: "hello" + self.name + s
N2.world = lambda s: "world " + N2.c_var + s

這讓我想起了USB, 支持熱插拔, 即插即用, 想插就插,Python老爹真任性. 這里使用了lambda來定義匿名函數.

Ruby屬性操作

Ruby沒有屬性一說, 但你也可以去寬泛地去理解. 相反的,Ruby的類變量, 實例變量, 類方法和實例方法是清晰地分開的, 畢竟是純粹地面向對象. 另一個,Ruby其實沒有函數一說, 所有函數都有其所屬的類, 沒有多帶帶的函數, 或者說Ruby只有方法. 關于屬性, 另一個其他面向對象語言中相似的概念是 _域_, 就是在類中占塊地, 放變量還是函數都行.

Ruby:

class N2
  @c_i_var = 1  #類的實例變量
  @@c_var = 3   #類變量, 子類可繼承
  def initialize(name)
    @name = name
  end
  def hello(s)
    "hello" + @name + s
  end
  def self.world(s)  #類方法
    "world " + @@c_var.to_s + s
  end
end
N2.new("lzp").hello(" is good man")  #=> "lzp is good man"
N2.world(" is good man") #=> "world 3 is good man"

在這里, 類的實例變量可以理解為類作為對象的實例變量. 實例變量是專屬于對象的. 而類變量則是屬于整個類體系的, 即它的所有子類都可以訪問.

回到原文, Python中的屬性對應Ruby的多個概念. 因此對屬性的操作也是分不同的在進行.

Ruby:

n1 = N1.new("lzp")
n1.instance_variables  # 返回所有實例變量
n1.instance_variable_set("@age", 3)  #=> 設置實例變量
n1.instance_variable_get("@age")  #=>實例變量
n1.instance_variable_defined?("@age")  #=> 判斷有無
n1.class_variables  # 返回所有類變量
n1.class_variable_get/set/defined?  #同上
N1.instance_methods(false)  # 列出所有非繼承的實例方法
N1.singleton_methods  # 列出所有非繼承的類方法

這里的singleton_methods可以理解為類方法. 但嚴格地說, 它是專屬于對象的方法. 若專屬于類, 則成為類方法. 換句話說,Ruby沒有類方法一說, 稱為單件方法.

Ruby中, 一切皆對象. 因此有必要來理解下Ruby的對象模型, 詳細地建議看 _Ruby元編程_一書.

對象由狀態, 所屬類的引用和操作構成. 狀態和操作都是專屬的, 只能由本對象進行.運算. 普通對象的狀態即實例變量, 操作即單件方法, 類對象的狀態即類的實例變量即類變量, 類對象的操作即類的單件方法即類方法, 其實本質是相同的. 每個對象都存儲有對所屬類的引用, 以此來知曉可調用的實例方法.

所謂所屬類的引用, 很簡單, 在對象上調用#class方法即可

1.class  #=> Fixnum
"1".class  #=> String
Fixnum.class  #=> Class
String.class  #=> Class

在后文會看到Python中相應的概念type.

動態地創建類

Ruby也能在函數中創建類.

def choose_class(name)
  if (name=="foo")
    Class.new {def hello "hello" end}
  else
    Class.new {def world "world" end}
  end
end
MyClass = choose_class("foo")
MyClass.new.hello  #=> "hello"

這里不能使用原文中相似的class, 會提示不能在def中定義類. 不得不提前使用大招Class.new.

之前寫到String.classClass, 也就是說, 在Ruby中, 所有的類都是Class的對象. 注意大小寫. 自然, 創建新的類, 也就是創建Class的實例對象, 使用new操作, 同其他所有類一樣. 不過創建的是匿名類, 賦值給一個首字母大寫的常量名即可.

a = Class.new
a.name  #=> nil
a.new.class  #=> xxx
A = a
A.name  #=> A
A.new.class  #=> A

好了, 原文進行到了Python的所有類的type都是type. 在本質上, 一切類的生成都是通過調用type進行的.

將上述Ruby代碼原樣翻譯過來, 對應的Python代碼為:

def choose_class(name):
  if name=="foo":
    return type("Foo", (), {"hello": lambda self:"hello"})
  else:
    return type("Bar", (), {"world": lambda self:"world"})
MyClass = choose_class("foo")
MyClass().hello()  #=> "hello"

解釋下參數, 第一個是類名字符串, 第二個基類的元組,Python支持多繼承, 可以有多個基類, 所謂的基類可以理解為超類, 父類等概念. 第三個是屬性, 由前所知, 類中的一切都是屬性. 如此即可定義一個新類.

但不同于Ruby, type的第一個參數即類名, 跟MyClass無關, 即賦值不會改變類名. 但Ruby是在將類對象第一次賦值給常量時生成類名的, 之后賦值也不會改變.

在Ruby中, Class.new(superclass)來表示繼承類.Ruby中只支持單繼承, 通過模塊來添加不同的功能.

前文提到,Ruby的類有打開性質, 給類添加方法和變量是非常方便.

到底什么是元類

這里需要先普及幾個常用的操作:

Python:

a = 1
a.__class__  #=> int, 對象的類
type(1)  #=> int
int.__base__  #=> object, 類的基類
int.__bases__  #=> (object,), 類的基類元組

Ruby:

1.class  #=> Fixnum, 對象的類
Fixnum.superclass  #=> Integer, 類的超類
Fixnum.ancestors  #=> [Fixnum, Integer, Numeric, Comparable, Object, Kernel, BasicObject], 類的祖先鏈

所謂祖先鏈, 即類, 類的超類, 類的超類的超類, ...一直到最初始的類, 即BasicObject. 其實, 在1.9之前, 所有類都是繼承自Object, 后來又在前面加入了BasicObject, 個人猜測是為了所謂潔凈室技術吧.

原文提到, 不斷地調用.__class__屬性, 最終會到達type類型 ,Ruby中對應的, 不斷調用.class方法, 最終會到達Class類型. 原文中可以從type繼承, 來創建元類. 但在Ruby中是不能創建Class的子類.

原文提到的__metaclass__屬性, 我思考了很久, 基本確認Ruby中沒有相似的概念. 就舉的將屬性名大寫的例子而言, 應該是在用class定義類時, 會自動調用這個屬性(所引用的函數對象). 初步看, 有種鉤子方法的感覺. 就是"定義類"這個事件發生時, 會自動觸發執行__metaclass__屬性.

Ruby也有一些鉤子方法:

included表示模塊被包含時執行,

extended表示模塊被后擴展時執行,

prepended表示模塊被前擴展時執行,

inherited表示類被繼承時執行,

method_missing表示對象調用不存在的方法時執行.

但目前沒找到當定義類時被執行的鉤子方法. 所以像原文的大寫屬性名的操作, 還真不知道如何進行. 但事實上,Ruby的對應屬性的標識符有嚴格的規定, 不可能大寫首字母. 如類變量@@var, 實例變量@var, 方法名two_method.

但如果實現不了這個, 總覺得Ruby有種被比下去的感覺, 雖然大寫所有屬性首字母的操作似乎沒有意義.

class N
  def hello; "hello"; end
  instance_methods(false).each {|x| alias_method x.capitalize, x; remove_method x}
end
N.new.Hello  #=> "hello"
N.new.hello  #=> 方法未定義

這是大寫所有實例方法名的首字母, 核心的思想是, 為原方法建立新的別名, 再刪掉原方法. 同Python一樣,Ruby的類是在執行代碼.

class N; puts "hello"; end  #=> "hello"

Ruby:

class N
  def self.world; "world"; end
  class << self
    instance_methods(false).each {|x| alias_method x.capitalize, x; remove_method x}
  end
end
N.World  #=> "world"
N.world  #=> 方法未定義

這是大寫所有的類方法名的首字母.

class N
  @name = "lzp"
  instance_variables.each {|x| instance_variable_set("@"+x.to_s[/w+/].capitalize, @name); @name = nil}
end
N.class_eval {@Name}  #=> "lzp"
N.class_eval {@name}  #=> nil

這是大寫所有的類的實例變量.

由于Ruby的實例變量默認是不能從外部訪問的, 不得不使用.class_eval來打開類的上下文.

不存在如何大寫所有實例變量的代碼, 因此在類實例化前, 實例對象的實例變量是不存在的.

好吧, 我承認, 這實現的很別扭. 在同一操作的表述上, 不同語言有不同的書面寫法, 也自然有簡單有繁雜.

函數式特性

談點別的, 有關函數式特性, 使用map/filter/reduce.

Python:

a = ["he", "hk", "ok"]
list(map(lambda x: x*2, a))  #=>["hehe", "hkhk", "okok"]
list(filter(lambda x: x.startswith("h"), a))  #=> ["he", "hk"]
import functiontools.reduce
reduce(lambda x,y: x+":"+y, a)  #=> "he:hk:ok"

用上述函數來替換原文中的語句:

dict(map(lambda i: (i[0].upper(), i[1]), filter(lambda i: not i[0].startswith("__"), future_class_attr.items())))

好吧, 我承認我的Python技術真不高, 如果真寫成一行, 完全看不懂了, 原文作者那樣寫更清晰簡潔易懂, 當然更主要的是, 用map/filter會引入新的難點, 容易偏離主題.

希望有高手能告訴我, 將一個類的所有非"__"的屬性的鍵變為大寫如何以更函數式的方式表達出來.

Ruby:

a = ["he", "hk", "ok"]
a.map {|x| x*2}  #=> ["hehe", "hkhk", "okok"]
a.select {|x| x.start_with? "h"}  #=> ["he", "hk"]
a.reject {|x| x.start_with? "h"}  #=> ["ok"]
a.reduce {|sum, x| sum + ":" + x}  #=> "he:hk:ok"

同樣的, 用上述來替換原文的代碼.

future_class_attr.reject {|k,v| k.start_with? "__"}.map {|k,v| k.upcase}

Python3.x刪除了reduce函數, 推薦使用for循環, 也可以使用funtools.reduce. 這跟Ruby完全不同,Ruby提倡使用each, map等迭代, 而for在底層也是在調用each.

一切皆對象.

Python和Ruby都號稱一切皆對象, 但很明顯兩個的對象概念并不完全對等.

Py: 1.__class__  #=> 語法錯誤
Py: a = 1; a.__class__  #=> int
Rb: 1.class  #=> Fixnum
Py: 1.real  #=> 語法錯誤
Py: b = 1; b.real  #=> 1
Rb: 1.real  #=> 1
Py: "lzp".upper()  #=>"LZP", 但在ipython中不補全方法
Py: s = "lzp"; s.upper()  #補全
Rb: "lzp".upcase  #=> "LZP", 補全

以上說明, 對對象和對象的引用調用方法是有區別的, 具體什么原理以及詳細的區別, 我說明不了.

def hello(name): return "hello" + name
hello.__class__  #=> function

Ruby的方法不是對象, 不能賦值, 不能為參傳遞.

def hello(name); "hello" + name; end
hello.class  #=> 參數錯誤
new_hello = hello  #=> 參數錯誤
def echo(o); o(); end
echo(hello)  #=> 參數錯誤

你是不是覺得問題挺大的, 這幾種對象的特征竟然都不滿足. 但這些其實是一個錯誤, 前文有提到, 對于Ruby的方法調用, 在不引起歧義的情況下, ()是可以省略. 在這里, 所有出現hello的位置都默認你在調用方法, 但方法定義有參數, 你不傳遞參數, 所以錯誤是同一個, 少參數.

函數作為對象最終用處都是被調用, 因此, 只從表面來看, Ruby中通過def定義的方法不是對象. 但本質上, 在Ruby中, 出現方法名的地方全被視為對方法的調用, 也就是說, hello是方法調用, 而不是方法引用, 并不表征方法本身. 那么如何獲取方法本身的對象呢?

new_hello = method :hello
new_hello.call("lzp")  #=> "hellolzp"
new_hello.("lzp")  #=> "hellolzp"
new_hello["lzp"]  #=> "hellolzp"
new_hello.class  #=> Method
new_2_hello = new_hello

注意, 在這里可以看出, 在絕大部分語言中, ()都是函數調用的標志. 但在Ruby中, ()只是在有歧義情況下, 區分哪個參數是哪個函數的. 因此, 當函數作為對象時, 不得不創建新的表示調用的標志, 在這里是.call, [], .().

函數并不是唯一的可調用對象.

hello = lambda {|name| "hello" + name}
hello = ->(name) {"hello" + name}
hello = proc {|name| "hello" + name}
hello = Proc.new {|name| "hello" + name}
后記

事實上, Class.new 屬于 Ruby 元編程的一部分, 但 Ruby 的元編程就像普通編程一樣, 沒有任何神秘復雜的語法. 這里真的只是冰山一角.

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/37983.html

相關文章

  • [譯]軟件的復雜性:命名的藝術

    摘要:軟件的復雜性命名的藝術在計算機科學中只有兩件困難的事情緩存失效和命名規范。到目前為止,我們依然將看做為開發人員找不到合適命名的一種替代方式。 軟件的復雜性:命名的藝術 在計算機科學中只有兩件困難的事情:緩存失效和命名規范。—— Phil Karlton 前言 編寫優質代碼本身是一件很困難的事情,為什么這么說?因為良好的編碼風格是為了能更好的理解與閱讀。通常我們會只注重前者,而忽略了后者...

    chunquedong 評論0 收藏0
  • [譯]軟件的復雜性:命名的藝術

    摘要:軟件的復雜性命名的藝術在計算機科學中只有兩件困難的事情緩存失效和命名規范。到目前為止,我們依然將看做為開發人員找不到合適命名的一種替代方式。 軟件的復雜性:命名的藝術 在計算機科學中只有兩件困難的事情:緩存失效和命名規范。—— Phil Karlton 前言 編寫優質代碼本身是一件很困難的事情,為什么這么說?因為良好的編碼風格是為了能更好的理解與閱讀。通常我們會只注重前者,而忽略了后者...

    Integ 評論0 收藏0
  • [譯]軟件的復雜性:命名的藝術

    摘要:軟件的復雜性命名的藝術在計算機科學中只有兩件困難的事情緩存失效和命名規范。到目前為止,我們依然將看做為開發人員找不到合適命名的一種替代方式。 軟件的復雜性:命名的藝術 在計算機科學中只有兩件困難的事情:緩存失效和命名規范。—— Phil Karlton 前言 編寫優質代碼本身是一件很困難的事情,為什么這么說?因為良好的編碼風格是為了能更好的理解與閱讀。通常我們會只注重前者,而忽略了后者...

    Lowky 評論0 收藏0
  • [譯]軟件的復雜性:命名的藝術

    摘要:軟件的復雜性命名的藝術在計算機科學中只有兩件困難的事情緩存失效和命名規范。到目前為止,我們依然將看做為開發人員找不到合適命名的一種替代方式。 軟件的復雜性:命名的藝術 在計算機科學中只有兩件困難的事情:緩存失效和命名規范。—— Phil Karlton 前言 編寫優質代碼本身是一件很困難的事情,為什么這么說?因為良好的編碼風格是為了能更好的理解與閱讀。通常我們會只注重前者,而忽略了后者...

    daydream 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<