Kamus python dari bidang objek


339

Apakah Anda tahu jika ada fungsi bawaan untuk membangun kamus dari objek yang arbitrer? Saya ingin melakukan sesuatu seperti ini:

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

CATATAN: Seharusnya tidak termasuk metode. Hanya bidang.

Jawaban:


419

Perhatikan bahwa praktik terbaik dalam Python 2.7 adalah menggunakan kelas gaya baru (tidak diperlukan dengan Python 3), yaitu

class Foo(object):
   ...

Juga, ada perbedaan antara 'objek' dan 'kelas'. Untuk membuat kamus dari objek yang arbitrer , cukup untuk digunakan __dict__. Biasanya, Anda akan mendeklarasikan metode Anda di tingkat kelas dan atribut Anda di tingkat instance, jadi __dict__harus baik-baik saja. Sebagai contoh:

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

Pendekatan yang lebih baik (disarankan oleh robert dalam komentar) adalah varsfungsi bawaan :

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

Atau, tergantung pada apa yang ingin Anda lakukan, mungkin lebih baik untuk mewarisi dari dict. Kemudian kelas Anda sudah menjadi kamus, dan jika Anda mau, Anda dapat mengganti getattrdan / atau setattruntuk memanggil dan mengatur dict. Sebagai contoh:

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

    # etc...

3
Apa yang terjadi jika salah satu atribut A memiliki pengambil kustom? (fungsi dengan dekor properti @)? Apakah masih muncul dalam _______ Belanda? Apa nilainya?
zakdances

11
__dict__tidak akan berfungsi jika objek menggunakan slot (atau didefinisikan dalam modul C).
Antimony

1
Apakah ada yang setara dengan metode ini untuk objek kelas? IE Alih-alih menggunakan f = Foo () dan kemudian melakukan f .__ dict__, lakukan langsung Foo .__ dict__?
chiffa

49
Maaf, saya datang selarut ini, tetapi tidak seharusnya vars(a)melakukan ini? Bagi saya itu lebih baik untuk memohon __dict__langsung.
robert

2
untuk contoh kedua akan lebih baik untuk melakukan __getattr__ = dict.__getitem__persis mereplikasi perilaku, maka Anda juga ingin __setattr__ = dict.__setitem__dan __delattr__ = dict.__delitem__untuk melengkapi.
Tadhg McDonald-Jensen

140

Alih-alih x.__dict__, itu sebenarnya lebih pythonic untuk digunakan vars(x).


3
Sepakat. Perhatikan bahwa Anda juga dapat mengonversi cara lain (kelas-> kelas) dengan mengetik MyClass(**my_dict), dengan asumsi Anda telah mendefinisikan konstruktor dengan parameter yang mencerminkan atribut kelas. Tidak perlu mengakses atribut pribadi atau mengganti dict.
tvt173

2
Bisakah Anda jelaskan mengapa ini lebih Pythonic?
Hugh W

1
Pertama, Python umumnya menghindari pemanggilan item dunder secara langsung, dan hampir selalu ada metode atau fungsi (atau operator) untuk mengaksesnya secara tidak langsung. Secara umum, atribut dan metode dunder adalah detail implementasi, dan menggunakan fungsi "wrapper" memungkinkan Anda untuk memisahkan keduanya. Kedua, dengan cara ini Anda dapat mengganti varsfungsi dan memperkenalkan fungsi tambahan tanpa mengubah objek itu sendiri.
Berislav Lopac

1
Masih gagal jika kelas Anda menggunakannya __slots__.
cz

Itu benar, dan saya selalu merasa bahwa itu akan menjadi arah yang baik untuk diperluas vars, yaitu untuk mengembalikan setara dengan __dict__untuk kelas "ditempatkan". Untuk saat ini, ini dapat ditiru dengan menambahkan __dict__properti yang kembali {x: getattr(self, x) for x in self.__slots__}(tidak yakin apakah itu mempengaruhi kinerja atau perilaku dengan cara apa pun).
Berislav Lopac

59

The dirbuiltin akan memberikan semua atribut objek, termasuk metode khusus seperti __str__, __dict__dan sejumlah orang lain yang Anda mungkin tidak ingin. Tetapi Anda dapat melakukan sesuatu seperti:

>>> 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' }

Jadi dapat memperluas ini hanya mengembalikan atribut data dan bukan metode, dengan mendefinisikan propsfungsi Anda seperti ini:

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
Kode ini mencakup metode. Apakah ada cara untuk mengecualikan metode? Saya hanya perlu bidang objek. Terima kasih
Julio César

ismethodtidak menangkap fungsi. Contoh: inspect.ismethod(str.upper). inspect.isfunctiontidak jauh lebih bermanfaat. Tidak yakin bagaimana cara mendekati ini segera.
Ehtesh Choudhury

