Tentukan jenis objek?

1791

Apakah ada cara sederhana untuk menentukan apakah suatu variabel adalah daftar, kamus, atau sesuatu yang lain? Saya mendapatkan objek kembali yang mungkin tipe baik dan saya harus bisa membedakannya.

Justin Ethier
sumber
44
Meskipun secara umum saya setuju dengan Anda, ada situasi di mana ada baiknya untuk diketahui. Dalam kasus khusus ini saya melakukan peretasan cepat yang akhirnya saya putar kembali, jadi Anda benar kali ini. Tetapi dalam beberapa kasus - ketika menggunakan refleksi, misalnya - penting untuk mengetahui jenis objek yang Anda hadapi.
Justin Ethier
67
@ S.Lott, saya tidak setuju dengan itu; dengan mengetahui jenisnya, Anda dapat menangani beberapa input varian cantik dan tetap melakukan hal yang benar. Ini memungkinkan Anda bekerja di sekitar masalah antarmuka yang melekat dengan mengandalkan pada bebek-mengetik murni (misalnya, .bark () metode pada Pohon berarti sesuatu yang sama sekali berbeda dari pada Anjing.) Misalnya, Anda dapat membuat fungsi yang melakukan beberapa pekerjaan pada file yang menerima string (misalnya, jalur), objek jalur, atau daftar. Semua memiliki antarmuka yang berbeda, tetapi hasil akhirnya sama: lakukan beberapa operasi pada file itu.
Robert P
22
@ S.Lott, saya berharap akan jelas bahwa ini adalah contoh yang dibuat-buat; namun itu adalah titik kegagalan utama dalam mengetik bebek, dan salah satu yang trytidak membantu. Misalnya, jika Anda tahu bahwa pengguna dapat meneruskan string atau array, keduanya dapat indeks, tetapi indeks itu berarti sesuatu yang sama sekali berbeda. Cukup mengandalkan try-catch dalam kasus-kasus itu akan gagal dengan cara yang tidak terduga dan aneh. Salah satu solusinya adalah dengan membuat metode terpisah, yang lain untuk menambahkan pengecekan tipe kecil. Saya pribadi lebih suka perilaku polimorfik daripada beberapa metode yang melakukan hal yang hampir sama ... tapi itu hanya saya :)
Robert P
22
@ S.Lott, bagaimana dengan pengujian unit? Terkadang Anda ingin pengujian Anda memverifikasi bahwa suatu fungsi mengembalikan sesuatu dengan jenis yang tepat. Contoh yang sangat nyata adalah ketika Anda memiliki pabrik kelas.
Elliot Cameron
17
Untuk contoh yang kurang dibuat-buat, pertimbangkan serializer / deserializer. Menurut definisi Anda mengkonversi antara objek yang disediakan pengguna dan representasi serial. Serializer perlu menentukan jenis objek yang Anda lewati, dan Anda mungkin tidak memiliki informasi yang memadai untuk menentukan jenis deserialisasi tanpa meminta runtime (atau paling tidak, Anda mungkin memerlukannya untuk memeriksa kewarasan untuk menangkap data buruk sebelum masuk) sistem Anda!)
Karl

Jawaban:

1977

Ada dua fungsi bawaan yang membantu Anda mengidentifikasi jenis objek. Anda dapat menggunakan type() jika Anda membutuhkan jenis objek yang tepat, dan isinstance()untuk memeriksa jenis objek terhadap sesuatu. Biasanya, Anda ingin menggunakan isistance()sebagian besar waktu karena sangat kuat dan juga mendukung tipe warisan.


Untuk mendapatkan jenis objek yang sebenarnya, Anda menggunakan type()fungsi bawaan. Melewati objek sebagai satu-satunya parameter akan mengembalikan objek tipe objek itu:

>>> type([]) is list
True
>>> type({}) is dict
True
>>> type('') is str
True
>>> type(0) is int
True

Ini tentu saja juga berfungsi untuk jenis khusus:

>>> class Test1 (object):
        pass
>>> class Test2 (Test1):
        pass
>>> a = Test1()
>>> b = Test2()
>>> type(a) is Test1
True
>>> type(b) is Test2
True

Perhatikan bahwa type()hanya akan mengembalikan tipe objek langsung, tetapi tidak akan dapat memberi tahu Anda tentang jenis pewarisan.

