Từ điển Python từ các trường của đối tượng


339

Bạn có biết nếu có một hàm dựng sẵn để xây dựng một từ điển từ một đối tượng tùy ý không? Tôi muốn làm một cái gì đó như thế này:

>>> class Foo:
...     bar = 'hello'
...     baz = 'world'
...
>>> f = Foo()
>>> props(f)
{ 'bar' : 'hello', 'baz' : 'world' }

LƯU Ý: Không nên bao gồm các phương pháp. Chỉ các lĩnh vực.

Câu trả lời:


419

Lưu ý rằng cách thực hành tốt nhất trong Python 2.7 là sử dụng các lớp kiểu mới (không cần thiết với Python 3), tức là

class Foo(object):
   ...

Ngoài ra, có một sự khác biệt giữa một 'đối tượng' và 'lớp'. Để xây dựng một từ điển từ một đối tượng tùy ý , nó đủ để sử dụng __dict__. Thông thường, bạn sẽ khai báo các phương thức của mình ở cấp độ lớp và các thuộc tính của bạn ở cấp độ cá thể, vì vậy __dict__sẽ ổn thôi. Ví dụ:

>>> class A(object):
...   def __init__(self):
...     self.b = 1
...     self.c = 2
...   def do_nothing(self):
...     pass
...
>>> a = A()
>>> a.__dict__
{'c': 2, 'b': 1}

Một cách tiếp cận tốt hơn (được đề xuất bởi robert trong các bình luận) là varshàm dựng sẵn :

>>> vars(a)
{'c': 2, 'b': 1}

Ngoài ra, tùy thuộc vào những gì bạn muốn làm, nó có thể là tốt để kế thừa từ dict. Sau đó, lớp của bạn đã là một từ điển và nếu bạn muốn, bạn có thể ghi đè getattrvà / hoặc setattrgọi qua và đặt lệnh. Ví dụ:

class Foo(dict):
    def __init__(self):
        pass
    def __getattr__(self, attr):
        return self[attr]

    # etc...

3
Điều gì xảy ra nếu một trong các thuộc tính của A có một getter tùy chỉnh? (một chức năng với một trang trí @property)? Nó vẫn hiển thị trong ____dict__? Giá trị của nó sẽ là gì?
zakdances

11
__dict__sẽ không hoạt động nếu đối tượng đang sử dụng các vị trí (hoặc được xác định trong mô-đun C).
Antimon

1
Có tương đương với phương thức này cho các đối tượng lớp không? IE Thay vì sử dụng f = Foo () và sau đó thực hiện f .__ dict__, hãy thực hiện trực tiếp Foo .__ dict__?
chiffa

49
Xin lỗi, tôi đến muộn thế này, nhưng không nên vars(a)làm điều này? Đối với tôi, tốt hơn là gọi __dict__trực tiếp.
robert

2
đối với ví dụ thứ hai, tốt hơn là nên __getattr__ = dict.__getitem__sao chép chính xác hành vi, sau đó bạn cũng muốn __setattr__ = dict.__setitem____delattr__ = dict.__delitem__hoàn thành.
Tadhg McDonald-Jensen

140

Thay vì x.__dict__, nó thực sự nhiều pythonic để sử dụng vars(x).


3
Đã đồng ý. Lưu ý rằng bạn cũng có thể chuyển đổi theo cách khác (dict-> class) bằng cách nhập MyClass(**my_dict), giả sử bạn đã xác định hàm tạo với các tham số phản ánh các thuộc tính của lớp. Không cần phải truy cập các thuộc tính riêng tư hoặc ghi đè dict.
tvt173

2
Bạn có thể giải thích tại sao nó nhiều Pythonic?
Hugh W

1
Đầu tiên, Python thường tránh các cuộc gọi dunder các mục trực tiếp và hầu như luôn luôn có một phương thức hoặc hàm (hoặc toán tử) để truy cập gián tiếp. Nói chung, các thuộc tính và phương thức dunder là một chi tiết triển khai và sử dụng chức năng "trình bao bọc" cho phép bạn tách hai phần. Thứ hai, bằng cách này bạn có thể ghi đè varschức năng và giới thiệu chức năng bổ sung mà không thay đổi chính đối tượng.
Berislav Lopac

1
Nó vẫn thất bại nếu lớp của bạn sử dụng __slots__mặc dù.
cz

Điều đó là chính xác, và tôi luôn cảm thấy rằng đó sẽ là một hướng tốt để mở rộng vars, tức là trả lại một khoản tương đương __dict__cho các lớp "có rãnh". Hiện tại, nó có thể được mô phỏng bằng cách thêm một thuộc __dict__tính trả về {x: getattr(self, x) for x in self.__slots__}(không chắc điều đó có ảnh hưởng đến hiệu suất hoặc hành vi theo bất kỳ cách nào hay không).
Berislav Lopac

59

