I'm spending today learning Ruby from a Python perspective. One thing I have completely failed to grapple with is an equivalent of decorators. To pare things down I'm trying to replicate a trivial Python decorator:
#! /usr/bin/env python import math def document(f): def wrap(x): print "I am going to square", x f(x) return wrap @document def square(x): print math.pow(x, 2) square(5)
Running this gives me:
I am going to square 5 25.0
So, I want to create a function square(x)
, but decorate it so it alerts me as to what it's going to square before it does it. Let's get rid of the sugar to make it more basic:
... def square(x): print math.pow(x, 2) square = document(square) ...
So, how do I replicate this in Ruby? Here's my first attempt:
#! /usr/bin/env ruby def document(f) def wrap(x) puts "I am going to square", x f(x) end return wrap end def square(x) puts x**2 end square = document(square) square(5)
Running this generates:
./ruby_decorate.rb:8:in `document': wrong number of arguments (0 for 1) (ArgumentError) from ./ruby_decorate.rb:15:in `<main>'
Which I guess is because parentheses aren't mandatory and it's taking my return wrap
as an attempt to return wrap()
. I know of no way to refer to a function without calling it.
I've tried various other things, but nothing gets me far.
So, here in this post, we are going to learn about Decorator Chaining. Chaining decorators means applying more than one decorator inside a function. Python allows us to implement more than one decorator to a function. It makes decorators useful for reusable building blocks as it accumulates several effects together.
Decorator is a structural pattern that allows adding new behaviors to objects dynamically by placing them inside special wrapper objects, called decorators. Using decorators you can wrap objects countless number of times since both target objects and decorators follow the same interface.
Decorators are a very powerful and useful tool in Python since it allows programmers to modify the behaviour of a function or class. Decorators allow us to wrap another function in order to extend the behaviour of the wrapped function, without permanently modifying it.
A decorator can be used to simply and cleanly implement retry functionality for asynchronous functions.
Here's another approach that eliminates the problem with conflicts between names of aliased methods (NOTE my other solution using modules for decoration is a good alternative too as it also avoids conflicts):
module Documenter def document(func_name) old_method = instance_method(func_name) define_method(func_name) do |*args| puts "about to call #{func_name}(#{args.join(', ')})" old_method.bind(self).call(*args) end end end
The above code works because the old_method
local variable is kept alive in the new 'hello' method by fact of define_method
block being a closure.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With