PEP-8 или предложение по улучшению Python представляет некоторые ключевые моменты, которые вы можете использовать, чтобы сделать ваш код более организованным и читабельным. Как говорит создатель Python Гвидо Ван Россум:

Код читается гораздо чаще, чем пишется.

В этом посте вы начнете изучать PEP-8 с помощью нескольких примеров кода! Вы затронете следующие темы:

  • Сначала вы познакомитесь с PEP-8, что это такое и зачем вам это нужно;
  • Далее вы займетесь отступами, горячей темой среди программистов. Должны ли вы использовать вкладки или пробелы? Вы найдете ответ в этом разделе;
  • Возможно, вы этого не ожидаете, но есть рекомендации по максимальной длине строки;
  • Также предлагается способ обработки пустых строк;
  • Далее, пробелы в выражениях и утверждениях — это то, с чем вы также можете легко справиться как новичок;
  • Что такое кодирование и зачем оно вам нужно при работе с Python? Какая кодировка по умолчанию для Python3? Обо всем этом будет рассказано в разделе кодирование исходной строки.
  • Вы, вероятно, часто делаете импорт, когда пишете код. В этом разделе будут рассмотрены такие темы, как порядок импорта, абсолютный и относительный импорт, импорт подстановочных знаков и т. д.;
  • Документация необходима для отслеживания всех аспектов приложения и повышения общего качества конечного продукта. Комментарии здесь обязательны!
  • Знаете ли вы названия дандеров уровня модели? Это особенно полезно в строках документации!
  • Наконец, вы также узнаете больше о соглашениях об именах: вы узнаете, как вы можете придумывать имена функций, какой тип стиля именования вы обычно используете и многое другое;
  • Соответствует ли ваш код PEP-8? Это определенно вопрос, который вы должны задать себе. Вот почему в последнем разделе рассматриваются некоторые инструменты, которые помогут вам проверить свой код, чтобы увидеть, соответствует ли он рекомендациям, представленным в этом посте, и многие другие, которые вы здесь не рассмотрели!

Введение в PEP-8

Язык программирования Python за последний год превратился в один из самых любимых языков программирования. Этот язык относительно прост в изучении, чем большинство языков программирования. Это мультипарадигма, он имеет множество модулей с открытым исходным кодом, которые дополняют полезность языка, и он набирает популярность в сообществе специалистов по данным и веб-разработке.

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

>>> import this
The Zen of Python, by Tim Peters 
Beautiful is better than ugly. 
Explicit is better than implicit. 
Simple is better than complex. 
Complex is better than complicated. 
Flat is better than nested. 
Sparse is better than dense. 
Readability counts. 
Special cases aren't special enough to break the rules. 
Although practicality beats purity. 
Errors should never pass silently. 
Unless explicitly silenced. 
In the face of ambiguity, refuse the temptation to guess. 
There should be one-- and preferably only one --obvious way to do it. 
Although that way may not be obvious at first unless you're Dutch. Now is better than never. 
Although never is often better than *right* now. 
If the implementation is hard to explain, it's a bad idea. 
If the implementation is easy to explain, it may be a good idea. Namespaces are one honking great idea -- let's do more of those!

Выше приведены 20 принципов, которые использует программирование на Python. В приведенном выше выводе вы также видите «Число читабельности», что должно быть вашей главной заботой при написании кода: другие программисты или специалисты по данным должны понимать и иметь возможность внести свой вклад в ваш код, чтобы он мог решить поставленную задачу.

Следующие разделы дадут вам больше информации о том, как вы можете выполнить вышеперечисленное!

Отступ

При программировании на Python вы обязательно будете использовать отступы. Однако с ним следует быть осторожным, так как это может привести к синтаксическим ошибкам. Поэтому рекомендуется использовать 4 пробела для отступа. Например, этот оператор использует 4 пробела отступа:

if True: 
    print("If works")

А также этот цикл for с оператором print имеет отступ в 4 пробела:

for element in range(0, 5): 
    print(element)

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

