Визначте тип об’єкта?


1790

Чи є простий спосіб визначити, чи є змінною список, словник чи щось інше? Я повертаю об'єкт назад, який може бути будь-якого типу, і мені потрібно вміти визначити різницю.


44
Хоча загалом я згоден з вами, є ситуації, коли корисно знати. У цьому конкретному випадку я робив швидкий злом, який я врешті-решт відкотив, тож ви цього разу правильні. Але в деяких випадках - наприклад, при використанні рефлексії - важливо знати, з яким типом об’єкта ви маєте справу.
Джастін Етьє

67
@ S.Lott Я не погодився з цим; маючи можливість знати тип, ви можете мати справу з деякими досить вкладними варіантами та все-таки робити все правильно. Це дозволяє обходити проблеми з інтерфейсом, властиві покладатися на чисто типізацію качок (наприклад, метод .bark () на дереві означає щось зовсім інше, ніж на собаці.) Наприклад, ви можете зробити функцію, яка виконує певну роботу над файл, який приймає рядок (наприклад, шлях), об'єкт шляху або список. Усі мають різні інтерфейси, але кінцевий результат однаковий: виконайте деяку операцію над цим файлом.
Роберт П

22
@ S.Lott Я сподівався, що це буде очевидно, що це надуманий приклад; тим не менше, це головний провал пункту набору качок, і той, що tryне допомагає. Наприклад, якщо ви знали, що користувач може передавати рядок або масив, вони обидва можуть індексувати, але цей індекс означає щось зовсім інше. Просто покладатися на спроби лову в цих випадках не вдасться несподівано і дивно. Одне рішення - зробити окремий метод, інший - додати трохи перевірки типу. Я особисто віддаю перевагу поліморфній поведінці над кількома методами, які роблять майже те саме ... але це тільки я :)
Роберт П

22
@ S.Lott, що з тестуванням одиниць? Іноді ви хочете, щоб ваші тести перевірили, що функція повертає щось потрібного типу. Дуже реальний приклад - коли у вас є фабрика класів.
Елліот Камерон

17
Для менш надуманого прикладу розглянемо серіалізатор / десеріалізатор. За визначенням ви конвертуєте між наданими користувачем об'єктами та серіалізованим поданням. Серіалізатору потрібно визначити тип об'єкта, який ви передали, і у вас може не бути адекватної інформації, щоб визначити тип дезаріалізованого типу, не запитуючи час виконання (або, принаймні, він може знадобитися для перевірки обґрунтованості, щоб зафіксувати погані дані перед тим, як він вводиться ваша система!)
Карл

Відповіді:


1975

Є дві вбудовані функції, які допомагають визначити тип об’єкта. Ви можете використовувати , type() якщо вам потрібен точний тип об'єкта, і isinstance()щоб перевірити тип об'єкта проти чого - то. Зазвичай ви хочете використовувати isistance()більшість разів, оскільки це дуже надійно і також підтримує успадкування типів.


Щоб отримати фактичний тип об’єкта, ви використовуєте вбудовану type()функцію. Передача об'єкта як єдиного параметра поверне об'єкт типу цього об'єкта:

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

Звичайно, це також працює для спеціальних типів:

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

Зверніть увагу, що type()він поверне лише безпосередній тип об'єкта, але не зможе повідомити вам про успадкування типу.

>>> type(b) is Test1
False

Щоб прикрити це, слід скористатися isinstanceфункцією. Це, звичайно, також працює для вбудованих типів:

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

isinstance()зазвичай є кращим способом забезпечення типу об'єкта, оскільки він також приймає похідні типи. Тому, якщо вам фактично не потрібен об'єкт типу (з будь-якої причини), використання isinstance()є кращим над type().

Другий параметр isinstance()також приймає кордону типів, тому можна перевірити відразу кілька типів. isinstanceТоді повернеться true, якщо об'єкт має будь-який із цих типів:

>>> isinstance([], (tuple, list, set))
True

68
Я думаю, що це зрозуміліше використовувати isзамість того, ==як типи одинаки
Джон Ла Руй

18
@gnibbler, у випадках , НЕ буде перевірці типів (які ви не повинні робити , щоб почати с), isinstanceє найкращою формою в будь-якому випадку, так що ні ==чи isпотрібно використовувати.
Майк Грехем

23
@Mike Graham, бувають випадки, коли typeнайкраща відповідь. Бувають випадки, коли isinstanceнайкраща відповідь, і бувають випадки, коли набирання качок - найкраща відповідь. Важливо знати всі варіанти, щоб ви могли вибрати, який більше підходить для ситуації.
Джон Ла Руй

6
@ gnibbler, Це може бути, хоча я ще не стикався з ситуацією, де type(foo) is SomeTypeбуло б краще, ніж isinstance(foo, SomeType).
Майк Грем