>>> type(b) is Test1
False

Untuk mengatasinya, Anda harus menggunakan isinstancefungsi ini. Ini tentu saja juga berfungsi untuk tipe bawaan:

>>> isinstance(b, Test1)
True
>>> isinstance(b, Test2)
True
>>> isinstance(a, Test1)
True
>>> isinstance(a, Test2)
False
>>> isinstance([], list)
True
>>> isinstance({}, dict)
True

isinstance()biasanya merupakan cara yang lebih disukai untuk memastikan jenis objek karena ia juga akan menerima jenis turunan. Jadi kecuali Anda benar-benar membutuhkan objek jenis (untuk alasan apa pun), menggunakan isinstance()lebih disukai type().

Parameter kedua isinstance()juga menerima tupel jenis, jadi dimungkinkan untuk memeriksa beberapa jenis sekaligus. isinstancekemudian akan mengembalikan true, jika objeknya adalah salah satu dari jenis tersebut:

>>> isinstance([], (tuple, list, set))
True
menyodok
sumber
68
Saya pikir ini lebih jelas untuk digunakan isdaripada ==jenisnya adalah lajang
John La Rooy
18
@gnibbler, Dalam kasus-kasus yang akan Anda ketik, periksa (yang seharusnya tidak Anda lakukan isinstancesejak awal), adalah bentuk yang disukai, jadi tidak perlu ==atau tidak isperlu digunakan.
Mike Graham
23
@ Mike Graham, ada kalanya typeadalah jawaban terbaik. Ada kalanya isinstanceadalah jawaban terbaik dan ada kalanya mengetik bebek adalah jawaban terbaik. Sangat penting untuk mengetahui semua opsi sehingga Anda dapat memilih mana yang lebih tepat untuk situasi tersebut.
John La Rooy
6
@gnibbler, Itu mungkin, meskipun saya belum mengalami situasi di mana type(foo) is SomeTypeakan lebih baik daripada isinstance(foo, SomeType).
Mike Graham
5
@poke: saya sepenuhnya setuju tentang PEP8, tetapi Anda menyerang seorang pelawak di sini: bagian penting dari argumen Sven bukanlah PEP8, tetapi Anda dapat menggunakan isinstanceuntuk usecase Anda (memeriksa berbagai jenis) juga, dan dengan membersihkan sintaks juga, yang memiliki keuntungan besar yang dapat Anda ambil subclass. seseorang yang menggunakan OrderedDictakan membenci kode Anda gagal karena hanya menerima dicts murni.
domba terbang
166

Anda dapat melakukannya menggunakan type():

>>> a = []
>>> type(a)
<type 'list'>
>>> f = ()
>>> type(f)
<type 'tuple'>
inkedmn
sumber
40

Mungkin lebih Pythonic untuk menggunakan try... exceptblok. Dengan begitu, jika Anda memiliki kelas yang dukun seperti daftar, atau dukun seperti dict, itu akan berperilaku baik terlepas dari apa tipenya sebenarnya .

Untuk memperjelas, metode yang disukai "mengatakan perbedaan" antara tipe variabel adalah dengan sesuatu yang disebut bebek mengetik : selama metode (dan tipe pengembalian) yang ditanggapi oleh variabel adalah apa yang diharapkan oleh subrutin Anda, perlakukan seperti apa yang Anda harapkan menjadi. Misalnya, jika Anda memiliki kelas yang membebani operator braket dengan getattrdan setattr, tetapi menggunakan beberapa skema internal yang lucu, itu akan sesuai untuk itu berperilaku sebagai kamus jika itu yang ingin ditiru.

Masalah lain dengan type(A) is type(B)pemeriksaan adalah bahwa jika Amerupakan subkelas dari B, itu mengevaluasi falsekapan, secara pemrograman, Anda akan berharap itu akan terjadi true. Jika suatu objek adalah subclass dari daftar, itu harus berfungsi seperti daftar: memeriksa jenis seperti yang disajikan dalam jawaban lain akan mencegah hal ini. ( isinstanceakan bekerja, namun).

