Python программист
0,0
рейтинг
1 января 2014 в 21:10

Разработка → Python: вещи, которых вы могли не знать из песочницы

Python — красивый и местами загадочный язык. И даже зная его весьма неплохо, рано или поздно находишь для себя нечто такое, что раньше не использовал. Этот пост отражает некоторые детали языка, на которые многие не обращают внимание. Сразу скажу: многие примеры являются непрактичными, но, оттого, не менее интересными. Так же, многие примеры демонстрируют unpythonic стиль, но я и не претендую на новые стандарты — я просто хочу показать, что можно делать вот так.

1. Бесконечно вложенный список

>>> a = [1, 2, 3, 4]
>>> a.append(a)
>>> a
[1, 2, 3, 4, [...]]
>>> a[4]
[1, 2, 3, 4, [...]]
>>> a[4][4][4][4][4][4][4][4][4][4] == a
True

То же самое со словарями:
>>> a = {}
>>> b = {}
>>> a['b'] = b
>>> b['a'] = a
>>> print a
{'b': {'a': {...}}}


2. Форматирование списка

>>> l = [[1, 2, 3], [4, 5], [6], [7, 8, 9]]
>>> sum(l, [])
[1, 2, 3, 4, 5, 6, 7, 8, 9]

Генератором списков (спасибоmagic4x):
[y for x in data for y in x]

Альтернативные, но более длинные варианты (спасибо monolithed):
import itertools
data = [[1, 2, 3], [4, 5, 6]]
list(itertools.chain.from_iterable(data))

from functools import reduce
from operator import add
data = [[1, 2, 3], [4, 5, 6]]
reduce(add, data)


3. Генератор словарей

Многие знают про генератор списков, а как насчет генераторов словарей?
>>> {a:a**2 for a in range(1, 10)}
{1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}


4. Одноразовая функция в классе

На тот случай, если вам нужна функция, которая будет использоваться всего один раз, после чего будет использоваться другая функция:
class foo:
    def normal_call(self):
        print("normal_call")
    def call(self):
        print("first_call")
        self.call = self.normal_call

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


5. Получение аттрибутов класса

class GetAttr(object):
	def __getattribute__(self, name):
		f = lambda: "Hello {}".format(name)
		return f

>>> g = GetAttr()
>>> g.Mark()
'Hello Mark'


6. Операции над множествами

Set — множество, в котором отсутствуют повторяющиеся элементы:
>>> a = set([1,2,3,4])
>>> b = set([3,4,5,6])
>>> a | b # Объединение
{1, 2, 3, 4, 5, 6}
>>> a & b # Пересечение
{3, 4}
>>> a < b # Подмножества
False
>>> a - b # Разница
{1, 2}
>>> a ^ b # Симметрическая разность
{1, 2, 5, 6}

Без множества set эти операции работать не будут. Если только это не генератор множеств (спасибо ZyXI):
{ x for x in range(10)} # Генератор множеств

set([1, 2, 3]) == {1, 2, 3}
set((i*2 for i in range(10))) == {i*2 for i in range(10)}


7. Операторы сравнения

>>> x = 5
>>> 1 < x < 10
True
>>> 10 < x < 20 
False
>>> x < 10 < x*10 < 100
True
>>> 10 > x <= 9
True
>>> 5 == x > 4
True


8. Динамичное создание новых классов

>>> NewType = type("NewType", (object,), {"x": "hello"})
>>> n = NewType()
>>> n.x
'hello'

Этот же вариант в обычном виде:
>>> class NewType(object):
>>>     x = "hello"
>>> n = NewType()
>>> n.x
"hello"


9. Подавление исключения «KeyError» в словарях

В словарях существует метод .get(). В обычном случае, если вы вызываете несуществующий ключ name_dict['key'], вы получите исключение KeyError. Однако, если вызвать ключ через метод d.get('key'), то исключения не будет и, если ключа нет, то словарь возвратит None. Если вы хотите назначить переменную вместо отсутствующего ключа, то можно назначить второй параметр: d.get('key', 0).

Лучше всего это применять при переборе числовых ключей:
sum[value] = sum.get(value, 0) + 1


10. Добавление в список, находящийся в словаре

