Python's Equivalent for Ruby's Define_Method

Python's equivalent for Ruby's define_method?

Functions are first-class objects in Python and can be assigned to attributes of a class or an instance. One way to do the same thing as in the Wikipedia example is:

colours = {"black": "000",
"red": "f00",
"green": "0f0",
"yellow": "ff0",
"blue": "00f",
"magenta": "f0f",
"cyan": "0ff",
"white": "fff"}

class MyString(str):
pass

for name, code in colours.iteritems():
def _in_colour(self, code=code):
return '<span style="color: %s">%s</span>' % (code, self)
setattr(MyString, "in_" + name, _in_colour)

Python's equivalent for define_method

You were almost there; bind the bindtype value as a default value for the function argument, and you want to call get_by_name() on self:

for bindtype in bindtypes:
def _in_bindtype(self, name, bindtype=bindtype):
self.get_by_name(name, bindtype)
setattr(Package, "get_{}_by_name".format(bindtype), _in_bindtype)

I took the liberty of using str.format() to format the method name rather than use + concatenation, it is more readable I find.

Alternatively, in Python 3 you could just create functools.partialmethod() objects:

from functools import partialmethod

for bindtype in bindtypes:
setattr(Package, "get_{}_by_name".format(bindtype),
partialmethod(Package.get_by_name, bindtype=bindtype))

define_method in Python

What happens is that you set e.g. 'with_red' attribute on your MyColor instance to a local function defined in Base constructor - note that this is not a class method, just a function, and it takes 2 arguments: 'self' and 'value':

import inspect
...
s = MyColor()
print(inspect.getargspec(s.with_red))

ArgSpec(args=['self', 'value'], varargs=None, keywords=None, defaults=None)

An easy fix here would be to make this function take a single argument:

def _with_field(value):
self.colors[field] = value
return self

With this change your code produces the expected output.

Another option is to set 'with_red' attribute on the class - which makes it a method, then self is passed implicitly and you can keep _with_field signature with two arguments.

use define_method to create a method with equal on console

Note that if you have an expression

x.y=z

the Method "y=" is invoked on the object x, with the argument z.

However, if you have an expression

y=z

y is interpreted as the name of a variable, and gets assigned the value z.

Since you want the a= method to be invoked, you need a receiver. While we often can leave out the receiver, if it is 'self', Ruby can't recognize here that you want to do a method invocation. Hence you have to explicitly use 'self'.

What does Ruby have that Python doesn't, and vice versa?

You can have code in the class definition in both Ruby and Python. However, in Ruby you have a reference to the class (self). In Python you don't have a reference to the class, as the class isn't defined yet.

An example:

class Kaka
puts self
end

self in this case is the class, and this code would print out "Kaka". There is no way to print out the class name or in other ways access the class from the class definition body in Python.

define_method in a class method

When you encounter seeming conundrums such as this one, try salting your code with puts self statements:

module HashInitialized
puts "self when parsed=#{self}"
def hash_initialized(*fields)
puts "self within hash_initialized=#{self}"
define_method(:initialize) do |h|
missing = fields - h.keys
raise ArgumentError, "Not all fields set: #{missing}" if missing.any?
fields.each { |k| instance_variable_set("@#{k}", h[k]) }
end
private :initialize
end
end
#-> self when parsed=HashInitialized

class Cheese
extend HashInitialized
attr_accessor :color, :odor, :taste
hash_initialized :color, :odor, :taste
end
#-> self within hash_initialized=Cheese

As you see, self is the class Cheese, not Cheese's singleton_class. Hence, the receiver for Module#define_method is Cheese, so the method obligingly creates the instance method initialize on Cheese.

Cheese.instance_methods(false)
#=> [:color, :color=, :odor, :odor=, :taste, :taste=]

initialize is not among the instance methods created on Cheese because I modified the code slightly to make it a private method:

Cheese.private_instance_methods(false)
#=> [:initialize]

I also slightly altered the code that assigns values to the instance variables, and made the type of exception more specific.

If appropriate, you could change your argument test to:

raise ArgumentError, "Fields #{fields} and keys #{h.keys} don't match" if
(fields-h.keys).any? || (h.keys-fields).any?

You may wish to have initialize create the assessors:

module HashInitialized
def hash_initialized(*fields)
define_method(:initialize) do |h|
missing = fields - h.keys
raise ArgumentError, "Not all fields set: #{missing}" if missing.any?
fields.each do |k|
instance_variable_set("@#{k}", h[k])
self.class.singleton_class.send(:attr_accessor, k)
end
end
private :initialize
end
end

class Cheese
extend HashInitialized
hash_initialized :color, :odor, :taste
end

Cheese.new :color=>'blue', odor: 'whew!', taste: "wow!"
=> #<Cheese:0x007f97fa07d2a0 @color="blue", @odor="whew!", @taste="wow!">

How are variables bound to the body of a define_method?

Blocks in Ruby are closures: the block you pass to define_method captures the variable name itself–not its value—so that it remains in scope whenever that block is called. That's the first piece of the puzzle.

The second piece is that the method defined by define_method is the block itself. Basically, it converts a Proc object (the block passed to it) into a Method object, and binds it to the receiver.

So what you end up with is a method that has captured (is closed over) the variable name, which by the time your loop completes is set to :destroy.

Addition: The for ... in construction actually creates a new local variable, which the corresponding [ ... ].each {|name| ... } construction would not do. That is, your for ... in loop is equivalent to the following (in Ruby 1.8 anyway):

name = nil
[ :new, :create, :destroy ].each do |name|
define_method("test_#{name}") do
puts name
end
end
name # => :destroy

Python vs. Ruby for metaprogramming

There's not really a huge difference between python and ruby at least at an ideological level. For the most part, they're just different flavors of the same thing. Thus, I would recommend seeing which one matches your programming style more.

define_method and the object Class: strange behavior

Long story short, sending foo to Test.class works because:

Test.class == Class and Class.class == Class

Class.class points to itself.



Related Topics



Leave a reply



Submit