Seth Johnson
sumber
16
Mengetik bebek tidak benar-benar menceritakan perbedaannya. Ini tentang menggunakan antarmuka umum.
Justin Ethier
5
Hati-hati - sebagian besar panduan gaya pengkodean merekomendasikan untuk tidak menggunakan penanganan pengecualian sebagai bagian dari aliran kontrol normal kode, biasanya karena membuat kode sulit dibaca. try... exceptadalah solusi yang baik ketika Anda ingin menangani kesalahan, tetapi tidak saat memutuskan perilaku berdasarkan tipe.
Rens van der Heijden
34

Pada contoh objek Anda juga memiliki:

__class__

atribut. Berikut adalah contoh yang diambil dari konsol Python 3.3

>>> str = "str"
>>> str.__class__
<class 'str'>
>>> i = 2
>>> i.__class__
<class 'int'>
>>> class Test():
...     pass
...
>>> a = Test()
>>> a.__class__
<class '__main__.Test'>

Berhati-hatilah bahwa dalam python 3.x dan di kelas New-Style (dapat dipilih secara opsional dari Python 2.6) kelas dan tipe telah digabungkan dan ini kadang-kadang dapat menyebabkan hasil yang tidak terduga. Terutama karena alasan ini cara favorit saya untuk menguji jenis / kelas adalah dengan isinstance built in function.

Lorenzo Persichetti
sumber
2
Poin Anda di akhir sangat penting. ketik (obj) adalah Kelas tidak berfungsi dengan benar, tetapi isinstance melakukan trik. Saya mengerti bahwa isinstance lebih disukai, tetapi lebih menguntungkan daripada hanya memeriksa jenis turunan, seperti yang disarankan dalam jawaban yang diterima.
mstbaum
__class__sebagian besar OK pada Python 2.x, satu-satunya objek di Python yang tidak memiliki __class__atribut adalah kelas gaya lama AFAIK. Ngomong-ngomong, saya tidak mengerti masalah Python 3 Anda - pada versi seperti itu, hanya setiap objek memiliki __class__atribut yang menunjuk ke kelas yang tepat.
Alan Franzoni
21

Tentukan jenis objek Python

Tentukan jenis objek dengan type

>>> obj = object()
>>> type(obj)
<class 'object'>

Meskipun berfungsi, hindari atribut bergaris bawah ganda seperti __class__- mereka tidak bersifat publik semantik, dan, meskipun mungkin tidak dalam kasus ini, fungsi bawaan biasanya memiliki perilaku yang lebih baik.

>>> obj.__class__ # avoid this!
<class 'object'>

memeriksa jenis

Apakah ada cara sederhana untuk menentukan apakah suatu variabel adalah daftar, kamus, atau sesuatu yang lain? Saya mendapatkan objek kembali yang mungkin tipe baik dan saya harus bisa membedakannya.

Nah itu pertanyaan yang berbeda, jangan gunakan tipe - gunakan isinstance:

def foo(obj):
    """given a string with items separated by spaces, 
    or a list or tuple, 
    do something sensible
    """
    if isinstance(obj, str):
        obj = str.split()
    return _foo_handles_only_lists_or_tuples(obj)

Ini mencakup kasus di mana pengguna Anda mungkin melakukan sesuatu yang pintar atau masuk akal dengan subkelas str- sesuai dengan prinsip Pergantian Liskov, Anda ingin dapat menggunakan contoh subkelas tanpa melanggar kode Anda - dan isinstancemendukung ini.

Gunakan Abstraksi

Bahkan lebih baik, Anda mungkin mencari Kelas Dasar Abstrak spesifik dari collectionsatau numbers:

from collections import Iterable
from numbers import Number

def bar(obj):
    """does something sensible with an iterable of numbers, 
    or just one number
    """
    if isinstance(obj, Number): # make it a 1-tuple
        obj = (obj,)
    if not isinstance(obj, Iterable):
        raise TypeError('obj must be either a number or iterable of numbers')
    return _bar_sensible_with_iterable(obj)

Atau Hanya Jangan Ketikkan-centang secara eksplisit

Atau, mungkin yang terbaik, gunakan bebek-mengetik, dan jangan ketik-periksa kode Anda secara eksplisit. Mengetik bebek mendukung Substitusi Liskov dengan keanggunan yang lebih tinggi dan lebih sedikit.

def baz(obj):
    """given an obj, a dict (or anything with an .items method) 
    do something sensible with each key-value pair
    """
    for key, value in obj.items():
        _baz_something_sensible(key, value)

