× К оглавлению На главную Об авторе

Дата и время публикации:    

Проблема и решение

1. Суть проблемы

Рассматриваются вопросы, объясняющие почему?

Поэтому, далее, рассмотрю динамическую типизацию, строгую типизацию для встроенных типов, как определять является тип класса обсъекта изменяемым или нет, чем отличается экземпляр объекта от класса типа объекта, а также как зачем и почему используется утиная типизация (duck tyiping) в Python

2. Решение

2.1 Динамическая типизация

Python использует динамическую типизацию , которая подразумевает изменение типа данных переменных во время выполнения программы [3.3], как показано в дампе 2.1.1

Дамп 2.1.1

>>> number = 1234.567
>>> phrase = "Python is a dynamic language"
>>> mylist = [number,phrase]
>>> type(number)
<class 'float'>
>>> type(phrase)
<class 'str'>
>>> type(mylist)
<class 'list'>

Для контроля за классом объекта, на экземпляр которого указывают переменные number, pharse и mylist, можно воспользоваться встроенной функцией type(), действие которой заключается в возвращение класса объекта (object class) или по сути метакласса [3.9], как показано в дампе 2.1.2

Дамп 2.1.2

>>> print(type(mylist) is list)
True
>>> print(type(number) is list)
False
>>> print(type(number) is str)
False
>>> print(type(number) is not str)
True
>>> number = phrase
>>> print(type(number) is str)
True

Поэтому исходя из выше показанного в дампе 2.1.1 и 2.1.2, переменная является динамически типизированной, когда тип объект(ов) не указывается во время компиляции, а программа считается динамически типизированной, если она использует по крайне мере одну переменную с динамическим типом. Язык программирования поддерживает динамическую типизацию, если написанная на нем программа имеет большинство переменных, которые являются динамически типизированными, как показано в дампе 2.1.3

Дамп 2.1.2

>>> number = 1234.567
>>> phrase = "Python is a dynamic language"
>>> number = phrase
>>> sentence = number + 10
Traceback (most recent call last):
  File "", line 1, in 
TypeError: can only concatenate str (not "int") to str
>>> print ( number )
Python is a dynamic language

Здесь применены одновременно синтаксическая динамическая типизация (syntactic dynamic typing), когда программисту нет необходимости декларировать тип данных при объявлении переменной, и семантическая динамическая типизацию (symantic dynamic typing), когда одна переменная может содержать два и более объекта. При этом, когда попытался применить последнюю, нарушил правило строгой типизации, которая привела к генерации исключения TypeError

2.2 Строгая типизация в Python

Python является строго типизированным, потому что интерпретатор отслеживает все типы переменных, которые он использует, так операция конкатенации, как уже видели выше, строки "Python is a dynamic language" + 10 привела к исключению TypeError: can only concatenate str (not "int") to str

Исключение TypeError случается, когда операция над тип данных объекта является неприемлемой [3.7]. — В данном случае, такой оказалась операция конкатенации двух типов объектов str("Python is a dynamic language") + int(10)

Поэтому, если int(10)

>>> str("Python is a dynamic language") + str(10)
'Python is a dynamic language10'

Таким образом, в Python каждый тип данных имеет строго определенный тип объекта, у большинства которых запрещено совмещение типов, путем наложения данных, как в случае с ошибкой конкатенации между целочисленным и строковым значениями двух объектов.

В языке программирования Python поддерживает несколько классы объектов, которым могут быть строго определены несколько типов данных [3.10]

Числовые типы данных

Существует три различных числовых типов: целочисленный, с плавающей точкой и комплексного числа. В то время как, например, булевый тип является всего лишь подтипом от числового и возвращающий значения True, логическую единицу в целочисленным значении 1 для истинного события и логический нуль в целочисленным значении 0 для ложного, как показано в дампе 2.2.1

Дамп 2.2.1

>>> True == 1
True
>>> False == 0
True

При этом, в отличие от целочисленных типов в С, которые описывал ранее во времена царствования стандарта HTML4, целочисленные значения имеют неограниченную точность, которую для чисел с плавающей запятой можно получить используя функции sys.float_info и sys.int_info, как показано в дампе 2.2.2

