Pull to refresh

Comments 120

UFO just landed and posted this here
UFO just landed and posted this here
Вполне устоявшееся выражение, также как и функции первого/высшего порядка
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
Лично мне вообще нравится перевод first-class object как «полноценный объект». Аккуратно передаёт смысл и не создаёт ненужных коннотаций: «А что тогда второй класс? А третий?»
set — это хорошо, только это не метод, а тип данных. См. также frozenset.
Спасибо, почерпнул пару интересных пунктов.
P.S.: 23-й должен быть первым :)
Спасибо огромное, как раз позавчера начал учить Питон. Все пункты полезны и интересны, особенно для меня — непрофессионального и начинающего программиста :)
Как я уже подчеркнул выше, некоторые пункты являют собой unpythonic стиль, поэтому я бы брал их на заметку только после прочтения мануала по стилю написания кода Python — PEP 8.
Дважды спасибо! Буду отталкиваться от PEP8
Обязательно пройдите мимо этой статьи, и почитайте про концепции в питоне. Тогда большинство пунктов станут более чем очевидными. Эта статья больше вред чем полезность если Вы языком не владеете.
по поводу фигурных скобок вместо отступов еще вот такой вот финт ушами углядел у кого-то в англоязычных блогах:
if True #{
    print "true"
#}
else: #{
    print "false!"
#}


Выглядит грязно, но первое время очень удобно, особенно после php :)
Я описал, что скобки вместо отступов — возможноe зло и ваш код становится нечитабельным (снова возвращаемся к PEP 8). По крайней мере, я не видел такой практики. Обычно так делать нельзя, но если очень хочется, то можно.
А вы сами-то пробовали import braces? Судя по высказыванию, нет :)
Вы правы — нет, не пробовал. Не было необходимости. Но я говорил не об этом. В python главное — простота, а не маты программиста-питониста, разбирающего ваш код. )
не знаю, мне после php было неплохо и без фигурных скобок…
За #{ #} я бы поставил разработчик pep8 check в git commit hook (у меня например стоит pep8 в dh_lintian), а потом возвращал с code review до момента, пока до него не дойдёт, что подобная конструкция есть мусор в Python'е, который лишь усложняет чтение кода, ни в коем разе не помогая ни структуре кода, ни видимости отдельных блоков.

Если разработчик не видит, где у него заканчивается блок, то либо у него не настроено IDE разработки, либо он пишет бессистемный код, который и без того не читаем.
UFO just landed and posted this here
Ну да, как я мог забыть!
image
UFO just landed and posted this here
14. Блок else вызывается если блок try отработал и не вызвал ни одного исключения
UFO just landed and posted this here
Если уж на то пошло, у while тоже есть else (тоже самое, если в теле цикла не был вызван break). Штука вроде удобная иногда, но выглядит ужасно, особенно когда используется пару раз во вложенных циклах
А зачем тогда его выносить из блока try?
UFO just landed and posted this here
Обязательно надо помнить, что finally отрабатывает всегда: результат функции или значение переменных может быть несколько иным :-)

Уже было))

10. Чтобы не писать дикую конструкцию d.setdefault(t, list()).append(a), можно использовать контейнер defaultdict из модуля collections:
import collections
d = collections.defaultdict(list)
d[t].append(a)
d[t].append(b)


29-й пункт дублирует 10-й
Что это? 30 советов для чайников?
Set — это метод превращения списка в контейнер с неповторяющимися элементами

Это не метод, это тип данных, по-русски называемый множество. Спутать метод и тип это верх невежества.
Еще понравилось про else в исключениях. Почему было не рассказать еще и про finally? И про реально неявную для новичков фишку, что если мы в блоке except вызвали return, то блок finally все равно будет выполнен до выхода из функции?
Простите, я в заголовке указал, что тут 30 советов для гуру-питонистов? Это список, которые многие не знают. Кому-то это будет интересно, а тем кому не интересно — я ничего не навязывал.