Если вам необходимо хранить несколько значений ключей, то вы можете хранить их в списке:

>>> d = {}
>>> a, b = 4, 5
>>> d[a] = list()
>>> d
{4: []}
>>> d[a].append(b)
>>> d
{4: [5]}


11. Назначение переменных по условию

x = 1 if (y == 10) else 2 # X равно 1, при условии, что Y равен 10. Если это не так - X равен 2
x = 3 if (y == 1) else 2 if (y == -1) else 1  # Более длинное условие. Не используйте здесь elif


12. Распаковка значений на переменные

Если при присваивании значений их окажется больше переменных — добавьте в начало имени переменной звездочку и ей будут присвоены остальные переменные (только Python 3.x):
>>> first,second,*rest = (1,2,3,4,5,6,7,8)
>>> first # Первое значение
1
>>> second # Второе значение
2
>>> rest # Все остальные значения
[3, 4, 5, 6, 7, 8]

>>> first,*rest,last = (1,2,3,4,5,6,7,8)
>>> first
1
>>> rest
[2, 3, 4, 5, 6, 7]
>>> last
8


13. Нумерация элементов списка

>>> l = ["spam", "ham", "eggs"]
>>> list(enumerate(l)) 
>>> [(0, "spam"), (1, "ham"), (2, "eggs")]
>>> list(enumerate(l, 1)) # Можно указать начало нумерации в качестве аргумента
>>> [(1, "spam"), (2, "ham"), (3, "eggs")]


14. Использование else в исключениях

Спасибо за исправление animeshneG
try: 
  function()
except Error:
  # Если не сработал try и объявлена ошибка Error
else:
  # Если сработал try и не сработал except
finally:
  # Выполняется в любом случае


15. Создание копии списка

При создании копии обычным методом произойдет следующее:
>>> x = [1, 2, 3]
>>> y = x
>>> y[2] = 5
>>> y
[1, 2, 5]
>>> x
[1, 2, 5]

Правильный вариант:
>>> x = [1,2,3]
>>> y = x[:]
>>> y.pop()
3
>>> y
[1, 2]
>>> x
[1, 2, 3]

Копирование вложенных списков\словарей (спасибо denisbalyko)
import copy
my_dict = {'a': [1, 2, 3], 'b': [4, 5, 6]} 
my_copy_dict = copy.deepcopy(my_dict)


16. Нумерация в for

Для нумерации элементов при выводе через цикл for используйте метод enumerate
>>> l = ['a', 'b', 'c', 'd', 'e']
>>> for i, value_list in enumerate(l, 1): # Аттрибут 1 - начало сортировки
>>>     print(i, value_list)
...     
1 a
2 b
3 c
4 d
5 e


17. Значения функции по умолчанию

При назначении функциям неправильные аргументы по умолчанию может получится нечто такое:
>>> def foo(x=[]):
...     x.append(1)
...     print x
... 
>>> foo()
[1]
>>> foo() 
[1, 1] # А должно быть [1]
>>> foo()
[1, 1, 1]


Вместо этого присваивайте аргументу значение None по умолчанию:
>>> def foo(x=None):
...     if x is None:
...         x = []
...     x.append(1)
...     print x
>>> foo()
[1]
>>> foo()
[1]


18. Метод __missing__ для словарей

Метод __missing__ позволяет избавить от исключения KeyError, возвращая вместо ошибки — имя запрошенного ключа.
class MyDict(dict): # Функция создания словаря
    def __missing__(self, key):
        return key
...
>>> m = MyDict(a=1, b=2, c=3) # Создание словаря через функцию
>>> m
{'a': 1, 'c': 3, 'b': 2}
>>> m['a'] # Ключ существует и вернет 1
1
>>> m['z'] # Ключа не существует и вернет имя запрошенног ключа
'z' 


19. Декораторы

Декораторы позволяют обернуть одну функцию другой, добавляя в нее функциональные возможности. Для обозначения декоратора на строку выше функции вы пишите знак "@«и имя функции. Пример:
>>> def print_args(function):
>>>     def wrapper(*args, **kwargs):
>>>         print 'Аргументы функции: ', args, kwargs
>>>         return function(*args, **kwargs)
>>>     return wrapper