Вот несколько примеров висячего отступа в больших выражениях, которые показывают некоторые варианты его использования:

value = square_of_numbers(num1, num2,
                       num3, num4)
def square_of_number(
     num1, num2, num3, 
     num4):
 return num1**2, num2**2, num3**2, num4**2
value = square_of_numbers(
              num1, num2,
              num3, num4)
list_of_people = [
 "Rama",
 "John",
 "Shiva"
]
dict_of_people_ages = {
 "ram": 25,
 "john": 29,
 "shiva": 26
}

Каждый разработчик, работающий с Python или другим языком программирования, в какой-то момент задает себе вопрос, использовать ли табуляции или пробелы для отступов. Разница между вкладками и пробелами постоянно обсуждается в сообществе. Посмотрите, например, эту статью.

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

Обратите внимание, что Python 3 не позволяет смешивать табуляции и пробелы для отступов. Вот почему вы должны выбрать один из двух и придерживаться его!

Максимальная длина линии

Как правило, рекомендуется стремиться к длине строки в 79 символов в вашем коде Python.

Следование этому целевому номеру имеет много преимуществ. Пара из них следующая:

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

Комментарии должны иметь длину строки 72 символа. Вы узнаете больше о наиболее распространенных соглашениях для комментариев позже в этом уроке!

В конце концов, вам решать, каким соглашениям и стилю кодирования вы хотите следовать, если вы работаете в небольшой группе, и для большинства разработчиков приемлемо отклонение от рекомендации по максимальной длине строки. Однако, если вы создаете или участвуете в проекте с открытым исходным кодом, вы, вероятно, захотите и/или должны соблюдать правило максимальной длины строки, установленное PEP-8.

При использовании оператора + вы можете лучше использовать правильный разрыв строки, что сделает ваш код более понятным:

вы должны использовать

total = (A +
         B +
         C)

следует избегать

total = (A
         + B
         + C)

В качестве альтернативы вы также можете написать:

total = A         
      + B         
      + C

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

Пустые строки

В сценариях Python функции и классы верхнего уровня разделяются двумя пустыми строками. Определения методов внутри классов должны быть разделены одной пустой строкой. Вы можете ясно увидеть это в следующем примере:

class SwapTestSuite(unittest.TestCase):
    """
        Swap Operation Test Case
    """
    def setUp(self):
        self.a = 1
        self.b = 2

    def test_swap_operations(self):
        instance = Swap(self.a,self.b)
        value1, value2 =instance.get_swap_values()
        self.assertEqual(self.a, value2)
        self.assertEqual(self.b, value1)


class OddOrEvenTestSuite(unittest.TestCase):
    """
        This is the Odd or Even Test case Suite
    """
    def setUp(self):
        self.value1 = 1
        self.value2 = 2

    def test_odd_even_operations(self):
        instance1 = OddOrEven(self.value1)
        instance2 = OddOrEven(self.value2)
        message1 = instance1.get_odd_or_even()
        message2 = instance2.get_odd_or_even()
        self.assertEqual(message1, 'Odd')
        self.assertEqual(message2, 'Even')

Классы SwapTestSuite и OddOrEvenTestSuite разделены двумя пустыми строками, тогда как определения методов, такие как .setUp() и .test_swap_operations(), имеют только одну пустую строку для их разделения.

Пробелы в выражениях и инструкциях

Вы должны стараться избегать пробелов, когда видите, что ваш код написан так же, как в следующих примерах:

вы должны использовать

func(data, {pivot: 4})
indexes = (0,)
if x == 4: print x, y; x, y = y, x
spam(1)
dct['key'] = lst[index]
x = 1
y = 2
long_variable = 3
ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:]
ham[lower:upper], ham[lower:upper:], ham[lower::step]
ham[lower+offset : upper+offset]
ham[: upper_fn(x) : step_fn(x)], ham[:: step_fn(x)]
ham[lower + offset : upper + offset]
i = i + 1
submitted += 1
x = x2 - 1
hypot2 = xx + yy
c = (a+b)  (a-b)
def complex(real, imag=0.0):
    return magic(r=real, i=imag)
