2016-06-24 13 views
5

Tôi có một enum đơn giản bằng Python trông như thế này:Làm thế nào tôi có thể tìm thấy giá trị tiếp theo và trước đó trong Python Enum một cách tao nhã?

from enum import Enum 

class MyEnum(Enum): 
    #All members have increasing non-consecutive integer values. 
    A = 0 
    B = 2 
    C = 10 
    D = 18 
    ... 

Tôi muốn chức năng pred()succ() mà đưa ra một thành viên của MyEnum trở lại các thành viên của MyEnum đi trước và thành công các yếu tố nhất định, tương ứng (giống như the functions of the same name in Haskell). Ví dụ: succ(MyEnum.B)pred(MyEnum.D) cả hai phải trả lại MyEnum.C. Một ngoại lệ có thể được nêu ra nếu succ được gọi vào thành viên cuối cùng của số pred được gọi vào thành viên đầu tiên.

Có vẻ như không có bất kỳ phương thức tích hợp nào để thực hiện việc này và trong khi tôi có thể gọi iter(MyEnum) để lặp qua các giá trị mà nó phải trải qua toàn bộ enum ngay từ đầu. Tôi có thể có thể thực hiện một vòng lặp cẩu thả để thực hiện điều này một mình, nhưng tôi biết có một số chuyên gia thực sự của Python trên trang này, vì vậy với bạn tôi hỏi: liệu có cách tiếp cận tốt hơn không?

+0

Mục đích của 'enum' bằng Python là để cung cấp một kiểu dữ liệu có thể được sử dụng thay cho "con số ma thuật "và như vậy, không thực sự cung cấp loại dữ liệu toán học tương ứng với các tập hợp đếm được. Vì vậy, mục đích của 'enum' của Haskell hơi khác một chút. Trong mọi trường hợp, không có gì ngăn cản bạn thực hiện 'pred' và' succ' làm phương thức 'MyEnum'. – Bakuriu

+0

Cung cấp bản dịch đơn giản của câu trả lời trong câu hỏi đó. –

Trả lời

2

Lưu ý rằng bạn có thể cung cấp succpred phương pháp bên trong một lớp Enum:

class Sequential(Enum): 
    A = 1 
    B = 2 
    C = 4 
    D = 8 
    E = 16 

    def succ(self): 
     v = self.value * 2 
     if v > 16: 
      raise ValueError('Enumeration ended') 
     return Sequential(v) 

    def pred(self): 
     v = self.value // 2 
     if v == 0: 
      raise ValueError('Enumeration ended') 
     return Sequential(v) 

Được sử dụng như:

>>> import myenums 
>>> myenums.Sequential.B.succ() 
<Sequential.C: 4> 
>>> myenums.Sequential.B.succ().succ() 
<Sequential.D: 8> 
>>> myenums.Sequential.B.succ().succ().pred() 
<Sequential.C: 4> 

Rõ ràng đây là hiệu quả chỉ khi bạn thực sự có một cách đơn giản để tính toán các giá trị từ một mục đến mục tiếp theo hoặc trước đó, có thể không phải lúc nào cũng đúng.

Nếu bạn muốn có giải pháp hiệu quả chung với chi phí thêm một số không gian, bạn có thể xây dựng ánh xạ của các hàm kế thừa và tiền nhiệm. Bạn cần phải thêm các như các thuộc tính sau việc tạo ra các lớp (từ Enum messes lên các thuộc tính), do đó bạn có thể sử dụng một trang trí để làm điều đó:

def add_succ_and_pred_maps(cls): 
    succ_map = {} 
    pred_map = {} 
    cur = None 
    nxt = None 
    for val in cls.__members__.values(): 
     if cur is None: 
      cur = val 
     elif nxt is None: 
      nxt = val 

     if cur is not None and nxt is not None: 
      succ_map[cur] = nxt 
      pred_map[nxt] = cur 
      cur = nxt 
      nxt = None 
    cls._succ_map = succ_map 
    cls._pred_map = pred_map 

    def succ(self): 
     return self._succ_map[self] 

    def pred(self): 
     return self._pred_map[self] 

    cls.succ = succ 
    cls.pred = pred 
    return cls 





@add_succ_and_pred_maps 
class MyEnum(Enum): 
    A = 0 
    B = 2 
    C = 8 
    D = 18 

Được sử dụng như:

>>> myenums.MyEnum.A.succ() 
<MyEnum.B: 2> 
>>> myenums.MyEnum.B.succ() 
<MyEnum.C: 8> 
>>> myenums.MyEnum.B.succ().pred() 
<MyEnum.B: 2> 
>>> myenums.MyEnum._succ_map 
{<MyEnum.A: 0>: <MyEnum.B: 2>, <MyEnum.C: 8>: <MyEnum.D: 18>, <MyEnum.B: 2>: <MyEnum.C: 8>} 

bạn có thể muốn một ngoại lệ tùy chỉnh thay vì KeyError nhưng bạn có ý tưởng.


Có lẽ là một cách để tích hợp các bước cuối cùng sử dụng metaclasses, nhưng nó notstraightforward cho một thực tế đơn giản mà Enum s được thực hiện sử dụng metaclasses và nó không phải tầm thường để soạn metaclasses.

+0

Câu trả lời của bạn rất hữu ích theo hai cách: nó giải quyết được vấn đề của tôi, và giải pháp đủ phức tạp đến mức bây giờ tôi tin chắc rằng một enum là một sự lựa chọn nghèo về cấu trúc dữ liệu cho những gì tôi đang cố gắng làm trong Python.Cảm ơn vì sự hỗ trợ của bạn! – ApproachingDarknessFish

1

Thêm nextprev bạn phương pháp (hoặc succpred) rất dễ dàng đủ:

def next(self): 
    cls = self.__class__ 
    members = list(cls) 
    index = members.index(self) + 1 
    if index >= len(members): 
     # to cycle around 
     # index = 0 
     # 
     # to error out 
     raise StopIteration('end of enumeration reached') 
    return members[index] 

def prev(self): 
    cls = self.__class__ 
    members = list(cls) 
    index = members.index(self) - 1 
    if index < 0: 
     # to cycle around 
     # index = len(members) - 1 
     # 
     # to error out 
     raise StopIteration('beginning of enumeration reached') 
    return members[index] 
+0

Lưu ý rằng phương thức 'index' tăng giá trị' ValueError' khi phần tử không được tìm thấy, bạn muốn sử dụng phương thức 'find' thay vì nhận' -1' khi không tìm thấy. Ngoài ra giải pháp này đòi hỏi phải thông qua tất cả các thành viên ít nhất một lần và có thể hai lần, do đó, không thực sự hiệu quả nhiều. – Bakuriu

+0

@Bakuriu: '.index()' được sử dụng trong trường hợp này vì thành viên sẽ luôn được tìm thấy. Trung bình nó chỉ trải qua một nửa số thành viên, và không bao giờ hai lần. –

+0

Không, 'danh sách (cls)' đi qua ** tất cả các phần tử ** một lần, và sau đó 'members.index' trung bình đi qua một nửa số thành viên, nhưng nó vẫn nhiều hơn một lần cho mọi thành viên cho mỗi cuộc gọi. Sẽ hiệu quả hơn khi thực hiện một vòng lặp 'cho tôi, thành viên trong liệt kê (cls)' đơn giản và theo dõi yếu tố trước đó của mỗi lần lặp. – Bakuriu

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