🐍 Python для людСй ΠΈ инопланСтян 🐍

Π­Ρ‚ΠΎ мСсто, Π³Π΄Π΅ я Π±ΡƒΠ΄Ρƒ Π΄Π΅Π»ΠΈΡ‚ΡŒΡΡ практичСскими Π½ΠΎΠ²ΠΈΠ½ΠΊΠ°ΠΌΠΈ Π½ΠΎΠ²ΠΎΠ³ΠΎ ΠΏΠΈΡ‚ΠΎΠ½Π° 🐍

ΠΊΠ°ΠΊ Π·Π°ΡΡ‚Π°Π²ΠΈΡ‚ΡŒ ваш ΠΊΠΎΠ΄ ΡΠΈΡΡ‚ΡŒ 🐍

поэтому, поТалуйста, ΠΎΠ±Π½ΠΎΠ²Π»ΡΠΉΡ‚Π΅ΡΡŒ ΠΊΠ°ΠΆΠ΄ΡƒΡŽ нСдСлю, я подСлюсь Ρ‡Π΅ΠΌ-Π½ΠΈΠ±ΡƒΠ΄ΡŒ интСрСсным ΠΈ ΠΏΠ»ΠΎΡ…ΠΎ обновлю эту ΠΈΡΡ‚ΠΎΡ€ΠΈΡŽ :)

ПониманиС списков в Python вСликолСпно

# Python's list comprehensions are awesome.

vals = [expression 
        for value in collection 
        if condition]

# This is equivalent to:

vals = []
for value in collection:
    if condition:
        vals.append(expression)

# Example:

>>> even_squares = [x * x for x in range(10) if not x % 2]
>>> even_squares
[0, 4, 16, 36, 64]

Π‘Π»ΠΎΠ²Π° ΠΌΠΎΠΆΠ½ΠΎ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ для эмуляции ΠΎΠΏΠ΅Ρ€Π°Ρ‚ΠΎΡ€ΠΎΠ² switch/case

# Because Python has first-class functions they can
# be used to emulate switch/case statements

def dispatch_if(operator, x, y):
    if operator == 'add':
        return x + y
    elif operator == 'sub':
        return x - y
    elif operator == 'mul':
        return x * y
    elif operator == 'div':
        return x / y
    else:
        return None


def dispatch_dict(operator, x, y):
    return {
        'add': lambda: x + y,
        'sub': lambda: x - y,
        'mul': lambda: x * y,
        'div': lambda: x / y,
    }.get(operator, lambda: None)()


>>> dispatch_if('mul', 2, 8)
16

>>> dispatch_dict('mul', 2, 8)
16

>>> dispatch_if('unknown', 2, 8)
None

>>> dispatch_dict('unknown', 2, 8)
None
# Python 3.5+ supports 'type annotations' that can be
# used with tools like Mypy to write statically typed Python:

def my_add(a: int, b: int) -> int:
    return a + b
>>> lst = [1, 2, 3, 4, 5]
>>> del lst[:]
>>> lst
[]

# You can replace all elements of a list
# without creating a new list object:
>>> a = lst
>>> lst[:] = [7, 8, 9]
>>> lst
[7, 8, 9]
>>> a
[7, 8, 9]
>>> a is lst
True

# You can also create a (shallow) copy of a list:
>>> b = lst[:]
>>> b
[7, 8, 9]
>>> b is lst
False
>>> import itertools
>>> for p in itertools.permutations('ABCD'):
...     print(p)

('A', 'B', 'C', 'D')
('A', 'B', 'D', 'C')
('A', 'C', 'B', 'D')
('A', 'C', 'D', 'B')
('A', 'D', 'B', 'C')
('A', 'D', 'C', 'B')
('B', 'A', 'C', 'D')
('B', 'A', 'D', 'C')
('B', 'C', 'A', 'D')
('B', 'C', 'D', 'A')
('B', 'D', 'A', 'C')
('B', 'D', 'C', 'A')
('C', 'A', 'B', 'D')
('C', 'A', 'D', 'B')
('C', 'B', 'A', 'D')
('C', 'B', 'D', 'A')
('C', 'D', 'A', 'B')
('C', 'D', 'B', 'A')
('D', 'A', 'B', 'C')
('D', 'A', 'C', 'B')
('D', 'B', 'A', 'C')
('D', 'B', 'C', 'A')
('D', 'C', 'A', 'B')
('D', 'C', 'B', 'A')
# elements in an iterable:

>>> import collections
>>> c = collections.Counter('helloworld')

>>> c
Counter({'l': 3, 'o': 2, 'e': 1, 'd': 1, 'h': 1, 'r': 1, 'w': 1})

>>> c.most_common(3)
[('l', 3), ('o', 2), ('e', 1)]
>>> import datetime
>>> today = datetime.date.today()

# Result of __str__ should be readable:
>>> str(today)
'2017-02-02'

