2012-10-19 19 views
5

Khi làm việc về triển khai kiểu liệt kê tùy chỉnh, tôi gặp phải tình huống xuất hiện mà tôi phải tách riêng gần như các lớp con giống hệt nhau từ cả intlong vì chúng là các lớp riêng biệt trong Python. Điều này có vẻ mỉa mai vì các trường hợp của hai thường có thể được sử dụng thay thế cho nhau bởi vì hầu hết chúng chỉ được tạo tự động bất cứ khi nào được yêu cầu.Tránh có hai lớp con số khác nhau (int và long)?

Những gì tôi đã làm tốt, nhưng theo tinh thần của DRY (Đừng lặp lại), tôi không thể không tự hỏi nếu không có bất kỳ tốt hơn, hoặc ít nhất là một cách ngắn gọn hơn, để thực hiện điều này . Mục đích là để có các thể hiện lớp con có thể được sử dụng ở khắp mọi nơi - hoặc càng gần càng tốt - các trường hợp của các lớp cơ sở của chúng có thể có. Lý tưởng nhất điều này sẽ xảy ra tự động tương tự như cách được xây dựng trong int() thực sự trả về một long bất cứ khi nào nó phát hiện một là bắt buộc.

Dưới đây là thực hiện hiện tại của tôi:

class NamedInt(int): 
    """Subclass of type int with a name attribute""" 
    __slots__ = "_name" # also prevents additional attributes from being added 

    def __setattr__(self, name, value): 
     if hasattr(self, name): 
      raise AttributeError(
       "'NamedInt' object attribute %r is read-only" % name) 
     else: 
      raise AttributeError(
       "Cannot add attribute %r to 'NamedInt' object" % name) 

    def __new__(cls, name, value): 
     self = super(NamedInt, NamedInt).__new__(cls, value) 
     # avoid call to this subclass's __setattr__ 
     super(NamedInt, self).__setattr__('_name', name) 
     return self 

    def __str__(self): # override string conversion to be name 
     return self._name 

    __repr__ = __str__ 


class NamedLong(long): 
    """Subclass of type long with a name attribute""" 
    # note: subtypes of variable length 'long' type can't have __slots__ 

    def __setattr__(self, name, value): 
     if hasattr(self, name): 
      raise AttributeError(
       "NamedLong object attribute %r is read-only" % name) 
     else: 
      raise AttributeError(
       "Cannot add attribute %r to 'NamedLong' object" % name) 

    def __new__(cls, name, value): 
     self = super(NamedLong, NamedLong).__new__(cls, value) 
     # avoid call to subclass's __setattr__ 
     super(NamedLong, self).__setattr__('_name', name) 
     return self 

    def __str__(self): 
     return self._name # override string conversion to be name 

    __repr__ = __str__ 

class NamedWholeNumber(object): 
    """Factory class which creates either a NamedInt or NamedLong 
    instance depending on magnitude of its numeric value. 
    Basically does the same thing as the built-in int() function 
    does but also assigns a '_name' attribute to the numeric value""" 
    class __metaclass__(type): 
     """NamedWholeNumber metaclass to allocate and initialize the 
      appropriate immutable numeric type.""" 
     def __call__(cls, name, value, base=None): 
      """Construct appropriate Named* subclass.""" 
      # note the int() call may return a long (it will also convert 
      # values given in a string along with optional base argument) 
      number = int(value) if base is None else int(value, base) 

      # determine the type of named numeric subclass to use 
      if -sys.maxint-1 <= number <= sys.maxint: 
       named_number_class = NamedInt 
      else: 
       named_number_class = NamedLong 

      # return instance of proper named number class 
      return named_number_class(name, number) 

Trả lời

2

Dưới đây là cách bạn có thể giải quyết vấn đề DRY qua nhiều lần kế thừa. Thật không may, nó không chơi tốt với __slots__ (nó gây ra biên dịch thời gian TypeError s) vì vậy tôi đã phải rời khỏi đó. Hy vọng rằng các giá trị __dict__ sẽ không lãng phí quá nhiều bộ nhớ cho trường hợp sử dụng của bạn.

class Named(object): 
    """Named object mix-in. Not useable directly.""" 
    def __setattr__(self, name, value): 
     if hasattr(self, name): 
      raise AttributeError(
       "%r object attribute %r is read-only" % 
       (self.__class__.__name__, name)) 
     else: 
      raise AttributeError(
       "Cannot add attribute %r to %r object" % 
       (name, self.__class__.__name__)) 

    def __new__(cls, name, *args): 
     self = super(Named, cls).__new__(cls, *args) 
     super(Named, self).__setattr__('_name', name) 
     return self 

    def __str__(self): # override string conversion to be name 
     return self._name 

    __repr__ = __str__ 