Дамп 2.2.2

>>> import sys
>>> sys.float_info
sys.float_info(max=1.7976931348623157e+308, max_exp=1024, max_10_exp=308, min=2.2250738585072014e-308, min_exp=-1021, min_10_exp=-307, dig=15, mant_dig=53, epsilon=2.220446049250313e-16, radix=2, rounds=1)
>>> sys.int_info
sys.int_info(bits_per_digit=30, sizeof_digit=4)
>>> 

Из которого вытекают экстремумы для обоих типов — с плавающей запятой и целочисленных значений – равные типу двойной точности и длинному целому в языке программирования С. При этом числа создаются с помощью числовых литералов или в результате использования встроенных функций и операторов. Отдельно стоящие числовое литералы, включая числа в 16-ти, 8-ми и двоичном представлении, также дают целое.

Комплексные числа имеют действительную и мнимую часть, каждая из которых содержит число с плавающей запятой [3.12], как показано в дампе 2.2.3

Дамп 2.2.3

 
>>> z = 3.14 + 2.71j
>>> type(z)

>>> z.real
3.14
>>> z.imag
2.71

Как показано в дампе 2.2.3, для извлечения действительной и мнимой частей из комплексного числа z воспользовался z.real и z.imag соответственно.

Запись комплексных чисел в Python повторяет математическую нотацию, но с тем отличаем, что вместо i используется 'j' or 'J' для числовых литералов, дающие мнимое число, которое можно добавить к целому числу или с плавающей точкой для получения целого числа с действительной и мнимой частями, как показано в дампе 2.2.4

Дамп 2.2.4

>>> z = 3.14 + 0j
>>> type(z)

>>> z = 0 + 2.71j
>>> type(z)

Действительная часть быть представлена как комплесное число $3.14 + 0j$, чья мнимая часть обнулена и, напротив, комплексным числом $0+2.71j$, чья действительная часть также обнулена [3.11].

Python полностью поддерживает смещанные арфиметические операции, когда бинарный арифметический оператор имеет операнды различных числовых типов, как показано в дампе 2.2.5

Дамп 2.2.5

>>> pi=3.1415
>>> type(pi)
<class 'float'>
>>> inum=1234
>>> type(inum)
<class 'int'>
>>> sum=inum+pi
>>> type(sum)
<class 'float'>  

При этом операнд "суженным" типом расширяется к другому, который имеет более "расширенный" тип (wide type).

Сравнение между числами различных типов производится так, как если бы сравнивали бы их точные значения, что и иллюстрирует дамп 2.2.6

Дамп 2.2.6

>>> compare = lambda x,y :  x < y 
>>> compare(pi,num)
>>> compare(pi,inum)
True
>>> compare(inum,pi)
False

Где в одном случае производится сравнение заведомо меньшего числа 3.1415 типа float и целочисленного 1234 дает True, а в другом — False

Для явного определения типа можно использовать конструкторы int(), float() и complex(), как показано в дампе 2.2.7

Дамп 2.2.5

>>> ipi=int(3.1415)
>>> type(ipi)
<class 'int'>
>>> pi=float(3.1415)
>>> type(pi)
<class 'float'>
>>> cpi=complex('3.1415+2.71j')
>>> type(cpi)
<class 'complex'>
>>> sum=ipi+pi+cpi
>>> type(sum)
<class 'complex'>

При этом результирующее значение sum будет приведено к более «широкому» типу операнда cpi, который является комплексными числом. Таким образом, саммым узким типом числа является 'int', а к более широким типам относятся 'float' и 'complex'.

Итераторы и итерируемые объекты

К итерируемым относятся классы объектов, которые могут быть итерированы путем генерации итератора, который возвращает функция iter() и являющийся объектом, используемым вместе с итерируемым объектом с использованием метода __next__(), возвращающий следующий элемент. При этом необходимо помнить, что каждый итератор является также итерируемым, но не каждый итерируемый объект является итератором.

Список

Для примера, список является итерируемым, но не является итератором. Потому что создается итератор функцией iter(), а класс объекта возвращает итератор в методах __iter__() и __getitem__() с соответствующей индексацией, начинающейся с нуля [3.14], как показано в дампе 2.2.8

