Python: Fragmenty

Pouczające fragmenty kodu w języku Python

Radomir Dopieralski (STX Next)
pycon@sheep.art.pl

PyCon.pl 2010

Źródła

Zen

>>> import this
...

Identyczność

Identyczność to nie to samo co równość:

>>> 1 == 1.0
True
>>> 1 is 1.0
False
>>> [1] == [1]
True
>>> [1] is [1]
False

is None

Zawsze używamy 'is' i 'is not' do porównywania z None:

>>> class EqualToEverything(object):
...     def __eq__(self, to_what):
...         return True
>>> EqualToEverything() == None
True
>>> EqualToEverything() is None
False

Etykiety/Pojemniki

Zmienne nie są pojemnikami, tylko etykietami

>>> x = [1]
>>> y = x
>>> x is y
True
>>> y = [1]
>>> x is y
False

Modyfikowalne Obiekty

>>> list1 = [1]
>>> list2 = list1 # Nie tworzy kopii!
>>> list1.append(2)
>>> list1
[1, 2]
>>> list2
[1, 2]
>>> list2 = [3, 4]
>>> list1.append(5)
>>> list1
[1, 2, 5]
>>> list2
[3, 4]

Referencje

Parametry są przekazywane przez referencję:

Źle:

>>> def empty_list(what):
...     what = []
>>> list1 = [1, 2, 3]
>>> empty_list(list1)
>>> list1
[1, 2, 3]

Dobrze:

>>> def empty_list(what):
...     what[:] = []
>>> list1 = [1, 2, 3]
>>> empty_list(list1)
>>> list1
[]

Przegadane

Źle:

>>> if x == True:
>>> if x != []:
>>> if x == []:
>>> if x == {}:
>>> if len(x) == 0:
>>> if x == '':
>>> return True if x else False

Dobrze:

>>> if x:
>>> if not x:
>>> return x
>>> return bool(x)

Strażnicy

Źle:

>>> ...
>>> if 0 < x and x < 5:
...     ...

Dobrze:

>>> ...
>>> if 0 < x < 5:
...     ...

Przy okazji:

>>> None < float('-inf') < 0 < float('+inf')
True

Uwaga, () jest większy od większości obiektów, ale to przypadek implementacyjny, nie należy tego uzywać.

Wielokrotne Przypisanie

>>> a = b = c = 0
>>> a, b, c
(0, 0, 0)

Uwaga!

>>> a = b = c = []
>>> a.append(1)
>>> a, b, c
([1], [1], [1])

Rozpakowywanie

>>> a, b, c = 1, 2, 3
>>> a, b, c
(1, 2, 3)
>>> FOO, BAR, BAZ = range(3)
>>> BAR
1
>>> head, tail = 'a, b, c'.split(',', 1)
>>> head, tail
('a', ' b, c')
>>> a, b = 1, 2
>>> b, a = a, b
>>> a, b
(2, 1)
>>> a, b = 1, 2, 3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: too many values to unpack

for else

>>> for i in [1, 2, 3]:
...     print 'a'
... else:
...     print 'b'
...
a
a
a
b
>>> for i in [1, 2, 3]:
...     print 'a'
...     break;
... else:
...     print 'b'
...
a

Krojenie

>>> hello = "Hello world!"
>>> hello[0]
'H'
>>> hello[0:5]
'Hello'
>>> hello[-1]
'!'
>>> hello[1:-1]
'ello world'
>>> hello[:5]+hello[5:]
'Hello world!'
>>> hello[::2]
'Hlowrd'
>>> hello[::-1]
'!dlrow olleH'

Przypisanie Do Plasterków

>>> a = [1, 2, 3, 4]
>>> a[1:3] = [5, 6, 7, 8]
>>> a
[1, 5, 6, 7, 8, 4]
>>> s = list('abc')
>>> s[1:2] = 'ABC'
>>> ''.join(s)
'aABCc'

Szukanie yap-ów

>>> input = u'yap foo yup bar yep yelp'
>>> for prev, this, next in zip(input, input[1:], input[2:]):
...     if prev.lower() == 'y' and next.lower() == 'p':
...         print prev+this+next
...
yap
yup
yep

listcomp

>>> [x+1 for x in (1, 2, 3)]
[2, 3, 4]
>>> [x for x in [1, 2, 3, 4] if x % 2]
[1, 3]
>>> ['(%s)' % c for c in 'a.b.c' if c != '.']
['(a)', '(b)', '(c)']
>>> [x for y in [[1, 2, 3], [4, 5, 6], [7, 8, 9]] for x in y]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> [part.strip() for part in
...  'a, b, c, '.split(',') if part.strip()]
['a', 'b', 'c']