Про set формулировка неверная, вы правы.

Насчет finally. Не рассказал хотя бы потому, что блок finally выполняется в любом случае и если человек хоть мало-мальски читал про исключения — он это знает. Что об этом рассказывать? А вот else — это нечто неопределенное. Он выполняется только тогда, когда не было исключения и на первый взгляд непонятно, как он работает.
А что будет, если в finally тоже будет return?).
Вернется последнее значение:

def text():
    try:
        return 1
    except ValueError:
        return 2
    else:
        return 3
    finally:
        return 4

Функция вернет «4»
Ну вообще-то в данном случае — это метод, конструктор класса set, который создает экземпляр класса структуры данных.

Не понравился перевод «dict comprehension» как «генератор словарей». Будет путаница с настоящими generators и generator expressions. Дословно «генератор словарей» будет примерно так выглядеть:

dict_generator = lambda n: [(yield {x: x*x}) for x in xrange(n)]

и чтобы получить словарь, как в тексте поста:

reduce(lambda x, y: dict(x, **y), dict_generator(10))

ОО, привет Александр!
set — именно тип данных, set() — метод-конструктор. В пункте налицо непонимание того, что такое тип, и что такое метод. Это важно, черт возьми. Про генераторы ты вроде как верно заметил.
Насчет генератора словарей — это наиболее близкое определение. К тому же, оно было на многих ресурсах как генератор словарей. Согласитесь, очень похоже:

Генератор списков:
list = [x for x in range(1, 10)]

Генератор словарей:
dict = {a:a**2 for a in range(1, 10)}

Чтобы получить генератор списка, скобочки должны быть круглые. То, что у вас — это уже готовый список, целиком хранящийся в памяти.
В оригинале эти штуки называются list comprehension и dict comprehension. Русская Википедия переводит первый термин как «списковое включение».
Списки используют квадратные скобки "[ ]", кортежи — круглые "( )", а множества и словари — образные скобки "{ }"
Поясню, что я имел в виду:

>>> [x for x in range(10)]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> (x for x in range(10))
<generator object <genexpr> at 0x403b46c0>
>>> list((x for x in range(10)))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]


Во втором случае мы получаем генератор (iterable), а не tuple.
Ну, тут уже дело в том, что в python понятие генератора изначально использовалось так. Вообще, это конструктор списка, что не вполне отражает его назначение, и посему было использовано понятие генератора списков. Возможно в соотношение с типами объектов в python это неверно, но на русском куда больше отражает суть выполняемых действий.
Вообще на английском генераторы списков/словарей/множеств называются «{type} comprehension», а такие генераторы — «generator expression». Соответственно такой путаницы нет. Но переводчики терминов побоялись придумать перевод comprehension, отличный от «генератора».

Конструкторам я бы это не называл, так как конструктор — это часть класса, а генератор типа — синтаксический сахар, даже не упомянутый в Objects/setobject.c, где содержится определение множества.
UFO just landed and posted this here
Скобки в условиях — это очень некрасиво. Не для того питон дизайнили что бы его так портить.
А вообще ведь все написанное и многое другое есть в любом неплохом учебнике\туториале. Куда лучше его прочитать, потому что большинство «пунктов» это следствия концептуальности питона. К ним можно придти самому, и большинство просто очевидны если понимать как работает язык.
к 15:
Если списки/словари вложены, то копировать можно через deepcopy:

import copy
my_dict = {'a': [1, 2, 3], 'b': [4, 5, 6]} 
my_copy_dict = copy.deepcopy(my_dict)
UFO just landed and posted this here
Python — красивый язык, и своих слов я обратно не возьму. Здесь дело не в том, какой язык красивее питона. Читать стандартные библиотеки можно вместо книг. Возможно, PHP и красивее в плане написания крупных проектов, но только вот это не делает python для меня более уродливым.
Я всегда любил PHP и с Python на данный момент имею вдвое меньше опыта, нежели с первым, но данный язык — действительно больше, чем просто язык. В то время, как PHP — не больше, чем интерпретатор для достижения некой цели, Python — целая философия (которая, не спорю, позволяет наделать такого, что глаза ревьюэра через уши вытекут). А главное — он форсит программистов по крайней мере писать читабельный вложенный код. Я ни разу не сталкивался с невозможностью реализации чего-то в нем.

