Python’s Type theory
Python -> Tuple[TypeTheory, typing] Home
Python added support for type hints in the typing module in Python version 3.5. Use this for:
- adding type hints (think of these as a very powerful form of adding comments to your code).
- static typechecking (by an external tool like mypy ).
- typechecking within an IDE (many editors and IDEs do this on the fly).
- a sanity check (to ensure that the code you are writing makes sense even without running it).
Two key points to remember:
- type hints are an optional feature of python. So, it is not necessary that we add type hints to every single line of code.
- type hints are ignored during runtime and do not affect performance of the code.
Using type hints is optional. But I have found it to be an extremely useful feature. I frequently find myself wishing for more features in the typing module – not less.
These are just my notes on Python’s type thoery. Do not use it as a fool-proof reference. For proper documentation, one can refer to typing module docs or PEP 483]].
1.2. Types vs Types
There are two meanings for types in python now. One is the type of an object that is returned by calling the in-built type() function. The other is the type inferred by an external typechecker like mypy , and which can be displayed using the reveal_type function and then running the script through mypy .
We will always refer to the second interpretation when we refer to the type of an object in these notes.
1.3. So, what is type theory?
Think of it as the math/rules that govern these types. A basic fact to understand is that every python object has a type, whether we specify it explicitly or not.
2. Types that are built-in in python
Python comes with some buil-in types.
In addition, for every class that we define, we can refer to the type of its instances using the name of the class itself.
2.1. Subtypes and NewType
So far, we have talked about types without specifically defining what a type is. We will continue to not define them. However, we will say that one way to think about a type is simply as a set of values (sometimes these values are called terms, or even instances). So, bool can be thought of as the set
This analogy is also handy when trying to understand Subtypes. A type A is a subtype of a type B if the set of values corresponding to A is a subset of the set of values corresponding to B . This is often written (in type-theory texts, but not in python itself) as A <: B .
For example, int <: float . In other words, every int is also a float . It may come as a surprise to some python users that in python, the following is also true: bool <: int . This is because bool inherits from int and False , True are just aliases of 0 and 1 respectively.
The typing library lets us define subtypes using the NewType function as follows:
2.2. Why use NewType ?
NewType offers a way to restrict the type of argument supplied to a function. Looking at the example above, we can see that PositiveInt is a subtype of int . This means that a function that accepts an int argument will also accept an argument of type PositiveInt without any hiccups. However, a function that accepts PositiveInt as an argument will not accept any run-of-the-mill int as its input (here, by «would not accept» we mean that such an argument will not pass through the typechecker without an error).
Our constructor function make_positive_int ensures that the only way to create a PositiveInt is to pass it through this function.
This subtype and its constructor can then be used to create safer functions that only accept the restricted type. For example, we can have:
2.3. Type coercion
Type coercion refers to the process by which the typechecker changes the type of some of the variables to match function call signatures.
In the above code example, if we ask the typechecker for the type of c , then it will correctly guess that c is a float . How did it reach that conclusion? The answer is that the + operator is overloaded (details on how to write overloaded functions are given in a later section). Since one of the arguments is a float , python coerces a from an int into a float and then computes the sum of two float ‘s (which is also obviously a float ).
In general, typecheckers coerce type A into type B (if required) whenever they know that A is a subtype of B .
3. Importing more Types
By importing from typing , we get access to some more types.
3.1. Basic data-structures
3.2. Understanding Any
Any is a sort of magic type. I like to think of Any as the type-theoretic counterpart of object in python’s class hierarchy.
Every object in python belongs to a class. And every class inherits methods from other classes. But what happens when we define a base class (a class that doesn’t inherit from other classes)? Even in that case, python defaults to inheriting from a class called object . Any is similar. Every type is a subtype of Any , just as every class inherits from object . (I will have more to say about subtypes in the later sections).
Any is the precise reason why type hints are optional in python. When the type-checker can’t figure out the type of a variable on its own (and when no type hints are provided by the user), python simply defaults to usign Any as the variable’s type.
3.3. Optional and Union
Union represents what in type-theory jargon are called sum-types.
One freqently needs the union of a given type with None . Python’s typing module therefore comes with the handy Optional type-constructor. The following code block illustrates the use of both of these.
Note that one might want to use either the unsafe or the optional variant of division (depending on the context). Each approach has its own way of handling the exceptional case. The advantage of using type hints is that we get a good idea of how the function behaves just by looking at the call signature of the function!
Side note: Exception is an in-built class in python. Almost all exceptions and errors are inherited from the base class Exception . (The ones that aren’t are inherited from BaseException ).
Python: Type Function Tutorial
![]()
Hi everyone, welcome back. Today, we will be going over the type() function in Python and see how it works. The type() function is a built in function and its purpose is to return the data type of the given object. Let’s get into it.
Type Function
Let’s see how the type function works:

