1 марта 2011 в 23:05

Pythonic

Итак, что же это значит, когда кто-либо говорит, что foo выглядит как pythonic? Что значит, когда кто-либо смотрит в наш код и говорит, что он unpythonic? Давайте попробуем разобраться.

В Python-сообществе существует неологизм pythonic, который можно трактовать по разному, но в общем случае он характеризует стиль кода. Поэтому утверждение, что какой-либо код является pythonic, равносильно утверждению, что он написан в соответствии с идиома Python’a. Аналогично, такое утверждение в отношении интерфейса, или какой-либо функциональности, означает, что он (она) согласуется с идиомами Python’a и хорошо вписывается в экосистему.

Напротив, метка unpythonic означает, что код представляет собой грубую попытку записать код какого-либо другого языка программирования в синтаксисе Python, а не идиоматическую трансформацию.

Понятие Pythonicity плотно связано с минималистической концепцией Python’a и уходом от принципа «существует много способов сделать это». Нечитабельный код, или непонятные идиомы – все это unpythonic.

При переходе от одного языка к другому, некоторые вещи должны быть «разучены». Что мы знаем из других языков программирования, что не будет к месту в Python’e?


Используйте стандартную библиотеку


Стандартная библиотека – наш друг. Давайте использовать ее.


>>> foo = "/home/sa"
>>> baz = "somefile"
>>> foo + "/" + baz                    # unpythonic
'/home/sa/somefile'
>>> import os.path
>>> os.path.join(foo, baz)             # pythonic
'/home/sa/somefile'
>>>

Другие полезные функции в os.path: basename(), dirname() и splitext().


>>> somefoo = list(range(9))
>>> somefoo
[0, 1, 2, 3, 4, 5, 6, 7, 8]
>>> import random
>>> random.shuffle(somefoo)                     # pythonic
>>> somefoo
[8, 4, 5, 0, 7, 2, 6, 3, 1]
>>> max(somefoo)                                # pythonic
8
>>> min(somefoo)                                # pythonic
0
>>>

Существует много полезных встроенных функций, о которых многие люди по каким-либо причинам не знают. Например, min() и max(). Стандартная библиотека включает много полезных модулей. Например, random, который содержит кучу функционала, который люди по незнанию реализую самостоятельно.

Создание пустых списков, кортежей, словарей и т.д.




>>> bar = list()                        # unpythonic
>>> type(bar)
<class 'list'>
>>> del bar
>>> bar = []                            # pythonic
>>> type(bar)
<class 'list'>
>>> foo = {}
>>> type(foo)
<class 'dict'>
>>> baz= set()                          # {} is a dictionary so we need to use set()
>>> type(baz)
<class 'set'>
>>>


Использование обратной косой черты


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


if foo.bar()['first'][0] == baz.ham(1, 2)[5:9] and \            # unpythonic
   verify(34, 20) != skip(500, 360):
      pass


Использование «\» не является хорошей идеей. Такой подход может вызвать неприятный баг: случайный пробел после косой черты сделает строку неправильной. В лучшем случае мы получим syntax error, но если код предствляет что-то вроде этого:


value = foo.bar()['first'][0]*baz.ham(1, 2)[5:9] \              # unpythonic
        + verify(34, 20)*skip(500, 360)


Тогда он будет просто нерабочим. Лучше использовать неявное продолжение строки в скобках. Подобный код является пуленепробиваемым:


value = (foo.bar()['first'][0]*baz.ham(1, 2)[5:9]               # pythonic
        + verify(34, 20)*skip(500, 360))


Import


Не используйте «from foo import *». Здесь и здесь можно найти более подробную информацию.

Общие исключения


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


try:
    foo = opne("somefile")                              # misspelled "open"
except:
    sys.exit("could not open file!")

Вторая строчка генерирует «NameError», который будет отловлен, что семантически неверно, поскольку «except» написан для отлавливания «IOError». Лучше написать такой код:


try:
    foo = opne("somefile")
except IOError:
    sys.exit("could not open file")

