Объекты в Python

Введение

Основной объект в Python, от которого всё наследуется это PyObject python.org

У него есть два атрибута: счётчик ссылок Py_REFCNT и тип Py_TYPE .

Про итерацию, итераторы и итерируемые объекты вы можете прочитать в статье «Итерация в Python»

Про типы данных в Python можно прочитать много интересного в официальной документации

id()

Рассмотрим очень полезную для дальнейшего понимания объектом функцию id()

# id() print("# id():\n") a = 1961 print("a = 1961") print(f"id(a) = {id(a)}")

# id(): a = 1961 id(a) = 140042834389712

b = 1065 print("b = 1065") print(f"id(b) = {id(b)}")

b = 1065 id(b) = 140042834389648

b - это новый элемент со своим уникальным id

Если выполнить b = a, то b получит id и значение от a, то есть станет тем же объектом, просто у объекта a появится второе название.

b = a print("b = a") print(f"id(b) = {id(b)}") print(f"id(a) == id(b): {id(a) == id(b)}") print(f"a is b: {a is b}")

b = a id(b) = 140042834389712 id(a) == id(b): True a is b: True

В циклах при увеличении счётчика создаётся новый объект.

i = 0 print("i = 0") print(f"id(i) = {id(i)}") i += 2 print("i += 2") # Создаётся новый объект print(f"id(i) = {id(i)}")

i = 0 id(i) = 140042835400976 i += 2 id(i) = 140042835401008

dir()

Получить все аттрибуты объекта можно с помощью функции dir()

Рассмотрим обычную строку

print("l =", l) print("m =", m)