Written by Jesse L
Hi, I'm a passionate technology enthusiast and lifelong learner. Beyond my technical pursuits, I'm also passionate about sharing my enthusiasm with others.
Заметки об объектной системе языка Python ч.2
Вторая часть заметок об объектной системе python’a (первая часть тут). В этой статье рассказывается, что такое классы, метаклассы, type, object и как происходит поиск атрибутов в классе.
Классы
Классы (типы) — это объектные фабрики. Их главная задача — создавать объекты, обладающие определенным поведением.
Классы определяют поведение объектов с помощью своих атрибутов (которые хранятся в __dict__ класса): методов, свойств, классовых переменные, дескрипторов, а также с помощью атрибутов, унаследованных от родительских классов.
Инстанцирование обычного объекта происходит в 2 этапа: сначала его создание, потом инициализация. Соответственно, сначала запускается метод класса __new__, который возвращает объект данного класса, потом выполняется метод класса __init__, который инициализирует уже созданный объект.
def __new__(cls, . ) — статический метод (но его можно таковым не объявлять), который создает объект класса cls.
def __init__(self, . ) — метод класса, который инициализирует созданный объект.
Например, объявим класс:
>>> class A ( object ):
. pass
.
>>>
Для класса A не определены ни __new__, ни __init__. В соответствии с алгоритмом поиска атрибутов для класса (типа), который не стоит путать с алгоритмом поиска атрибутов для обычных объектов, когда класс не найдет их в своем__dict__, он будет искать эти методы в __dict__ своих базовых (родительских) классах.
Класс А имеет в качестве родителя встроенный класс object. Таким образом он будет их искать в object.__dict__
>>> object . __dict__[ '__init__' ]
<slot wrapper '__init__' of 'object' objects>
>>> object . __dict__[ '__new__' ]
<built-in method __new__ of type object at 0x82e780>
>>>
Раз есть такие методы, значит, получается, что a = A() аналогичен последовательности вызовов:
a = object.__new__(A)
object.__init__(a)
В общем виде, используя super, который как раз и реализует алгоритм поиска атрибутов по родительским классам [1]:
a = super(A, A).__new__(A)
super(A, A).__init__(a)
>>> class A ( object ):
. def __new__ (cls):
. obj = super (A, cls) . __new__(cls)
. print 'created object' , obj
. return obj
. def __init__ ( self ):
. print 'initing object' , self
.
>>> A()
created object <__main__.A object at 0x1620ed0>
initing object <__main__.A object at 0x1620ed0>
<__main__.A object at 0x1620ed0>
>>>
Singleton v.1
Понимая, как происходит создание объекта, можно написать реализацию паттерна одиночка.
Мы должны гарантировать, что у класса есть только один экземпляр. Т.е. при вызове конструктора класса, всегда возвращаем один и тот же экземпляр класса.
А это значит, что при вызов метода __new__ должен возвращать каждый раз один и тот же объект. Хранить сам объект можно, например, в классовой переменной instance.
В результате получаем:
>>> class C ( object ):
. instance = None
. def __new__ (cls):
. if cls . instance is None :
. cls . instance = super (C, cls) . __new__(cls)
. return cls . instance
.
>>> C() is C()
True
>>> C() . x = 1
>>> c = C()
>>> d = C()
>>> c . x
1
>>> d . x
1
>>> c . x =2
>>> d . x
2
>>> c . x
2
Классы и метаклассы.
Для класса (типа), так же как и для обычного объекта, существует класс (тип), который создает классы и определяет поведение класса. Этот класс называется метаклассом.
Создание класса, как и обычного объекта происходит с помощью вызова конструктора, но т.к. в классе есть несколько дополнительных специальных атрибутов, которые должны быть инициализированы, в конструктор передаются и соответствующие обязательные параметры.
XClass = XMetaClass(name, bases, attrs)
Тогда, сразу после создания
XClass.__name__ равно name,
XClass.__bases__ равен bases,
XClass.__dict__ равен attrs, а
XClass.__class__ равен XMetaClass
По умолчанию для всех определяемых классов метаклассом является type.
>>> class A ( object ):
. pass
.
Эквивалентно, по аналогии с обычными объектами:
>>> class B (A):
. def foo ( self ):
. 42
.
При определении класса, можно задать свой метакласс с помощью
классовой переменной __metaclass__:
>>> class A ( object ):
. __metaclass__ = Meta
.
>>>
Что равносильно: A = Meta(‘A’, (object,), <>)
О type и object
Прежде всего type и object — это объекты. И, как у всех порядочных объектов, у них есть специальные атрибуты __class__ и __dict__:
>>> object . __class__
<type 'type'>
>>> type . __class__
<type 'type'>
>>> object . __dict__
<dictproxy object at 0x7f7797a1cf30>
>>> type . __dict__
<dictproxy object at 0x7f7797a1cfa0>
Более того object, и type — это объекты типа (классы), и у них тоже есть специальные атрибуты __name__, __bases___:
>>> object . __name__
'object'
>>> type . __name__
'type'
>>> object . __bases__
()
>>> type . __bases__
(<type 'object'>,)
>>>
Экземпляры типа или класса object — это объекты (любые). Т.е. любой объект — экземпляр класса object:
>>> isinstance ( 1 , object )
True
>>> isinstance ( setattr , object )
True
>>> isinstance ( "foo" , object )
True
>>> isinstance (A, object )
True
Даже функция является объектом:
>>> def bar ():
. pass
.
>>> isinstance (bar, object )
True
Кроме того, класс object сам является своим экземпляром:
>>> isinstance ( object , object )
True
type тоже является его экземпляром:
>>> isinstance ( type , object )
True
Инстанцирование — object() возвращает самый простой и общий объект:
>>> o = object ()
У которого даже __dict__ нет, есть только __class__.
>>> o . __dict__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'object' object has no attribute '__dict__'
>>> o . __class__
<type 'object'>
>>>
Экземпляры класса или типа type — это только другие классы или другие типы:
Число — это не класс
>>> isinstance ( 1 , type )
False
>>> isinstance ( "foo" , type )
False
Встроенная функция setattr тоже не класс.
>>> isinstance ( setattr , type )
False
Класс — это класс.
>>> isinstance (A, type )
True
Тип строки — это класс.
>>> isinstance ( "foo" . __class__, type )
True
Т.к. object и type — тоже классы, то они являются экземплярами класса type:
>>> isinstance ( object , type )
True
>>> isinstance ( type , type )
True
>>>
Т.к. множество классов (типов) являются подмножеством множества объектов, то логично предположить, что type является подклассом object, т.е.
>>> issubclass ( type , object )
True
>>> issubclass ( object , type )
False
type — это просто класс, экземплярами которого являются другие классы. (т.е. метакласс). А сами классы можно считать расширением простых, обычных объектов.
Таким образом, когда мы наследуем класс от object, этот класс автоматически наследует поведение класса object, т.е. при инстанцировании он будет возвращать обычный объект. А когда мы наследуем от класса type, мы также автоматически наследуем поведение класса type, т.е. при инстацированни будет создаваться класс. А класс, который создает класс, называется метаклассом.
Значит, чтобы определить просто класс, нужно наследовать его от object, чтобы определить метакласс — наследуем его от type.
И еще: не нужно путать type(a) и type(name, bases, attrs).
type(a) — вызов с одним аргументом, возвращает тип объекта,
a type(name, bases, attrs) — вызов с тремя аргументами — это вызов конструктора класса.
О поиске атрибутов в классе
Как уже было отмечено, алгоритм поиска атрибутов в обычном объекте, но есть некоторые тонкости, т.к. у типов (классов) есть __bases__ — родительские классы (типы).
Если атрибут есть в __dict__ возвращается он, затем идет поиск по базовым классам из __bases__, а потом идет обращение к __dict__ __class__’а (т.е. фактически метакласса) и его (метакласса) родительских классов (метаклассов).
>>> class Ameta ( type ):
. def foo (cls):
. print 'Ameta.foo'
.
>>> class A ( object ):
. __metaclass__ = Ameta
.
>>> A . foo()
Ameta.foo
Все что определяется в метаклассе доступно для класса, но не доступно для экзмепляров класса — обычных объектов, т.к. поиск атрибутов в обычном объекте ведется только по __dict__ словарям класса.
>>> a = A()
>>> a . foo
Traceback (most recent call last):
File "<stdin>" , line 1 , in <module>
AttributeError : 'A' object has no attribute 'foo'
В A.__dict__ ‘foo’ нет:
>>> A . __dict__[ 'foo' ]
Traceback (most recent call last):
File "<stdin>" , line 1 , in <module>
KeyError : 'foo'
Зато он есть в метаклассе, поэтому:
>>> A . foo()
Ameta.foo
>>> class B (A):
. @classmethod
. def foo (cls):
. print 'B.foo'
.
>>> B . foo # т.к. foo есть B.__dict__ вернется значение B.__dict__['foo']
<bound method Ameta.foo of <class '__main__.B'>>
>>> B . foo()
B.foo
>>> class C (B):
. pass
.
>>> C . foo() # вернет значение из базового класса B.
B.foo
Экземпляр класса C также вызовет метод foo из класса B.
>>> c = C()
>>> c . foo()
B.foo
>>> class D (A):
. pass
.
>>> D . foo()
Ameta.foo
А экземпляр D не найдет:
>>> d = D()
>>> d . foo()
Traceback (most recent call last):
File "<stdin>" , line 1 , in <module>
AttributeError : 'D' object has no attribute 'foo'
Метаклассы
Метаклассы являются фабриками классов (или типов). Инстанцирование класса тоже проходит в 2 этапа — создание объекта типа (класса) и его инициализация. Это также делается с помощью двух методов метакласса. Сначала вызывается метод __new__ метакласса с параметрами, необходимыми для создания класса — name, bases, attrs, а потом __init__ с теми же параметрами и уже созданным классом.
>>> class Meta ( type ):
. pass
.
>>> Meta( 'A' , ( object ,), <>)
<class '__main__.A'>
В начале метакласс Meta ищет метод __new__ у себя в словаре __dict__, не находит его там и начинает искать в __dict__ своих родительских классах (т.е. метаклассах, в данном случае type), т.е. происходит обычный поиск атрибута в классе. В результате исполнения __new__ с соответствующими параметрами получает новый класс, который потом инициализируется вызовом __init__ метода метакласса.
В совсем развернутом виде получается:
cls = type.__dict__[‘__new__’](Meta, ‘A’, (object,), <>)
type.__dict__[‘__init__’](cls, ‘A’, (object,), <>)
Или с помощью super
cls = super(Meta, Meta).__new__(Meta, ‘A’, (object,), <>)
super(Meta, Meta).__init__(cls, ‘A’, (object,), <>)
Стоит отметить, что в отличие от инстанцирования обычных объектов, используется не object.__new__ и object.__init__, а type.__new__ и type.__init__. У object.__new__ и type.__new__ разные сигнатуры, и object.__new__ возвращает обычный объект (regular object), а type.__new__ — объект типа (typeobject), т.е. класс.
Посмотрим, как это все работает на примере.
>>> class Meta ( type ):
. def __new__ (mcls, name, bases, attrs):
. print 'creating new class' , name
. return super (Meta, mcls) . __new__(mcls, name, bases, attrs)
. def __init__ (cls, name, bases, attrs):
. print 'initing new class' , name
.
.
>>> class A ( object ):
. __metaclass__ = Meta
.
creating new class A
initing new class A
Во время инстанцирования просто объекта, никаких надписей не выводится.
>>> a = A()
>>>
Кроме того, соответственно, во методах __new__ и __init__ метакласса можно менять все: имя, список суперклассов, атрибуты.
Что такое type в python
In this article, we will cover about type() and isinstance() function in Python, and what are the differences between type() and isinstance().
What is type in Python?
Python has a built-in method called type which generally comes in handy while figuring out the type of the variable used in the program in the runtime. The canonical way to check for type in Python is given below:
Syntax of type() function
Example 1: Example of type() with a Single Object Parameter
In this example, we are trying to check the data type of each variable, such as x, s, and y using type() function.