def munge(input: AnyStr): ...
def munge() -> AnyStr: ...
def munge(sep: AnyStr = None): ...
def munge(input: AnyStr, sep: AnyStr = None, limit=1000): ...
if foo == 'blah':
    do_blah_thing()
do_one()
do_two()
do_three()
FILES = ('setup.cfg',)
FILES = [
    'setup.cfg',
    'tox.ini',
]
initialize(FILES,
           error=True,
)

тебе следует избегать

func( data, { pivot: 4 } )
indexes = (0, )
if x == 4 : print x , y ; x , y = y , x
spam (1)
dct ['key'] = lst [index]
x             = 1
y             = 2
long_variable = 3
ham[lower + offset:upper + offset]
ham[1: 9], ham[1 :9], ham[1:9 :3]
ham[lower : : upper]
ham[ : upper]
i=i+1
submitted +=1
x = x  2 - 1
hypot2 = x  x + y  y
c = (a + b)  (a - b)
def complex(real, imag = 0.0):
    return magic(r = real, i = imag)
def munge(input:AnyStr): ...
def munge()->PosInt: ...
def munge(input: AnyStr=None): ...
def munge(input: AnyStr, limit = 1000): ...
if foo == 'blah': do_blah_thing()
do_one(); do_two(); do_three()
or
if foo == 'blah': do_blah_thing()
for x in lst: total += x
while t < 10: t = delay()
or 
if foo == 'blah': do_blah_thing()
else: do_non_blah_thing()

try: something()
finally: cleanup()

do_one(); do_two(); do_three(long, argument,list, like, this)

if foo == 'blah': one(); two(); three()
FILES = 'setup.cfg',
FILES = ['setup.cfg', 'tox.ini',]
initialize(FILES, error=True,)

Эти примеры были взяты из ПЭП-8.

Кодировка исходного файла

Компьютер не может хранить «буквы», «цифры», «картинки» или что-то еще; Он может хранить и работать только с битами, которые могут иметь только двоичные значения: yes или no, true или false, 1 или 0 и т. д. Как вы уже знаете, компьютер работает с электричеством; Это означает, что «фактический» бит — это наличие или отсутствие всплеска электричества. Обычно вы представляете это (отсутствие) присутствия с помощью 1 и 0.

Чтобы использовать биты для представления чего-либо, кроме битов, вам нужен набор правил. Вам нужно преобразовать последовательность битов во что-то вроде букв, цифр и изображений, используя схему кодирования или кодировку. Примеры схем кодирования: ASCII, UTF-8 и т. д.:

  • Американский стандартный код для обмена информацией (ASCII) является наиболее распространенным форматом текстовых файлов на компьютерах и в Интернете. В файлах этого типа каждый буквенный, числовой или специальный символ представлен 7-битным двоичным числом (строкой из семи нулей или единиц).
  • Unicode Worldwide Character Standard, или сокращенно Unicode, представляет собой систему для «обмена, обработки и отображения письменных текстов на различных языках современного мира». Короче говоря, Unicode предназначен для всех известных в мире систем письма. В настоящее время Unicode использует три различных кодировки для представления наборов символов Unicode: UTF-8, UTF-16 и UTF-32.
  • UTF-16 — это кодировка Unicode с переменной длиной: кодовые точки кодируются одной или двумя 16-битными кодовыми единицами.
  • UTF-8 — это еще один тип кодировки Unicode переменной длины, использующий от одного до четырех 8-битных байтов.
  • UTF-32 — это кодировка фиксированной длины, в которой используется ровно 32 бита на кодовую точку Unicode.

Совет: если вы хотите узнать больше о кодировании, прочтите этот пост.

Теперь, почему это важно?