Впрочем, к чему сравнивать пулемёт и танк?
UFO just landed and posted this here
Этот комментарий доказывает что красота и читабельность кода — субъективные понятия.
О господи. Правила написания читаемого кода для большинства языков одинаковы. Базовые вещи изложены в книге Боба Мартина «Чистый код». Много достойных советов по написанию более вменяемого кода, поговаривают есть и сомнительные. Конечно при чтении нужно адаптировать эту книгу к своему языку, но это все происходит в голове само. А самое главное для производства читаемого кода, это постоянные ревью команды.
У меня 8+ лет опыта в PHP и 4+ года на Python'е. Я скажу, что Python в разы системней PHP, все изменения что происходят от версии к версии проходят несколько этапов обсуждения и вливаются в язык так, как будто они там были всегда — не нарушая и не «подсахаривая» текущий вариант синтаксиса. Посмотрите на этапы развития PHP: str_*, array_* бардак в сравнении с Java-style интерфейсами типа DirectoryIterator (плюс рекурсивные обёртки). Мне жаль этот язык, очень сильно жаль. Его пытаются вытянуть из той задницы, в которой он погряз, когда был в 3ей и 4ой своей редакции. Те версии дали отличный толчок (который к сожалению можно назвать и пинком под зад), но в итоге создатели и группа поддержки, пытается вывести нестыковки, которые тут и там начинают свисать, если что-то где-то изменить. Я рад, что случился Composer — это хороший шаг к улучшению и стандартизации кода на PHP. Я рад, что он постепенно меняет свою парадигму на ОО с рубленного топором Ф. Но он всё ещё не способен делать тот объём прикладных задач, на которые способен Python. И речь идёт не только о web-программировании.
>>> sum(l, [])

Более эффективный способ:

import itertools

data = [[1, 2, 3], [4, 5, 6]]
list(itertools.chain.from_iterable(data))
Да чего уж там:
[y for x in data for y in x]
обычно так делаю:

import itertools

data = [[1, 2, 3], [4, 5, 6]]
list(itertools.chain(*data))
Кстати, №4, который назвали «одноразовая функция в классе», на самом деле, можно использовать гораздо шире. Например, для юнит-тестов можно заменять функции, которые, например, отправляют e-mail, пустышками. А еще, таким образом можно отлаживать скомпилированные python-приложения (и не только отлаживать, хы-хы)

Называется такой способ Monkey Patching. Применяется в некоторых библиотеках тоже, например, в gevent.
Для юнит-тестов и пустышек есть mock-объекты и они реализованы в прекрасной библиотеке mock, которая входит в коробку c Python 3.3.
А вообще №4 — ужасный подход/паттерн/трюк. Удачного вам дебаггинга))

Если вы так любите Monkey Patching, можете еще в Python 2 вот такое поделать:

>>> True, False = False, True
>>> True
False
>>> False
True

UFO just landed and posted this here
Ок. Я к тому, что когда явно используешь какой-то декоратор из mock библиотеки — то ты ожидаешь какое-то поведение. А вот это:

>>> y = foo()
>>> y.call()
first_call
>>> y.call()
normal_call
>>> y.call()
normal_call


ну просто жестяк какой-то. Я все веду к одному — поменьше магии, потому что этот код еще кому-то читать.
UFO just landed and posted this here
В целом и вправду адово. Я бы попробовал написать одноразовый декоратор.

def meow(*args, **kwargs):
    print 'Meow?'
    

def once(predictor):
    def wrapper(function):
        def decorator(*args, **kwargs):
            if not getattr(function, '__predictor_called', False):
                predictor(*args, **kwargs)
            function.__predictor_called = True
            return function(*args, **kwargs)
        
        return decorator
    return wrapper


