Ruby as a DSL in Python
I am currently writing my first larger project in Python and I am now wondering how to define a class method so that you can execute it in the class cube of the class subclass.
First, to give some more context, dropped out (I removed everything that is not important for this question), an example of how I would do what I am trying to do in Ruby: If I define a class Item
like this:
class Item
def initialize(data={})
@data = data
end
def self.define_field(name)
define_method("#{name}"){ instance_variable_get("@data")[name.to_s] }
define_method("#{name}=") do |value|
instance_variable_get("@data")[name.to_s] = value
end
end
end
I can use it like this:
class MyItem < Item
define_field("name")
end
item = MyItem.new
item.name = "World"
puts "Hello #{item.name}!"
Now I was trying to achieve something similar in Python, but I'm not happy with the result I have so far:
class ItemField(object):
def __init__(self, name):
self.name = name
def __get__(self, item, owner=None):
return item.values[self.name]
def __set__(self, item, value):
item.values[self.name] = value
def __delete__(self, item):
del item.values[self.name]
class Item(object):
def __init__(self, data=None):
if data == None: data = {}
self.values = data
for field in type(self).fields:
self.values[field.name] = None
setattr(self, field.name, field)
@classmethod
def define_field(cls, name):
if not hasattr(cls, "fields"): cls.fields = []
cls.fields.append(ItemField(name, default))
Now I don't know how I can call define_field
using the body of the subclass. This is what I wanted it to be possible:
class MyItem(Item):
define_field("name")
item = MyItem({"name": "World"})
puts "Hello {}!".format(item.name)
item.name = "reader"
puts "Hello {}!".format(item.name)
Here's this similar question , but none of the answers are really satisfying, someone recommends assigning a function with __func__()
, but I think I can't do that because I can't get a class reference from my anonymous body (please , correct me if I am wrong about this). Someone else pointed out that it is better to use a module level function for this, which in my opinion would be the easiest way, however the main intent I do is to keep the subclassing implementation clean and load that module function Don't be nice. (Also I will need to make a function call outside of the class body, and I don't know, but I think it's messy.)
So basically I think my approach is wrong because Python was not designed to be done. What would be the best way to achieve something like the Ruby with Python example?
(If there is no better way, I was already thinking that I have a method in a subclass that returns an array of parameters for the method define_field
.)
source to share
Perhaps calling a class method is not the correct way. I'm not entirely sure how and when Python creates classes, but I'm assuming the class object doesn't exist yet when you call the class method to create the attribute.
It sounds like you want to create something like a post. First, note that Python allows you to add attributes to the classes you create after creation:
class Foo(object):
pass
>>> foo = Foo()
>>> foo.x = 42
>>> foo.x
42
Perhaps you want to restrict what attributes the user can set. There is one way.
class Item(object):
def __init__(self):
if type(self) is Item:
raise NotImplementedError("Item must be subclassed.")
def __setattr__(self, name, value):
if name not in self.fields:
raise AttributeError("Invalid attribute name.")
else:
self.__dict__[name] = value
class MyItem(Item):
fields = ("foo", "bar", "baz")
So to:
>>> m = MyItem()
>>> m.foo = 42 # works
>>> m.bar = "hello" # works
>>> m.test = 12 # raises AttributeError
Finally, the above allows you to custom subclass Item
without definition fields
, like so:
class MyItem(Item):
pass
This will result in a cryptic attribute error indicating that the attribute was fields
not found. You can require the attribute to fields
be defined at class creation time using metaclasses. Additionally, you can abstract away the need for the user to specify the metaclass by inheriting from the superclass you wrote to use the metaclass:
class ItemMetaclass(type):
def __new__(cls, clsname, bases, dct):
if "fields" not in dct:
raise TypeError("Subclass must define 'fields'.")
return type.__new__(cls, clsname, bases, dct)
class Item(object):
__metaclass__ = ItemMetaclass
fields = None
def __init__(self):
if type(self) == Item:
raise NotImplementedError("Must subclass Type.")
def __setattr__(self, name, value):
if name in self.fields:
self.__dict__[name] = value
else:
raise AttributeError("The item has no such attribute.")
class MyItem(Item):
fields = ("one", "two", "three")
source to share
You're almost there! If I understand you correctly:
class Item(object):
def __init__(self, data=None):
fields = data or {}
for field, value in data.items():
if hasattr(self, field):
setattr(self, field, value)
@classmethod
def define_field(cls, name):
setattr(cls, name, None)
EDIT: As far as I know, it is not possible to access a specific class while defining it. However, you can call a method by method __init__
:
class Something(Item):
def __init__(self):
type(self).define_field("name")
But then you just reinvent the wheel.
source to share
When defining a class, you cannot refer to the class itself within your own definition block. Therefore, after defining it, you must call define_field(...)
on MyItem
. For example.
class MyItem(Item):
pass
MyItem.define_field("name")
item = MyItem({"name": "World"})
print("Hello {}!".format(item.name))
item.name = "reader"
print("Hello {}!".format(item.name))
source to share