2010-07-20 21 views
7

Tôi có một mã như:Làm cách nào để nhận các trường theo thứ tự ban đầu?

class Ordered(object): 
    x = 0 
    z = 0 
    b = 0 
    a = 0 

print(dir(Ordered)) 

nó in:

[ ......., a, b, x, z] 

Làm thế nào tôi có thể nhận được các lĩnh vực trong một trật tự ban đầu: x, z, b, a? Tôi đã nhìn thấy hành vi tương tự trong Mô hình Django.

+1

phương pháp, tài sản vv được lưu dưới dạng từ điển trong các đối tượng, và từ điển là theo định nghĩa thứ tự. Bạn đang cố gắng đạt được điều gì (hoặc tốt hơn, tại sao)? –

+0

Bạn cần Python 3 để làm điều này mà không cần hack: http://stackoverflow.com/a/4460565/821378 –

Trả lời

15

Như đã đề cập ở trên, nếu bạn muốn giữ mọi thứ đơn giản, chỉ cần sử dụng thuộc tính ví dụ: _ordering, theo cách thủ công theo dõi đặt hàng. Nếu không, đây là một cách tiếp cận metaclass (giống như một Django sử dụng), mà tạo ra một thuộc tính đặt hàng tự động.

ghi thứ tự ban đầu

Lớp học không theo dõi các Trật tự của các thuộc tính. Tuy nhiên, bạn có thể theo dõi thứ tự các trường thể hiện được tạo ra. Đối với điều đó, bạn sẽ phải sử dụng lớp của riêng bạn cho các trường (không phải int). Lớp này theo dõi bao nhiêu trường hợp đã được tạo và mỗi cá thể sẽ ghi lại vị trí của nó. Đây là cách bạn sẽ làm điều đó ví dụ của bạn (lưu trữ số nguyên):

class MyOrderedField(int): 
    creation_counter = 0 

    def __init__(self, val): 
    # Set the instance's counter, to keep track of ordering 
    self.creation_counter = MyOrderedField.creation_counter 
    # Increment the class's counter for future instances 
    MyOrderedField.creation_counter += 1 

Tạo một thuộc tính ordered_items tự động

Bây giờ các lĩnh vực của bạn có một số có thể được sử dụng để ra lệnh cho họ, cha mẹ của bạn lớp học cần phải sử dụng bằng cách nào đó. Bạn có thể làm điều này theo nhiều cách khác nhau, nếu tôi nhớ chính xác, Django sử dụng Metaclasses để làm điều này, điều này hơi hoang dã đối với một lớp đơn giản.

class BaseWithOrderedFields(type): 
    """ Metaclass, which provides an attribute "ordered_fields", being an ordered 
     list of class attributes that have a "creation_counter" attribute. """ 

    def __new__(cls, name, bases, attrs): 
    new_class = super(BaseWithOrderedFields, cls).__new__(cls, name, bases, attrs) 
    # Add an attribute to access ordered, orderable fields 
    new_class._ordered_items = [(name, attrs.pop(name)) for name, obj in attrs.items() 
            if hasattr(obj, "creation_counter")] 
    new_class._ordered_items.sort(key=lambda item: item[1].creation_counter) 
    return new_class 

Sử dụng metaclass này

Vì vậy, làm thế nào để bạn sử dụng này? Trước tiên, bạn cần sử dụng lớp MyOrderedField mới của chúng tôi khi xác định thuộc tính của bạn.lớp mới này sẽ theo dõi thứ tự mà các lĩnh vực đã được tạo ra:

class Ordered(object): 
    __metaclass__ = BaseWithOrderedFields 

    x = MyOrderedField(0) 
    z = MyOrderedField(0) 
    b = MyOrderedField(0) 
    a = MyOrderedField(0) 

Sau đó, bạn có thể truy cập vào các lĩnh vực ra lệnh tự động tạo ra thuộc tính của chúng tôi ordered_fields:

>>> ordered = Ordered() 
>>> ordered.ordered_fields 
[('x', 0), ('z', 0), ('b', 0), ('a', 0)] 

