Określić typ obiektu?

1790

Czy istnieje prosty sposób ustalenia, czy zmienna jest listą, słownikiem czy czymś innym? Otrzymuję z powrotem obiekt, który może być dowolnego typu i muszę być w stanie odróżnić.

Justin Ethier
źródło
44
Chociaż ogólnie zgadzam się z tobą, są sytuacje, w których warto wiedzieć. W tym konkretnym przypadku robiłem szybkie hakowanie, które ostatecznie wycofałem, więc tym razem masz rację. Ale w niektórych przypadkach - na przykład przy użyciu odbicia - ważne jest, aby wiedzieć, z jakim typem obiektu masz do czynienia.
Justin Ethier,
67
@ S.Lott Nie zgadzam się z tym; znając typ, możesz poradzić sobie z ładnymi wariantami i nadal robić właściwe rzeczy. Pozwala na obejście problemów związanych z interfejsem związanych z pisaniem na klawiaturze (np. Metoda .bark () na drzewie oznacza coś zupełnie innego niż na psie). Na przykład możesz stworzyć funkcję, która będzie działała na plik, który akceptuje ciąg (np. ścieżkę), obiekt ścieżki lub listę. Wszystkie mają różne interfejsy, ale końcowy wynik jest taki sam: wykonaj operację na tym pliku.
Robert P
22
@ S.Lott Miałem nadzieję, że będzie oczywiste, że jest to wymyślony przykład; niemniej jednak jest to poważny błąd polegający na pisaniu kaczek i taki, tryktóry nie pomaga. Na przykład, jeśli wiesz, że użytkownik może przekazać ciąg lub tablicę, oba są w stanie indeksować, ale ten indeks oznacza coś zupełnie innego. Po prostu poleganie na try-catch w tych przypadkach zawiedzie w nieoczekiwany i dziwny sposób. Jednym rozwiązaniem jest stworzenie osobnej metody, innym sposobem dodania małego sprawdzania typu. Osobiście wolę zachowanie polimorficzne niż wiele metod, które robią prawie to samo ... ale to tylko ja :)
Robert P
22
@ S.Lott, a co z testowaniem jednostkowym? Czasami chcesz, aby twoje testy zweryfikowały, że funkcja zwraca coś odpowiedniego typu. Bardzo realnym przykładem jest fabryka klas.
Elliot Cameron
17
Dla mniej wymyślnego przykładu rozważmy serializator / deserializator. Z definicji konwertujesz między obiektami dostarczonymi przez użytkownika a serializacją. Serializator musi określić typ przekazywanego obiektu, a użytkownik może nie mieć wystarczających informacji, aby określić zdejrializowany typ bez pytania środowiska wykonawczego (a przynajmniej może być potrzebny do sprawdzenia poprawności, aby przechwycić złe dane przed wejściem do niego twój system!)
Karl

Odpowiedzi:

1975

Istnieją dwie wbudowane funkcje, które pomagają zidentyfikować typ obiektu. Można użyć type() , jeśli potrzebujesz dokładny typ obiektu, a isinstance()do sprawdzenia typ obiektu, przed czymś. Zazwyczaj chcesz używać przez isistance()większość czasu, ponieważ jest bardzo solidny i obsługuje dziedziczenie typu.


Aby uzyskać rzeczywisty typ obiektu, użyj wbudowanej type()funkcji. Przekazanie obiektu jako jedynego parametru zwróci obiekt typu tego obiektu:

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

To oczywiście działa również w przypadku niestandardowych typów:

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

Zauważ, że type()zwróci tylko bezpośredni typ obiektu, ale nie będzie w stanie powiedzieć ci o dziedziczeniu typu.

>>> type(b) is Test1
False

Aby to pokryć, powinieneś użyć isinstancefunkcji. To oczywiście działa również w przypadku typów wbudowanych:

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

isinstance()jest zwykle preferowanym sposobem zapewnienia typu obiektu, ponieważ akceptuje on także typy pochodne. Tak więc, chyba że faktycznie potrzebujesz obiektu typu (z jakiegokolwiek powodu), użycie isinstance()jest lepsze niż type().

