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
Getting List of Lists into Pandas Dataframe
How to Get Reproducible Results in Keras
What Is the Performance Impact of Non-Unique Indexes in Pandas
Fastapi Runs API-Calls in Serial Instead of Parallel Fashion
Open File in a Relative Location in Python
Using Headers with the Python Requests Library's Get Method
Django - No Such Table: Main.Auth_User_Old
What Is the Cause of the Bad Request Error When Submitting Form in Flask Application
Proper Indentation for Multiline Strings
Cosine Similarity Between 2 Number Lists
Import Pandas Dataframe Column as String Not Int
Find All Combinations of a List of Numbers with a Given Sum
Make Executable File from Multiple Pyx Files Using Cython
Matplotlib Does Not Show My Drawings Although I Call Pyplot.Show()
How to Parse a Time String Containing Milliseconds in It with Python