Hãy để thay đổi điều này để một lệnh dict hoặc chỉ cần trả lại tên hoặc bất cứ điều gì bạn cần. Ngoài ra, bạn có thể xác định một lớp trống với __metaclass__ và kế thừa từ đó.

Không sử dụng quyền này!

Như bạn có thể thấy, cách tiếp cận này hơi phức tạp và có thể không phù hợp với hầu hết các tác vụ hoặc nhà phát triển python. Nếu bạn là người mới bắt đầu với python, bạn có thể sẽ dành nhiều thời gian và công sức để phát triển metaclass của bạn hơn bạn sẽ có nếu bạn chỉ định đặt hàng theo cách thủ công. Xác định đặt hàng của bạn bằng tay là hầu như luôn luôn sẽ là cách tiếp cận tốt nhất. Django tự động làm điều đó vì mã phức tạp bị ẩn khỏi nhà phát triển cuối và Django được sử dụng thường xuyên hơn chính nó được viết/duy trì. Vì vậy, chỉ khi bạn đang phát triển một khung cho các nhà phát triển khác, thì metaclasses có thể hữu ích cho bạn.

+0

Tôi phát triển khuôn khổ cho thương nhân và giải pháp của bạn đó là những gì tôi cần. Tôi muốn đơn giản hóa việc khai báo các tham số đầu vào và đầu ra cho các chỉ số, và tôi thực sự sử dụng các cá thể như các trường. Với "int" tôi đã quá đơn giản hóa một ví dụ))) Cảm ơn bạn cho câu trả lời tốt nhất! :) – DenisKolodin

+1

CẢNH BÁO! Phiên bản __new__ này không nhận được các trường từ các cơ sở. Do đó các lớp con mất các trường được sắp xếp của cha mẹ! Thêm chúng nếu cần. – DenisKolodin

1

Bạn không thể theo dõi thứ tự bổ sung các biến lớp. Các thuộc tính này (cũng như các thuộc tính trên các đối tượng) được lưu trữ nội bộ như một từ điển, được tối ưu hóa để tra cứu nhanh và không hỗ trợ đặt hàng.

Bạn có thể thấy thực tế này:

class A(object): 
    x = 0 
    y = 0 
    z = 0 

A.__dict__.items() 

# [('__module__', '__main__'), 
# ('__dict__', <attribute '__dict__' of 'A' objects>), 
# ('y', 0), ('x', 0), ('z', 0), 
# ('__weakref__', <attribute '__weakref__' of 'A' objects>), 
# ('__doc__', None)] 

Nếu bạn muốn thuộc tính của bạn theo một thứ tự cụ thể, bạn có thể chỉ cần thêm các lĩnh vực khác, có chứa thông tin này:

class B(object): 
    x = 0 
    y = 0 
    z = 0 
    a = 0 
    _ordering = ['x', 'y', 'z', 'a'] 

print B._ordering 
# => ['x', 'y', 'z', 'a'] 

Phụ chú: Trong python 2.7 và 3,2 từ điển được đặt hàng sẽ được giới thiệu như một phần của thư viện chuẩn.

+0

Nó không phải là xấu nhưng không phù hợp với nguyên tắc DRY. – DenisKolodin

1

Mô hình và hình thức metaclasses của Django hoạt động cùng với bộ mô tả trường để duy trì thứ tự ban đầu. Không có cách nào để làm những gì bạn yêu cầu mà không cần nhảy qua một số lô hàng . Xem mã nguồn Django nếu bạn vẫn quan tâm.

5

Tôi đã hoàn thành 80% câu trả lời này khi Will đăng bài, nhưng tôi đã quyết định đăng bài để nỗ lực không bị lãng phí (câu trả lời của chúng tôi về cơ bản mô tả cùng một điều).

Đây là cách Django thực hiện. Tôi đã chọn để giữ cùng một danh pháp, phương pháp luận, và cấu trúc dữ liệu như Django, để câu trả lời này cũng có thể hữu ích cho những người cố gắng để hiểu làm thế nào tên trường được sắp xếp trong Django.

from bisect import bisect 