# Result of __repr__ should be unambiguous:
>>> repr(today)
'datetime.date(2017, 2, 2)'

# Python interpreter sessions use 
# __repr__ to inspect objects:
>>> today
datetime.date(2017, 2, 2)
# You can use Python's built-in "dis"
# module to disassemble functions and
# inspect their CPython VM bytecode:

>>> def greet(name):
...     return 'Hello, ' + name + '!'

>>> greet('Dan')
'Hello, Dan!'

>>> import dis
>>> dis.dis(greet)
2   0 LOAD_CONST     1 ('Hello, ')
    2 LOAD_FAST      0 (name)
    4 BINARY_ADD
    6 LOAD_CONST     2 ('!')
    8 BINARY_ADD
   10 RETURN_VALUE
# @classmethod vs @staticmethod vs "plain" methods
# What's the difference?

class MyClass:
    def method(self):
        """
        Instance methods need a class instance and
        can access the instance through `self`.
        """
        return 'instance method called', self

    @classmethod
    def classmethod(cls):
        """
        Class methods don't need a class instance.
        They can't access the instance (self) but
        they have access to the class itself via `cls`.
        """
        return 'class method called', cls

    @staticmethod
    def staticmethod():
        """
        Static methods don't have access to `cls` or `self`.
        They work like regular functions but belong to
        the class's namespace.
        """
        return 'static method called'

# All methods types can be
# called on a class instance:
>>> obj = MyClass()
>>> obj.method()
('instance method called', <MyClass instance at 0x1019381b8>)
>>> obj.classmethod()
('class method called', <class MyClass at 0x101a2f4c8>)
>>> obj.staticmethod()
'static method called'

# Calling instance methods fails
# if we only have the class object:
>>> MyClass.classmethod()
('class method called', <class MyClass at 0x101a2f4c8>)
>>> MyClass.staticmethod()
'static method called'
>>> MyClass.method()
TypeError: 
    "unbound method method() must be called with MyClass "
    "instance as first argument (got nothing instead)"
# The lambda keyword in Python provides a
# shortcut for declaring small and 
# anonymous functions:

>>> add = lambda x, y: x + y
>>> add(5, 3)
8

# You could declare the same add() 
# function with the def keyword:

>>> def add(x, y):
...     return x + y
>>> add(5, 3)
8

# So what's the big fuss about?
# Lambdas are *function expressions*:
>>> (lambda x, y: x + y)(5, 3)
8

# β€’ Lambda functions are single-expression 
# functions that are not necessarily bound
# to a name (they can be anonymous).

# β€’ Lambda functions can't use regular 
# Python statements and always include an
# implicit `return` statement.
# Python 3 has a std lib
# module for working with
# IP addresses:

>>> import ipaddress

>>> ipaddress.ip_address('192.168.1.2')
IPv4Address('192.168.1.2')

>>> ipaddress.ip_address('2001:af3::')
IPv6Address('2001:af3::')

# Learn more here:
# https://docs.python.org/3/library/ipaddress.html
# You can get the name of
# an object's class as a
# string:

>>> class MyClass: pass

>>> obj = MyClass()
>>> obj.__class__.__name__
'MyClass'

# Functions have a
# similar feature:

>>> def myfunc(): pass

>>> myfunc.__name__
'myfunc'
>>> class BaseClass: pass
>>> class SubClass(BaseClass): pass

>>> issubclass(SubClass, BaseClass)
True
>>> issubclass(SubClass, object)
True
>>> issubclass(BaseClass, SubClass)
False
>>> Ο€ = math.pi
>>> class Spin̈alTap: pass
>>> Spin̈alTap()
<Spin̈alTap object at 0x10e58d908>

# Only letter-like characters
# work, however:

>>> 🍺 = "beer"
SyntaxError:
"invalid character in identifier"
# Python 3.3+ has a std
# lib module for displaying
# tracebacks even when Python
# "dies", e.g with a segfault:

import faulthandler
faulthandler.enable()

# Can also be enabled with
# "python -X faulthandler"
# from the command line.

# Learn more here:
# https://docs.python.org/3/library/faulthandler.html
# Virtual Environments ("virtualenvs") keep
# your project dependencies separated.
# They help you avoid version conflicts
# between packages and different versions
# of the Python runtime.

# Before creating & activating a virtualenv:
# `python` and `pip` map to the system
# version of the Python interpreter
# (e.g. Python 2.7)
$ which python
/usr/local/bin/python

# Let's create a fresh virtualenv using
# another version of Python (Python 3):
$ python3 -m venv ./venv

# A virtualenv is just a "Python
# environment in a folder":
$ ls ./venv
bin      include    lib      pyvenv.cfg

# Activating a virtualenv configures the
# current shell session to use the python
# (and pip) commands from the virtualenv
# folder instead of the global environment:
$ source ./venv/bin/activate