Когда Вы запустите этот код, Python сгенерирует «NameError», и Вы моментально увидите и исправите ошибку.

Поскольку «except» отлавливает все исключения, включая «SystemExit», «KeyboardInterrupt», и «GeneratorExit» (которые по сути не являются ошибками и не должны отлавливаться пользовательским кодом), использование голого «except» в любом случае плохая идея. В ситуациях, когда нам нужно все-таки покрыть все возможные исключительные ситуации, мы можем использовать базовый класс для всех исключений – «Exception».

Нам редко нужны счетчики




>>> counter = 0                         # unpythonic
>>> while counter < 10:
...     # do some stuff
...     counter += 1
...
...
>>> counter
10
>>> for counter in range(10):           # pythonic
...     # do some stuff
...     pass
...
...
>>>

Другой пример:


>>> food = ['donkey', 'orange', 'fish']
>>> for i in range(len(food)):          # unpythonic
...     print(food[i])
...
...
donkey
orange
fish
>>> for item in food:                   # pythonic
...     print(item)
...
...
donkey
orange
fish
>>>


Явные итераторы


Внутри Python использует много итераторов… для циклов не должно быть исключений:


>>> counter = 0                                                     # unpythonic
>>> while counter < len(somecontainer):
...     callable_consuming_container_elements(somecontainer[counter])
...     counter += 1
...
...
>>> for item in somecontainer:                                      # pythonic
...     callable_consuming_container_elements(item)
...
...
>>>

Можно сказать, что для простых вещей мы не должны явно создавать итераторы. Есть ряд случаев, когда явные итераторы будут полезны. Например, когда мы что-то обрабатываем, останавливаемся, делаем что-либо еще, затем возвращаемся назад и продолжаем. Итератор запоминает наше положение, и это прекрасно:


>>> somecontainer = list(range(7))
>>> type(somecontainer)
<class 'list'>
>>> somecontainer
[0, 1, 2, 3, 4, 5, 6]
>>> somecontaineriterator = iter(somecontainer)
>>> type(somecontaineriterator)
<class 'list_iterator'>

Теперь мы можем начать использовать наш итератор:


>>> for item in somecontaineriterator:          # start consuming the iterable somecontainer
...     if item < 4:
...         print(item)
...
...     else:
...         break                               # breaks out of the nearest enclosing for/while loop
...
...
...
0
1
2
3

Не дайте себя обмануть, итератор остановился на «somecontaineriterator[5]», который равен 4, а не 3. Давайте посмотрим, что будет дальше:


>>> print("Something unrelated to somecontaineriterator.")
Something unrelated to somecontaineriterator.
>>> next(somecontaineriterator)                    # continues where previous for/while loop left off
5
>>> next(somecontaineriterator)
6
>>> next(somecontaineriterator)
Traceback (most recent call last):                 # we have exhausted the iterator
  File "<input>", line 1, in <module>
StopIteration
>>>

Некоторым может показаться, что данный пример неоднозначен, на самом деле это не так. Итератор в цикле проходит массив, выходит из цикла по break на индексе 5 (значение внутри равно 4). Затем мы совершаем некие действия (выводим текст в консоль), и после этого продолжаем перебор итератора. Вот и все.

Присваивание


Здесь можно почитать подробно.

Циклы только когда это действительно необходимо


Существует много случаев, для которых в других языках программирования мы бы использовали выражения циклов, но в случае Python в этом нет необходимости.

Python предоставляет много высокоуровневого функционала для оперирования любыми объектами. Для последовательностей это могут быть функции zip(), min(), max(). Затем, это такие вещи, как «list comprehensions», генераторы, «set comprehensions» и т.д.

Дело в том, что если мы сохраняем наши данные в базовых структурах Python, таких как списки, кортежи, словари, множества и д.р., мы получаем кучу функционала для работы с ними «из коробки». Даже если мы нуждаемся в специфичной структуре, скорее всего не составит труда создать ее, используя базовый структуры данных. Итак, в чем же преимущества. Как мы можем получить список имен некоторых людей, хранящийся в дисковом файле.