Вы узнали, что строки являются одними из наиболее часто используемых типов данных в Python. Как и следовало ожидать, настанет время, когда вы захотите работать со строками, которые либо содержат, либо полностью состоят из символов, не входящих в стандартный набор ASCII. Ведь может случиться так, что вам придется работать с текстами, содержащими символы с диакритическими знаками, такие как á, ž, ç и т. д.

Теперь, в Python 3, UTF-8 является исходной кодировкой по умолчанию. Но для тех из вас, кто использует Python 2, вы, вероятно, уже знаете, что по умолчанию там используется ASCII.

Но что тогда, если у вас есть строка, содержащая не-ASCII-символ, например "Flügel"?

Когда вы ссылаетесь на строку в Python 2, вы получите следующее:

>>> s
'Fl\xfcgel'

Это не похоже на вашу строку! Что происходит, когда вы его печатаете?

>>> print(s) 
Flügel

Печать дала вам значение, которое вы присвоили переменной. Был закодирован не-ASCII-символ ÃŒ. Вот почему вы получили \xfc, когда ссылались на строку. Чтобы справиться с этим, вы можете использовать строковые методы .encode() и .decode(). Первый возвращает 8-битную версию строки Unicode, закодированную в запрошенной кодировке, а второй интерпретирует строку, используя заданную кодировку.

Импорт

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

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

Взгляните на следующую таблицу, чтобы понять это немного лучше:

вы должны использовать

from config import settings
or
import os
import sys

следует избегать

import os, sys

Кроме того, вы должны учитывать, что существует порядок, который необходимо соблюдать при импорте библиотек. В целом можно следовать такому порядку:

  1. Импорт стандартных библиотек.
  2. Связанный сторонний импорт.
  3. Импорт локального приложения/библиотеки.

Абсолютный и относительный импорт

Далее, полезно знать разницу между абсолютным и относительным импортом. В целом, в Python предпочтительнее использовать абсолютный импорт, так как он повышает читабельность. Однако по мере того, как ваше приложение становится более сложным, вы также можете продолжать использовать относительный импорт. Неявный относительный импорт никогда не должен использоваться и был удален в Python 3.

Но что такое абсолютный и относительный импорт?

  • Абсолютный импорт — это импорт, в котором используется абсолютный путь к функции или классу, разделенный символом .. Например,
import sklearn.linear_model.LogisticRegression
  • Относительный импорт — это импорт относительно текущей позиции, в которой находится ваш файл Python. Вы можете использовать этот тип импорта, если структура вашего проекта растет, так как это сделает ваш проект более читабельным. Это означает, что если у вас есть структура проекта Python, подобная следующей:
  .
  ├── __init__.py
  ├── __init__.pyc
  ├── __pycache__
  │   ├── __init__.cpython-35.pyc
  │   ├── bubble_sort.cpython-35.pyc
  │   ├── selection_sort.cpython-35.pyc
  ├── bubble_sort.py
  ├── heap_sort.py
  ├── insertion_sort.py
  ├── insertion_sort.pyc
  ├── merge_sort.py
  ├── merge_sort.pyc
  ├── quick_sort.py
  ├── radix_sort.py
  ├── selection_sort.py
  ├── selection_sort.pyc
  ├── shell_sort.py
  ├── tests
  │   ├── test1.py

Вы можете использовать относительный импорт для импорта алгоритма пузырьковой сортировки, хранящегося в bubble_sort.py в test1. Это будет выглядеть так:

from ..bubble_sort import BubbleSort

Если вы хотите узнать больше об абсолютном и относительном импорте, вы можете проверить PEP 328.

Импорт подстановочных знаков

Наконец, вы должны стараться избегать импорта подстановочных знаков, потому что они не улучшают читабельность; У вас нет представления о том, какие классы, методы или переменные вы используете из своего модуля, например:

from scikit import *

Комментарии используются для документации в коде Python. Они улучшают понимание кода. Существует множество инструментов, которые вы можете использовать для создания документации, такой как комментарии и строки документации, для вашего собственного модуля. Комментарии должны быть более подробными, чтобы, когда кто-то читает код, человек правильно понимал код и то, как он используется с другими частями кода.