Các dirBUILTIN sẽ cung cấp cho bạn tất cả các thuộc tính của đối tượng, trong đó có phương pháp đặc biệt như __str__, __dict__và một bó toàn bộ những người khác mà có thể bạn không muốn. Nhưng bạn có thể làm một cái gì đó như:

>>> class Foo(object):
...     bar = 'hello'
...     baz = 'world'
...
>>> f = Foo()
>>> [name for name in dir(f) if not name.startswith('__')]
[ 'bar', 'baz' ]
>>> dict((name, getattr(f, name)) for name in dir(f) if not name.startswith('__')) 
{ 'bar': 'hello', 'baz': 'world' }

Vì vậy, có thể mở rộng điều này để chỉ trả về các thuộc tính dữ liệu chứ không phải các phương thức, bằng cách xác định propshàm của bạn như thế này:

import inspect

def props(obj):
    pr = {}
    for name in dir(obj):
        value = getattr(obj, name)
        if not name.startswith('__') and not inspect.ismethod(value):
            pr[name] = value
    return pr

1
Mã này bao gồm các phương thức. Có cách nào để loại trừ các phương pháp? Tôi chỉ cần các trường của đối tượng. Cảm ơn
Julio César

ismethodkhông bắt các chức năng. Ví dụ : inspect.ismethod(str.upper). inspect.isfunctionMặc dù vậy, không hữu ích hơn nhiều. Không chắc chắn làm thế nào để tiếp cận điều này ngay lập tức.
Ehtesh Choudhury

Tôi đã thực hiện một số điều chỉnh để hoàn toàn tái diễn và bỏ qua tất cả các lỗi đến độ sâu ở đây, cảm ơn! gist.github.com/thorsummoner/bf0142fd24974a0ced778768a33a3069
ThorSummoner

26

Tôi đã giải quyết với sự kết hợp của cả hai câu trả lời:

dict((key, value) for key, value in f.__dict__.iteritems() 
    if not callable(value) and not key.startswith('__'))

Điều đó cũng hoạt động, nhưng lưu ý rằng nó sẽ chỉ cung cấp cho bạn các thuộc tính được đặt trong thể hiện, không phải trên lớp (như lớp Foo trong ví dụ của bạn) ...
dF.

Vì vậy, jcarrascal, tốt hơn hết là bạn nên bọc đoạn mã trên trong một hàm như props (), sau đó bạn có thể gọi là đạo cụ (f) hoặc đạo cụ (Foo). Lưu ý rằng bạn hầu như luôn luôn tốt hơn khi viết một hàm, thay vì viết mã 'nội tuyến'.
quamrana

Rất vui, btw lưu ý đây là cho python2.7, cho python3 relpace iteritems () với các mục đơn giản ().
Morten

Còn bạn thì staticmethodsao? Không phải vậy callable.
Alex

16

Tôi nghĩ rằng tôi sẽ dành chút thời gian để chỉ cho bạn cách bạn có thể dịch một đối tượng để đọc chính tả dict(obj).

class A(object):
    d = '4'
    e = '5'
    f = '6'

    def __init__(self):
        self.a = '1'
        self.b = '2'
        self.c = '3'

    def __iter__(self):
        # first start by grabbing the Class items
        iters = dict((x,y) for x,y in A.__dict__.items() if x[:2] != '__')

        # then update the class items with the instance items
        iters.update(self.__dict__)

        # now 'yield' through the items
        for x,y in iters.items():
            yield x,y

a = A()
print(dict(a)) 
# prints "{'a': '1', 'c': '3', 'b': '2', 'e': '5', 'd': '4', 'f': '6'}"

Phần quan trọng của mã này là __iter__ chức năng.

Như các ý kiến ​​giải thích, điều đầu tiên chúng tôi làm là lấy các mục Class và ngăn chặn mọi thứ bắt đầu bằng '__'.

Khi bạn đã tạo điều đó dict, sau đó bạn có thể sử dụng updatehàm dict và truyền vào ví dụ__dict__ .

Chúng sẽ cung cấp cho bạn một lớp hoàn chỉnh + từ điển ví dụ của các thành viên. Bây giờ tất cả những gì còn lại là lặp đi lặp lại chúng và mang lại lợi nhuận.

Ngoài ra, nếu bạn có kế hoạch sử dụng nó rất nhiều, bạn có thể tạo một trình @iterabletrang trí lớp.

def iterable(cls):
    def iterfn(self):
        iters = dict((x,y) for x,y in cls.__dict__.items() if x[:2] != '__')
        iters.update(self.__dict__)

        for x,y in iters.items():
            yield x,y

    cls.__iter__ = iterfn
    return cls

@iterable
class B(object):
    d = 'd'
    e = 'e'
    f = 'f'

    def __init__(self):
        self.a = 'a'
        self.b = 'b'
        self.c = 'c'

b = B()
print(dict(b))