class Field(object): 
    # A global creation counter that will contain the number of Field objects 
    # created globally. 
    creation_counter = 0 

    def __init__(self, *args, **kwargs): 
     super(Field, self).__init__(*args, **kwargs) 
     # Store the creation index in the "creation_counter" of the field. 
     self.creation_counter = Field.creation_counter 
     # Increment the global counter. 
     Field.creation_counter += 1 
     # As with Django, we'll be storing the name of the model property 
     # that holds this field in "name". 
     self.name = None 

    def __cmp__(self, other): 
     # This specifies that fields should be compared based on their creation 
     # counters, allowing sorted lists to be built using bisect. 
     return cmp(self.creation_counter, other.creation_counter) 

# A metaclass used by all Models 
class ModelBase(type): 
    def __new__(cls, name, bases, attrs): 
     klass = super(ModelBase, cls).__new__(cls, name, bases, attrs) 
     fields = [] 
     # Add all fields defined for the model into "fields". 
     for key, value in attrs.items(): 
      if isinstance(value, Field): 
       # Store the name of the model property. 
       value.name = key 
       # This ensures the list is sorted based on the creation order 
       fields.insert(bisect(fields, value), value) 
     # In Django, "_meta" is an "Options" object and contains both a 
     # "local_fields" and a "many_to_many_fields" property. We'll use a 
     # dictionary with a "fields" key to keep things simple. 
     klass._meta = { 'fields': fields } 
     return klass 

class Model(object): 
    __metaclass__ = ModelBase 

Bây giờ chúng ta hãy xác định một số mô hình ví dụ:

class Model1(Model): 
    a = Field() 
    b = Field() 
    c = Field() 
    z = Field() 

class Model2(Model): 
    c = Field() 
    z = Field() 
    b = Field() 
    a = Field() 

Và chúng ta hãy kiểm tra chúng:

>>>> [f.name for f in Model1()._meta['fields']] 
['a', 'b', 'c', 'z'] 
>>>> [f.name for f in Model2()._meta['fields']] 
['c', 'z', 'b', 'a'] 

Hy vọng điều này sẽ giúp làm rõ bất cứ điều gì đó là chưa rõ ràng trong câu trả lời của Will.

+0

Giải pháp của bạn nên được cập nhật thành python 3.x, trong đó 'cmp()' dường như biến mất. Xác định '__lt __()' thay vì '__cmp __()'. Khác hơn thế, cảm ơn! –

+0

Và định nghĩa metaclass nên được thực hiện như thế này: 'class Model (metaclass = ModelBase)'. –

3
class SchemaItem(): 
    def __init__(self,item): 
     self.item = item 
     time.sleep(0.1) 
     self.order = datetime.now() 

    def __str__(self): 
     return "Item = %s, Order = %s"%(self.item, self.order) 

class DefiningClass(): 
    B  = SchemaItem("B") 
    C  = SchemaItem("C") 
    A  = SchemaItem("A") 
    PRODUCT = SchemaItem("PRODUCT") 
    ASSSET = SchemaItem("ASSET") 
    TENOR = SchemaItem("TENOR") 

    def get_schema(self): 
     self_class = self.__class__ 
     attributes = [x for x in dir(self_class) if x not in ["class","name","schema","values"]] 
     schema  = [(attribute_name,getattr(self_class,attribute_name)) for attribute_name in attributes if isinstance(getattr(self_class,attribute_name),SchemaItem)] 
     return dict(schema) 

# Actual usage 
ss = [(name,schema_item) for name,schema_item in s.get_schema().items()] 
print "Before = %s" % ss 
ss.sort(key=lambda a:a[1].order) 
print "After =%s" % ss 
1

Hiện chỉ cần sử dụng Python 3.6!

class OrderedClass(): 
    x = 0 
    z = 0 
    a = 0 
    b = 0 

print(list(OrderedClass.__dict__)) 

này kết quả đầu ra tôi:

['__module__', 'x', 'z', 'a', 'b', '__dict__', '__weakref__', '__doc__']

+1

Đó là vì PEP 520 – DenisKolodin

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