Saya membuat beberapa tweak untuk berulang secara kasar dan mengabaikan semua kesalahan secara mendalam di sini, terima kasih! gist.github.com/thorsummoner/bf0142fd24974a0ced778768a33a3069
ThorSummoner

26

Saya sudah puas dengan kombinasi kedua jawaban:

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

Itu juga berfungsi, tetapi perlu diketahui bahwa itu hanya akan memberi Anda atribut yang ditetapkan pada instance, bukan pada kelas (seperti kelas Foo dalam contoh Anda) ...
dF.

Jadi, jcarrascal, Anda lebih baik membungkus kode di atas dalam fungsi seperti props (), maka Anda dapat memanggil props (f) atau props (Foo). Perhatikan bahwa Anda hampir selalu lebih baik menulis fungsi daripada menulis kode 'inline'.
quamrana

Bagus, btw perhatikan ini untuk python2.7, untuk python3 relpace iteritems () hanya dengan item ().
Morten

Dan bagaimana staticmethod? Bukan itu callable.
Alex

16

Saya pikir saya akan meluangkan waktu untuk menunjukkan kepada Anda bagaimana Anda dapat menerjemahkan objek untuk didiktekan 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'}"

Bagian kunci dari kode ini adalah __iter__fungsinya.

Seperti yang dijelaskan dalam komentar, hal pertama yang kami lakukan adalah mengambil item-item Kelas dan mencegah apa pun yang dimulai dengan '__'.

Setelah Anda membuat itu dict, maka Anda dapat menggunakan updatefungsi dict dan mengirimkan instance__dict__ .

Ini akan memberi Anda kamus anggota + kelas + instance lengkap. Sekarang yang tersisa adalah beralih pada mereka dan menghasilkan pengembalian.

Juga, jika Anda berencana untuk menggunakan ini banyak, Anda dapat membuat @iterabledekorator kelas.

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

Ini juga akan mengambil semua metode, tetapi kita hanya perlu bidang kelas + instance. Mungkin dict((x, y) for x, y in KpiRow.__dict__.items() if x[:2] != '__' and not callable(y))akan menyelesaikannya? Tapi masih ada staticmetode :(
Alex

15

Untuk membuat kamus dari objek yang arbitrer , cukup untuk digunakan __dict__.

Ini melewatkan atribut yang diwarisi objek dari kelasnya. Sebagai contoh,

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

hasattr (a, 'x') benar, tetapi 'x' tidak muncul dalam .__ dict__


Dalam hal ini apa solusinya? Karena vars()tidak berfungsi
should_be_working

@should_be_working diradalah solusi dalam kasus ini. Lihat jawaban lain tentang itu.
Albert

8

Jawaban terlambat tetapi disediakan untuk kelengkapan dan manfaat para pengguna Google:

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

Ini tidak akan menunjukkan metode yang didefinisikan di kelas, tetapi masih akan menampilkan bidang termasuk yang ditugaskan untuk lambdas atau yang dimulai dengan garis bawah ganda.


6

Saya pikir cara termudah adalah membuat atribut getitem untuk kelas. Jika Anda perlu menulis ke objek, Anda dapat membuat setattr khusus . Berikut ini adalah contoh untuk 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}

dikt menghasilkan atribut objek menjadi kamus dan objek kamus dapat digunakan untuk mendapatkan item yang Anda butuhkan.


Setelah jawaban ini masih belum jelas bagaimana cara mendapatkan kamus dari suatu objek. Bukan properti, tapi seluruh kamus;)
maxkoryukov

6

Kelemahan menggunakan __dict__ adalah bahwa itu dangkal; itu tidak akan mengonversi subclass apa pun ke kamus.

Jika Anda menggunakan Python3.5 atau lebih tinggi, Anda dapat menggunakan jsons:

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

3

Jika Anda ingin membuat daftar bagian dari atribut Anda, ganti __dict__:

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

# Call __dict__
d = instance.__dict__()

Ini sangat membantu jika Anda instancemendapatkan beberapa data blok besar dan Anda ingin mendorong dke Redis seperti antrian pesan.


__dict__adalah atribut, bukan metode, jadi contoh ini mengubah antarmuka (yaitu Anda harus menyebutnya sebagai callable), jadi itu bukan menimpanya.
Berislav Lopac

0

PYTHON 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

Sekarang Anda dapat menggunakan kode di atas di dalam kelas Anda sendiri:

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() bagus, tetapi tidak berfungsi untuk objek objek bersarang

Konversi objek objek bersarang menjadi dikt:

def to_dict(self):
    return json.loads(json.dumps(self, default=lambda o: o.__dict__))
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.