Дамп 2.2.8

'''Define an iterable object to be a list that contains textual names of numbers'''
>>> textual_nums=['one','two','three']
>>>textual_nums=['one','two','three']
>>>iter=textual_nums.__iter__()
>>> print(iter.__next__())
one
>>> print(iter.__next__())
two
>>> print(iter.__next__())
three
>>> print(iter.__next__())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> type(iter)
<class 'list_iterator'>
>>> type(textual_nums)
<class 'list'>

Как показано в дампе 2.2.8, после того как был создан (возвращен) итератор, каждый последующей элемент из списка textual_nums был возвращен с использованием метода __next__() до тех пор пока элементы списка не закончились, а Python сгенерировал исключение StopIteration [3.15]

Для создания списков корме квадратных скобок (brackets) также можно использовать конструктор list().

Cтроки

По мимо списков к итерируемым объектам относятся словари, множества, кортежи [3.15] и строки, пример использования итератора с последними показано в дампе 2.2.9

Дамп 2.2.9

...
>>> string = 'This object  is a string'
>>> print(string)
This object  is a string
>>> iter=string.__iter__()
>>> print(iter.__next__())
T
>>> print(iter.__next__())
h
>>> print(iter.__next__())
i
>>> print(iter.__next__())
s
>>> print(iter.__next__())
… 
>>> type(string)
<class 'str'>
>>> type(iter)
<class 'str_iterator'>
...

Для создания строк также можно использовать конструктор str().

Кортежи

Пример создания итератора для кортежа показан в дампе 2.2.10

Дамп 2.2.10

...
>>> (a,b,c,d)='one',1,'two',2
>>> type((a,b,c,d))
<class 'tuple'>
>>> myiter=(a,b,c,d).__iter__()
>>> type(myiter)
<class 'tuple_iterator'>
>>> print(myiter.__next__())
one
>>> print(myiter.__next__())
1
>>> print(myiter.__next__())
two
>>> print(myiter.__next__())
2
>>> print(myiter.__next__())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
...

Для создания кортежей также можно использовать конструктор tuple().

Множества

Содержит однородный итериуемый список элементов, итератор которого можно получить, как показано в дампе 2.2.11

Дамп 2.2.11

...
>>> myset={1,2,3,4,5,5,6}
>>> type(myset)

>>> myiter=(a,b,c,d).__iter__()
>>> type(myiter)
>>> myiter=myset.__iter__()
>>> type(myiter)

...

Cловари

Содержат итерируемый объект с ключевые значения в виде пары — ключа и значения. Пример получения итератора для словаря показан в дампе 2.2.12

Дамп 2.2.12

...
>>> mydict = { (1,2) : "typle1", (3,4) : "typle2" }
>>> type(mydict)
<class 'dict'>
>>> myiter=mydict.__iter__()
>>> type(myiter)
<class 'dict_keyiterator'> 
>>> key=myiter.__next__()
>>> mydict.get(key)
'typle1'
...

При этом, если продолжить перечислять ключевые значения в списке с использованием метода __next__(), Python также сгенерит исключение StopIteration, как показано в дампе 2.2.13

Дамп 2.2.13

...
>>> key=myiter.__next__()
>>> mydict.get(key)
'typle2'
>>> key=myiter.__next__()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
... 

Для создания словарей кроме фигурных скобок (curly brackets) также можно использовать конструктор dict().

Неизменные / изменяемые типы объектов

К неизменяемым типам объектов (Immutable data types) относятся те типы классов объектов, значения которых на протяжении жизненного цикла (lifecycle) остается неизменными, таким как целое и сплавающий точкой числовых значений, в том числе значения, содержащие булевый подтип, строки и кортежи [3.16].

Так, как было показано выше в дампе 2.2.12, кортежи в силу незыблемости своих значений могут использоваться в словарях в качестве ключевых значений. При этом, сами словари относятся к изменяемому типу.

К изменяемым типам (Mutable data types) относятся те типы классов объектов, значения которых могут быть изменены в любой момент. К таким типам классов объектов относятся словари , списки и множества [3.16].

Насколько класс типа объекта является изменяемым или неизменяемым можно судить по изменению или отсутствию такого изменения функции id(), которая возвращает числовой идентификатор объекта, назначаемый во время создания [3.17].