Комментарии начинаются с символа #. Все, что написано после хэштега, не выполняется интерпретатором. Например, следующий фрагмент кода вернет только "This is a Python comment".

# This is a Python single line comment 
print("This is a Python comment")

Помните: в предыдущем разделе вы читали, что длина строки комментариев должна составлять 72 символа!

При этом есть три типа комментариев:

Взгляните на следующий отрывок, взятый из библиотеки scikit-learn, чтобы понять, как выглядят эти комментарии:

if Gram is None or Gram is False:         
    Gram = None
    if copy_X:             
        # force copy. setting the array to be fortran-ordered
        # speeds up the calculation of the (partial) Gram matrix
        # and allows to easily swap columns
        X = X.copy('F')

Например:

counter = 0 # initialize the counter
  • Вы пишете строки документации или строки документации в начале общедоступных модулей, файлов, классов и методов. Комментарии такого типа начинаются с """ и заканчиваются на """:
"""         
    This module is intended to provide functions for scientific  computing      
"""

Названия уровня модуля

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

Дандер уровня модуля, такой как (__all__, __author__, __version__), должен быть помещен в основную строку документации модуля и должен стоять перед всеми операторами import. Вы должны определить импорт from __future__ перед любым другим кодом, кроме строк документации:

"""     
    Algos module consists of all the basic algorithms and their             implementation
"""
from __future__ import print
__all__ = ['searching', 'sorting']
__version__ = '0.0.1'
__author__ = 'Chitrank Dixit'  
import os 
import sys

Совет: ознакомьтесь со следующими условиями написания строк документации.

Соглашения об именах

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

Если вы не знаете, какие существуют стили именования, рассмотрите следующие:

  • b или одна строчная буква;
  • B или одна заглавная буква;
  • lower_case_with_underscores
  • UPPER_CASE_WITH_UNDERSCORES
  • CapitalizedWords, который также известен как CapWords, CamelCase или StudlyCaps.
  • Capitalized_Words_With_Underscores
  • _single_leading_underscore: слабый показатель «внутреннего использования». например, from M import * не импортирует объекты, имя которых начинается с подчеркивания.
  • single_trailing_underscore_: используется по соглашению, чтобы избежать конфликтов с ключевым словом Python, например, Tkinter.Toplevel(master, class_='ClassName')
  • __double_leading_underscore: при именовании атрибута класса вызывает изменение имени (внутри класса FooBar __boo становится _FooBar__boo).
  • __double_leading_and_trailing_underscore__: «магические» объекты или атрибуты, находящиеся в управляемых пользователем пространствах имен. Например, __init__, __import__ или __file__. Вы никогда не должны придумывать такие имена, а использовать их только в соответствии с документацией.

Общие соглашения об именах

В следующей таблице приведены некоторые общие рекомендации по именованию идентификаторов:

  • Не используйте «l», «O» или «I» в качестве имени одной переменной: в некоторых шрифтах эти символы похожи на ноль (0) и (1).
  • Как правило, лучше использовать короткие имена, если это возможно. В некоторых случаях вы можете использовать символы подчеркивания для улучшения читаемости.

Если вы хотите узнать больше об исключениях из общих соглашений об именах, ознакомьтесь с этой статьей.

Соответствует ли ваш код PEP-8?

Узнав больше о PEP-8, вы, вероятно, задаетесь вопросом, как вы можете проверить, действительно ли ваш код соответствует этим рекомендациям (и многим другим, которые не были рассмотрены в этом руководстве!).

Помимо самостоятельной проверки PEP-8 и получения дополнительной информации о нем, вам определенно следует изучить удобный модуль pep8, пакет coala и некоторые другие альтернативы, описанные в следующих разделах!

Пакет Python pep8

Пакет pep8 предназначен для поиска несовместимости PEP-8 в вашем коде и предлагает изменения, которые вы можете внести, чтобы ваш код соответствовал PEP-8.

вы можете установить модуль pep8, используя pip.

$ pip install pep8

Например,