>>> @print_args
>>> def write(text):
>>>     print text
>>> write('foo')
Arguments: ('foo',) {}
foo

Объяснить попроще может только документация:
@f1(arg)
@f2
def func(): pass

эквивалентно коду
def func(): pass
func = f1(arg)(f2(func))


20. Обмен значениями между переменными

>>> a = 10
>>> b = 5
>>> a, b
(10, 5)
>>> a, b = b, a
>>> a, b
(5, 10)


21. Функции первого класса

>>> def jim(phrase):
...   return 'Jim says, "%s".' % phrase
>>> def say_something(person, phrase):
...   print person(phrase)

>>> say_something(jim, 'hey guys') # Передаем второй функции первую
'Jim says, "hey guys".'


Функции высшего порядка (спасибо Andrey_Solomatin):
# Здесь рассматривается функция высшего порядка g(), которая в качестве первого аргумента принимает функцию. 
# В результате на экран будет выведено  "100" (результат вычисления (7+3)×(7+3)).
def f(x):
    return x + 3
 
def g(function, x):
    return function(x) * function(x)
 
print g(f, 7)


22. Отрицательный round

>>> round(1234.5678, -2)
1200.0
>>> round(1234.5678, 2)
1234.57


23. Дзен Python'a

import this


24. Использование стиля C т.е. {} вместо отступов

Это, возможно, зло, но если вам хочется использовать скобки {} вместо отступов, для обозначения областей:
from __future__ import braces


25. Шаг в срезе списка

a = [1,2,3,4,5]
>>> a[::2]  # Указываем шаг
[1,3,5]

Значение -1 равносильно методу reverse — переворачивает список:
>>> a[::-1] # Переворачиваем список
[5,4,3,2,1]


26. Открытие вкладки в браузере

Открывает вкладку с указанным адресом в браузере по умолчанию.
import webbrowser
webbrowser.open_new_tab('http://habrahabr.ru/') # Вернет True и откроет вкладку


27. zip. Объединение списков

a = [(1,2), (3,4), (5,6)]
zip(*a)
# [(1, 3, 5), (2, 4, 6)]

Слияние двух списков в словарь:
>>> t1 = (1, 2, 3)
>>> t2 = (4, 5, 6)
>>> dict (zip(t1,t2))
{1: 4, 2: 5, 3: 6}


28. Срезы в списках и работа с ними

>>> a = range(10)
>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> a[:5] = [42] # Все символы до 5 заменяются элементом "42"
>>> a
[42, 5, 6, 7, 8, 9]
>>> a[:1] = range(5) 
>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> del a[::2] # Удаление каждого второго элемента
>>> a
[1, 3, 5, 7, 9]
>>> a[::2] = a[::-2] # Альтернатива reserved
>>> a
[9, 3, 5, 7, 1]


29. Хранение нескольких элементов в ключе словаря

Если на ключ нужно назначить больше одного элемента, лучше хранить их в списке:
>>> m = {}
>>> m.setdefault('foo', []).append(1)
>>> m
{'foo': [1]}
>>> m.setdefault('foo', []).append(2)
>>> m
{'foo': [1, 2]}


30. Последнее и самое главное
Ради всего неизвестного, читайте ДОКУМЕНТАЦИЮ! Не собирайте очередной велосипед — возможно ваш код еще кому-то читать. Ни одна книга не сможет отразить полноту документации, а документация у python'а отличная.


UPD:
31. @Alex222 | комментарий
import antigravity


32. tanenn | комментарий
Использование else в цикле for:
>>> for n in range(2, 10):
...     for x in range(2, n):
...         if n % x == 0:
...             print n, 'equals', x, '*', n/x
...             break
...     else:
...         # loop fell through without finding a factor
...         print n, 'is a prime number'


33. monolithed | комментарий
Пишем первую программу — »Hello World!"
import __hello__


