2015-01-09 40 views
3

我正在用Python編寫我的第一個更大的項目,現在我想知道如何定義一個類方法,以便您可以在類的子類的類體中執行它。在Python中像DSL一樣的Ruby

首先給一些更多的上下文,鬆懈下來(我刪除了這個問題的一切非必要的)我如何做我想做的事情的例子Ruby: 如果我定義類Item類似這樣的:

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 

我可以這樣使用它:

class MyItem < Item 
    define_field("name") 
end 

item = MyItem.new 
item.name = "World" 
puts "Hello #{item.name}!" 

現在爲止我試圖實現的Python類似的東西,但我不愉快的結果,到目前爲止,我有:

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)) 

現在我不知道如何使用子類的主體調用define_field。這就是我想這是可能的:

class MyItem(Item): 
    define_field("name") 

item = MyItem({"name": "World"}) 
puts "Hello {}!".format(item.name) 
item.name = "reader" 
puts "Hello {}!".format(item.name) 

this similar question但沒有一個答案是真正滿意,有人建議用__func__() caling的功能,但我想我不能這樣做,因爲我不能從匿名內部獲得對類的引用(如果我對此有錯誤,請糾正我) 有人指出,使用模塊級函數來做這件事更好,我認爲這也是最簡單的方法,但是我這樣做的主要目的是使子類的實現變得乾淨,並且不得不加載該模塊函數也不會很好。 (另外我不得不在類體外進行函數調用,但我不知道但我認爲這很麻煩。)

所以基本上我認爲我的方法是錯誤的,因爲Python的設計不允許這種事情要做。用Python實現Ruby實例的最佳方式是什麼?

(如果沒有更好的辦法,我已經想過只是有在它返回的參數數組爲define_field方法的子類的方法。)

回答

2

也許調用類方法並不是正確的路線。我並不完全知道的確切速度 Python如何以及何時創建類,但我的猜測是,當您調用類方法創建屬性時,類對象不存在。

它看起來像你想創建一個像記錄。首先,請注意,Python允許您在創建後向用戶創建的類添加屬性:

class Foo(object): 
    pass 

>>> foo = Foo() 
>>> foo.x = 42 
>>> foo.x 
42 

也許您想限制用戶可以設置的屬性。這是一種方法。

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") 

這樣:

>>> m = MyItem() 
>>> m.foo = 42  # works 
>>> m.bar = "hello" # works 
>>> m.test = 12  # raises AttributeError 

最後,上面讓你的用戶子類Item沒有定義fields,像這樣的:

class MyItem(Item): 
    pass 

這將導致一個神祕的屬性錯誤說找不到屬性fields。您可以要求在創建課程時通過使用元類來定義fields屬性。此外,您可以抽象出用戶需要通過繼承您所寫的使用元類的超類來指定元類:

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") 
1

你幾乎沒有!如果我理解正確:

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) 

編輯:據我所知,這是不可能的訪問,同時定義它被定義的類。但是,您可以調用該方法在__init__方法:

class Something(Item): 
    def __init__(self): 
     type(self).define_field("name") 

但隨後你只是重新發明輪子。

+1

對不起,我可能在我的問題中不夠清楚,但我真正的問題是我不知道如何調用子類中的'define_field'方法。我剛剛編輯了我的問題。 – evotopid 2015-01-09 19:42:19

+0

噢,我不認爲這是可能的。不過,你可以在'__init__'裏面做。 – Kroltan 2015-01-09 20:03:14

1

定義類時,不能在自己的定義塊中引用類本身。所以你必須在MyItem定義後致電define_field(...)。例如,

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))