Drugi parametr isinstance()akceptuje również krotkę typów, więc możliwe jest sprawdzenie wielu typów jednocześnie. isinstancezwróci wówczas wartość true, jeśli obiekt jest dowolnego z tych typów:

>>> isinstance([], (tuple, list, set))
True
szturchać
źródło
68
Myślę, że to bardziej przejrzyste, aby używać iszamiast ==jak typy są samotnymi
John La Rooy
18
@gnibbler w przypadkach można byłoby typechecking (czego nie należy robić, aby rozpocząć) isinstancejest preferowaną formą byle jak, więc nie ==bądź ismuszą być użyte.
Mike Graham
23
@Mike Graham, są chwile, kiedy typejest najlepsza odpowiedź. Są chwile, kiedy isinstancenajlepsza odpowiedź, a czasami pisanie kaczki jest najlepszą odpowiedzią. Ważne jest, aby znać wszystkie opcje, abyś mógł wybrać bardziej odpowiedni dla danej sytuacji.
John La Rooy,
6
@gnibbler, Być może, chociaż nie spotkałem się jeszcze z sytuacją, w której type(foo) is SomeTypebyłoby lepiej niż isinstance(foo, SomeType).
Mike Graham,
5
@poke: całkowicie zgadzam się na temat PEP8, ale atakujesz tutaj słomkę: ważną częścią argumentu Svena nie była PEP8, ale którą możesz wykorzystać również w isinstanceprzypadku użycia (sprawdzanie zakresu typów), a także z jako czystą składnię, która ma tę wielką zaletę, że można przechwytywać podklasy. ktoś, kto go używa OrderedDict, nienawidzi twojego kodu do niepowodzenia, ponieważ akceptuje on tylko czyste dyktanda.
latające owce
165

Możesz to zrobić za pomocą type():

>>> a = []
>>> type(a)
<type 'list'>
>>> f = ()
>>> type(f)
<type 'tuple'>
inkedmn
źródło
40

Używanie try... exceptbloku może być bardziej Pythoniczne . W ten sposób, jeśli masz klasę, która znachorów jak liście lub znachorów jak dict, będzie zachowywać się poprawnie niezależnie od jej rodzaju naprawdę jest.

Aby wyjaśnić, preferowaną metodą „odróżniania” typów zmiennych jest coś, co nazywa się pisaniem kaczym : o ile metody (i typy zwracane), na które reaguje zmienna, są zgodne z oczekiwaniami podprogramu, należy traktować to tak, jak się tego oczekuje być. Na przykład, jeśli masz klasę, która przeciąża operatorów nawiasów getattri setattr, ale używa jakiegoś śmiesznego schematu wewnętrznego, byłoby odpowiednie, aby zachowywał się jak słownik, jeśli to właśnie próbuje naśladować.

Innym problemem związanym ze type(A) is type(B)sprawdzaniem jest to, że jeśli Ajest podklasą B, ocenia, falsekiedy programowo można się spodziewać true. Jeśli obiekt jest podklasą listy, powinien działać jak lista: sprawdzenie typu przedstawionego w drugiej odpowiedzi zapobiegnie temu. ( isinstancebędzie jednak działać).

Seth Johnson
źródło
16
Pisanie kaczek nie polega jednak na odróżnianiu. Chodzi o korzystanie ze wspólnego interfejsu.
Justin Ethier,
5
Bądź ostrożny - większość przewodników po stylu kodowania zaleca nie używanie obsługi wyjątków jako części normalnego przepływu kontrolnego kodu, zwykle dlatego, że utrudnia to odczytanie kodu. try... exceptto dobre rozwiązanie, gdy chcesz poradzić sobie z błędami, ale nie przy podejmowaniu decyzji dotyczących zachowania na podstawie typu.
Rens van der Heijden
34

Na instancjach obiektu masz również:

__class__

atrybut. Oto próbka pobrana z konsoli 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'>

Uwaga: w Pythonie 3.xi klasach New Style (dostępne opcjonalnie z Pythona 2.6) klasy i typy zostały scalone, co może czasem prowadzić do nieoczekiwanych rezultatów. Głównie z tego powodu moim ulubionym sposobem testowania typów / klas jest wbudowana funkcja isinstance .