@once(meow)
def woof():
    print 'Woof!'


for _ in range(3):
    woof()


Результат:
Meow?
Woof!
Woof!
Woof!
Тут кстати всплывает интересный факт — такое переопределение оставляет функционал Python'а работать как надо (истинные True и False всё ещё существуют), но нельзя сравнивать с переопределёнными булевыми константами:

In [1]: True, False = False, True

In [2]: a = 1

In [3]: b = 1

In [4]: a == b
Out[4]: True

In [5]: c = a == b

In [6]: c is True
Out[6]: False
Интересная статья, узнал для себя пару новые вещей.
От себя добавил бы еще про OrderedDict (сортированном dict), о котором узнал относительно недавно и который местами очень сильно помогает (например, при формировании REST-запросов, где важен порядок аргументов) и, кроме того, полностью совместим с dict.
Другой интересный приём — изменение класса объекта на лету изнутри класса через self.__class__. Это немного нарушает предсказуемость кода, но даёт большую гибкость (например, при работе с сетевыми пакетами в протоколах пользовательского уровня, тип которых заранее неизвестен).
Битовое отрицание на службе добра:

>>> a = [1, 2, 3, 4, 5]
>>> a[0]
1
>>> a[~0]  # эквивалентно a[-1]
5

UFO just landed and posted this here
Семантически между 0 и ~0 вполне себе есть связь: ~ осуществляет операцию ones' complement, которая в питоне переходит от начала списка к его концу. Не только a[~0] даст вам последний элемент, но и a[~10] даст вам десятый элемент (если в a их столько есть, конечно). Более того, даже в вашем безумном примере a[~True] срабатывает ровно потому, что True == 1!
UFO just landed and posted this here
Нет. a[~10] даст мне 11 элемент с конца списка.
Ой ли?

И это отличный пример того, что лучше так не делать; приходится считать, чему равен обратный код от 10.
То есть при простом обращении вас не напрягает, что a[10] даёт не 10й элемент, а 11й, а при обращении a[~10] эта же путаница вам аж спать не даёт?

Да, есть проблема с тем, что многие языки программирования, в том числе и python нумеруют элементы с нуля, а человек часто нумерует их с единицы, ну и что? Она есть и без всяких ~ и/или - …

Мне проще считать, что a[10] даёт мне десятый элемент с начала, a[~10] — десятый с конца, вы можете называть a[10] одиннадцатым элементом с начала, а a[~10] — одиннадцатым с конца, но сути это не меняет: операция ~ «перебрасывает» вас из начала в конец списка и обратно, операция - не только «перебрасывает» вас из начала в конец, но ещё и сдвигает нумерацию на единицу. Честное слово — мне проще пользоваться операцией, которая делает одно действие, а не два.

Чего ради? Слишком много лишних знаний для трех символов кода. Можно просто написать -11.
Вот прямо даже так? Написать при «отражении» алгоритма a[-11] вместо a[0] или a[-N-1] вместо a[N] вам «проще и понятнее»?

По-моему вы просто в плену официальной документации и не хотите думать. По мне обращение к первому элементу списка через a[0], к последнему через a[~0], к десятому (для вас — к одиннадцатому) с начала через a[10], к десятом (для вас — к одиннадцатому) с конца через a[~10] выглядит куда как проще и логичнее, чем ситуация когда у вас элементы нумеруются с нуля при заходе «слева», но почему-то с единицы при заходе «справа».
UFO just landed and posted this here
Можно просто написать -11


Вся проблема в том, что элементам a[0], a[1], a[3] и a[6] соответствуют элементы a[-1], a[-2], a[-4] и a[-7].
Пока вы знаете их точные позиции — все идет нормально. Но стоит перейти к переменным и мы получаем вот такой код:
a[i], a[j], a[k], a[m]

и соответствующие им c конца
a[-i - 1], a[-j - 1], a[-k - 1], a[-m - 1]