class NamedInt(Named, int): 
    """NamedInt class. Constructor will return a NamedLong if value is big.""" 
    def __new__(cls, name, *args): 
     value = int(*args) # will raise an exception on invalid arguments 
     if isinstance(value, int): 
      return super(NamedInt, cls).__new__(cls, name, value) 
     elif isinstance(value, long): 
      return NamedLong(name, value) 

class NamedLong(Named, long): 
    """Nothing to see here.""" 
    pass 
+0

Câu trả lời hay, nhưng không xử lý 'NamedInt ('HexBased', 'deadbeef', 16)'. – martineau

+0

Hmm, điểm tốt. Tôi nghĩ rằng nó có thể được cố định với varargs. Tôi sẽ chỉnh sửa để làm điều đó. – Blckknght

+0

Cảm ơn bạn đã sửa chữa. Rất khó để quyết định giữa câu trả lời này và câu trả lời của @ eryksun, vì cả hai đều giải quyết vấn đề DRY rất tốt - nhưng cuối cùng đã chọn câu trả lời này vì đó là IMHO đơn giản và dễ hiểu nhất. BTW, có thể thêm thuộc tính '__slots__' vào chỉ lớp con' NamedInt' (như eryksun đã làm) mà dường như giải quyết nhu cầu đó cho trường hợp 'int' phổ biến hơn (và _is_ một đặc tính quan trọng cho việc sử dụng dự định). – martineau

2

Trọng the allocator sẽ cho phép bạn quay trở lại một đối tượng của loại thích hợp.

class NamedInt(int): 
    def __new__(...): 
    if should_be_NamedLong(...): 
     return NamedLong(...) 
    ... 
+0

Cấp này sẽ loại bỏ sự cần thiết của lớp 'NamedWholeNumber' (và có lẽ có giá trị thực hiện), nhưng nó có vẻ như hầu hết các mã mà đã trong nó sẽ chỉ kết thúc việc chuyển sang phương thức 'NamedInt .__ new __()' và tôi vẫn sẽ cần phải có một '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' - hay tôi thiếu một cái gì đó? – martineau

+0

Điều đó nghe có vẻ đúng, nhưng hãy nhớ rằng Python 2.x có các lớp 'int' và' long' riêng biệt bất kể giá trị được chuyển đến 'int()'. –

2

Dưới đây là một phiên bản lớp trang trí:

def named_number(Named): 

    @staticmethod 
    def __new__(cls, name, value, base=None): 
     value = int(value) if base is None else int(value, base) 
     if isinstance(value, int): 
      NamedNumber = Named # NamedInt/NamedLong 
     else: 
      NamedNumber = cls = NamedLong 
     self = super(NamedNumber, cls).__new__(cls, value) 
     super(NamedNumber, self).__setattr__('_name', name) 
     return self 

    def __setattr__(self, name, value): 
     if hasattr(self, name): 
      raise AttributeError(
       "'%r' object attribute %r is read-only" % (Named, name)) 
     else: 
      raise AttributeError(
       "Cannot add attribute %r to '%r' object" % (name, Named)) 

    def __repr__(self): 
     return self._name 

    __str__ = __repr__ 

    for k, v in locals().items(): 
     if k != 'Named': 
      setattr(Named, k, v) 

    return Named 

@named_number 
class NamedInt(int): 
    __slots__ = '_name' 

@named_number 
class NamedLong(long): pass 
+0

Chắc chắn dẫn đầu gói IMHO. Tôi đang cố gắng quyết định giữa nó và @ Blckknght's. Tôi thích thực tế rằng bạn hỗ trợ tối ưu hóa '__slots__', các giá trị chuỗi không dựa trên thập phân và chỉ yêu cầu thừa kế đơn. – martineau

+0

Thật không may tôi đã quyết định đi với câu trả lời của @ Blckknght vì những lý do được nêu trong một bình luận tôi đã thêm vào nó.Đó là một sự lựa chọn khó khăn để thực hiện, và dù sao, cách tiếp cận của bạn có vẻ hoàn toàn khả thi và chỉ là một tốt bởi hầu hết các biện pháp. Tôi cũng tìm thấy cách tiếp cận của bạn để thực hiện trang trí lớp học rất thông minh - và học được một số kỹ thuật mới từ nó. Cảm ơn! – martineau

Các vấn đề liên quan