Lorenzo Persichetti
źródło
2
Twój punkt na końcu jest bardzo ważny. type (obj) is Klasa nie działała poprawnie, ale isinstance załatwiło sprawę. Rozumiem, że isinstance i tak jest preferowana, ale jest bardziej korzystna niż tylko sprawdzanie typów pochodnych, jak sugerowano w zaakceptowanej odpowiedzi.
mstbaum
__class__działa głównie w Pythonie 2.x, jedynymi obiektami w Pythonie, które nie mają __class__atrybutu, są klasy AFAIK w starym stylu. Nawiasem mówiąc, nie rozumiem twojego problemu z Python 3 - w takiej wersji tylko każdy obiekt ma __class__atrybut wskazujący odpowiednią klasę.
Alan Franzoni
21

Określ typ obiektu Python

Określ typ obiektu za pomocą type

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

Chociaż działa, unikaj podwójnych atrybutów podkreślenia, takich jak __class__- nie są one semantycznie publiczne, i chociaż być może nie w tym przypadku, wbudowane funkcje zwykle działają lepiej.

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

sprawdzanie typu

Czy istnieje prosty sposób ustalenia, czy zmienna jest listą, słownikiem czy czymś innym? Otrzymuję z powrotem obiekt, który może być dowolnego typu i muszę być w stanie odróżnić.

Cóż, to inne pytanie, nie używaj tekstu - użyj 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)

Obejmuje to przypadek, w którym użytkownik może robić coś sprytnego lub sensownego przez podklasowanie str- zgodnie z zasadą substytucji Liskova, chcesz mieć możliwość korzystania z instancji podklasy bez łamania kodu - i isinstanceobsługuje to.

Użyj abstrakcji

Co więcej, możesz poszukać konkretnej abstrakcyjnej klasy bazowej z collectionslub 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)

Lub po prostu nie zaznaczaj wyraźnie typu

Lub, co najlepsze, użyj kaczych pism i nie sprawdzaj bezpośrednio swojego kodu. Pisanie na kaczkach obsługuje substytucję Liskova z większą elegancją i mniej gadatliwością.

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)

Wniosek

  • Służy typedo uzyskania klasy instancji.
  • Służy isinstancedo jawnego sprawdzania rzeczywistych podklas lub zarejestrowanych abstrakcji.
  • I po prostu unikaj sprawdzania typu tam, gdzie ma to sens.
Aaron Hall
źródło
Zawsze jest try/ exceptzamiast jawnego sprawdzania.
toonarmycaptain
Prawdopodobnie tak postąpi użytkownik, jeśli nie będzie pewien, jakie typy będą przekazywane. Nie lubię zaśmiecać poprawnej implementacji obsługi wyjątków, chyba że mam coś bardzo dobrego z tym wyjątkiem. Zgłoszony wyjątek powinien wystarczyć, aby poinformować użytkownika, że ​​musi poprawić swoje użycie.
Aaron Hall
13

Możesz użyć type()lub isinstance().

>>> type([]) is list
True

Ostrzegamy, że możesz zablokować listlub użyć dowolnego innego typu, przypisując zmienną w bieżącym zakresie o tej samej nazwie.

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

Powyżej widzimy, że dictzostaje on ponownie przypisany do łańcucha, dlatego test:

type({}) is dict

... zawodzi.

Aby obejść ten problem i type()zachować ostrożność:

>>> import __builtin__
>>> the_d = {}
>>> type({}) is dict
True
>>> dict =""
>>> type({}) is dict
False
>>> type({}) is __builtin__.dict
True
deed02392
źródło
2
Nie jestem pewien, czy konieczne jest wskazanie, że zacienianie nazwy wbudowanego typu danych jest złe w tym przypadku. Twój dictciąg również nie powiedzie się w przypadku wielu innych kodów, takich jak dict([("key1", "value1"), ("key2", "value2")]). Odpowiedzią na tego rodzaju problemy jest „Więc nie rób tego” . Nie cień nazw wbudowanych typów i oczekuj, że wszystko będzie działać poprawnie.
Blckknght
3
Zgadzam się z tobą w części „nie rób tego”. Ale rzeczywiście, aby powiedzieć komuś, żeby nie robił czegoś, powinieneś przynajmniej wyjaśnić, dlaczego nie, i pomyślałem, że to odpowiednia okazja, aby to zrobić. Chciałem, aby ostrożna metoda wyglądała brzydko i zilustrowała, dlaczego nie chcą tego robić, pozostawiając im decyzję.
deed02392
type () nie działa zgodnie z oczekiwaniami w Pythonie 2.x dla klasycznych instancji.
Alan Franzoni
5

