I found a very strange behavior in the Enum class in Python. So the enumerated type is simple:
from enum import Enum
Analysis = Enum('Analysis', 'static dynamic')
So I use this enumerated type in for step objects so that they store it in the attribute analysis, as follows:
class Step:
def __init__(self):
self.analysis = None
self.bcs = []
Very simple so far, so when I have a few of these steps in a list, then I try to see the enumerated type and it has been assigned correctly. But they are not equal:
# loop over steps
for s, step in enumerate(kwargs['steps']):
print(kwargs)
print(step)
print(step.analysis)
print("test for equality: ",(step.analysis == Analysis.static))
quit()
which prints
{'mesh': <fem.mesh.mesh.Mesh object at 0x10614d438>,
'steps': [<hybrida.fem.step.Step object at 0x10614d278>,
<hybrida.fem.step.Step object at 0x10616a710>,
<hybrida.fem.step.Step object at 0x10616a390>]}
Step:
analysis: Analysis.static
bcs: [<hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a0f0>,
<hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a320>,
<hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a3c8>,
<hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a470>,
<hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a518>,
<hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a5c0>,
<hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a668>]
Analysis.static
test for equality: False
This is not correct, but I have no ideas on how to debug this.
UPDATE
Following the suggestion by #martineau, I created an IntEnum instead and that solved my problem. Yet, I don't understand why the normal Enum doesn't work.
In the comments, you say:
The input file contains many steps, and every time I add a new step I
have to set up the analysis type
If I understand you correctly, you're saying that you create a new Enum object each time you add a new step. This may be why you're seeing your "bug". The values of two different Enum objects, despite having the same name and order, do not necessarily compare as equal. For example:
import enum
Analysis1 = enum.Enum("Analysis", "static dynamic")
Analysis2 = enum.Enum("Analysis", "static dynamic")
But:
>>> Analysis1.static == Analysis2.static
False
This happens because the equality operator is not defined for Enum objects, as far as I can tell, so the default behavior of checking ids is used.
As #martineau suggests in the comments, one way of avoiding this issue is to instead use the IntEnum type, which subclasses int, and therefore defines the equality operator in terms of the value of the Enum, not the id:
import enum
Analysis1 = enum.IntEnum("Analysis", "static dynamic")
Analysis2 = enum.IntEnum("Analysis", "static dynamic")
Then:
>>> Analysis1.static == Analysis2.static
True
Why have Enum and IntEnum?
It may seem at first glance that IntEnum is always what we want. So what's the point of Enum?
Suppose you want to enumerate two sets of items, say, fruits and colors. Now, "orange" is both a fruit, and a color. So we write:
Fruits = enum.IntEnum("Fruits", "orange apple lemon")
Colors = enum.IntEnum("Colors", "orange red blue")
But now:
>>> Fruits.orange == Colors.orange
True
But, philosophically speaking, "orange" (the fruit) is not the same as "orange" (the color)! Shouldn't we be able to distinguish the two? Here, the subclassing of int by IntEnum works against us, as both Fruits.orange and Colors.orange equate to 1. Of course, as we saw above, comparison of Enums compares ids, not values. Since Fruits.orange and Colors.orange are unique objects, they do not compare as equal:
Fruits = enum.Enum("Fruits", "orange apple lemon")
Colors = enum.Enum("Colors", "orange red blue")
So that:
>>> Fruits.orange == Colors.orange
False
and we no longer live in a world where some colors are things that you can find in the produce section of your local grocery store.
In case anyone else finds themselves here after us, we experienced the same issue. We were able to trace it back to an unintentional mixing of absolute and relative imports, in a manner similar to the situation described below.
# File: package/module/analysis_types.py
Analysis = enum.Enum("Analysis", "static dynamic")
# File: package/module/static_thing.py
from .analysis_types import Analysis
class StaticThing:
...
analysis = Analysis.static
...
# File: package/module/static_thing_test.py
from package.module.static_thing import StaticThing
from .analysis_types import Analysis
# This throws an AssertionError because as
# id(StaticThing.analysis) != id(Analysis.static)
assert StaticThing.analysis == Analysis.static
Expected behavior was restored with the following changes:
# File: package/module/static_thing_test.py
from .static_thing import StaticThing
from .analysis_types import Analysis
# This does NOT throw an AssertionError because as
# id(StaticThing.analysis) == id(Analysis.static)
assert StaticThing.analysis == Analysis.static
For anyone who's stuck in the situation as described by Austin Basye, and cannot resolve the issue by just changing the imports, try using the enum value.
That is, if obj1.type1 == obj2.type1 is False (but it should be True), check if obj1.type1.value == obj2.type2.value works.
In this case, Analysis1.static.value == Analysis2.static.value should return the correct value all the time.
Related
I need to reassign the attribute value in Enum.
from enum import Enum
class Number(Enum):
number = "1"
Number.number = "2" # AttributeError: cannot reassign member 'number'
I tried to reassign the attribute, but I got:
AttributeError: cannot reassign member 'number'
Author's note: This is a horrible idea.
Let's just delete the string "1" from Python and replace it with "2"
from ctypes import c_byte
from enum import Enum
from sys import getsizeof
def change_enum_value(old: object, new: object) -> None:
"""
Assigns contents of new object to old object.
The size of new and old objection should be identical.
Args:
old (Any): Any object
new (Any): Any object
Raises:
ValueError: Size of objects don't match
Faults:
Segfault: OOB write on destination
"""
src_s, des_s = getsizeof(new), getsizeof(old)
if src_s != des_s:
raise ValueError("Size of new and old objects don't match")
src_arr = (c_byte * src_s).from_address(id(new))
des_arr = (c_byte * des_s).from_address(id(old))
for index in range(len(des_arr)):
des_arr[index] = src_arr[index]
class Number(Enum):
number = "1"
change_enum_value(Number.number.value, "2")
print(Number.number.value) # 2
You don't have the "1" anymore, quite literally.
>>> "1"
'2'
>>>
which sure is a tad concerning...
When using Enum, your number = "1" is considered as one of the enum entry.
So, firstly, it is thought to be used this way, for example :
from enum import Enum
class Number(Enum):
numberOne = "1"
numberTwo = "2"
By the way, when you access to Number.number, you access to the "enum item", which is more than just the value. The item has, indeed, a name (number) and a value ("1").
So, in theory, the good way to change the value should be :
Number.number.value = "2"
But, in any was, an Enum is made to not be mutable. So you can't do that anyway.
Conceptually, an Enum is a way to give "nicer names" to "constants" and then use the nicer names in your code rather than using the constants.
This intent is implemented by Python by making the enum members as "functionally constant" ... See Python Enum Documentation.
The names associated with Enums are not like variables.
Hence you cannot change the value of the name.
If you read the enum Python documentation:
The attributes, in your case Number.number, etc., are enumeration members (or members) and are functionally constants.
I have a dataclass and I use it as a constant store.
#dataclass
class MyClass:
CONSTANT_1 = "first"
CONSTANT_2 = "second"
I have a function:
def my_func(value: ?):
print(value)
I want to add annotation to my function to specify that possible value is one of attribute of MyClass
How to do it (I am using python 3.10) ?
Hopefully I not misunderstand the ask, please let me know if so. But I think in this case is best to use Enum type in python.
Here is a simple example:
from enum import Enum
class MyEnum(Enum):
CONSTANT_1 = "first"
CONSTANT_2 = "second"
Then to answer the second part, for annotation the ? becomes a MyEnum. This means any enum member of this type, but not the type (class) itself.
def my_func(value: MyEnum):
print(value, value.name, value.value)
Putting it all together, it becomes like:
from enum import Enum
class MyEnum(Enum):
CONSTANT_1 = "first"
CONSTANT_2 = "second"
def my_func(value: MyEnum):
# technically you can remove this check
if not isinstance(value, MyEnum):
return
print(value, value.name, value.value)
# note below: only type checker or ide complain, but code still runs fine
my_func('hello') # not OK!
my_func('second') # not OK!
my_func(MyEnum) # not OK!
my_func(MyEnum.CONSTANT_1) # OK
I think you're asking an XY problem. From your response in the comments, it seems like what you want is rather:
Have a class-like interface to hold a bunch of constant values.
Constraint the argument to only take the above values.
As as mentioned in rv.kvetch's answer, the conventional way of doing this is to use enums. I'm not sure what you mean by "wanting to skip .value", the value field of an enum simply gives you what's associated with that enum, and I would say that it's not important at all. Here's an example:
class StrEnum(enum.Enum):
FIRST = "first"
SECOND = "second"
class StrEnum2(enum.Enum):
FIRST = "first"
SECOND = "second"
print(StrEnum.FIRST.value) # first
print(StrEnum2.FIRST.value) # first
print(StrEnum.FIRST.value == StrEnum2.FIRST.value) # True
print(StrEnum.FIRST == StrEnum2.FIRST) # False
class IntEnum(enum.Enum):
FIRST = enum.auto()
SECOND = enum.auto()
print(IntEnum.FIRST.value) # 1
print(IntEnum.SECOND.value) # 2
What I want to show with this example are two things:
You don't really need .value at all if you're just comparing enums.
You don't even need to manually assign values to the enums; you can use enum.auto() to auto-assign a unique value to it.
Because at the end of the day, enums themselves already represent a choice among valid choices, so it doesn't matter what values it has.
That said, if what you want is just to put a type constraint on what values an argument can type, and not have to use enums, then you can use the Literal type. See this answer for details. For your example, you could do something like:
from typing import Literal, Final
def my_func(value: Literal["first", "second"]):
print(value)
my_func("first") # ok
my_func("not first") # bad
x = "first"
y: Final = "first"
my_func(x) # bad, because `x` is not final
my_func(y) # ok
But note that type annotations don't really prevent you from calling a function with an invalid value, it's just a hint for IDEs and type checkers.
I found a very strange behavior in the Enum class in Python. So the enumerated type is simple:
from enum import Enum
Analysis = Enum('Analysis', 'static dynamic')
So I use this enumerated type in for step objects so that they store it in the attribute analysis, as follows:
class Step:
def __init__(self):
self.analysis = None
self.bcs = []
Very simple so far, so when I have a few of these steps in a list, then I try to see the enumerated type and it has been assigned correctly. But they are not equal:
# loop over steps
for s, step in enumerate(kwargs['steps']):
print(kwargs)
print(step)
print(step.analysis)
print("test for equality: ",(step.analysis == Analysis.static))
quit()
which prints
{'mesh': <fem.mesh.mesh.Mesh object at 0x10614d438>,
'steps': [<hybrida.fem.step.Step object at 0x10614d278>,
<hybrida.fem.step.Step object at 0x10616a710>,
<hybrida.fem.step.Step object at 0x10616a390>]}
Step:
analysis: Analysis.static
bcs: [<hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a0f0>,
<hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a320>,
<hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a3c8>,
<hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a470>,
<hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a518>,
<hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a5c0>,
<hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a668>]
Analysis.static
test for equality: False
This is not correct, but I have no ideas on how to debug this.
UPDATE
Following the suggestion by #martineau, I created an IntEnum instead and that solved my problem. Yet, I don't understand why the normal Enum doesn't work.
In the comments, you say:
The input file contains many steps, and every time I add a new step I
have to set up the analysis type
If I understand you correctly, you're saying that you create a new Enum object each time you add a new step. This may be why you're seeing your "bug". The values of two different Enum objects, despite having the same name and order, do not necessarily compare as equal. For example:
import enum
Analysis1 = enum.Enum("Analysis", "static dynamic")
Analysis2 = enum.Enum("Analysis", "static dynamic")
But:
>>> Analysis1.static == Analysis2.static
False
This happens because the equality operator is not defined for Enum objects, as far as I can tell, so the default behavior of checking ids is used.
As #martineau suggests in the comments, one way of avoiding this issue is to instead use the IntEnum type, which subclasses int, and therefore defines the equality operator in terms of the value of the Enum, not the id:
import enum
Analysis1 = enum.IntEnum("Analysis", "static dynamic")
Analysis2 = enum.IntEnum("Analysis", "static dynamic")
Then:
>>> Analysis1.static == Analysis2.static
True
Why have Enum and IntEnum?
It may seem at first glance that IntEnum is always what we want. So what's the point of Enum?
Suppose you want to enumerate two sets of items, say, fruits and colors. Now, "orange" is both a fruit, and a color. So we write:
Fruits = enum.IntEnum("Fruits", "orange apple lemon")
Colors = enum.IntEnum("Colors", "orange red blue")
But now:
>>> Fruits.orange == Colors.orange
True
But, philosophically speaking, "orange" (the fruit) is not the same as "orange" (the color)! Shouldn't we be able to distinguish the two? Here, the subclassing of int by IntEnum works against us, as both Fruits.orange and Colors.orange equate to 1. Of course, as we saw above, comparison of Enums compares ids, not values. Since Fruits.orange and Colors.orange are unique objects, they do not compare as equal:
Fruits = enum.Enum("Fruits", "orange apple lemon")
Colors = enum.Enum("Colors", "orange red blue")
So that:
>>> Fruits.orange == Colors.orange
False
and we no longer live in a world where some colors are things that you can find in the produce section of your local grocery store.
In case anyone else finds themselves here after us, we experienced the same issue. We were able to trace it back to an unintentional mixing of absolute and relative imports, in a manner similar to the situation described below.
# File: package/module/analysis_types.py
Analysis = enum.Enum("Analysis", "static dynamic")
# File: package/module/static_thing.py
from .analysis_types import Analysis
class StaticThing:
...
analysis = Analysis.static
...
# File: package/module/static_thing_test.py
from package.module.static_thing import StaticThing
from .analysis_types import Analysis
# This throws an AssertionError because as
# id(StaticThing.analysis) != id(Analysis.static)
assert StaticThing.analysis == Analysis.static
Expected behavior was restored with the following changes:
# File: package/module/static_thing_test.py
from .static_thing import StaticThing
from .analysis_types import Analysis
# This does NOT throw an AssertionError because as
# id(StaticThing.analysis) == id(Analysis.static)
assert StaticThing.analysis == Analysis.static
For anyone who's stuck in the situation as described by Austin Basye, and cannot resolve the issue by just changing the imports, try using the enum value.
That is, if obj1.type1 == obj2.type1 is False (but it should be True), check if obj1.type1.value == obj2.type2.value works.
In this case, Analysis1.static.value == Analysis2.static.value should return the correct value all the time.
How can I retain Uniqueness feature of Set for modifying attributes of user-defined instance after adding them into the Set?
like in the code below:
both Person "Jack" and "John" are different in term of equality "Name" . So they both are added into the set
but if I change Person "Jack" name to "John, then the 2 instance jack and john will be equal
however my Set doesn't reflect that. They still consider those 2 instances are different
Note: this leads to potential bug when someone accidentally modifies the user-defined instances after they have been added into the set
Do we have a way to refresh the Set or how i can avoid this issue?
class Person:
def __init__(self, name):
self.name = name
def __eq__(self, other):
return self.name == other.name
def __hash__(self):
return hash(self.name)
jack = Person("Jack")
john = Person("John")
set1 = {jack, john}
jack.name = "John"
print(set1) // return 2 instance instead of 1. This is undesired behavior because now both jack & john are equal
You should only use sets of immutable objects or references. See Python docs:
Having a __hash__() implies that instances of the class are immutable.
The Person objects in your set are mutable but you have implemented your own hash and equality functions that get around this, bypassing safety, as you have pointed out.
I think it's fine to define custom hash and equality functions but they should always return the same thing no matter what you do to the things they reference: e.g., using an ID or memory address to hash.
I suggest one of two options, with a strong preference on the first:
Option A: Immutable Person
Make Person immutable when constructed. My favourite way of doing this is with a dataclass:
from dataclasses import dataclass
#dataclass(frozen=True)
class Person:
name: str
jack = Person("Jack")
john = Person("John")
# Note you don't need to define your own hash method.
set1 = {jack, john}
# This will fail:
jack.name = "Jaques"
# Consider the need for this. But if you have, say, a lot of different
# fields on the Person and want to just change one or a few, try:
import dataclasses
jaques = dataclasses.replace(jack, {"name": "Jaques"})
# But note this is a different object. The set is still the same as before.
# You need to remove "jack" from the set and add "jaques" to it.
Option B: Recalculate the Set
I should note that I don't think this is a good idea, but you could simply run:
set1 = {jack, john}
...again, and it will recalculate the set.
You created two different object and if you print set1 you'll get something like:{<__main__.Person object at 0x7f8dfbfc5e10>, <__main__.Person object at 0x7f8dfbfe2a10>}
Even though their attribute names are different, they're still two different objects saved in different memory spaces. That's why you have the unexpected behavior of still having both of them when you put them into a set!
When you do jack.name = "John" you're only changing the attribute self.name.
In order to get the outcome you wanted you have to do: set1 = {jack.name, john.name}
It'll return you {'John'}
This question already has answers here:
What's the canonical way to check for type in Python?
(15 answers)
Closed 6 months ago.
Is there a simple way to determine if a variable is a list, dictionary, or something else?
There are two built-in functions that help you identify the type of an object. You can use type() if you need the exact type of an object, and isinstance() to check an object’s type against something. Usually, you want to use isinstance() most of the times since it is very robust and also supports type inheritance.
To get the actual type of an object, you use the built-in type() function. Passing an object as the only parameter will return the type object of that object:
>>> type([]) is list
True
>>> type({}) is dict
True
>>> type('') is str
True
>>> type(0) is int
True
This of course also works for custom types:
>>> class Test1 (object):
pass
>>> class Test2 (Test1):
pass
>>> a = Test1()
>>> b = Test2()
>>> type(a) is Test1
True
>>> type(b) is Test2
True
Note that type() will only return the immediate type of the object, but won’t be able to tell you about type inheritance.
>>> type(b) is Test1
False
To cover that, you should use the isinstance function. This of course also works for built-in types:
>>> isinstance(b, Test1)
True
>>> isinstance(b, Test2)
True
>>> isinstance(a, Test1)
True
>>> isinstance(a, Test2)
False
>>> isinstance([], list)
True
>>> isinstance({}, dict)
True
isinstance() is usually the preferred way to ensure the type of an object because it will also accept derived types. So unless you actually need the type object (for whatever reason), using isinstance() is preferred over type().
The second parameter of isinstance() also accepts a tuple of types, so it’s possible to check for multiple types at once. isinstance will then return true, if the object is of any of those types:
>>> isinstance([], (tuple, list, set))
True
Use type():
>>> a = []
>>> type(a)
<type 'list'>
>>> f = ()
>>> type(f)
<type 'tuple'>
It might be more Pythonic to use a try...except block. That way, if you have a class which quacks like a list, or quacks like a dict, it will behave properly regardless of what its type really is.
To clarify, the preferred method of "telling the difference" between variable types is with something called duck typing: as long as the methods (and return types) that a variable responds to are what your subroutine expects, treat it like what you expect it to be. For example, if you have a class that overloads the bracket operators with getattr and setattr, but uses some funny internal scheme, it would be appropriate for it to behave as a dictionary if that's what it's trying to emulate.
The other problem with the type(A) is type(B) checking is that if A is a subclass of B, it evaluates to false when, programmatically, you would hope it would be true. If an object is a subclass of a list, it should work like a list: checking the type as presented in the other answer will prevent this. (isinstance will work, however).
On instances of object you also have the:
__class__
attribute. Here is a sample taken from Python 3.3 console
>>> str = "str"
>>> str.__class__
<class 'str'>
>>> i = 2
>>> i.__class__
<class 'int'>
>>> class Test():
... pass
...
>>> a = Test()
>>> a.__class__
<class '__main__.Test'>
Beware that in python 3.x and in New-Style classes (aviable optionally from Python 2.6) class and type have been merged and this can sometime lead to unexpected results. Mainly for this reason my favorite way of testing types/classes is to the isinstance built in function.
Determine the type of a Python object
Determine the type of an object with type
>>> obj = object()
>>> type(obj)
<class 'object'>
Although it works, avoid double underscore attributes like __class__ - they're not semantically public, and, while perhaps not in this case, the builtin functions usually have better behavior.
>>> obj.__class__ # avoid this!
<class 'object'>
type checking
Is there a simple way to determine if a variable is a list, dictionary, or something else? I am getting an object back that may be either type and I need to be able to tell the difference.
Well that's a different question, don't use type - use 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)
This covers the case where your user might be doing something clever or sensible by subclassing str - according to the principle of Liskov Substitution, you want to be able to use subclass instances without breaking your code - and isinstance supports this.
Use Abstractions
Even better, you might look for a specific Abstract Base Class from collections or 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)
Or Just Don't explicitly Type-check
Or, perhaps best of all, use duck-typing, and don't explicitly type-check your code. Duck-typing supports Liskov Substitution with more elegance and less verbosity.
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)
Conclusion
Use type to actually get an instance's class.
Use isinstance to explicitly check for actual subclasses or registered abstractions.
And just avoid type-checking where it makes sense.
You can use type() or isinstance().
>>> type([]) is list
True
Be warned that you can clobber list or any other type by assigning a variable in the current scope of the same name.
>>> the_d = {}
>>> t = lambda x: "aight" if type(x) is dict else "NOPE"
>>> t(the_d) 'aight'
>>> dict = "dude."
>>> t(the_d) 'NOPE'
Above we see that dict gets reassigned to a string, therefore the test:
type({}) is dict
...fails.
To get around this and use type() more cautiously:
>>> import __builtin__
>>> the_d = {}
>>> type({}) is dict
True
>>> dict =""
>>> type({}) is dict
False
>>> type({}) is __builtin__.dict
True
be careful using isinstance
isinstance(True, bool)
True
>>> isinstance(True, int)
True
but type
type(True) == bool
True
>>> type(True) == int
False
While the questions is pretty old, I stumbled across this while finding out a proper way myself, and I think it still needs clarifying, at least for Python 2.x (did not check on Python 3, but since the issue arises with classic classes which are gone on such version, it probably doesn't matter).
Here I'm trying to answer the title's question: how can I determine the type of an arbitrary object? Other suggestions about using or not using isinstance are fine in many comments and answers, but I'm not addressing those concerns.
The main issue with the type() approach is that it doesn't work properly with old-style instances:
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
Executing this snippet would yield:
Are o and t instances of the same class? True
Which, I argue, is not what most people would expect.
The __class__ approach is the most close to correctness, but it won't work in one crucial case: when the passed-in object is an old-style class (not an instance!), since those objects lack such attribute.
This is the smallest snippet of code I could think of that satisfies such legitimate question in a consistent fashion:
#!/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
using type()
x='hello this is a string'
print(type(x))
output
<class 'str'>
to extract only the str use this
x='this is a string'
print(type(x).__name__)#you can use__name__to find class
output
str
if you use type(variable).__name__ it can be read by us
In many practical cases instead of using type or isinstance you can also use #functools.singledispatch, which is used to define generic functions (function composed of multiple functions implementing the same operation for different types).
In other words, you would want to use it when you have a code like the following:
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
Here is a small example of how it works:
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'>
Additionaly we can use abstract classes to cover several types at once:
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!
As an aside to the previous answers, it's worth mentioning the existence of collections.abc which contains several abstract base classes (ABCs) that complement duck-typing.
For example, instead of explicitly checking if something is a list with:
isinstance(my_obj, list)
you could, if you're only interested in seeing if the object you have allows getting items, use collections.abc.Sequence:
from collections.abc import Sequence
isinstance(my_obj, Sequence)
if you're strictly interested in objects that allow getting, setting and deleting items (i.e mutable sequences), you'd opt for collections.abc.MutableSequence.
Many other ABCs are defined there, Mapping for objects that can be used as maps, Iterable, Callable, et cetera. A full list of all these can be seen in the documentation for collections.abc.
value = 12
print(type(value)) # will return <class 'int'> (means integer)
or you can do something like this
value = 12
print(type(value) == int) # will return true
type() is a better solution than isinstance(), particularly for booleans:
True and False are just keywords that mean 1 and 0 in python. Thus,
isinstance(True, int)
and
isinstance(False, int)
both return True. Both booleans are an instance of an integer. type(), however, is more clever:
type(True) == int
returns False.
In general you can extract a string from object with the class name,
str_class = object.__class__.__name__
and using it for comparison,
if str_class == 'dict':
# blablabla..
elif str_class == 'customclass':
# blebleble..
For the sake of completeness, isinstance will not work for type checking of a subtype that is not an instance. While that makes perfect sense, none of the answers (including the accepted one) covers it. Use issubclass for that.
>>> class a(list):
... pass
...
>>> isinstance(a, list)
False
>>> issubclass(a, list)
True