5
@poke: я повністю згоден з приводу PEP8, але ви нападаєте на солом’яника тут: важливою частиною аргументу Свена не був PEP8, але те, що ви можете використовувати і isinstanceдля вашої корисної справи (перевірка діапазону типів), і з як чистий синтаксис, що має велику перевагу в тому, що ви можете захоплювати підкласи. хтось, хто використовує OrderedDict, ненавидить невдачу вашого коду, оскільки він просто приймає чисті дикти.
літаючі вівці

165

Це можна зробити за допомогою type():

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

40

Можливо, буде більш пітонічним використовувати try... exceptблок. Таким чином, якщо у вас є клас, який блукає як список, або трясе як дикт, він буде вести себе належним чином незалежно від типу його насправді .

Для уточнення, кращим методом "визначити різницю" між змінними типами є те, що називається типом качки : до тих пір, поки методи (і типи повернення), на які відповідає змінна, - це те, що очікує ваша підпрограма, ставитесь до неї так, як ви цього очікуєте бути. Наприклад, якщо у вас є клас, який перевантажує оператори дужок getattrі setattr, але використовує якусь смішну внутрішню схему, було б доречно, щоб він поводився як словник, якщо саме це намагається наслідувати.

Інша проблема type(A) is type(B)перевірки полягає в тому, що якщо Aце підклас B, він оцінює, falseколи, програмно, ви би сподівались, що це буде true. Якщо об’єкт є підкласом списку, він повинен працювати як список: перевірка типу, представленого в іншій відповіді, запобігає цьому. ( isinstanceбуде працювати, однак).


16
Введення качок насправді не полягає в тому, щоб визначити різницю. Йдеться про використання загального інтерфейсу.
Джастін Етьє

5
Будьте уважні - більшість посібників стилів кодування рекомендують не використовувати обробку винятків як частину нормального потоку управління кодом, як правило, тому, що це ускладнює читання коду. try... exceptє хорошим рішенням, коли ви хочете мати справу з помилками, але не при прийнятті рішення про поведінку на основі типу.
Rens van der Heijden

34

У примірниках об'єкта ви також маєте:

__class__

атрибут. Ось зразок, взятий з консолі 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'>

Будьте уважні, що в класах python 3.x та класах New-Style (придатні для вибору з Python 2.6) клас та тип були об'єднані, і це колись може призвести до несподіваних результатів. З цієї причини, мій улюблений спосіб тестування типів / класів полягає в тому, що це вбудована функція.


2
Ваша точка в кінці дуже важлива. type (obj) є Class не працює належним чином, але в тому випадку, що зробило трюк. Я розумію, що будь-яка речовина є кращою, але це вигідніше, ніж просто перевірка похідних типів, як це запропоновано у прийнятій відповіді.
mstbaum

__class__здебільшого добре на Python 2.x, єдині об’єкти в Python, які не мають __class__атрибуту, - це класи старого стилю AFAIK. До речі, я не розумію вашої турботи Python 3 - у такій версії просто кожен об’єкт має __class__атрибут, який вказує на належний клас.
Алан Францоні

21

Визначте тип об’єкта Python

Визначте тип об’єкта за допомогою type

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

Хоча це працює, уникайте подвійних атрибутів підкреслення на кшталт __class__- вони не є семантично відкритими, і, мабуть, не в цьому випадку, вбудовані функції зазвичай мають кращу поведінку.

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

перевірка типу

Чи є простий спосіб визначити, чи є змінною список, словник чи щось інше? Я повертаю об'єкт назад, який може бути будь-якого типу, і мені потрібно вміти визначити різницю.

Ну це інше питання, не використовуйте тип 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)

Це стосується випадку, коли ваш користувач може робити щось розумне або розумне підкласифікацією str- згідно з принципом заміни Ліскова, ви хочете мати можливість використовувати екземпляри підкласу, не порушуючи код - і isinstanceпідтримує це.

Використовуйте абстракції

Ще краще, ви можете шукати конкретний абстрактний базовий клас з collectionsабо 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)

Або просто не чітко перевіряйте тип

Або, можливо, найкраще використовувати введення качок і не чітко вводити код. Набір качок підтримує заміщення Ліскова з більшою елегантністю та меншою багатослівністю.

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)

Висновок

  • Використовуйте typeдля отримання класу екземпляра.
  • Використовуйте isinstanceдля прямої перевірки фактичних підкласів або зареєстрованих абстракцій.
  • І просто уникайте перевірки типу, де це має сенс.

Там завжди try/ exceptзамість прямої перевірки.
toonarmycaptain

Імовірно, що це зробить користувач, якщо вони не впевнені в типах, які вони передаватимуть. Мені не подобається захаращувати правильну реалізацію з обробкою винятків, якщо у мене немає чогось дуже хорошого з винятком. Піднятий виняток повинен бути достатнім, щоб повідомити користувача про необхідність виправити його використання.
Аарон Холл