sa@wks:/tmp$ cat people.txt
   Dora
John
 Dora
Mike
Dora
     Alex
Alex
sa@wks:/tmp$ python
>>> with open('people.txt', encoding='utf-8') as a_file:     # context manager
...     { line.strip() for line in a_file }                  # set comprehension
...
...
{'Alex', 'Mike', 'John', 'Dora'}
>>>

Никаких циклов, пользовательских структур данных, убраны лишние пробелы и дубликаты, все pythonic ;-]

Кортежи – это не просто read-only списки


Это распространенное заблуждение. Очень часто списки и кортежи применяются для одних и тех же целей. Списки предназначены для хранения однотипных данных. В то время как кортежи – для объединения данных разного типа в набор. Другими словами

Целое — больше чем сумма его частей.
— Аристотель (384 д.н.э — 322 д.н.э)



>>> person = ("Steve", 23, "male", "London")
>>> print("{} is {}, {} and lives in {}.".format(person[0], person[1], person[2], person[3]))
Steve is 23, male and lives in London.
>>> person = ("male", "Steve", 23, "London")              #different tuple, same code
>>> print("{} is {}, {} and lives in {}.".format(person[0], person[1], person[2], person[3]))
male is Steve, 23 and lives in London.
>>>

Индекс в кортеже несет смысловую нагрузку. Давайте сравним эти структуры:


>>> foo = 2011, 11, 3, 15, 23, 59
>>> foo
(2011, 11, 3, 15, 23, 59)                               # tuple
>>> list(range(9))
[0, 1, 2, 3, 4, 5, 6, 7, 8]                             # list
>>>

  • Первая из них, кортеж, представляет собой структуру, где позиция элемента несет некий смысл (Первый элемент — год).
  • Вторая же, список, представляет собой последовательность, в которой значения функционально равнозначны, индекс не несет никакого смысла.

Отличный пример использования обоих структур – метод fetchmany() из Python DB API, который возвращает результат как список кортежей.

Классы не предназначены для группировки функциональности


C# и Java содержат код только внутри классов. В итоге возникают утилитарные классы, содержащие одни статические методы. Например, математическая функция sin(). В Python мы просто используем модуль верхнего уровня:


sa@wks:/tmp$ echo -e 'def sin():\n    pass' > foo.py; cat foo.py
def sin():
    pass
sa@wks:/tmp$ python
>>> import foo
>>> foo.sin()
>>>


Скажите нет геттерам и сеттерам


Способ достичь инкапсуляции в Python – использование свойств, а не геттеров и сеттеров. Используя свойства, мы можем изменить атрибуты объекта и исправить реализацию, не затрагивая вызываемый код (читайте, stable API).

Функции являются объектами


В Python – все является объектами. Функции тоже объекты. Функции – это объекты, которые можно вызывать.


>>> somefoo = [{'price': 9.99}, {'price': 4.99}, {'price': 10}]
>>> somefoo
[{'price': 9.99}, {'price': 4.99}, {'price': 10}]
>>> def lookup_price(someobject):
...     return someobject['price']
...
...
>>> somefoo.sort(key=lookup_price)                        # pass function object lookup_price
>>> somefoo
[{'price': 4.99}, {'price': 9.99}, {'price': 10}]         # in-place sort of somefoo took place
>>> type(somefoo)
<class 'list'>
>>> type(somefoo[0])
<class 'dict'>
>>>