['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'removeprefix', 'removesuffix', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

А теперь простой список

l = ["l", "m"] print(dir(l))

['__add__', '__class__', '__class_getitem__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getstate__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

Видно, что некоторые атрибуты совпадают, а некоторые нет. Например, у строк нет метода copy а у списков upper.

Равенство по значению и по идентичности

== сравнивает значения объектов.

is проверяет являются ли объекты одним и тем же объектом в памяти. То есть сравнивает ссылки на область в памяти.

# Value vs. Identity Equality print("\n# Value vs. Identity Equality:\n") p = [5, 6, 7] print(f"p = {p}") q = [5, 6, 7] print(f"q = {q}") print(f"p == q: {p == q}") print(f"p is q: {p is q}") print(f"p is p: {p is p}") # Value-equality and identity equality are fundamentally # different concepts. # Comparison by value can be controlled programatically # Identity comparison is unalterable defined # by the language # Value vs. Identity Equality:

p = [5, 6, 7] q = [5, 6, 7] p == q: True p is q: False p is p: True

Изменяемые и неизменяемые объекты

Объекты могут быть изменяемыми (mutable) и неизменяемыми (immutable)

Очень хорошей иллюстрацией разницы в их применении являются аргументы по умолчанию

Множество (set) , Словарь (dict) и Список (list) это изменяемые объекты.

Строка - это неизменяемый объект

# Пример 1. Строка a = "aaa" b = a print("a = ", a) print("b = ", b) print("id(a) == id(b):", id(a) == id(b)) # True так как a и b это один и тот же объект print("id(a): ", id(a)) b = "bbb" print("id(a) == id(b):", id(a) == id(b)) # False так как b теперь новый объект print("id(a): ", id(a)) print("id(b): ", id(b)) print("a =", a) print("b =", b)

a = aaa b = aaa id(a) == id(b): True id(a): 2037982796544 id(a) == id(b): False id(a): 2037982796544 id(b): 2037982798368 a = aaa b = bbb

Список - это изменяемый объект, но в этом примере поведение списка и строки пока что одинаковое потому что и
b = "bbb"
и
m = ["m", "m"]
создают новые объекты.

# Пример 1. Список lst = ["l", "l"] m = lst print("lst: ", lst) print("m: ", m) print("id(lst) == id(m): ", id(lst) == id(m)) # True m = ["m", "m"] print("lst = ", lst) print("m = ", m) print("id(lst) == id(m): ", id(lst) == id(m)) # False

lst: ['l', 'l'] m: ['l', 'l'] id(lst) == id(m): True lst = ['l', 'l'] m = ['m', 'm'] id(lst) == id(m): False

А в следующем примере уже видна разница. Изначальная строка a останется без изменений, а вот список l изменится из-за изменения m

# Пример 2. Строка a = "heihei" b = a print("a =", a) print("b =", b) print("id(a) == id(b): ", id(a) == id(b)) # True b += ".ru" print("a =", a) print("b =", b) print("id(a) == id(b): ", id(a) == id(b)) # False # Пример 2. Список lst = ["heihei.ru", "topbicycle.ru"] m = lst print("lst = ", lst) print("m = ", m) print("id(lst) == id(m)", id(lst) == id(m)) # True m.append("beget.com") print("lst =", lst) print("m =", m) print("id(lst) == id(m)", id(lst) == id(m)) # True здесь проявилась изменяемость списка.

a = heihei b = heihei id(a) == id(b): True a = heihei b = heihei.ru id(a) == id(b): False lst = ['heihei.ru', 'topbicycle.ru'] m = ['heihei.ru', 'topbicycle.ru'] id(lst) == id(m) True lst = ['heihei.ru', 'topbicycle.ru', 'beget.com'] m = ['heihei.ru', 'topbicycle.ru', 'beget.com'] id(lst) == id(m) True

С помощью функции id() можно убедиться в том, что строка b стала новым объектом после b += "b" а список m и список l остались одним и тем же объектом.

В следующем примере мы изменим список не с помощью append(), а напрямую, но результат будет таким же.

# Mutable Objects print("\n# Mutable Objects:\n") my_list = [0, 1, 2] print(f"my_list = {my_list}") s = my_list print("s = my_list") print(f"s = {s}") # Изменим элемент в s # и увидим, что и изначальный my_list изменится s[1] = 34 print("s[1] = 34") print(f"s = {s}") # Изменился не только s но и my_list print(f"my_list = {my_list}") print(f"s is my_list: {s is my_list}") # Python doesn't have variables in the sense of boxes # holding a value # Python has named references to objects

python objects_example.py

# Mutable Objects: my_list = [0, 1, 2] s = my_list s = [0, 1, 2] s[1] = 34 s = [0, 34, 2] my_list = [0, 34, 2] s is my_list: True

Передача аргументов

m = [0, 1, 2] print(f"m = {m}") def modify(k): k.append(3) print("k =", k) print("\nmodify(m)\n") modify(m) print(f"m = {m}") print("\nNo copy of m is made\n") print("\nmodify(m)\n") modify(m) print(f"m = {m}")

m = [0, 1, 2] modify(m) k = [0, 1, 2, 3] m = [0, 1, 2, 3] No copy of m is made modify(m) k = [0, 1, 2, 3, 3] m = [0, 1, 2, 3, 3]

Функция modify() работает с тем же объектом, поэтому его значения изменяются. Обойти эту особенность можно с помощью функции copy()

Рассмотрим функцию replace() в которой создается новый объект

f = [4, 5, 6] print("f = ", f) def replace(g): g = [7, 8, 9] print("g =", g) print("\nreplace(f)\n") replace(f) print("f = ", f)

python replace.py

f = [4, 5, 6] replace(f) g = [7, 8, 9] f = [4, 5, 6]

Благодаря тому, что ссылка на старый объект не используется (вместо это переменная g указывает на новый объект) значение переменной f не изменилось.

Можно переписать функцию replace() так, чтобы она заменяла исходный объект.

Назовём новую функцию replace_contents()

def replace_contents(g): g[0] = 20 g[1] = 21 g[2] = 22 print("g = ", g) f = [10, 11, 12] print("f = ", f) print("\nreplace_contents(f)\n") replace_contents(f) print("f = ", f) # Function arguments are transferred using # pass-by-object-reference # References to objects are copied, # not the objects themselves.

python replace_contents.py

f = [10, 11, 12] replace_contents(f) g = [20, 21, 22] f = [20, 21, 22]

Убедиться в том, что если не произведено специальных действий, функция вернёт тот же самый объект довольно просто

def f(d): return d c = [0, 1, 2] print("c = ", c) e = f(c) print("e = f(c)") print(f"c is e: {c is e}")

python return_semantics.py

c = [0, 1, 2] e = f(c) c is e: True

Всё является объектом

Рассмотрим скрипт words.py со следюущим содержанием.

"""Retrieve and print words from a URL. Usage: python3 words.py <URL> """ import sys from urllib.request import urlopen def fetch_words(url): """Fetch a list of words from a URL. Args: url: The URL of a UTF-8 text document. Returns: A list of strings containing the words from the document """ # PEP 257 # story = urlopen("http://sixty-north.com/c/t.txt") story = urlopen(url) story_words = [] for line in story: line_words = line.decode("utf8").split() for word in line_words: story_words.append(word) story.close() return story_words def print_items(story_words): """Print items one per line. Args: An iterable series of printable items. """ for word in story_words: print(word) def main(url): words = fetch_words(url) print_items(words) if __name__ == "__main__": main(sys.argv[1])

➭ python

Python 3.9.5 (default, Jun 15 2021, 15:30:04) [GCC 9.3.0] on linux Type "help", "copyright", "credits" or "license" for more information.

>>> import words
>>> type(words)

<class 'module'>

Получить все аттрибуты объекта можно с помощью функции dir()

>>> dir(words)

['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'fetch_words', 'main', 'print_items', 'sys', 'urlopen']

>>> type(words.fetch_words)

<class 'function'>

>>> dir(words.fetch_words)

['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

>>> words.fetch_words.__name__

'fetch_words'

>>> words.fetch_words.__doc__

'Fetch a list of words from a URL.\n \n Args:\n url: The URL of a UTF-8 text document.\n\n Returns:\n A list of strings containing the words from\n the document\n '

Размер объекта в памяти

Рассмотрим несколько способов изменения размера объекта. Начнём со списка

import pickle from sys import getsizeof from pympler import asizeof l = ["apple", "banana", "cherry"] print(f"getsizeof(l): {getsizeof(l)}") print(f"asizeof(l): {asizeof.asizeof(l)}") print(f"pickle.dumps(l): {len(pickle.dumps(l))}")

getsizeof(l): 80 asizeof(l): 248 pickle.dumps(l): 42

Теперь проверим на строке

s = "applebananacherry" print(f"getsizeof(s): {getsizeof(s)}") print(f"asizeof(s): {asizeof.asizeof(s)}") print(f"pickle.dumps(s): {len(pickle.dumps(s))}")

getsizeof(s): 66 asizeof(s): 72 pickle.dumps(s): 32

Теперь проверим на кастомном классе из статьи про __slots__

import timeit from pympler import asizeof class Developer: __slots__ = ("name", "age", "salary", "framework") def __init__(self, name, age, salary, framework): self.name = name self.age = age self.salary = salary self.framework = framework class Programmer: def __init__(self, name, age, salary, framework): self.name = name self.age = age self.salary = salary self.framework = framework slotted_emp = Developer("Andrei", 36, 5000, "FastAPI") not_slotted_emp = Programmer("Eugene", 59, 95000, "Spring") print(f"asizeof(s) slotted size: {asizeof.asizeof(slotted_emp)}") print(f"asizeof(s) not slotted size: {asizeof.asizeof(not_slotted_emp)}") print(f"getsize() slotted size: {getsizeof(slotted_emp)}") print(f"getsize() not slotted size: {getsizeof(not_slotted_emp)}") print(f"pickle.dumps() slotted size: {len(pickle.dumps(slotted_emp))}") print(f"pickle.dumps() not slotted size: {len(pickle.dumps(not_slotted_emp))}")

sizeof(s) slotted size: 240 asizeof(s) not slotted size: 560 getsize() slotted size: 64 getsize() not slotted size: 48 pickle.dumps() slotted size: 106 pickle.dumps() not slotted size: 105

Автор статьи: Андрей Олегович

Изображение баннера