Мой подход кажется чуть менее явном только потому, что битовая арифметика не так популярна, как традиционная.
Зато код выглядит чище:
a[~i], a[~j], a[~k], a[~m]


BTW все, кто видели такой код сразу догадывались, что в нем происходит сначала на уровне интуиции, а потом проверяли в консоли. Я уверен, что однажды узнав такой подход — его уже не забыть никогда.

Но возможно я заблуждаюсь. Поэтому я никого не призываю так делать. У вас своя голова для этого есть, ведь правда? ;)
>>> a = [1, 2, 3, 4, 5]

>>> [a[i] for i in range(5)]
[1, 2, 3, 4, 5]

>>> [a[~i] for i in range(5)]
[5, 4, 3, 2, 1]

>>> [a[-i - 1] for i in range(5)]
[5, 4, 3, 2, 1]

>>> [a[-(i + 1)] for i in range(5)]
[5, 4, 3, 2, 1]
«Генератор словарей» или dict comprehensions доступны с версии 2.7 вроде.

Важно понимать, что без метода set эти операции работать не будут.

set — это всетаки не метод, а конструктор типа, т.ч. не совсем ясна формулировка.
from collections import Counter
Counter('habrahabr')  # Counter({'a': 3, 'h': 2, 'r': 2, 'b': 2})
Habrahabr. Какой показательный пример. )
а вот как вам такое? :)

class Worm:
    def creep(self):
	print("i am creeping")


class Butterfly:
    def fly(self):
	print("i am flying")


creature = Worm()
creature.creep()
creature.__class__ = Butterfly
creature.fly()



Говорил об этом несколькими комментами выше, но ваш пример красивее :)
Предлагаю ещё написать пример, где кто-нибудь изменит __mro__ класса. Советую подобным не увлекаться, люди, что будут поддерживать ваш код, всё же будут ожидать увидеть червяка, нежели бабочку. IDE впрочем тоже.

Для подобных случаев настоятельно советую использовать обёртки типа, creature.act_as(Butterfly), которые будут возвращать инстанцию нового объекта, без увечия существующего.
Э, ребята, давайте понимать, переопределять __class__ это не более чем шутка.
Нет проблем, но в каждой шутке есть доля… сами понимаете. В этом сила Python'а в руках специалиста и его слабость в руках деструктивного маньяка с топором.
А вот мне интересно, чисто в порядке дискуссии, какой такой язык «в руках деструктивного маньяка с топором» будет выглядеть хорошо? Ну или хотя бы лучше чем Python.
Java достаточно успешно сопротивляется (почему её и используют всякие Ынтырпрайзы, нанимающие толпы индусов и сажающие их писать программы в стиле «вавилонская баншня»). В последних версиях, впрочем, появилось уже достаточно «шнурков» для того, чтобы на них можно было повеситься.
Очень хаотичное распределение.

пункт 16(enumerate + распаковка в for) и 13(enumerate(iterable)) очень близки по смыслу, да и 12й(unpack to variable) рядом.

19 Декораторы способ принять функцию в виде аргумента и вернуть функцию
21 А вы знаете что в питоне есть функции высшего порядка, которые позволяют принимать функцию в виде аргумента?

8(создание класса через type) Еще collections.namedtuple, который очень часто в стандартных библиотеках используется для создания нового класса.
Близкое по смыслу != одно и то же.

Да, знаю про функции высшего порядка. Удобная вещь, вот только забыл про нее. Сейчас опубликую.

Если честно, collections и itertools — это темы отдельных постов. Это два ужасно интересных модуля и в одну строку их не описать.
К обмену переменных и распаковке в цикле я бы добавил такие примеры:
# 1.
a, (b, c) = c, (a, b)

# 2.
for x, (y, z) in enumerate(zip('ab', 'cd')):
    print(x, y, z)  # python 3

>>> 0 a c
>>> 1 b d

А что необычного в ваших примерах?

