Интересности и полезности python. Часть 2
В предыдущей статье мы рассмотрели несколько интересных моментов языка python, разумеется, одной статьёй они не исчерпываются, поэтому продолжим.
В одном из комментариев рассматривался следующий код:
SEX = 'Female', 'Male'
sex = SEX[True] # -> Male
sex = SEX[False] # -> Female
Он работает так как логический тип данных эквивалентен 0 и 1, более того по началу никакого особого типа не было и использовались именно значения 0 и 1. А когда его всё же ввели, то решили унаследовать от целого.
print(int.__subclasses__()) # -> []
Но вернёмся к получению значений списка, допустим у нас есть следующие данные:
books = [["Sherlock Holmes", "Arthur Conan Doyle", 1986, True, 12.51],
["The Lost World", "Arthur Conan Doyle", 2015, False, 5.95],
["The Art of Computer Programming", "Donald E. Knuth ", 2017, True, 190.54] ]
Разумеется, обращаться к значениям по захардкоженным индексам — не наш путь. Можно создать константу, например AUTHOR и указывать её, но можно пойти дальше:
TITLE_AUTHOR = slice(0, 2)
PRICE_IN_STOCK = slice(3, 5)
print(books[0][TITLE_AUTHOR])
print([book for book in books if book[PRICE_IN_STOCK] > [True, 10.0]])
Мы можем создать объект среза и указать ему, по каким индексам находятся, интересующие нас поля. Мы можем даже достаточно расхрабриться, чтобы написать что-то подобное последней строке в данном блоке, который выводит книги, имеющиеся на складе и дороже 10 чего бы то ни было. Данный код будет работать, так как сравнение списков и кортежей происходит лексикографически: сначала сравниваются первые элементы, потом вторые и так далее, то есть любой объект, у которого «поле» in_stock равно false, будет меньше списка [True, 10.0], так как True > False.
Примечание: разумеется данный код не стоит использовать в проектах, а лучше ограничиться лишь получением каких-то значений по срезу, выборки лучше делать в базе данных, либо, в Pandas, либо в виде метода класса Book. Можно и использовать namedtuple и написать небольшой модуль, куда положить методы с подобными выборками.
Мы уже рассмотрели аргументы, которые можно передать в функцию только по ключу. Недавно, изучая документацию функции bool, я обнаружил приписку:
Changed in version 3.7: x is now a positional-only parameter.
Сейчас в разработке находится PEP 570, который откроет возможность задавать подобные параметры, это может быть полезно, когда имя параметра не имеет значения, вроде, bool (), pow () и т.д.
Черновик синтаксиса выглядит так:
def name(positional_only_parameters, /, positional_or_keyword_parameters,
*, keyword_only_parameters):
Возвращаясь к функции bool, которая работает точно так же, как проверка в условиях и циклах, когда вы исполняете код
if obj:
# или
while obj:
python выполняет довольно интересную процедуру вывода булевого значения:
- Если объект реализует метод __bool__(), то возвращается результат вызова этого метода
- В противном случае проверяется, реализован ли метод __len__(), если да, то проверяется, что он возвращает, если 0 — то результат будет False.
- Если ни тот, ни другой метод не реализован, то возвращается True.
class A:
pass
a = A()
print(bool(a)) # -> True
Целые числа реализуют метод __bool__, стандартные коллекции его не реализуют, но реализуют метод __len__:
print(dir(int)) # -> ['__abs__', '__add__', '__and__', '__bool__', '__ceil__', ...
print(dir(list)) # -> ['__add__', '__class__', '__contains__', ...
В другом комментарии было приведено «нестандартное» поведение атрибутов класса:
class Example:
arr=[]
def __init__(self):
pass
a = Example()
b = Example()
a.arr.append(1)
print(a.arr) # -> [1]
print(b.arr) # -> [1]
print(Example.arr) # -> [1]
Очень важно различать атрибуты класса и объекта, атрибуты объекта создаются, ВНЕЗАПНО, у объекта.
Важно понимать, что любые объявленные в теле класса поля и методы принадлежат классу. Чтобы создать атрибут у объекта его нужно присвоить объекту, в методе класса через self или в тексте программы, через переменную объекта.
Если запутались, давайте разбирать предыдущий код, в нём создаётся два экземпляра класса, после чего к полю arr добавляется 1 через один из объектов. Но в объекте такого атрибута нет, конструктор у нас пуст, поэтому python ищет данный атрибут в классе и находит его, однако он разделяется всеми экземплярами класса. Что это именно атрибут класса можно убедиться по последней строке, так как обращение по имени данного атрибута к классу не сгенерировало никакой ошибки.
При изучении нового языка важно понимать, что у каждого языка своя философия, и не во всех языках должно присутствовать ключевое слово static. Иногда похожий механизм обеспечивается другими механизмами.
Ярче всего идею, что python это не java иллюстрирует то, что добавление полей в объектах и классах осуществляется через обычное присваивание:
def make_title(self):
if self.title == 'Mister':
return 'Mr. ' + self.surname
class Person:
pass
john = Person()
john.title = 'Mister'
john.name = 'John'
john.surname = 'Peterson'
john.age = 33
Person.make_title = make_title
print(john.make_title())
Общая рекомендация: не храните изменяемые типы как экземпляры класса, это чревато, однако, если вы знаете, что делаете, то используйте данный функционал во всю катушку, главное хорошо всё продумайте вначале и задокументируйте после.
Например, очень просто можно создать класс, который хранит ссылки на все свои экземпляры:
class RememberAll():
instances = []
def __init__(self):
self.instances.append(self)
a = RememberAll()
b = RememberAll()
print(RememberAll.instances) #-> [<__main__.RememberAll object at 0x7faa4a5ab7b8>, <__main__.RememberAll object at 0x7faa4a523c88>]
Можно реализовать класс какого-то физического объекта и при изменении его характеристик вызывать методы перерасчёта характеристик других элементов системы. Либо запомнить все ползунки на форме и при сдвиге одного двигать остальные автоматически. Не берусь утверждать, что подобная реализация прям всегда хорошо, но иногда может быть полезна.
Ну и раз мы коснулись физики, хочется упомянуть про оператор @. Да, такой есть. Вводился он для умножения матриц, что самое забавное, но, вроде бы, ни для одного из стандартных типов данных он не реализован, то есть он изначально вводился только для сторонних библиотек. Но ничто не мешает вам добавить его поддержку для своих типов данных реализовав методы __matmul__, __rmatmul__, __imatmul__
class Person:
def __init__(self, name):
self.name = name
def __matmul__(self, msg):
return "@" + self.name + " " + msg
john = Person("John")
print(john @ "hello") # -> @John hello