Chociaż pytania są dość stare, natknąłem się na to, jednocześnie sam znajdując właściwy sposób, i myślę, że nadal wymaga wyjaśnienia, przynajmniej w przypadku Python 2.x (nie sprawdziłem w Python 3, ale ponieważ problem pojawia się w klasycznych klasach które zostały usunięte w takiej wersji, prawdopodobnie nie ma to znaczenia).

Tutaj próbuję odpowiedzieć na pytanie tytułu: jak mogę określić typ dowolnego obiektu ? Inne sugestie dotyczące używania lub nieużywania isinstance są w porządku w wielu komentarzach i odpowiedziach, ale nie odpowiadam na te obawy.

Głównym problemem związanym z tym type()podejściem jest to, że nie działa ono poprawnie w instancjach w starym stylu :

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

Wykonanie tego fragmentu kodu dałoby:

Are o and t instances of the same class? True

Twierdzę, że nie jest to, czego większość ludzi by się spodziewała.

__class__Podejście jest najbardziej blisko poprawności, ale to nie będzie działać w jednej kluczowej sprawie: gdy przeszedł w obiekcie jest starym stylu klasy (! Nie instancją), ponieważ obiekty te nie mają takiego atrybutu.

Jest to najmniejszy fragment kodu, który mógłbym wymyślić, który spełnia takie uzasadnione pytanie w spójny sposób:

#!/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
źródło
5

bądź ostrożny używając isinstance

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

ale wpisz

type(True) == bool
True
>>> type(True) == int
False
tnusraddinov
źródło
3

Oprócz poprzednich odpowiedzi, warto wspomnieć o istnieniu tego, collections.abcktóry zawiera kilka abstrakcyjnych klas bazowych (ABC), które uzupełniają pisanie kaczek.

Na przykład zamiast jawnego sprawdzania, czy coś jest listą z:

isinstance(my_obj, list)

możesz, jeśli chcesz tylko sprawdzić, czy posiadany obiekt pozwala na zdobywanie przedmiotów, użyj collections.abc.Sequence:

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

jeśli jesteś bardzo zainteresowany obiektami, które umożliwiają pobieranie, ustawianie i usuwanie elementów (tj. sekwencje zmienne ), wybierz collections.abc.MutableSequence.

Wiele innych ABC zdefiniowane są tam Mappingdla obiektów, które mogą być stosowane jako mapy, Iterable, Callable, et cetera. Pełna lista tych wszystkich znajduje się w dokumentacji dla collections.abc.

Dimitris Fasarakis Hilliard
źródło
1

Ogólnie rzecz biorąc, możesz wyodrębnić ciąg z obiektu o nazwie klasy,

str_class = object.__class__.__name__

i używając go do porównania,

if str_class == 'dict':
    # blablabla..
elif str_class == 'customclass':
    # blebleble..
José Crespo Barrios
źródło
1

W wielu praktycznych przypadkach zamiast używać typelub isinstancemożna również użyć @functools.singledispatch, który służy do definiowania funkcji ogólnych ( funkcja złożona z wielu funkcji realizujących tę samą operację dla różnych typów ).

Innymi słowy, powinieneś go użyć, gdy masz kod podobny do następującego:

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

Oto mały przykład tego, jak to działa:

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

Dodatkowo możemy użyć klas abstrakcyjnych, aby objąć kilka typów jednocześnie:

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
źródło
0

type()jest lepszym rozwiązaniem niż isinstance(), szczególnie dla booleans:

Truei Falseto tylko słowa kluczowe, to znaczy, że 1i 0w Pythonie. A zatem,

isinstance(True, int)

i

isinstance(False, int)

oba wracają True. Oba booleany są wystąpieniem liczby całkowitej. type()jest jednak bardziej sprytny:

type(True) == int

zwraca False.

Alec Alameddine
źródło