Kesimpulan

  • Gunakan typeuntuk benar-benar mendapatkan kelas instance.
  • Gunakan isinstanceuntuk secara eksplisit memeriksa subclass aktual atau abstraksi terdaftar.
  • Dan hindari pengecekan tipe yang masuk akal.
Aaron Hall
sumber
Selalu ada try/ exceptbukannya memeriksa secara eksplisit.
toonarmycaptain
Mungkin itulah yang akan dilakukan pengguna jika mereka tidak yakin tentang jenis yang akan mereka lewati. Saya tidak suka mengacaukan implementasi yang benar dengan penanganan pengecualian kecuali jika saya memiliki sesuatu yang sangat baik untuk dilakukan dengan pengecualian. Pengecualian yang diajukan harus cukup untuk memberi tahu pengguna bahwa mereka harus memperbaiki penggunaannya.
Aaron Hall
13

Anda bisa menggunakan type()atau isinstance().

>>> type([]) is list
True

Berhati-hatilah bahwa Anda dapat mengalahkan listatau tipe lain dengan menetapkan variabel dalam lingkup nama yang sama saat ini.

>>> the_d = {}
>>> t = lambda x: "aight" if type(x) is dict else "NOPE"
>>> t(the_d) 'aight'
>>> dict = "dude."
>>> t(the_d) 'NOPE'

Di atas kita melihat bahwa dictakan dipindahkan ke string, oleh karena itu tes:

type({}) is dict

... gagal.

Untuk menyiasati ini dan menggunakan type()lebih hati-hati:

>>> import __builtin__
>>> the_d = {}
>>> type({}) is dict
True
>>> dict =""
>>> type({}) is dict
False
>>> type({}) is __builtin__.dict
True
akta02392
sumber
2
Saya tidak yakin perlunya menunjukkan bahwa membayangi nama tipe data builtin buruk untuk kasus ini. dictString Anda juga akan gagal untuk banyak kode lain, seperti dict([("key1", "value1"), ("key2", "value2")]). Jawaban untuk masalah-masalah semacam itu adalah "Kalau begitu jangan lakukan itu" . Jangan membayangi nama tipe bawaan dan mengharapkan sesuatu berfungsi dengan baik.
Blckknght
3
Saya setuju dengan Anda pada bagian "jangan lakukan itu". Tetapi memang untuk memberitahu seseorang untuk tidak melakukan sesuatu, setidaknya Anda harus menjelaskan mengapa tidak dan saya pikir ini adalah kesempatan yang relevan untuk melakukan hal itu. Saya bermaksud agar metode yang hati-hati terlihat jelek dan menggambarkan mengapa mereka mungkin tidak mau melakukannya, meninggalkan mereka untuk memutuskan.
deed02392
type () tidak berfungsi seperti yang diharapkan pada Python 2.x untuk instance klasik.
Alan Franzoni
5

Meskipun pertanyaannya sudah cukup lama, saya menemukan ini sambil menemukan sendiri cara yang tepat, dan saya pikir itu masih perlu diklarifikasi, setidaknya untuk Python 2.x (tidak memeriksa Python 3, tetapi karena masalah muncul dengan kelas klasik yang hilang pada versi seperti itu, mungkin tidak masalah).

Di sini saya mencoba menjawab pertanyaan judul: bagaimana saya bisa menentukan jenis objek yang berubah-ubah ? Saran lain tentang menggunakan atau tidak menggunakan isinstance baik-baik saja di banyak komentar dan jawaban, tapi saya tidak membahas masalah itu.

Masalah utama dengan type()pendekatan ini adalah bahwa itu tidak berfungsi dengan baik dengan contoh gaya lama :

class One:
    pass

class Two:
    pass


o = One()
t = Two()

o_type = type(o)
t_type = type(t)

print "Are o and t instances of the same class?", o_type is t_type

Menjalankan potongan ini akan menghasilkan:

Are o and t instances of the same class? True

Yang, menurut saya, bukan yang diharapkan kebanyakan orang.

The __class__pendekatan adalah yang paling dekat dengan kebenaran, tetapi itu tidak akan bekerja dalam satu kasus penting: ketika lulus-in objek adalah gaya lama kelas (! Bukan sebuah contoh), karena benda-benda kekurangan atribut tersebut.

Ini adalah cuplikan kode terkecil yang dapat saya pikirkan yang memenuhi pertanyaan sah seperti itu secara konsisten:

#!/usr/bin/env python
from types import ClassType
#we adopt the null object pattern in the (unlikely) case
#that __class__ is None for some strange reason
_NO_CLASS=object()
def get_object_type(obj):
    obj_type = getattr(obj, "__class__", _NO_CLASS)
    if obj_type is not _NO_CLASS:
        return obj_type
    # AFAIK the only situation where this happens is an old-style class
    obj_type = type(obj)
    if obj_type is not ClassType:
        raise ValueError("Could not determine object '{}' type.".format(obj_type))
    return obj_type
Alan Franzoni
sumber
5

hati-hati menggunakan isinstance

isinstance(True, bool)
True
>>> isinstance(True, int)
True

tapi ketik

type(True) == bool
True
>>> type(True) == int
False
tnusraddinov
sumber
3

Selain jawaban sebelumnya, ada baiknya menyebutkan keberadaan collections.abcyang berisi beberapa kelas dasar abstrak (ABC) yang melengkapi mengetik bebek.

Misalnya, alih-alih secara eksplisit memeriksa apakah ada daftar dengan:

isinstance(my_obj, list)

Anda bisa, jika Anda hanya tertarik untuk melihat apakah objek yang Anda miliki memungkinkan mendapatkan barang, gunakan collections.abc.Sequence:

from collections.abc import Sequence
isinstance(my_obj, Sequence) 

jika Anda benar-benar tertarik pada objek yang memungkinkan untuk mendapatkan, mengatur, dan menghapus item (yaitu urutan yang dapat diubah ), Anda akan memilih collections.abc.MutableSequence.

Banyak ABC lainnya didefinisikan di sana, Mappinguntuk objek yang dapat digunakan sebagai peta, Iterable, Callable, dan sebagainya. Daftar lengkap semua ini dapat dilihat pada dokumentasi untuk collections.abc.

Dimitris Fasarakis Hilliard
sumber
1

Secara umum Anda dapat mengekstraksi string dari objek dengan nama kelas,

str_class = object.__class__.__name__

dan menggunakannya untuk perbandingan,

if str_class == 'dict':
    # blablabla..
elif str_class == 'customclass':
    # blebleble..
José Crespo Barrios
sumber
1

Dalam banyak kasus praktis alih-alih menggunakan typeatau isinstanceAnda juga dapat menggunakan @functools.singledispatch, yang digunakan untuk mendefinisikan fungsi generik ( fungsi yang terdiri dari beberapa fungsi yang menerapkan operasi yang sama untuk jenis yang berbeda ).

Dengan kata lain, Anda ingin menggunakannya ketika Anda memiliki kode seperti berikut:

def do_something(arg):
    if isinstance(arg, int):
        ... # some code specific to processing integers
    if isinstance(arg, str):
        ... # some code specific to processing strings
    if isinstance(arg, list):
        ... # some code specific to processing lists
    ...  # etc

Berikut ini adalah contoh kecil cara kerjanya:

from functools import singledispatch


@singledispatch
def say_type(arg):
    raise NotImplementedError(f"I don't work with {type(arg)}")


@say_type.register
def _(arg: int):
    print(f"{arg} is an integer")


@say_type.register
def _(arg: bool):
    print(f"{arg} is a boolean")
>>> say_type(0)
0 is an integer
>>> say_type(False)
False is a boolean
>>> say_type(dict())
# long error traceback ending with:
NotImplementedError: I don't work with <class 'dict'>

Selain itu, kita dapat menggunakan kelas abstrak untuk mencakup beberapa jenis sekaligus:

from collections.abc import Sequence


@say_type.register
def _(arg: Sequence):
    print(f"{arg} is a sequence!")
>>> say_type([0, 1, 2])
[0, 1, 2] is a sequence!
>>> say_type((1, 2, 3))
(1, 2, 3) is a sequence!
Georgy
sumber
0

type()adalah solusi yang lebih baik daripada isinstance(), terutama untuk booleans:

Truedan Falsehanya kata kunci yang berarti 1dan 0dengan python. Jadi,

isinstance(True, int)

dan

isinstance(False, int)

keduanya kembali True. Kedua boolean adalah turunan dari bilangan bulat. type()Namun, lebih pintar:

type(True) == int

kembali False.

Alec Alameddine
sumber