Между lookup_price и lookup_price() существует разница — последнее вызывает функцию, а первое смотрим биндинг по имени lookup_price. Это дает нам возможность использовать функции в роли обычных объектов.
Автор оригинала: Markus Gattol
Андрей Кондратович @cursed
карма
102,7
рейтинг 0,0
Самое читаемое Разработка

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

  • +4
    Любопытно, но я рад, что до всего этого дошел сам в процессе изучения
  • +1
    Зачем somefoo = list(range(9)), если range уже возвращает список.
    • +3
      >>> type(range(5))
      <class 'range'>


      Objects of type range are created using the range() function. They don’t support slicing, concatenation or repetition, and using in, not in, min() or max() on them is inefficient.
      docs.python.org/release/3.0.1/library/stdtypes.html#sequence-types-str-bytes-bytearray-list-tuple-range

      Благодаря вам, я теперь знаю, что range() возвращает не список)
      • +3
        Да я уже понял, когда увидел форматирование, что автор использует Python 3.x, где range был заменен по-сути более быстрым xrange, который, конечно выдает не список. Для 2.x range() возвращает список.
  • +1
    
    >>> type(range(5))
    <type 'list'>
    


    Теперь вы знаете еще одно различие 2.# и 3.#.
    • +2
      Прошу прощения, ошибся веткой.
      • 0
        Да я уже тоже успел понять, когда прочитал дальше. :)
  • +2
    Начать следовало бы с того, что «Pythonic» это путь использования Python правильно, знаючи, воспользовавшись существующими инструментами, применяя их там, где нужно. Иными словами, «как правильно программировать на Python» и не более того.
    Хорошо что Python имеет такой путь, а точнее его прокладывают для первопроходцев, т.к. на других языках его не дают и, в результате чего, мы имеем сотни версий того, как правильно писать на других языках.
  • +13
    somecontaineriterator = iter(somecontainer)   #unpythonic
    
    some_container_iterator = iter(somecontainer)   #pythonic
  • 0
    как-то попадалась библиотека для конструирования путей к файлам, позволяющая вместо
    foo + "/" + baz 
    записывать просто
    foo / baz
    не напомните :?
  • 0
    >>> bar = []                            # pythonic

    на днях, рассматривая логи коммитов, нашел удивительную чехорду патчей:
    -foo = []
    +foo = [ ]
    затем
    -foo = [ ]
    +foo = []

    и так несколько раз. а как должно быть pythonic?

    а нужно-ли делать, отступы на пустых строках, или нет:
    def foo(x):
    ....
    .... if x is None:
    

    или
    def foo(x):
    
    .... if x is None:
    
    ?
    • 0
      Верно foo = [] (об этом и в PEP 8 говорится)
      Отступы в строках делать не нужно. Это нигде не упоминается (поправьте, если не прав), но никакой необходимости в отбивке пустой строки нет, как ни крути.
      • 0
        Вот что примечательно: Отбивку IDE обычно делает автоматом и, если позже применить к такому коду pypi.python.org/pypi/pep8, то валидатор на отбивку ругнётся, хотя в PEP 8, таки да, об этом нет ни слова.
        • 0
          В Eclipse PyDev такая комбинация настроек удаляет отбивку для пустых строк при сохранении (конкретнее — «Auto-format… before saving»+«Right trim lines»)
  • +3
    value = (foo.bar()['first'][0]*baz.ham(1, 2)[5:9]               # pythonic
            + verify(34, 20)*skip(500, 360))

    не-а. «The preferred place to break around a binary operator is *after* the operator, not before it.» (© pep-0008)

  • 0
    print("{} is {}, {} and lives in {}.".format(person[0], person[1], person[2], person[3]))

    Если по новому стилю определять позиционные параметры, необходимо в фигурных скобках указывать их позицию:

    print("{0} is {1}, {2} and lives in {3}.".format(person[0], person[1], person[2], person[3]))

    А иначе — ValueError: zero length field name in format
    • +14
      Уродство какое-то.
      print("{0} is {1}, {2} and lives in {3}.".format(*person))
      • +1
        Я лишь поправил очевидную ошибку в строковой операции. Ваше решение элегантно, спасибо. Буду впредь использовать именно такое.
      • 0
        А по моему такой вариант еще лучше:
        print("%s is %d, %s and lives in %s." % person )
        • 0
          в 3-м питоне поддержку % уберут…
    • +1
      > А иначе — ValueError: zero length field name in format

      Только до версии 2.7
  • +1
    Ну по поводу "\" несколько спорно.
    Часто это удобней, чем вставлять скобки (например, этих скобок там уже и так много).
    В том же Django в исходниках видел много раз.
    • 0
      исходники django анти-pythonic до мозга костей. попробуйте отстроить pylint для таких проектов :)
      • НЛО прилетело и опубликовало эту надпись здесь
        • 0
          то как выглядит код с вашей точки зрения и с точки зрения pylint — это просто две разные точки зрения. запустите и сами всё увидите. :)
          • НЛО прилетело и опубликовало эту надпись здесь
  • 0
    >> Не используйте «from foo import *»

    Как раз сегодня столкнулся.

    У меня есть 2 модуля: один с объектами-контейнерами, и второй с фабриками, где вызывается rest api, парсится xml и штампуются готовые объекты.
    Во втором модуле используются ВСЕ объекты первого.
    Что может случиться плохого, если я импортирую их как «from module1 import *»?

    Запихивать всё в один модуль не очень круто: несколько дюжин тупых объектов, и файл легко разростется на тысячи строчек кода. Импортировать же явно всю эту безразмерную кучу объектов тоже не красиво. «Засорение» глобального пространства имен в этом конкретном случае не выглядит как источник проблем.
    • +3
      хм…
      from module1 import *
      container


      vs

      import module1
      module1.container


      или в виде алиаса (если «module1» лень печатать или конфликтует с кем-то)

      import module1 as m
      m.container
  • –4
    а
    >>> foo = "path1/path2/path3"
    >>> baz = "somefile"
    >>> os.path.join(foo, baz)


    на Windows как сработает? Я правда не в курсе…

    «Общие исключения» — это к pythonic/unpythonic не относится. Это для всех ЯП верно в равной степени вроде бы.

    «Функции являются объектами» — что то пример как то не в тему… Так же можно сказать что в C функции — объекты. Может кривой перевод?

    Ну и отметить, что речь о Python3 не помешало бы.
    • –1
      а
      >>> foo = «path1/path2/path3»
      >>> baz = «somefile»
      >>> os.path.join(foo, baz)

      на Windows как сработает? Я правда не в курсе…

      отлично работает…

      «Функции являются объектами» — что то пример как то не в тему… Так же можно сказать что в C функции — объекты. Может кривой перевод?

      Ничего кривого в переводе нет, сказано именно то, что хотел и сказал автор. В C функции — объекты? Простите, но вы несете какой-то бред! В C можно делать так?:
      def some_func():
          return 42
      
      some_func.some_property = 42
      some_func.some_method = some_func
      
      print(some_func.some_property)
      print(some_func.some_method() == some_func())
      
      • 0
        Т.е. пример с os.path.join() выведет не path1/path2/path3\somefile а самый что ни на есть path1\path2\path3\somefile? Если так, то здорово, я не задавался этим вопросом раньше (НЕ использовать os.path.join НЕ призываю, просто уточнил).

        Насчет «функции есть объекты» — я не ставил этот факт под сомнение (и не утверждал что в C функции это объекты тоже), а именно указал что пример не совсем убедительный. Передавать функцию в качестве аргумента другой функции можно во многих языках, в том числе в НЕ объектно-ориентированных), даже в С можно (по ссылке), не говоря уж о функциональных ЯП. И я не воспринимаю пример из статьи как доказательство того, что функции — объекты.
        Привели бы в статье пример, который привели сейчас — ни слова не возразил бы.
        Ишь, налетели))
        • 0
          Логика работы os.path.join() аналогичка «cd ..».
          os.path.join("/a/b/", «c») -> "/a/b/c"
          os.path.join("/a/b/", "/c") -> "/c"
          • 0
            Я имею в виду — развернет ли оно POSIX слеши "/" в виндовые бекслеши "\", если аргументы уже содержат в себе слеши или нет?

            т.е. корректна ли запись
            os.path.join("a/b/", "c")
            в Windows, или нужно писать что то в стиле
            os.path.join(*"a/b/".split("/"), "c")

            Например если в линуксе задать путь в виндовом стиле (с бекслешами)
            os.path.join(r"c:\a\b\c", "d")
            то выведет чушь
            'c:a\\b\\c/d'

            Я бы сам попробовал, просто не на чем.
            • +1
              image
              Да )
              • 0
                Нет )
                >>> os.path.join('a/b', 'c')
                'a/b\\c'
                

                функция лишь вставляет os.sep между аргументами. а файл открывается потому, что винда понимает и прямые и обратные слеши (см os.sep, os.altsep ).
                • +2
                  Посмотрел код ntpath.py… Вы правы
                  >>> os.path.join('a/b', 'c')
                  'a/b\\c'
                  но зато
                  >>> os.path.join('a/b/', 'c')
                  'a/b/c'
                  • 0
                    Ну вот, получается еще один пример в статье можно считать не совсем удачным (Сама статья ничего, это мне все за заминусованный комментарий обидно)
        • +1
          здесь терминологическая путаница — понятие first-class object (aka значение) и object в ООП (который содержит свойства и методы) — это две разные штуки.

          функции в С не являются first-class object — ими являются указатели. а функции в python и то и другое. ;)
    • 0
      В Си (точнее, в C++, т.к. в Си вообще нет объектов) функции не являются объектами.
      И даже не являются переменными. Есть только типизированные указатели на функцию, для которых есть операция вызова ().
  • +1
    >Не используйте «from foo import *»

    Все зависит от места применения, если это файл с правилами роутинга (например urls в django) то как раз хорошая идея написать from foo.views import *

    Вот тут:
    >>> counter = 0                                                     # unpythonic
    >>> while counter < len(somecontainer):
    ...     callable_consuming_container_elements(somecontainer[counter])
    ...     counter += 1
    ...
    ...
    >>> for item in somecontainer:                                      # pythonic
    ...     callable_consuming_container_elements(item)
    


    Тоже не корректность, ибо данный случай конечно правилен, но если вы хотите изменять элементы массива достаточно большим алгоритмом (чтоб без lambda), то придется-таки делать while.
    • 0
      for counter, item in enumerate(somecontainer):
          pass


      Нет?
      • 0
        нет :)
  • +1
    Хотя многое из описанного уже знал, но этот топик безусловно идет в избранное.
  • 0
    Читаю dive into python — там есть большая часть написанного. На редкость удачное руководство.
  • 0
    Классы не предназначены для группировки функциональности

    Не понял как это понимать. В классе не должно быть статических методов или не должно быть только их, или ещё что?

    И ещё вопрос по модулям — видел разные варианты: от весь код модуля в init до «один файл — один класс», как и различные комбинации (что-то в инит, где-то несколько классов в файле, где-то один). Какой стиль pythonic?

    А вообще неплохо бы автокорректор какой-то чтобы был, расставлял что pythonic, а что нет. Я вот в своём коде на python практически не вижу отличий от php, а исходя из «уходом от принципа «существует много способов сделать это»» делаю формальный вывод, что код pythonic (раз код работает, значит я нашёл единственный способ сделать это :) ), хотя интуитивно понимаю, что нет, способ не единственный и вряд ли оптимальный по любому критерию, кроме легкости чтения php-шником, изучающим python :(
    • 0
      Под «Классы не предназначены для группировки функциональности» понимается следующее. Очень часто программисты чистых ООП языков по привычки пишут код полностью классами. Хотя очень часто в этом нет необходимости. Например в Java вся математика собрана в классе Math, т.к. по другому реализовать язык не позволяет. В python же не нужно делать подобные классы, когда можно просто реализовать набор функций в модуле.

      По поводу ухода от «существует много способов сделать это». Это больше касается разработки языка, к этому стремятся разработчики Python. К этому стоит стремится и когда пишешь собственную функциональность.
      • 0
        Грубо говоря, модуль используется там, где привычно использовать глобальные классы, а классы там, где вложенные?

        Привычно оставляет единственный способ «сделать это» (например изменить состояние объекта) полная инкапсуляция данных и разграничение уровней доступа к методам на public (интерфейсы), protected, private и т. п., то есть нельзя извне обратиться к свойству объекта, кроме как через публичные методы объекта, предусмотренные разработчиком. В python же, с его полной интроспекцией, это практически невозможно, как я понял? Или я вообще не понял о чём фраза «К этому стоит стремится и когда пишешь собственную функциональность»?
    • +1
      >Классы не предназначены для группировки функциональности

      В том смысле, что если у вас есть набор каких-то функций, которые не работают над общими (инкапсулированными?) данными, то их лучше оформить не как класс со статическими методами а как модуль с набором простых функций. Пример:
      #UNPYTHONIC
      # math.py
      class trig:
      
          @staticmethod
          sin(self, val):
              pass
      
          @staticmethod
          cos(self, val):
              pass
      
          @staticmethod
          tan(self, val):
              pass
      
      #PYTHONIC
      # math/trig.py
      sin(val):
          pass
      
      cos(val):
          pass
      
      tan(self, val):
          pass
      


      В то же время если нужно работать с общими данными, например формирование запроса в CURL или работа с картинкой, то нужно это оформлять в виде класса, чтоб не передавать обрабатываемые данные первым аргументом (как делают в CURL или в GD в PHP)

      Насчет того как код модуля распределять по файлам, я рекомендаций, к сожалению, не встречал. Тоже интересно. Наверное если один класс не имеет смысла без другого, то лучше их в одном файле оставить. А если классы отчуждаемы, то в разных. Но это моя догадка только.

      Корректоры для Python есть, Pylint например, но я с ним не работал, не знаю что он проверяет а что нет. Вообще научиться писать pythonic код не очень сложно как мне кажется. Пара статей вроде этой и вроде ясно.
  • +2
    Самое распространенное забыли: Использовать enumerate
  • 0
    По-поводу использования from foo import * — это вполне допустимо, если вы объявляете
    __all__ = ["echo", "surround", "reverse"]  # список используемых штук из модуля
    

    Тогда область видимости не засоряется.
  • +1
    А это чего за конструкция? Какой тип возвращает?
    { line.strip() for line in a_file }
    • +1
      set docs.python.org/library/stdtypes.html#set-types-set-frozenset. Типа неупорядоченный список уникальных значений

      Ну и генератор сета работает только в Python3 вроде. В 2.x можно делать так
      set([line.strip() for line in a_file])
      • 0
        В 2.7 это допустимо.
        В 2.6 можно делать set(line.strip() for line in a_file).
    • 0
      это сет. Такую конструкцию в третьей ветке ввели
  • +2
    «Используйте стандартную библиотеку» это надо вырезать на камне и поставить в центре каждого города. В ней действительно очень много всего, а народ продолжает писать свои велосипедики… Например сам долго писал какие-то свои парсеры, вместо строки import csv :)
    • 0
      Добавлю, что стандартную библиотеку не только очень приятно использовать, но её ещё интересно читать (в смысле исходники).
  • 0
    «Списки предназначены для хранения однотипных данных.»
    чего это вдруг?

    *args

    • 0
      Это семантически верно. Обычно в списках хранят однотипные данные, которые последовательно (и однотипно) кто-то обрабатывает. А для разнотипных значений, которые должны обрабатываться вместе, служат кортежи (typles). Например тот же *arg — кортеж.
      В питоне просто не стали делать это правило строгим, чтобы не путать людей. Но во многих языках, в том же Хаскеле например, можно только так.
  • 0
    Хорошая статья для начинающих.

    А есть еще вопросы «неоднозначной питоничности»
    Например использование функциональных фич вроде генераторов и лямбд.

    Как только новичок удосуживается таки взять и разобраться с ними, это как правило начинает создавать больше проблем, чем решать, так как итоговый код получается плохо читаемым, плохо отлаживаемым и сложным для рефакторинга.

    В итоге, если вы можете упихать функцию в 10 строк, хотя на С или Java она бы занимала 100, то лучше этого не делать и обойтись 30-ю.

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