Sortowanie

>>> a = [(0, 1), (3, 0), (1, 2)]
>>> a.sort()
>>> a
[(0, 1), (1, 2), (3, 0)]
>>> import operator
>>> a.sort(key=operator.itemgetter(1))
>>> a
[(3, 0), (0, 1), (1, 2)]
>>> sorted(a)
[(0, 1), (1, 2), (3, 0)]
>>> a
[(3, 0), (0, 1), (1, 2)]

Sumowanie

>>> sum([1, 2, 3])
6
>>> max([1, 2, 3])
3

Źle:

>>> sum([0.0000000001]*10000000)
0.0010000000001300626

Dobrze:

>>> import math
>>> math.fsum([0.0000000001]*10000000)
0.001

Iterowanie

Źle:

>>> a = ['a', 'b', 'c']
>>> for i in xrange(len(a)):
>>>     print a[i]

Dobrze:

>>> for item in ['a', 'b', 'c']:
>>>     print item

Iteratory

>>> class MyIter(object):
...     def __init__(self):
...          self.count = 0
...     def next(self):
...          self.count += 1
...          if self.count > 3:
...              raise StopIteration()
...          return self.count
...
>>> class MyIterable(object):
...     def __iter__(self):
...         return MyIter()
...
>>> for i in MyIterable():
...     print i
...
1
2
3

Generatory

>>> class MyIterable(object):
...     def __iter__(self):
...         yield 1
...         yield 2
...         yield 3
...
>>> for i in MyIterable():
...     print i
...
1
2
3

Filtrowanie

>>> def parenthize(what):
...     for item in what:
...         yield '(%s)' % item
...
>>> ', '.join(parenthize('abc'))
'(a), (b), (c)'

gencomp

>>> g = (c for c in 'a.b.c.d' if c != '.')
>>> list(g)
['a', 'b', 'c']
>>> list(g)
[]
>>> g = iter('abcd')
>>> for a, b in zip(g, g):
...     print a, b
...
a b
c d
>>> g = iter('abcd')
>>> for i in g:
...   print i, g.next()
...
a b
c d

enumerate i zip

>>> for i, c in enumerate('abc'):
...    print i, c
...
0 a
1 b
2 c
>>> def integers():
...     i = 0
...     while True:
...         yield i
...         i += 1
...
>>> for i, c in zip(integers(), 'abc'):
...     print i, c
...
0 a
1 b
2 c

Funkcje Pierwszej Klasy

Funkcje mozna przekazywac jako zmienne i parametry:

>>> def foo(name):
...     return 'hello %s' % name
>>> bar = foo
>>> bar('world')
'hello world'
>>> functions = {
...     'foo': foo,
>>> }
>>> functions['foo']('world')
'hello world'

Wywołanie Funkcji

Łatwo zapomniec wywolac funckje:

>>> f = open('filename')
>>> data = f.read()
>>> f.close
<built-in method close of file object at 0xb786a020>
>>> f.close()

Domyślne Parametry

Dobrze:

>>> def function(foo=1, bar=None):
...     if bar is None:
...         bar = []

Źle:

>>> def append_one(where=[]):
...     where.append(1)
...     return where
>>> append_one()
[1]
>>> append_one()
[1, 1]

Dowolna Liczba Argumentów

>>> def function(first, second, *args, **kwargs):
...     return first, second, args, kwargs
>>> function(1, 2, 3, 4, 5, foo=6, bar=7)
(1, 2, [3, 4, 5], {'foo': 6, 'bar':7})

Łapanie Za Dużo

Źle:

>>> foo = {'bar': 2}
>>> try:
...     print fooo['bar']
>>> except:
...     print "nie ma bar"

Dobrze:

>>> foo = {'bar': 2}
>>> try:
...     print fooo['bar']
>>> except KeyError:
...     print "nie ma bar"

Za Dużo w try

Źle:

>>> foo = {'bar': 2}
>>> try:
...     print foo['bar']
...     baz = foo['bar'] + foo['foo']
>>> except KeyError:
...     print "nie ma bar"

Dobrze:

>>> foo = {'bar': 2}
>>> try:
...     print foo['bar']
>>> except KeyError:
...     print "nie ma bar"
>>> else:
...     baz = foo['bar'] + foo['foo']

Przebaczenie/Pozwolenie

Źle:

>>> foo = {'bar': 2}
>>> if 'bar' in foo:
...     bar = foo['bar']
>>> else:
...     bar = 0
>>> bar = foo['bar'] if foo.has_key('foo') else 0

Dobrze:

