Ruby Python 修饰器,Ruby 怎么写?

chenge · 2014年03月21日 · 最后由 michael 回复于 2015年07月09日 · 6465 次阅读

今天看到陈老师的好文《Python 修饰器的函数式编程》http://coolshell.cn/articles/11265.html

Ruby 怎么写?

#1 楼 @quakewang 感觉不错,学习了。谢谢!

ruby 的 method 不是 object,所以不能像 python 那样,传入一个 function 再传出一个 function。类型系统的一大败笔

#3 楼 @reus Ruby method 可以是对象 http://www.ruby-doc.org/core-2.1.1/Method.html 顺便山寨一下 makeHtmlTag 那个例子

module Decrorator

    def wrap method_name
        WarppedObj.new method_name, self
    end

    class WarppedObj

        def initialize method_name, context
            @method_name = method_name
            @context = context
            context.class_eval do 
                @@_warped_counter ||= {}
                @@_warped_counter[method_name] ||= 0
            end
        end

        def with method_name, *args
            warpped_method = @method_name
            @context.class_eval do
                @@_warped_counter[warpped_method] += 1
                alias_method :"#{@@_warped_counter[warpped_method]}_#{warpped_method}", warpped_method
                i = @@_warped_counter[warpped_method]
                define_method warpped_method do |*_args, &_blk|
                    ret = __send__ :"#{i}_#{warpped_method}", *_args, &_blk                 
                    __send__(method_name, *args) {ret}
                end
            end
        end
    end

end


class HTMLHelper
    extend Decrorator

    def makeHtmlTag tag, opts
        "<#{tag} #{opts.map {|key, value| %Q{#{key}="#{value}"}}.join(' ')}>" + yield + "</#{tag}>"
    end

    def hello
        'hello world'
    end

    wrap(:hello).with :makeHtmlTag, 'i', class: 'italic_css'
    wrap(:hello).with :makeHtmlTag, 'b', class: 'bold_css'
end

puts HTMLHelper.new.hello # <b class="bold_css"><i class="italic_css">hello world</i></b>

#4 楼 @piecehealth 这个是元编程的绝好例子,看懂的难度不小。

一点个人看法 这个方法是不错,不过貌似 Ruby 一般都不这么写,比较复杂的话,就写个语法把方法名字注入,比如 before_filter

或者简单的就类似于 lambda 或者 proc 的东西写进去,比如 scope

#4 楼 @piecehealth 那是包装了 method 的 Method,不是 method 本身。如果 method 是一个可以被引用的对象,那 decorator 就只是一个高阶函数,接受一个 method 作为参数,输出另一个 method。你可以传递 Method 对象,但 Method 对象的调用方式和普通 method 不同,不能把他们看作是同样的东西。在 method 是 object 的语言里就不需要区分。

让我想起了多年前的一个名字 AOP,面向方面编程。

#7 楼 @reus 这样算不算可以被引用的对象

def hello method
    puts "hello, #{method.name}"
    method.call
    puts "goodby, #{method.name}"
end

def foo
    puts "i am foo"
end

hello method(:foo)

#9 楼 @piecehealth 这个写法没见过,有啥说法?

#10 楼 @chenge 就是想说 ruby method 一样可以是对象,可以当参数传进别的方法。但是本质上没什么用,send 就已经很好用了。

#9 楼 @piecehealth 传递的是 Method object,不是 method。假设 ruby 的 method 是对象,且可以用~引用,则会写成

def hello m
  ...
  m
  ...
end
def foo end
hello ~foo

而不需要包装成 Method object,也不需要 call。memo decorator 会写成

def memo fn
  cache = {}
  def wrapper *args
    return cache[args] if cache.has_key? args
    ret = fn *args
    cache[args] = ret
    ret
  end
  ~wrapper
end

def fib n
  ...
end

fib = memo ~fib
fib 10 # 不需要fib.call 10

和 python 的类似。method/function 是一个 object/value 的语言就可以这样写,ruby 不是所以不能。

还有一个区别,如果 method 是 object,那在 def ... end 之后,这个 object 就已经存在了,不需要另外创建或者包装

#12 楼 @reus 因为 Python 中的 foo 跟 foo() 不是一个东西,所以用 Python 写起来更自然。 对方法的引用 Python 是 foo,而 Ruby 是 self.method(:foo),其间并没有另外创建或者包装,因为这个 method object 实际就存在,只是通过一种很不 python 的方式取 (既 self.method(:foo))。 同样,调用一个作为参数的方法 Python 也更简单,加个括号foo()就行了,Ruby 就只能傻傻的 call 了,但是我认为本质都是一样的,只是 python 的马甲 (语法糖) 更好看一些就是了。

不过话说回来,个人认为修饰器不是很 Ruby way,如#6 楼所说,当你用 Ruby 怀念修饰器的时候,肯定有别的更合适的方案,就算非要用修饰器,自己写一个满足自己需求的也不是很难,一楼已经证明。

#14 楼 @piecehealth 每次调用 method 方法都会产生新的 Method 对象。Method 只是对 method 的包装,并不是 method 本身

def foo 
end

puts method(:foo).object_id
puts method(:foo).object_id
puts method(:foo).object_id

#13 楼 @bhuztez 不单只 python,php、lua、js、golang 里的 function 都能作为实参传递或者作为返回值

#16 楼 @reus 所以它们都是 Callable Oriented Language,Ruby 才是 Object Oriented(TM) Language

#9 楼 @piecehealth

def hello(method2)
    puts "hello, #{method2.name}"
    method2.call
    puts "goodby, #{method2.name}"
end

def foo
    puts "i am foo"
end

hello method(:foo)

我简单修改了下,这不就是简单的装饰么?有什么问题?

#17 楼 @bhuztez 原来如此。那 erlang 也不能算 Object Oriented(TM) 了

#19 楼 @reus 我是说只有冒牌的才会互相攀比谁仿得像的

我写过一个 ruby decorators 的库:https://github.com/fredwu/ruby_decorators 😄

#9 楼 @piecehealth

def log(m)
    p m.name

end
class Calculator

  def self.multipy(x, y)
    x * y
  end

  def self.sum_num(n)
    1.upto(n).inject(:+)
  end
  log method(:sum_num)
  log method(:multipy)
end

Calculator.multipy(2,10)
#Calculator.new.sum_num(100)

这样基本可以,就是那个参数没法传递,可有办法么?

不用生搬硬套吧,利用 def ... end 返回的 symbol 做 meta programming 就可以了,连这样都可以:

public void static def main argc, *argv
  ...
end

#4 楼 @piecehealth 你好。刚开始看有些头晕,今天睡醒了再看一次,感觉基本看懂了。 我想把这个用在我写的免费教程的高级篇,想征求你同意,谢谢了。

#24 楼 @chenge 可以,我没想太多随便写的,你最好修改一下。ruby-china 没有站内信功能么。。。

#25 楼 @piecehealth 那就谢谢了,我会附上这个页面链接。

陈这篇文章不错

需要 登录 后方可回复, 如果你还没有账号请 注册新账号