1 пример — обмен проигнорирует скобки:
>>> a, b, c = 1, 2, 3
>>> a, (b, c) = c, (a, b)
>>> a, b, c
(3, 1, 2)

2 пример — объединение двух примеров из списка. Если объединять все примеры друг с другом, то скроллер превратится в точку.
Раз началась вакханалия, то я оставлю ссылку на BytePlay — модуль, который умеет изменять результат генерации bytecode'а, т.е. теоретически у вас будет возможность заменить True на round(False + random.random()) или что-нибудь подобное
Кстати спасибо за рекурсивные списки и словари, никогда не пробовал реализовать подобные штуки, но тем не менее. Отмечу правда, что подобная конструкция создаёт ещё и рекурсивные ссылки между объектами — будь осторожней при чистке памяти, боюсь стандартные gc Python'а не будет очень счастлив ;)
Автору спасибо за статью, радуют комментарии и дискуссия.
def dict_copy(d):
    """
    much, much faster than deepcopy, for a dict of the simple python types.
    """
    out = d.copy()
    for k, v in d.iteritems():
        if isinstance(v, dict):
            out[k] = dict_copy(v)
        elif isinstance(v, list):
            out[k] = v[:]
    return out
...     if x is None:
...         x = []


не лучше ли:
...     x = x or []

?
Ваша версия валит в одну кучу None, False и даже 0. Хотя это может быть не очень важно в конкретном случае, конечно, но заставляет для понимания достаточно простой, теоретически, конструкции привлекать разнообразное знание из других частей программы.
Ещё пустую коллекцию (список, множество, словарь, …), а также любой объект, для которого object.__nonzero__() (object.__bool__() для Python 3) возвращает False либо (если __nonzero__/__bool__ не определён) object.__len__() возвращает 0. В общем случае может оказаться, что для вычисления истинности нужны сложные (и длительные) вычисления, хотя это и маловероятно. Выражение x is None же — просто сравнение двух целых чисел (адресов объектов в памяти) на равенство.
ну в данном случае и False и 0 завалятся с AttributeError на x.append(), так-что пускай уж лучше становится списком.
Мы о программировании на python вроде говорим? Подход «лучше уж выдать чушь, чем сообщение об ошибке» — это, скорее, подход PHP.
Этот вариант, кстати, активно использовался в python до версии 2.5, когда появился тернарный if else оператор…
Интересно, а почему вы указали генераторы словарей и списков, но не указали то же самое для множеств? Т.е.
set([1, 2, 3]) == {1, 2, 3}
set((i*2 for i in range(10))) == {i*2 for i in range(10)}
Все три (set literals, set comprehension, dict comprehension) возможности появились в версиях 2.7 и 3.0.
Можно добавить условия в генераторах списков / словарей:
>>> [i for i in xrange(10) if i % 2]
[1, 3, 5, 7, 9]
Условия — достаточно известная вещь. Смысл поста в том, чтобы отразить некоторые малоизвестные моменты. Нет смысла переписывать документацию в один пост.
==
[1,2,3,4,5,6,7,8,9][0::2]

Но не уверен, сработает ли моё разыменование с генератором вместо списка.
Читайте Марка Лутца. Будет Вам откровение.
И это «вещи, которые вы могли не знать»? ОМГ, куда катится мир. А чего бы доку-то разок не прочесть? Там всё это есть.
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
xrange устарел с 3 версии, range нынче торт.
xrange не устарел, просто его переименовали. Удалён был именно range. Для бо́льшей переносимости лучше просто всегда писать range. А для скорости — добавлять в начале модуля

try:
    from __builtin__ import xrange as range
except ImportError:
    pass
Транспонирование метрицы:

>>> matrix = [[1,2,3],[4,5,6],[7,8,9],[10,11,12]]
>>> matrix
[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]
>>> trans = list(zip(*matrix))
>>> trans
[(1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)]
return True if x>y else False

тоже работает :)
Sign up to leave a comment.

Articles

Change theme settings