Điều này cũng sẽ lấy tất cả các phương thức, nhưng chúng ta chỉ cần các trường lớp + thể hiện. Có lẽ dict((x, y) for x, y in KpiRow.__dict__.items() if x[:2] != '__' and not callable(y))sẽ giải quyết nó? Nhưng vẫn có thể có staticcác phương thức :(
Alex

15

Để xây dựng một từ điển từ một đối tượng tùy ý , nó đủ để sử dụng __dict__.

Điều này bỏ lỡ các thuộc tính mà đối tượng kế thừa từ lớp của nó. Ví dụ,

class c(object):
    x = 3
a = c()

hasattr (a, 'x') là đúng, nhưng 'x' không xuất hiện trong .__ dict__


Trong trường hợp này giải pháp là gì? Vì vars()không hoạt động
nên_be_

@should_be_work dirlà giải pháp trong trường hợp này. Xem câu trả lời khác về điều đó.
Albert

8

Câu trả lời muộn nhưng được cung cấp cho sự đầy đủ và lợi ích của nhân viên Google:

def props(x):
    return dict((key, getattr(x, key)) for key in dir(x) if key not in dir(x.__class__))

Điều này sẽ không hiển thị các phương thức được xác định trong lớp, nhưng nó vẫn sẽ hiển thị các trường bao gồm các trường được gán cho lambdas hoặc các phương thức bắt đầu bằng dấu gạch dưới kép.


6

Tôi nghĩ cách dễ nhất là tạo một thuộc tính getitem cho lớp. Nếu bạn cần ghi vào đối tượng, bạn có thể tạo một setattr tùy chỉnh . Đây là một ví dụ cho getitem :

class A(object):
    def __init__(self):
        self.b = 1
        self.c = 2
    def __getitem__(self, item):
        return self.__dict__[item]

# Usage: 
a = A()
a.__getitem__('b')  # Outputs 1
a.__dict__  # Outputs {'c': 2, 'b': 1}
vars(a)  # Outputs {'c': 2, 'b': 1}

dict tạo các thuộc tính đối tượng vào một từ điển và đối tượng từ điển có thể được sử dụng để lấy mục bạn cần.


Sau câu trả lời này vẫn chưa rõ làm thế nào để có được một từ điển từ một đối tượng. Không phải thuộc tính, mà là toàn bộ từ điển;)
maxkoryukov

6

Một nhược điểm của việc sử dụng __dict__ là nó nông; nó sẽ không chuyển đổi bất kỳ lớp con nào thành từ điển.

Nếu bạn đang sử dụng Python3.5 trở lên, bạn có thể sử dụng jsons:

>>> import jsons
>>> jsons.dump(f)
{'bar': 'hello', 'baz': 'world'}

3

Nếu bạn muốn liệt kê một phần thuộc tính của mình, hãy ghi đè __dict__:

def __dict__(self):
    d = {
    'attr_1' : self.attr_1,
    ...
    }
    return d

# Call __dict__
d = instance.__dict__()

Điều này giúp ích rất nhiều nếu bạn instancenhận được một số dữ liệu khối lớn và bạn muốn đẩy dsang Redis như hàng đợi tin nhắn.


__dict__là một thuộc tính, không phải là một phương thức, vì vậy ví dụ này thay đổi giao diện (tức là bạn cần gọi nó là một phương thức có thể gọi được), vì vậy nó không ghi đè lên nó.
Berislav Lopac

0

THỨ 3:

class DateTimeDecoder(json.JSONDecoder):

   def __init__(self, *args, **kargs):
        JSONDecoder.__init__(self, object_hook=self.dict_to_object,
                         *args, **kargs)

   def dict_to_object(self, d):
       if '__type__' not in d:
          return d

       type = d.pop('__type__')
       try:
          dateobj = datetime(**d)
          return dateobj
       except:
          d['__type__'] = type
          return d

def json_default_format(value):
    try:
        if isinstance(value, datetime):
            return {
                '__type__': 'datetime',
                'year': value.year,
                'month': value.month,
                'day': value.day,
                'hour': value.hour,
                'minute': value.minute,
                'second': value.second,
                'microsecond': value.microsecond,
            }
        if isinstance(value, decimal.Decimal):
            return float(value)
        if isinstance(value, Enum):
            return value.name
        else:
            return vars(value)
    except Exception as e:
        raise ValueError

Bây giờ bạn có thể sử dụng mã ở trên trong lớp của riêng bạn:

class Foo():
  def toJSON(self):
        return json.loads(
            json.dumps(self, sort_keys=True, indent=4, separators=(',', ': '), default=json_default_format), cls=DateTimeDecoder)


Foo().toJSON() 

0

vars() là tuyệt vời, nhưng không hoạt động cho các đối tượng lồng nhau của các đối tượng

Chuyển đổi đối tượng lồng nhau của các đối tượng thành dict:

def to_dict(self):
    return json.loads(json.dumps(self, default=lambda o: o.__dict__))
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.