Например, если взять два комплексных числа в нотации $2+3j$, сначала добавить, а потом помножить на два, как показано в дампе 2.2.14

Дамп 2.2.14

...
>>> mycmplx=2+3j
>>> id(mycmplx)
140224236723728
>>> mycmplx+=2
>>> id(mycmplx)
140224236724080
>>> mycmplx*=2
>>> id(mycmplx)
140224236723728
...

При каждом изменении переменной mycmplx, наблюдалось изменение идентификатора объекта, что указывает на фактическое создание нового экземпляра данной переменной.

А вот в случае со списком или словарем идентификатор останется неизменным, как показано в дапме 2.2.15, на примере добавления нового члена в mydict

Дамп 2.2.15

...
>>> mydict = { (1,2) : "typle1", (3,4) : "typle2" }
>>> id(mydict)
140224238492672
>>> mydict[(4,5)] = "tuple3"
...

2.3 Класс и экземпляр объекта

Экземпляр каждой переменной в отдельности, как мы уже видели выше, содержит уникальные данные для каждого экземпляра в отдельности и класса переменных, атрибуты и методы которых являются разделяемыми для всех экземпляров, порожденных от этого класса, например, как показано в дампе 2.3.1

Дамп 2.3.1

>>> class MyCat(object):
...       def __init__(self,name,color,age):
...           self._name = name
...           self._color = color
...           self._age=age
...       def print_info(self):
...           print (  str(self._name) +str('-') + str(self._color) +str('-')+ str(self._age) )
... 
>>> cat1 = MyCat('Tom', 'white', 1.5 )
>>> cat2 = MyCat('Carl', 'black', 4.8 )
>>> cat1.print_info()
Tom-white-1.5
>>> cat2.print_info()
Carl-black-4.8
>>> isinstance(cat1,MyCat)
True
>>> isinstance(cat2,(MyCat,int,list,dict))
True

Что с легкостью проверяется с использованием isinstance(), который проверяет как соотносится объект с классом, типом или с одним из типов, классом в кортеже [3.18]

2.4 2.4 Утиная типизация (Duck typing)

Непосредственно связана с динамической типизацией, где тип или класс объекта является менее важным чем метод, определяющий его. Когда используется утиная типизация, нет необходимости проверять типы классов объектов. Вместо этого, проверяется наличие атрибута или метода в нем [3.19], т.е. код не заботит, чтобы объект был самой уткой, самое главное чтобы он квакал, как утка, что и показано в дампе 2.4.1

Дамп 2.4.1

  
>>> class Duck: 
...       def __init__(self, name):
...           self.name = name
...       def quack(self):
...           print('Quack!')

>>> class Car:
...       def __init__(self, model):
...           self.model = model
...       def quack(self):
...           print('I can quack, too!')
... 
>>> def quacks(obj):
...     obj.quack()
>>> quacks(donald)
Quack!
>>> quacks(car)
I can quack, too! 

Во взятом мной с Towards Data Science шуточном примере показано [3.20], что здесь главным является метод, а не класс и порожденный от него экземпляр класса объекта.

3. Библиография

3.1 Python 3.X documentation — Built-in Types

3.2 Type inference

3.3 RealPython — Dynamic vs Static

3.4 DynamicTyping

3.5 Dynamic semantics — Interpretation as a Process

3.6 RealPython — Dynamic vs static

3.7 What is TypeError in Python?

3.8 Dynamic typing in python

3.9 type() function in Python

3.11 What is a zero real part of complex number?

3.12 Complex numbers in python

3.13 Wide and narrow data

3.14 Python — Difference between iterable and iterator

3.15 Simplify Complex Numbers With Python

3.16 What are mutable and immutable data types in python/

3.17 Python Basics: Mutable vs Immutable Objects

3.18 type and isinstance in Python

3.19 RealPython — Duck Typing

3.20 Towards Data Science — What is Duck Typing in Python?

Сайт разработан в соответствии с рекомендациями консорциума W3C для языка разметки HTML5.

Об авторе можно прочитать здесь.

Copyright © 2015-2019 Андрей Ржавсков