34. AndersonDunai | комментарий
OrderedDict или сортировка словарей:
>>> # Не сортированный словарь
>>> d = {'banana': 3, 'apple':4, 'pear': 1, 'orange': 2}
>>> # Словарь сортирован по ключ
>>> OrderedDict(sorted(d.items(), key=lambda t: t[0]))
OrderedDict([('apple', 4), ('banana', 3), ('orange', 2), ('pear', 1)])
>>> # Сортировка по значениям
>>> OrderedDict(sorted(d.items(), key=lambda t: t[1]))
OrderedDict([('pear', 1), ('orange', 2), ('banana', 3), ('apple', 4)])
>>> # Сортировка по длине названия ключа
>>> OrderedDict(sorted(d.items(), key=lambda t: len(t[0])))
OrderedDict([('pear', 1), ('apple', 4), ('orange', 2), ('banana', 3)])


35. AndersonDunai | комментарий
Изменение класса объекта на лету изнутри класса через self.__class__ (подробнее)
Показательный пример (спасибо dmitriko):
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()


36. skovorodkin | комментарий
Подсчет количества элементов (в примере — количество букв в слове):
from collections import Counter
Counter('habrahabr')  # Counter({'a': 3, 'h': 2, 'r': 2, 'b': 2})


37. assert
Assert — это специальная конструкция, позволяющая проверять предположения о значениях произвольных данных в произвольном месте программы. Данная конструкция используется в целях тестирования/отладки кода, например Вы можете написать такую инструкцию:
assert x>y 

и если данная инструкция вернет false будет возбуждено исключение AssertationError. По сути, данная инструкция
assert <test>, <data>

это краткий эквивалент такой:
 if __debug__:
              if not <test>:
                  raise AssertationError, <data> 



Сохранить страницу с комментариями в pdf
Если у вас Google Chrome — просто нажмите Печать -> Принтер «Сохранить в PDF». Насчет подобной фичи в других браузерах — не знаю.