$ pep8 --first optparse.py
optparse.py:69:11: E401 multiple imports on one line
optparse.py:77:1: E302 expected 2 blank lines, found 1
optparse.py:88:5: E301 expected 1 blank line, found 0
optparse.py:222:34: W602 deprecated form of raising exception
optparse.py:347:31: E211 whitespace before '('
optparse.py:357:17: E201 whitespace after '{'
optparse.py:472:29: E221 multiple spaces before operator
optparse.py:544:21: W601 .has_key() is deprecated, use 'in'

Вы также можете просмотреть исходный код, в котором обнаружена несовместимость с аргументом --show-source:

$ pep8 --show-source --show-pep8 testsuite/E40.py
testsuite/E40.py:2:10: E401 multiple imports on one line
import os, sys
         ^
    Imports should usually be on separate lines.

    Okay: import os\nimport sys
    E401: import sys, os

Или вы можете отобразить, как часто обнаруживалась каждая ошибка, добавив --statistics:

$ pep8 --statistics -qq Python-2.5/Lib
232     E201 whitespace after '['
599     E202 whitespace before ')'
631     E203 whitespace before ','
842     E211 whitespace before '('
2531    E221 multiple spaces before operator
4473    E301 expected 1 blank line, found 0
4006    E302 expected 2 blank lines, found 1
165     E303 too many blank lines (4)
325     E401 multiple imports on one line
3615    E501 line too long (82 characters)
612     W601 .has_key() is deprecated, use 'in'
1188    W602 deprecated form of raising exception

Анализ вашего кода с помощью coala

coala обеспечивает линтинг и исправление для всех языков, но здесь вас больше интересует программирование на Python, вы можете установить coala с помощью pip:

$ pip3 install coala-bears

В фрагменте кода выше вы видите, что на самом деле вы устанавливаете coala-bears: bears — это плагины или простые модули, которые расширяют возможности вашего coala и различаются от языка к языку. В этом случае вы хотите использовать pep8bear, который находит несовместимый с PEP-8 код и исправляет его на месте. Вам определенно следует рассмотреть возможность использования этого для проверки кода Python.

$ coala -S python.bears=PEP8Bear python.files=\*\*/\*.py \ python.default_actions=PEP8Bear:ApplyPatchAction --save 
# other output ... 
Executing section python... 
[INFO][11:03:37] Applied 'ApplyPatchAction' for 'PEP8Bear'. 
[INFO][11:03:37] Applied 'ApplyPatchAction' for 'PEP8Bear'.

pep8online: проверьте свой код Python онлайн

Помимо удобного модуля pep8 и пакета coala, вы также можете проверить, соответствует ли ваш код Python PEP-8, перейдя на pep8online. На этом сайте есть онлайн-редактор, который позволяет вам просто вставить код, нажав кнопку Проверить код! В результате вы получите отзыв о том, что вам нужно улучшить. Красиво и удобно!

Вывод

При использовании Python вы иногда не заботитесь о качестве кода из-за беспокойства о более быстром выпуске функций. Однако методы, которые были описаны в этом руководстве, и многие другие, которые не были здесь рассмотрены, должны быть частью вашего цикла разработки-постановки-тестирования-развертывания. Это приносит пользу всем, кто работает над проектом, чтобы понять, и в большинстве случаев внесение изменений в код может быть выполнено без глубокого копания и понимания кода с помощью запуска отладчиков. Если вы работаете над проектом с открытым исходным кодом, ваши участники сочтут PEP-8 блаженством и лучше поймут ваш код, поскольку это универсальный стандарт, которому следует каждый разработчик Python.

Теперь, когда вы прошли этот учебник, очень хорошая идея проверить PEP-8 для себя! Еще многое предстоит открыть.

Если у вас есть еще советы по соблюдению PEP-8 или вы считаете, что мы упустили что-то важное из этой статьи, не стесняйтесь, дайте нам знать @DataCamp.

Первоначально опубликовано на https://www.datacamp.com 25 января 2018 г.