>>> foo = {'bar': 2}
>>> try:
...     bar = foo['bar']
>>> except KeyError:
...     bar = 0
>>> bar = foo.get('bar', 0)

finally

>>> f = open('filename')
>>> try:
...     data = f.read()
>>> finally:
...     f.close()

Po nowemu:

>>> from __future__ import with_statement
>>> with open('filename') as f:
...     data = f.read()

Wiele Rodzajów Wyjątków

Źle:

>>> try:
...     f = open('filename')
>>> except OSError, IOError:
...     # OSError jest łapany i przypisywany do zmiennej IOError

Dobrze:

>>> try:
...     f = open('filename')
>>> except (OSError, IOError):
...     print 'something went wrong'

Tylko Niektóre Błędy

>>> try:
...     f = open('filename')
>>> except IOError, e:
...     if e.errno == 2:
...         print "nie ma pliku"
...     else:
...         raise
>>> except OSError:
...     print "blad systemowy"

Słowniki

>>> d1 = {
...     'foo': 1,
...     'bar': 2,
... }
>>> d2 = dict(baz=3, bop=4)
>>> d3 = dict((k, v) for k, v in d.iteritems() if v % 2)
>>> d3
{'foo': 1}
>>> d3.update(d2)
>>> d3
{'foo': 1, 'bar': 2}
>>> d3.update(baz=3)

setdefault

>>> d = {}
>>> for a, b in [(1, 'a'), (1, 'b'), (2, 'a')]:
...     d.setdefault(a, []).append(b)
...
>>> d
{1: ['a', 'b'], 2: ['a']}

defaultdict

>>> import collections
>>> d = collections.defaultdict(list)
>>> for a, b in [(1, 'a'), (1, 'b'), (2, 'a')]:
...     d[a].append(b)
...
>>> d
defaultdict(<type 'list'>, {1: ['a', 'b'], 2: ['a']})

Stos

>>> stack = [root]
>>> while stack:
>>>     node = stack.pop()
>>>     for child in node.children:
>>>         stack.append(child)
>>>     print node

Kolejka

Źle:

>>> queue = [root]
>>> while queue:
>>>     node = queue.pop(0)
>>>     for child in node.children:
>>>         queue.append(child)
>>>     print node

Dobrze:

>>> import collections
>>> queue = collections.deque([root])
>>> while queue:
>>>     node = queue.popleft()
>>>     for child in node.children:
>>>         queue.append(child)
>>>     print node

Domknięcia

>>> def make_adder(additive):
...     def adder(what):
...         return what + additive
...
...     return adder
...
>>> adder = make_adder(3)
>>> adder(2)
5

Domknięcia - Uwaga

>>> def get_closures():
...     closures = []
...     for c in ['A', 'B', 'C']:
...         def closure():
...             return c
...
...         closures.append(closure)
...
...     return closures
...
>>> for c in get_closures():
>>>     print c()
C
C
C

getattr/setattr

Źle:

>>> class Foo(object):
...     foo = 1
...     bar = 2
...
>>> foo = Foo()
>>> attr = 'foo'
>>> x = eval('foo.%s' % attr)
>>> y = 3
>>> exec('foo.%s = %s' % (attr, repr(y))

Dobrze:

>>> x = getattr(foo, attr)
>>> setattr(foo, attr, y)

ast.literal_eval

Źle:

>>> data = open('config.cfg').read()
>>> config = eval(data)

Tak sobie:

>>> import ast
>>> config = ast.literal_eval(data)

Dobrze:

>>> import json
>>> config = json.loads(data)
>>> config = json.load(open('config.cfg'))

Dekoratory

>>> marked = {}
>>> def mark(func):
...     marked[func.__name__] = func
...     return func
...
>>> @mark
... def foo(bar):
...     return bar
...
>>> marked
{'foo': <function foo at 0xb753c72c>}

property

>>> class Foo(object):
...     bar = 3
...     @property
...     def foo(self):
...         return self.bar + 1
...
...     @foo.setter
...     def foo(self, value):
...         self.bar = value - 1
...
...     @foo.deleter
...     def foo(self):
...         self.bar = 0

super

>>> class Base(object):
...     def __init__(self, *args, **kwargs):
...         print 'base'
...
>>> class A(Base):
...     def __init__(self, *args, **kwargs):
...         super(A, self).__init__(*args, **kwargs)
...         print 'A'
...
>>> class B(Base):
...     def __init__(self, *args, **kwargs):
...         super(B, self).__init__(*args, **kwargs)
...         print 'B'
...
>>> class C(A, B):
...     pass
...
>>> c = C()
base
B
A