Если у вас не Chrome
doPDF обрезает меню habr'ы, а комментарии оставляет. Страница Хабры сделана так, чтобы отправляться на печать без меню, так что подойдет любой виртуальный принтер. В итоге — имеем читабельный PDF файл. Очень удобно. Скачиваете, устанавливаете вместо принтера и отправляете на печать. (потом еще спасибо скажете).
Евгений @JRazor
карма
17,0
рейтинг 0,0
Python программист
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (120)

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


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

      Если разработчик не видит, где у него заканчивается блок, то либо у него не настроено IDE разработки, либо он пишет бессистемный код, который и без того не читаем.
      • 0
        Понял, спасибо!
  • НЛО прилетело и опубликовало эту надпись здесь
    • +10
      Ну да, как я мог забыть!
      image
  • НЛО прилетело и опубликовало эту надпись здесь
  • +3
    14. Блок else вызывается если блок try отработал и не вызвал ни одного исключения
    • НЛО прилетело и опубликовало эту надпись здесь
      • +4
        Если уж на то пошло, у while тоже есть else (тоже самое, если в теле цикла не был вызван break). Штука вроде удобная иногда, но выглядит ужасно, особенно когда используется пару раз во вложенных циклах
    • 0
      А зачем тогда его выносить из блока try?
      • НЛО прилетело и опубликовало эту надпись здесь
        • 0
          Обязательно надо помнить, что finally отрабатывает всегда: результат функции или значение переменных может быть несколько иным :-)

          Уже было))

  • +3
    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-й
  • +14
    Что это? 30 советов для чайников?
    Set — это метод превращения списка в контейнер с неповторяющимися элементами

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

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

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

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

        Функция вернет «4»
    • 0
      Ну вообще-то в данном случае — это метод, конструктор класса 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))

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

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

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

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

              >>> [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.
              • 0
                Ну, тут уже дело в том, что в python понятие генератора изначально использовалось так. Вообще, это конструктор списка, что не вполне отражает его назначение, и посему было использовано понятие генератора списков. Возможно в соотношение с типами объектов в python это неверно, но на русском куда больше отражает суть выполняемых действий.
                • +1
                  Вообще на английском генераторы списков/словарей/множеств называются «{type} comprehension», а такие генераторы — «generator expression». Соответственно такой путаницы нет. Но переводчики терминов побоялись придумать перевод comprehension, отличный от «генератора».

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

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

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

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

    import itertools
    
    data = [[1, 2, 3], [4, 5, 6]]
    list(itertools.chain.from_iterable(data))
    
    • 0
      Еще на вскидку:

      from functools import reduce
      from operator import add
      
      data = [[1, 2, 3], [4, 5, 6]]
      reduce(add, data)
      
    • 0
      обычно так делаю:

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

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

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

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

      • НЛО прилетело и опубликовало эту надпись здесь
        • +1
          Ок. Я к тому, что когда явно используешь какой-то декоратор из mock библиотеки — то ты ожидаешь какое-то поведение. А вот это:

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


          ну просто жестяк какой-то. Я все веду к одному — поменьше магии, потому что этот код еще кому-то читать.
          • НЛО прилетело и опубликовало эту надпись здесь
          • +2
            В целом и вправду адово. Я бы попробовал написать одноразовый декоратор.

            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!
      • 0
        Тут кстати всплывает интересный факт — такое переопределение оставляет функционал 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
        
  • +2
    Интересная статья, узнал для себя пару новые вещей.
    От себя добавил бы еще про OrderedDict (сортированном dict), о котором узнал относительно недавно и который местами очень сильно помогает (например, при формировании REST-запросов, где важен порядок аргументов) и, кроме того, полностью совместим с dict.
    • +3
      Другой интересный приём — изменение класса объекта на лету изнутри класса через self.__class__. Это немного нарушает предсказуемость кода, но даёт большую гибкость (например, при работе с сетевыми пакетами в протоколах пользовательского уровня, тип которых заранее неизвестен).
  • +2
    Битовое отрицание на службе добра:

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

    • НЛО прилетело и опубликовало эту надпись здесь
      • +1
        Семантически между 0 и ~0 вполне себе есть связь: ~ осуществляет операцию ones' complement, которая в питоне переходит от начала списка к его концу. Не только a[~0] даст вам последний элемент, но и a[~10] даст вам десятый элемент (если в a их столько есть, конечно). Более того, даже в вашем безумном примере a[~True] срабатывает ровно потому, что True == 1!
        • НЛО прилетело и опубликовало эту надпись здесь
          • +2
            Нет. 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] выглядит куда как проще и логичнее, чем ситуация когда у вас элементы нумеруются с нуля при заходе «слева», но почему-то с единицы при заходе «справа».
            • НЛО прилетело и опубликовало эту надпись здесь
          • +4
            Можно просто написать -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 все, кто видели такой код сразу догадывались, что в нем происходит сначала на уровне интуиции, а потом проверяли в консоли. Я уверен, что однажды узнав такой подход — его уже не забыть никогда.

            Но возможно я заблуждаюсь. Поэтому я никого не призываю так делать. У вас своя голова для этого есть, ведь правда? ;)
      • +3
        >>> 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]
        
  • +3
    «Генератор словарей» или dict comprehensions доступны с версии 2.7 вроде.

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

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

    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()
    
    


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

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

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

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

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

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

      Если честно, collections и itertools — это темы отдельных постов. Это два ужасно интересных модуля и в одну строку их не описать.
      • +1
        К обмену переменных и распаковке в цикле я бы добавил такие примеры:
        # 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
          А что необычного в ваших примерах?

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

          2 пример — объединение двух примеров из списка. Если объединять все примеры друг с другом, то скроллер превратится в точку.
  • 0
    Раз началась вакханалия, то я оставлю ссылку на BytePlay — модуль, который умеет изменять результат генерации bytecode'а, т.е. теоретически у вас будет возможность заменить True на round(False + random.random()) или что-нибудь подобное
  • 0
    Кстати спасибо за рекурсивные списки и словари, никогда не пробовал реализовать подобные штуки, но тем не менее. Отмечу правда, что подобная конструкция создаёт ещё и рекурсивные ссылки между объектами — будь осторожней при чистке памяти, боюсь стандартные gc Python'а не будет очень счастлив ;)
  • +1
    Автору спасибо за статью, радуют комментарии и дискуссия.
  • 0
    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
    
  • –1
    ...     if x is None:
    ...         x = []
    


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

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

      Но не уверен, сработает ли моё разыменование с генератором вместо списка.
      • 0
        Ноль в диапозоне можно опустить.

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

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

    >>> 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)]
  • 0
    return True if x>y else False

    тоже работает :)
    • 0
      return x > y

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.