# Note how activating a virtualenv modifies
# your shell prompt with a little note
# showing the name of the virtualenv folder:
(venv) $ echo "wee!"

# With an active virtualenv, the `python`
# command maps to the interpreter binary
# *inside the active virtualenv*:
(venv) $ which python
/Users/dan/my-project/venv/bin/python3

# Installing new libraries and frameworks
# with `pip` now installs them *into the
# virtualenv sandbox*, leaving your global
# environment (and any other virtualenvs)
# completely unmodified:
(venv) $ pip install requests

# To get back to the global Python
# environment, run the following command:
(venv) $ deactivate

# (See how the prompt changed back
# to "normal" again?)
$ echo "yay!"

# Deactivating the virtualenv flipped the
# `python` and `pip` commands back to
# the global environment:
$ which python
/usr/local/bin/python
# Python's `for` and `while` loops
# support an `else` clause that executes
# only if the loops terminates without
# hitting a `break` statement.

def contains(haystack, needle):
    """
    Throw a ValueError if `needle` not
    in `haystack`.
    """
    for item in haystack:
        if item == needle:
            break
    else:
        # The `else` here is a
        # "completion clause" that runs
        # only if the loop ran to completion
        # without hitting a `break` statement.
        raise ValueError('Needle not found')


>>> contains([23, 'needle', 0xbadc0ffee], 'needle')
None

>>> contains([23, 42, 0xbadc0ffee], 'needle')
ValueError: "Needle not found"


# Personally, I'm not a fan of the `else`
# "completion clause" in loops because
# I find it confusing. I'd rather do
# something like this:
def better_contains(haystack, needle):
    for item in haystack:
        if item == needle:
            return
    raise ValueError('Needle not found')

# Note: Typically you'd write something
# like this to do a membership test,
# which is much more Pythonic:
if needle not in haystack:
    raise ValueError('Needle not found')
# Pythonic ways of checking if all
# items in a list are equal:

>>> lst = ['a', 'a', 'a']

>>> len(set(lst)) == 1
True

>>> all(x == lst[0] for x in lst)
True

>>> lst.count(lst[0]) == len(lst)
True

# I ordered those from "most Pythonic" to "least Pythonic" 
# and  "least efficient" to "most efficient". 
# The len(set()) solution is idiomatic,  but constructing 
# a set is less efficient memory and speed-wise.
# In Python 3.4+ you can use
# contextlib.suppress() to selectively
# ignore specific exceptions:

import contextlib

with contextlib.suppress(FileNotFoundError):
    os.remove('somefile.tmp')

# This is equivalent to:

try:
    os.remove('somefile.tmp')
except FileNotFoundError:
    pass

# contextlib.suppress docstring: 
#
# "Return a context manager that suppresses any 
#  of the specified exceptions if they occur in the body
#  of a with statement and then resumes execution with 
#  the first statement following the end of 
#  the with statement."
# In Python 3 you can use a bare "*" asterisk
# in function parameter lists to force the
# caller to use keyword arguments for certain
# parameters:

>>> def f(a, b, *, c='x', d='y', e='z'):
...     return 'Hello'

# To pass the value for c, d, and e you 
# will need to explicitly pass it as 
# "key=value" named arguments:
>>> f(1, 2, 'p', 'q', 'v')
TypeError: 
"f() takes 2 positional arguments but 5 were given"

>>> f(1, 2, c='p', d='q',e='v')
'Hello'
# Python's `for` and `while` loops
# support an `else` clause that executes
# only if the loops terminates without
# hitting a `break` statement.

def contains(haystack, needle):
    """
    Throw a ValueError if `needle` not
    in `haystack`.
    """
    for item in haystack:
        if item == needle:
            break
    else:
        # The `else` here is a
        # "completion clause" that runs
        # only if the loop ran to completion
        # without hitting a `break` statement.
        raise ValueError('Needle not found')


>>> contains([23, 'needle', 0xbadc0ffee], 'needle')
None

>>> contains([23, 42, 0xbadc0ffee], 'needle')
ValueError: "Needle not found"


# Personally, I'm not a fan of the `else`
# "completion clause" in loops because
# I find it confusing. I'd rather do
# something like this:
def better_contains(haystack, needle):
    for item in haystack:
        if item == needle:
            return
    raise ValueError('Needle not found')

# Note: Typically you'd write something
# like this to do a membership test,
# which is much more Pythonic:
if needle not in haystack:
    raise ValueError('Needle not found')
# Python 3.5+ allows passing multiple sets
# of keyword arguments ("kwargs") to a
# function within a single call, using
# the "**" syntax:

>>> def process_data(a, b, c, d):
>>>    print(a, b, c, d)

>>> x = {'a': 1, 'b': 2}
>>> y = {'c': 3, 'd': 4}

>>> process_data(**x, **y)
1 2 3 4

>>> process_data(**x, c=23, d=42)
1 2 23 42