13

Ви можете використовувати type()або isinstance().

>>> type([]) is list
True

Будьте попереджені, що ви можете клобувати listабо будь-який інший тип, призначивши змінну в поточній області того ж імені.

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

Вище ми бачимо, що dictпереназначається на рядок, тому тест:

type({}) is dict

... не вдається.

Щоб обійти це і використовувати type()обережніше:

>>> import __builtin__
>>> the_d = {}
>>> type({}) is dict
True
>>> dict =""
>>> type({}) is dict
False
>>> type({}) is __builtin__.dict
True

2
Я не впевнений, що потрібно зазначити, що затінення імені вбудованого типу даних погано для цього випадку. Ваша dictрядок також вийде з ладу для багатьох інших кодів, наприклад dict([("key1", "value1"), ("key2", "value2")]). Відповідь на такі питання є "Тоді не робіть цього" . Не затінюйте вбудовані назви типів і не очікуйте, що вони працюватимуть належним чином.
Blckknght

3
Я згоден з вами щодо частини "не робіть цього". Але дійсно, щоб сказати комусь не робити чогось, ви повинні принаймні пояснити, чому ні, і я подумав, що це відповідна можливість зробити саме це. Я мав на увазі обережний метод виглядати некрасиво і пояснювати, чому вони можуть не хотіти цього робити, залишаючи їх вирішити.
de0202392

type () не працює, як очікувалося, на Python 2.x для класичних екземплярів.
Алан Францоні

5

Хоча питання досить старі, я натрапив на це, поки сам з'ясував належний спосіб, і я думаю, що йому ще потрібно уточнити, принаймні для Python 2.x (не перевіряв на Python 3, але оскільки проблема виникає з класичними класами які пішли на такій версії, це, мабуть, не має значення).

Тут я намагаюся відповісти на запитання заголовка: як я можу визначити тип довільного об’єкта ? Інші пропозиції щодо використання чи не використання речовини є чудовими у багатьох коментарях та відповідях, але я не вирішую ці проблеми.

Основна проблема type()підходу полягає в тому, що він не працює належним чином із екземплярами старого стилю :

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

Виконання цього фрагмента дасть:

Are o and t instances of the same class? True

Що, я запевняю, не те, чого очікували б більшість людей.

Цей __class__підхід є найбільш близьким за правильністю, але він не працюватиме в одному з найважливіших випадків: коли переданий об'єкт є класом старого стилю (а не екземпляром!), Оскільки цим об’єктам відсутній такий атрибут.

Це найменший фрагмент коду, який я міг би придумати, що відповідає такому законному питанню послідовно:

#!/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

5

будьте обережні, використовуючи речовини

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

але тип

type(True) == bool
True
>>> type(True) == int
False

3

Як осторонь попередніх відповідей, варто згадати існування collections.abcякого містить декілька абстрактних базових класів (АВС), які доповнюють тип качки.

Наприклад, замість того, щоб чітко перевіряти, чи є щось із списку:

isinstance(my_obj, list)

Ви можете, якщо Вас цікавить лише те, чи маєте Ви об'єкт, можна отримати предмети, скористайтеся collections.abc.Sequence:

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

якщо ви суворо цікавитесь об'єктами, які дозволяють отримувати, налаштовувати та видаляти елементи (тобто змінні послідовності), ви вибрали б collections.abc.MutableSequence.

Багато інших ABCs визначаються там, Mappingдля об'єктів , які можуть бути використані в якості карти, Iterable, Callableі так далі. Повний перелік усіх цих питань можна побачити в документації на collections.abc.


1

Взагалі ви можете витягти рядок з об'єкта з назвою класу,

str_class = object.__class__.__name__

і використовуючи його для порівняння,

if str_class == 'dict':
    # blablabla..
elif str_class == 'customclass':
    # blebleble..

1

У багатьох практичних випадках замість використання typeабо isinstanceви також можете використовувати @functools.singledispatch, що використовується для визначення загальних функцій ( функція, що складається з декількох функцій, що реалізують одну і ту ж операцію для різних типів ).

Іншими словами, ви хочете використовувати його, коли у вас є такий код, як наступний:

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

Ось невеликий приклад того, як це працює:

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

Крім того, ми можемо використовувати абстрактні класи для охоплення декількох типів одночасно:

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!

0

type()є кращим рішенням, ніж isinstance(), зокрема, для booleans:

Trueі Falseце лише ключові слова, які означають 1і 0в python. Таким чином,

isinstance(True, int)

і

isinstance(False, int)

обидва повертаються True. Обидва булеві екземпляри є цілим числом. type()однак розумніший:

type(True) == int

повертає False.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.