Python tips & tricks
Недавно прочитал книгу Марка Лутца “Learning Python”, 5-ое издание. Привожу список самых интересных фишек (по моему мнению) оттуда, что-то вроде конспекта.
-
генерация set’a:
{x for x in [1,2]} set(x for x in [1,2]) assert set(x for x in [1,2]) == {x for x in [1,2]}
-
генерация dict’а:
{x:x**2 for x in [1,2]} dict((x, x**2) for x in [1,2]) assert {x:x**2 for x in [1,2]} == dict((x, x**2) for x in [1,2])
-
деление целых чисел
В python 3 деление целых чисел возвращает float
>>> 1 / 2 0.5 >>> - 1 / 2 -0.5
В python 2 деление - округление в меньшую сторону, это не truncate
>>> 1 / 2 # 0.5 floor округление -> 0 0 >>> - 1 / 2 # -0.5 floor округление -> -1 (не 0) -1
В python 2 и 3 целочисленное деление
>>> 1 // 2 0 >>> - 1 // 2 -1 >>> 13 // 2.0 6.0
-
is
- проверяет, что переменные указывают на один и тот же адрес,==
проверяет, что объекты имеют одинаковые значения -
python 3:
[1, 'spam'].sort()
возбуждает исключение (разные типы) -
python 3: dict().keys() возвращает итератор (view объект, зависимый от dict). Это set-like объект, к нему можно применять set операции (union и пр.)
>>> dict(a=1, b=2).keys() dict_keys(['b', 'a']) >>> dict(a=1, b=2).keys() | {'c', 'd'} {'b', 'd', 'a', 'c'}
-
frozenset - immutable set, он является hashable, можно использовать например как ключ в dict
>>> fz = frozenset([1,2]) >>> fz.add(3) AttributeError: 'frozenset' object has no attribute 'add' >>> {fz: 5} {frozenset([1, 2]): 5}
-
list поддерживает операторы сравнения: ==, <, >, <=, >=. Сравнение аналогично сравнению срок. Для py3 все объекты должны быть одного типа
>>> [1, 2] == [1, 2] True >>> [2, 2] > [1, 2] True >>> [1] > ['sh'] # python2 False >>> [1] > ['sh'] # python3 TypeError: unorderable types: int() > str()
-
сравнение dict’ов
python 2 and 3
>>> dict(a=1) == dict(a=1) True
python 2 only
>>> dict(a=3) > dict(a=2) True >>> dict(a=3) > dict(a=2, b=1) False
-
нельзя list + string, list + tuple, однако можно list += string
>>> L = [] >>> L + 'spam' TypeError: can only concatenate list (not "str") to list >>> L = [] >>> L += 'spam' >>> L ['s', 'p', 'a', 'm']
-
L += a is faster that L = L + a.
-
L += [1,2] is in place modification! (не создается новый список)
>>> L = [] >>> id(L) 4368997048 >>> L += [1,2] >>> id(L) 4368997048 >>> L = L + [1,2] >>> id(L) 4368996976
-
‘spam’[0][0][0] можно до бесконечности, каждый раз будет возвращатся односимвольная строка ‘s’
-
распаковка аргументов в python 3 при присваивании
>>> a, *b = 'spam' >>> a 's' >>> b ['p', 'a', 'm'] >>> *a, b = 'spam' >>> a ['s', 'p', 'a'] >>> b 'm' >>> a, *b, c = 'spam' >>> a 's' >>> b ['p', 'a'] >>> c 'm'
-
python 2: True = 0, но не в python 3
python 2
>>> True = 0 >>> True 0
python 3
>>> True = 0 SyntaxError: can't assign to keyword
-
sys.stdout = open(‘temp.txt’, ‘w’) - все print’ы будут идти в файл temp.txt
-
and
,or
возвращают объект, а не True/False -
while
haselse
-
python 3:
...
все равно чтоpass
-
reversed works with lists, not generator
>>> reversed([1,2,3]) <list_reverseiterator object at 0x10127c550> >>> reversed((x for x in [1,2,3])) TypeError: argument to reversed() must be a sequence
-
zip итерирует до самой маленькой последовательности
>>> [x for x in zip([1,2,3], [4,5])] [(1, 4), (2, 5)]
-
python 2: map(None, s1, s2) тоже самое, что zip, но добавялет None для элементов из более длинной последовательности
python 2
>>> map(None, [1,2,3], [4,5]) [(1, 4), (2, 5), (3, None)] >>> map(None, [1,2], [4,5,6]) [(1, 4), (2, 5), (None, 6)]
python 3
>>> list(map(None, [1,2,3], [4,5])) TypeError: 'NoneType' object is not callable
-
map can take more than one iterators (похоже на zip)
python 2
>>> map(lambda x, y: (x, y), [1,2], [3,4]) [(1, 3), (2, 4)] >>> map(lambda x, y: (x, y), [1,2], [3,4,5]) [(1, 3), (2, 4), (None, 5)]
python 3
>>> list(map(lambda x, y: (x, y), [1,2], [3,4])) [(1, 3), (2, 4)] >>> list(map(lambda x, y: (x, y), [1,2], [3,4,5])) [(1, 3), (2, 4)]
-
nested list comprehensions
>>> [x+y for x in 'abc' for y in 'lmn'] ['al', 'am', 'an', 'bl', 'bm', 'bn', 'cl', 'cm', 'cn'] # flat list of lists >>> csv = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] >>> [col for row in csv for col in row] [1, 2, 3, 4, 5, 6, 7, 8, 9]
-
sorted возвращает список (не генератор) в py2 и py3
>>> sorted(x for x in [2,1,3]) [1, 2, 3]
-
*args понимает любой итератор, не обязательно list
-
unzip: zip(*zip(a,b))
>>> zip(*zip([1,2],[3,4])) [(1, 2), (3, 4)]
-
py3: map возвращает генератор, пройти по нему можно только раз
>>> m = map(lambda x: x, [1,2,3]) >>> [x for x in m] [1, 2, 3] >>> [x for x in m] []
-
py3: хоть range и генератор (это хитрый генератор), он поддерживает len() и доступ по индексам
>>> r = range(10) >>> r range(0, 10) >>> len(r) 10 >>> r[3] 3
-
generator allows only single scan
-
циклические импорты работают! Но только с import, без from
-
у try есть else, который вызовится, если exception не случилось
-
with
similar tofinally
-
except (name1, name2) - orders from top to bottom, from left to right
-
except Exception:
vsexcept:
- первое не перехватывает системные исключения (KeyboardInterrupt, SystemExit, GeneratorExit например) -
set().remove(x) - удаляет x или KeyError, set().discard(x) - удаляет x или ничего
-
py3.3+ accept u””, U”” для обратной совместимости с py2
-
default encoding is in sys module sys.getdefaultencoding()
python 2
>>> sys.getdefaultencoding() 'ascii'
python 3
>>> sys.getdefaultencoding() 'utf-8'
-
[c for c in sorted([1,2,3], key=lambda c: -c)] - тут переменная
c
конфликтовать не будет -
в py2 переменная внутри comprehension может изменять внешние переменные а также доступна после, в py3 - нет.
python 2
>>> x = 1 >>> [x for x in range(3)] [0, 1, 2] >>> x 2 # creates new var >>> [y for y in range(3)] [0, 1, 2] >>> y 2
python 3
>>> x = 1 >>> [x for x in range(3)] >>> x 1 # no new var >>> [y for y in range(3)] [0, 1, 2] >>> y NameError: name 'y' is not defined
-
py3 имеет инструкцию nonlocal. Используется для ссылки на имя во внешнем def блоке (в py2 к такой переменной нельзя обратиться)
def f(): x = 2 # local for f def g(): nonlocal x # python3 only x = 3 # local for g g() print(x) >>> f() # python3 only 3 >>> f() # with commented nonlocal 2
-
LEGB rule (local, enclosing, global, builtin) или LNGB (N=nonlocal) - порядок поиска переменной в python
-
py3 переменная исключения
as name
удаляется после выполнения блока (даже если переменная была объявлена до try)python 2
>>> x = 1 >>> try: ... 1/0 ... except Exception as x: ... pass >>> x ZeroDivisionError('integer division or modulo by zero',)
python 3
>>> x = 1 >>> try: ... 1/0 ... except Exception as x: ... pass >>> x NameError: name 'x' is not defined
-
переопределить builtin и отменить переопределение
>>> open = 99 >>> open 99 >>> del open >>> open <built-in function open>
-
py2 fun:
__builtins__.True = False
-
lambda может принимать аргументы по умолчанию
-
nonlocal можно заменить mutable объектом или аттрибутом функции
def f(): x = [1] def g(): print x[0] x.append(2) g() print x >>> f() 1 [1, 2] def f(): x = 1 def g(): print g.x g.x = 2 g.x = x g() print g.x >>> f() 1 2
-
py3 keyword only arguments
def f(*args, name): print("args", args) print("name", name) >>> f(1, 2) TypeError: f() missing 1 required keyword-only argument: 'name' >>> f(1, 2, name=3) args (1, 2) name 3 def f(*args, name=3): print("args", args) print("name", name) >>> f(1, 2) args (1, 2) name 3
-
py3 есть распаковка при присваивании, она возвращает list, а распаковка при вызове функции возвращает tuple
python 2 and 3
def f(a, *b): print(b) >>> f(1, *[2, 3]) (2, 3)
python 3
>>> a, *b = [1, 2, 3] >>> print(b) [2, 3] >>> a, *b = (1, 2, 3) >>> print(b) [2, 3]
-
добавить список в начало существующего: L[:0] = [1, 2, 3]
-
посмотреть и задать максимальный уровень рекурсии
>>> sys.getrecursionlimit() # 1000 >>> sys.setrecursionlimit(10000) >>> help(sys.setrecursionlimit)
-
аргументы функций
>>> def f(a): ... b = 1 ... >>> f.__name__ 'f' >>> f.__code__.co_varnames ('a', 'b') >>> f.__code__.co_argcount 1
-
py3 к аргументам функции можно добавить аннотации. Эти данные доступны в
func.__annotations__
. Автоматически ничего с этими аннотациями не происходит, но с ними можно работать вручную по ситуации (например, для проверки типа или диапазона с помощью своего декоратора)>>> def func(a: 'spam', b: (1, 10), c: float): ... return a + b + c >>> func.__annotations__ {'b': (1, 10), 'c': <class 'float'>, 'a': 'spam'} # default values >>> def func(a: 'spam'=4, b: (1, 10)=5, c: float=0.1): ... return a + b + c
-
внутри lambda нельзя присвоить, но можно
setattr
,__dict__
-
operator module in std lib
import operator as op reduce(op.add, [2, 4, 6]) # same as reduce(lambda x, y: x+y, [2, 4, 6])
-
KISS: Keep It Simple [Sir/Stupid]
-
comprehension vs map в общем случае (лучше проверить на вашей системе)
map(lambda x: x ..)
slower than[x for x ..]
[ord(x) for x ..]
slower thanmap(ord for x ..)
map(lambda x: L.append(x+10), range(10))
even slower thanfor x in range(10): L.append(x+10)
-
распаковка в lambda отличатся для py2 и py3
python 2
>>> map(lambda (a, b, c): a, [(0,1,2), (3,4,5)]) [0, 3]
python 3
>>> list(map(lambda (a, b, c): a, [(0,1,2), (3,4,5)])) SyntaxError: invalid syntax >>> list(map(lambda a, b, c: a, [(0,1,2), (3,4,5)])) TypeError: <lambda>() missing 2 required positional arguments: 'b' and 'c' >>> list(map(lambda row: row[0], [(0,1,2), (3,4,5)])) [0, 3]
-
многие встроенные функции могут принимать генераторы, тогда не нужны дополнительные скобки
>>> "".join(str(x) for x in [1, 2]) '12' >>> sorted(str(x) for x in [1, 2]) ['1', '2']
but for args () is needed
>>> sorted(str(x) for x in [1, 2], reverse=True) SyntaxError: Generator expression must be parenthesized if not sole argument >>> sorted((str(x) for x in [1, 2]), reverse=True) ['2', '1']
-
py3: yield from iterator (приведенные ниже функции идентичны)
def f(): for i in range(5): yield i def g(): yield from range(5)
-
поместить первый элемент в конец списка
L = L[1:] + L[:1]
-
zip одного списка
>>> zip([1,2,3]) [(1,), (2,), (3,)]
-
map и zip похожи
map(lambda x,y: (x,y), S1, S2) == zip(S1, S2)
-
python -m script_name
- запускает модуль (модуль - это файл .py, т.е. считай скрипт), который ищется в текущих путях поиска. Модуль может лежать где-нибудь в site-packages, а запустится от main (__name__ == '__main__'
). Если это package (директория с__init__.py
), то запустится файл__main__.py
. Если такого нет, то ошибка. Некоторые модули умные и берут аргументы из командной строки, например timeit:python -m timeit '"-".join(str(n) for n in range(100))'
-
прямой возможности использовать одноименную переменную в одной функции: и глобальную и локальную нет. Можно только играть c
__main__.my_global_var
# OK X = 99 def f(): print(X) >>> f() 99 # ERROR def f(): print(X) # <- error X = 99 >>> f() UnboundLocalError: local variable 'X' referenced before assignment # global everywhere def f(): global X print(X) X = 88 # hack with main def f(): import __main__ print(__main__.X) X = 88
-
скорость вычисления корня числа
math.sqrt(x) # fastest x ** .5 # fast pow(x, .5) # slow
-
py3.2+ создает папку
__pycache__
, чтобы сохранять разные байткоды для разных версий python’а и не пересоздавать их впоследствии. В корне уже нет *.pyc. -
.pyc для вызывающегося скрипта (
__name__ = '__main__'
) не создается, только для import -
порядок поиска при импорте (можно посмотреть в sys.path):
- home of program (+ in some versions current dir, from where program is launched, i.e. current dir)
- PYTHONPATH
- std lib dir
- content of any .pth file (if exists)
- site-packages dir
-
sys.path можно изменять в runtime, это затронет всю программу
-
python -O создает слегка опимизированный байткод .pyo вместо .pyc, он на ~5% быстрее. Также этот флаг убирает все assert’ы из кода. А так же влияет на переменную
__debug__
# main.py print __debug__ assert True == False # python main.py True AssertionError # python -O main.py False
-
в py2 в функции можно сделать
from some_module import *
, но будет warning. В py3 - error# python 2 def f(): from urllib import * print('after import') >>> f() SyntaxWarning: import * only allowed at module level after import # python 3 >>> f() SyntaxError: import * only allowed at module level
-
reload не обновляет объекты, загруженные с помощью from:
from x import y
.y
не обновится послеreload(x)
-
reload не обновляет c-шные модули
-
py3: в package текущей папки пакета нету в sys.path. Если модуль в пакете хочет импортировать другой модуль из этого же пакета, надо использовать относительный импорт:
from . import smth
. Однако, если модуль запускается как__main__
, то есть. -
py2:
from __future__ import absolute_import
делает поведение import в py2 таким же, как в py3. Это позволит импортировать модуль string из стандартной библиотеки в данном случае довольно просто:mypkg ├── __init__.py ├── main.py # import string from std here? └── string.py
-
относительный импорт запрещен вне пакета:
# test.py from . import a # python 2 python test.py ValueError: Attempted relative import in non-package # python 3 python test.py SystemError: Parent module '' not loaded, cannot perform relative import
-
минусы относительного импорта:
- модуль, в котором используются относительные импорты нельзя использовать как скрипт (
__main__
). Решение: использовать в модуле абсолютный импорт с именем пакета в начале. - как следствие предыдущего пункта, нельзя запустить тесты, которые запускаются при запуске модуля как скрипта
- модуль, в котором используются относительные импорты нельзя использовать как скрипт (
-
в py3.3+ есть namespace packages. Это такие пакеты, в которых нет
__init__.py
. Два (или более) namespace package с одним и тем же именем могут лежать в разных директориях из sys.path. При этом модули пакетов собираются под этим именем. Если у модулей одинаковые имена - берется тот, который найден раньше в порядке sys.path. namespace пакет всегда имеет меньший приоритет над обычным пакетом (с__init__.py
). Как только где-то найден обычный пакет - используется он, все найденные namespace packages отметаются. namespace пакеты медленнее импортятся, чем обычные.# collect modules in namespace package current_dir └── mypkg └── mymod1.py site-packages └── mypkg └── mymod2.py >>> import mypkg.mymod1 >>> import mypkg.mymod2 # redefine module in namespace package current_dir └── mypkg └── mymod1.py └── mymod2.py site-packages └── mypkg └── mymod2.py >>> import mypkg.mymod1 >>> import mypkg.mymod2 # current_dir.mypkg.mymod2 # regular package is used current_dir └── mypkg └── mymod1.py site-packages └── mypkg └── mymod2.py another-packages └── mypkg └── mymod1.py >>> import sys >>> sys.append('another-packages') >>> import mypkg.mymod1 # another-packages.mypkg.mymod1 >>> import mypkg.mymod2 ImportError: No module named 'mypkg.mymod2'
-
В py3 и в py2 new style classes (отнаследованные от object) магические методы при выполнении оператора ищутся в классе, минуя инстанс (
__getattr__
,__getattribute__
не вызываются). Но если явно вызвать магический метод - то вызывается от инстанса (__getattr__
,__getattribute__
вызываются).class A(object): def __repr__(self): return "class level repr" def normal_method(self): return "class level normal method" def instance_repr(): return "instance level repr" def instance_normal_method(): return "instance level normal method" a = A() print(a) # class level repr print(a.normal_method()) # class level normal method a.__repr__ = instance_repr a.normal_method = instance_normal_method print(a) # class level repr print(a.normal_method()) # instance level normal method print(a.__repr__()) # instance level repr
-
ZODB - объектно ориентированная база данных для python объектов, поддерживает ACID транзакции (включая savepoints)
-
slice object:
L[2:4] == L[slice(2,4)]
-
iteration context (for, while, …) will try
__iter__
-
__getitem__
class Gen(object): def __getitem__(self, index): if index > 5: raise StopIteration() return index for x in Gen(): print x, # output 0 1 2 3 4 5
-
for вызывает
__iter__()
. Потом к полученному объекту вызывает__next__()
(в py2.next()
), пока не получитStopIteration
. В классе можно использовать__iter__(): yield ...
, тогда не надо реализовать__next__
-
__call__
вызывается, когда скобки()
применяются к инстансу, а не к классуclass A(object): def __call__(self): print("call") a = A() # nothing a() # print call
-
__eq__
= True не подразумевает, что__ne__
= False -
boolean context:
__bool__
(__nonzero__
in py2)__len__
- True
-
паттерны ООП
- inheritance - “is a”
- composition - “has a” (контейнер хранит другие объекты)
- delegation - вид composition, когда хранится только один объект. Wrapper сохраняет основной интерфейс, добавляя какие-то шаги.
-
аттрибуты и методы класса, которые начинаются с двух подчеркиваний
__
, но не заканчиваются ими, имеют особое поведение. Они не пересекаются с одноименными аттрибутами и методами унаследованного класса. В__dict__
они попадают под именем_ClassName__attrname
.class A(object): __x = 1 def show_a(self): print self.__x class B(A): def show_b(self): print self.__x >>> a = A() >>> a.show_a() 1 >>> b = B() >>> b.show_a() 1 >>> b.show_b() AttributeError: 'B' object has no attribute '_B__x' class B(A): __x = 2 def show_b(self): print self.__x >>> b = B() >>> b.show_a() 1 >>> b.show_b() 2
-
в py3 можно в методе класса не указывать аргумент self, и использовать его только от имени класса (не инстанса) - он будет работать как static method. В py2 так нельзя.
class A(object): def f(): print("f") # python 2 >>> A.f() TypeError: unbound method f() must be called with A instance as first argument (got nothing instead) # python 3 >>> A.f() f >>> a = A() >>> a.f() TypeError: f() takes 0 positional arguments but 1 was given
-
bound function:
class A(object): def f(self): pass a = A() print(a.f.__self__) # вот где хранится self
-
поиск аттрибутов в classic (old-style) классах и new-style классах:
- classic. DFLR: Depth First, Left to Right
- new-style. Diamond pattern, L-R, D-F; MRO (хитрее, чем просто LRDF)
MRO исключает класс, от которого унаследованны >= 2 других класса, от поиска аттрибуты дважды. Т.е. класс пападает в поиск только 1 раз.
# python 2 old-style class A: attr = 1 class B(A): pass class C(A): attr = 2 class D(B,C): pass >>> x = D() >>> print(x.attr) # x, D, B, A 1 # python 2 new-style class A(object): attr = 1 class B(A): pass class C(A): attr = 2 class D(B,C): pass >>> x = D() >>> print(x.attr) # x, D, B, C 2 # scheme A A | | B C \ / | D | X
Посмотреть порядок поиска в new-style (по алгоритму mro):
>>> D.__mro__ (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>) >>> D.mro() # все равно что list(D.__mro__) [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>]
-
format() конструкция вызывает метод
__format__
. Если его нет, то в py2 - TypeError.python 2
>>> print('{0}'.format(object)) <type 'object'> >>> print('{0}'.format(object.__reduce__)) TypeError: Type method_descriptor doesn't define __format__ # явно вызовим __str__ >>> print('{0!s}'.format(object.__reduce__)) <method '__reduce__' of 'object' objects>
python 3.4
>>> print('{0}'.format(object.__reduce__)) <method '__reduce__' of 'object' objects>
python 2 & 3
class A(object): def __format__(self, *args): return "A.__format__" def __str__(self): return "A.__str__" >>> a = A() >>> "{0}".format(a) 'A.__format__' >>> print(a) A.__str__ >>> '%s' % a 'A.__str__'
-
В
__dict__
не попадают “виртуальные” аттрибуты:- new-style properties (
@property
) - slots
- descriptors
- dynamic attrs computed with tools like
__getattr__
- new-style properties (
-
MRO - method resolution order
-
diamond pattern - разновидность ‘multi inheritance’, когда 2 или более класса могут быть потомками одного и того же класса (object). Этот паттерн используется в python.
-
прокси объект, который возвращает
super()
, не работает с операторами:# python 3 class A(list): def get_some(self): return super()[0] >>> a = A([1, 2]) >>> a.get_some() TypeError: 'super' object is not subscriptable class A(list): def get_some(self): return super().__getitem__(0) >>> a = A([1,2]) >>> a.get_some() 1 # python 2 class A(list): def get_some(self): return super(A, self)[0] >>> a = A([1,2]) >>> a.get_some() TypeError: 'super' object has no attribute '__getitem__' class A(list): def get_some(self): return super(A, self).__getitem__(0) >>> a = A([1,2]) >>> a.get_some() 1
-
super()
-
Плюсы super():
- если superclass нужно изменить в runtime, без super не получится:
C.__bases__ = (Y, )
-
когда нужно вызвать цепочки унаследованных методов в multi inheritance классе, в порядке MRO.
Если попытаться вызвать без super, то можем вызвать метод какого-то класса дважды.
class A(object): def __init__(self): print("A") class B(A): def __init__(self): print("B") super(B, self).__init__() class C(A): def __init__(self): print("C") super(C, self).__init__() class D(B, C): pass >>> d = D() B C A >>> B.mro() [<class '__main__.B'>, <class '__main__.A'>, <type 'object'>] >>> D.mro() [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>]
Вызов цепочки методов
class B(object): def __init__(self): print("B") # for B super is C here, by MRO order super(B, self).__init__() class C(object): def __init__(self): print("C") # it is ok here to call super().__init__ # because object also has __init__ super(C, self).__init__() class D(B, C): pass >>> d = D() B C
- super будет искать метод в иерархии MRO. Он будет искать пока не найдет или пока не упрется. Т.е. допустим иерархия для super: A, С, и в A нет метода, а в C есть, то вызовится C.method без ошибки
- если superclass нужно изменить в runtime, без super не получится:
-
минусы (или особенности) super():
- при использовании super все методы в цепочке должны принимать одинаковые аргументы
- super().m - все классы должны иметь метод m и вызывать super().m, кроме последнего, который вызывать super не должен
-
-
унаследовать метод от конкретного класса:
class A(B, C): other = C.other # not B other
-
finally вызовится даже если исключение случается в except блоке или else блоке
-
исключение - всегда instance, даже если raise ExceptionClass (без
()
), автоматически (без аргументов) создается instance:raise Exception # == raise Exception() raise # возбуждается перехваченное исключение
-
py2, посмотреть исключения builtin:
import exceptions help(exceptions)
-
минус чтения байтов из файла с последующим ручным decode в том, что если мы будем читать по кускам, то может быть сложный случай, когда один байт одного символа попадает в один кусок, а другой - в другой. Поэтому в py2 лучше использовать codecs.
-
Когда имя файла даем в unicode, python автоматически декодит и кодит в байты. Когда имя файла - байты, то не кодит нечего. encoding для имен файлов (дефолтный):
>>> sys.getfilesystemencoding() 'utf-8'
-
дескриптор - это класс, который реализует хотя бы один из методов
__get__
__set__
__delete__
-
Если в дескрипторе не реализовать
__set__
, то это еще не значит, что соответствующий аттрибут будет read-only. Аттрибут просто перезатрется. Надо делать__set__
с исключением. -
декоратры можно совмещать, тогда они будут применяться в порядке снизу вверх:
@A @B @C def f(): pass # все равно что f = A(B(C(f)))
-
декоратор может принимать аргументы. Реализовать можно через вложенные функции
@dec(a, b) def f(): pass # все равно что f = dec(a, b)(f) # реализация: def dec(a, b): def actual_dec(f): return f return actual_dec
Т.е. декоратор может включать 3 уровня callables:
- callable to accept decorator args
- callable to serve as decorator
- callable to handle calls to the original function
-
при создании класса вызываются два метода класса type:
type.__new__(type_class, class_name, super_classes, attr_dict) type.__init__(class, class_name, super_classes, attr_dict) # python 3 class Eggs: pass class Spam(Eggs): data = 1 def method(self, arg): pass # все равно, что Eggs = type('Eggs', (), ...) # в () object добавится автоматически Spam = type('Spam', (Eggs, ), {'data': 1, 'method': method, '__module__': '__main__'})
-
Задать метакласс для класса
python 2
class Spam(object): __metaclass__ = Meta
Наследовать от object не обязательно, но если его нет, а
__metaclass__
есть, то результат все равно будет new-style, и в__bases__
будет object. Но лучше явно указать object, т.к. могут быть проблемы, например с наследованием.python 3
class Spam(Eggs, metaclass=Meta): pass
аттрибут
__metaclass__
просто игнорируется -
Метакласс сам не обязательно должен быть классом. Просто его вызов должен возвращать класс. Это может быть и функция:
def meta_func(class_name, bases, attr_dict): return type(class_name, bases, attr_dict) # python 2 class Spam(object): __metaclass__ = meta_func
-
У обычных классов тоже есть метод
__new__
. Но он не создает класс, он вызывается при создании инстанса класса (получает готовый класс в качестве аргумента). Он же и вызывает__init__
. -
Магические методы метакласса и класса:
class Meta(type): pass
при создании класса Class (
class Class(metaclass=Meta): ...
) вызываются методыMeta.__new__ Meta.__init__
при создании инстанса класса Class (
instance = Class(...)
) вызываются методы (внешний вызывает вложенный)Meta.__call__ calls Class.__new__ calls Class.__init__
при вызове инстанса класса Class (
instance()
) вызывается методClass.__call__
-
Метакласс можно не наследовать от type, а определить метод
__new__
. Но тогда методы__init__
,__call__
нашего метакласса не будут вызываться:class MySimpleMetaClass(object): def __new__(cls, *args, **kwargs): new_class = type.__new__(type, *args, **kwargs) return new_class def __init__(new_class, *args, **kwargs): print("__init__ won't be called...") def __call__(*args, **kwargs): print("__call__ won't be called...")
-
Метакласс класса вызывается и для всех потомков класса. Когда
__new__
метакласса вызывается для родительского класса, в bases будет(<type 'object'>,)
, а для дочернего класса - класс родителя. -
Аттрибуты метакласса наследуются классом, но не инстансами класса.
python 2 (в python 3 небольшие отличия в синтаксисе)
class MyMetaClass(type): attr = 2 def __new__(*args, **kwargs): return type.__new__(*args, **kwargs) def toast(*args, **kwargs): print(args, kwargs) class A(object): __metaclass__ = MyMetaClass
Метакласс входит в цепочку поиска аттрибутов класса
>>> A.toast() ((<class '__main__.A'>,), {})
Интересно, что метод от метакласса - bound, хотя вызывается от класса, не от инстанса. На самом деле класс - это инстанс метакласса:
>>> A.toast <bound method MyMetaClass.toast of <class '__main__.A'>>
Но метакласс не входит в цепочку поиска аттрибутов инстанса класса
>>> a = A() >>> a.toast() AttributeError: 'A' object has no attribute 'toast'
Если в каком-нибудь super классе объявлен аттрибут с тем же именем, что и в метаклассе, то он имеет преимущество (не важно насколько глубоко super)
class B(object): attr = 1 class C(B): __metaclass__ = MyMetaClass >>> C.attr 1 # MyMetaClass.attr = 2 is ignored
Аттрибуты инстанса ищутся в его
__dict__
, дальше в__dict__
‘ах__class__.__mro__
. Аттрибуты класса ищутся еще и в__class__.__mro__
, это другой класс, со стороны инстанса это будет__class__.__class__.__mro__
.>>> inst = C() >>> inst.__class__ -> <class '__main__.C'> >>> C.__bases__ -> (<class '__main__.B'>,) >>> C.__class__ -> <class '__main__.MyMetaClass'>
Инстансы наследуют аттрибуты от всех суперклассов. Классы - от суперклассов и метаклассов. Метаклассы - от супер-метаклассов (и вероятно от мета-метаклассов).
Data descriptor’ы (те, которые определяют
__set__
) вносят небольшие поправки в порядок поиска аттрибутов для инстанса. Для инстанса, data descriptor будут иметь преимущество в поиске, даже если они объявлены в супер классах:class DataDescriptor(object): def __get__(self, instance, owner): print("DataDescriptor.__get__") return 5 def __set__(self, instance, value): print("DataDescriptor.__set__", value) class B(object): attr = DataDescriptor() class C(B): pass >>> c = C() >>> c.__dict__['attr'] = 88 >>> c.attr DataDescriptor.__get__ 5 >>> c.attr = 8 ('DataDescriptor.__set__', 8)
Вызвался дескриптор, несмотря на то, что мы задали аттрибут с тем же именем в
c.__dict__
. Аттрибут не затер дескриптор суперкласса, сработал дескриптор. Такого поведения не будет с обычным дескриптором (nondata):class SimpleDescriptor(object): def __get__(self, instance, owner): print("SimpleDescriptor.__get__") return 5 class B(object): attr = SimpleDescriptor() class C(B): pass >>> c = C() >>> c.attr SimpleDescriptor.__get__ 5 >>> c.__dict__['attr'] = 88 >>> c.attr 88
Так же, для builtin операторов, которые вызывают магические методы, поиск особый. Он минует
instance.__dict__
, сразу идет поиск в__dict__
классов из__mro__
. -
магические методы, которые вызываются неявно путем использования builtin операторов для классов ищутся в метаклассе, минуя сами классы (сам класс и всего его суперклассы):
python 2 (в python 3 небольшие отличия в синтаксисе)
class MyMetaClass(type): def __new__(*args, **kwargs): return type.__new__(*args, **kwargs) def __str__(cls): return "__str__ from meta" class A(object): __metaclass__ = MyMetaClass def __str__(self): return "__str__ from class A"
Вызывается метод
__str__
метакласса, а не класса:>>> print A __str__ from meta
А тут вызывается метод
__str__
от object:>>> print MyMetaClass <class '__main__.MyMetaClass'>
-
Автор Марк Лутц немного беспокоится, что python становится слишком сложным и обрастает дублирующим функционалом, например:
str.format
и%
with
иtry/finally
Это противоречит
import this