理解Ruby中的类

live with scopepython

序言

源起于Python开发者'公众号转载的深入理解Python中的元类一文, 回忆着本身看过的 Ruby元编程 一书, 参照写个相应的Ruby版.编程

Python和Ruby在不少方面都很是相像, 特别是Ruby的设计部分参考了Python. 但在不少方面, 它俩的不少概念并不是一一对应的. 在这里的 元类 在Ruby 中并无相应的概念, 若是理解为建立类的类, 最相近的应该是Class .api

这里不会将那篇文章的内容都复制过来, 只是挑选不同的地方写一写, 所以, 你最好已经读过那篇文章了. 读这篇时, 最好对照着读.ruby

类也是对象

相比Python, Ruby语言有着最纯粹的面向对象编程的设计. 一样的,Ruby的类的概念也是借鉴于Smalltalk. 关于什么是类, 我更倾向于理解为, 描述一个对象的状态(实例变量)和操做(方法)的代码段.函数

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

说明:翻译

  • 类默认继承自Object, 所以< Object非必要. 原文的Python代码也是.设计

  • 在Ruby 中, 在不引发歧义的前提下, 函数调用的()能够省略. 这里同原文的Python 代码虽然看起来相同, 但原理彻底不一样.Python2.7中, print实现为语句, 但在Python 3.x中, 实现为全局函数, 则必须加()表示调用.code

  • 这里的#=>表示输出的结果, 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)  #=> <class '__main__.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)  #=>#<ObjectCreator:0x...>, 新的实例对象

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 的元编程就像普通编程同样, 没有任何神秘复杂的语法. 这里真的只是冰山一角.

相关文章
相关标签/搜索