Idiomatikus Python
Raymond Hettinger:
Transforming Code into Beautiful, Idiomatic Python
Python alapgondolat
A kódot többször olvassuk mint írjuk.
- jól olvasható
- könnyen érthető
- azt csinálja amit állít hogy csinál (helyes)
- rugalmas, könnyen módosítható
- egyszerű
- rövid ☺
- öndokumentáló
Mitől jó kód a jó kód?
>>> 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!
Python filozófia
Python előnyei
Jól olvasható
- szignifikáns white space
- nincs kapcsos zárójel:
- Beszédes függvénynevek
pl. startswith(), endswith(), zfill()
>>> from __future__ import braces
File "<ipython-input-2-2aebb3fc8ecf>", line 1
from __future__ import braces
SyntaxError: not a chance
Könnyen érthető
- nyelvi konstruktorok
pl.
import json
for line in file:
print(line)
Egyszerű
class Zoo:
...
Könnyen módosítható
def count_banana():
return 5
class Zoo:
@staticmethod
def count_banana():
return 5
Öndokumentáló
class PokerRange:
def to_html(self):
"""Returns a 13x13 HTML table representing the hand range."""
"""
flask
~~~~~
A microframework based on Werkzeug. It's extensively documented
and follows best practice patterns.
:copyright: (c) 2015 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
__init__.py:
Használjuk ki ezeket az előnyöket!
Iteráció
- Sokkal egyszerűbb mint más nyelvekben.
Python vs más nyelvek
iterable = ['Jóska', 'Pista', 'Sári', 'Mariska']
for val in iterable:
print(val)
var iterable = ['Jóska', 'Pista', 'Sári', 'Mariska'];
for (var i = 0; i < iterable.length; i++) {
console.log(iterable[i]);
}
JavaScript
Python
const char *iterable[4];
iterable[0] = "Jóska";
iterable[1] = "Pista";
iterable[2] = "Sári";
iterable[3] = "Mariska";
int i;
for (i = 0; i < 4; i++) {
printf ("%s\n", iterable[i]);
}
C
for i in [0, 1, 2, 3, 4, 5]:
print(i**2)
for i in range(6):
print(i**2)
# python 2:
for i in xrange(6):
print(i**2)
Egyszerű számsor bejárása
colors = ['red', 'green', 'blue', 'yellow']
for i in range(len(colors)):
print(colors[i])
for color in colors:
print(color)
Lista bejárása
colors = ['red', 'green', 'blue', 'yellow']
for i in range(len(colors)-1, -1, -1):
print(colors[i])
for color in reversed(colors):
print(color)
Bejárás visszafelé
i = 0
for color in colors:
print(i, '-->', color)
i += 1
for i, color in enumerate(colors):
print(i, '-->', color)
colors = ['red', 'green', 'blue', 'yellow']
for i in range(len(colors)):
print(i, '-->', colors[i])
Iterálás indexekkel
names = ['raymond', 'rachel', 'matthew']
colors = ['red', 'green', 'blue', 'yellow']
n = min(len(names), len(colors))
for i in range(n):
print(names[i], '-->', colors[i])
Két collection iterálása egyszerre
for name, color in zip(names, colors):
print(name, '-->', color)
# python 2:
for name, color in izip(names, colors):
print(name, '-->', color)
colors = ['red', 'green', 'blue', 'yellow']
for color in sorted(colors):
print(color)
for color in sorted(colors, reverse=True):
print(color)
Iterálás sorrendben
# python 2.4+
print(sorted(colors, key=len)
colors = ['red', 'green', 'blue', 'yellow']
# python <2.4
def compare_length(c1, c2):
if len(c1) < len(c2): return -1
if len(c1) > len(c2): return 1
return 0
print sorted(colors, cmp=compare_length)
Iterálás egyedi sorrendben
blocks = []
while True:
block = f.read(32)
if block == '':
break
blocks.append(block)
def read32():
return f.read(32)
blocks = []
for block in iter(read32, ''):
blocks.append(block)
from functools import partial
blocks = []
for block in iter(partial(f.read, 32), ''):
blocks.append(block)
Függvény hívása bizonyos pontig (sentinel value)
def find(seq, target):
found = False
for i, value in enumerate(seq):
if value == target:
found = True
break
if not found:
return -1
return i
def find(seq, target):
for i, value in enumerate(seq):
if value == target:
break
else: # finished the body
return -1
return i
def find(seq, target):
i = -1
for i, value in enumerate(seq):
if value == target:
break
return i
Több kilépési pont
explicit megkülönböztetése
Dictionary
- A dictionary használatának elsajátítása
alapvető Python skill
- Eszköz kapcsolatok, számlálók és csoportok kifejezésére
Dictionary kulcsok bejárása
d = {'matthew': 'blue', 'rachel': 'green', 'raymond': 'red'}
for k in d:
print(k)
for k in list(d):
if k.startswith('r'):
del d[k]
# Python 2:
for k in d.keys():
if k.startswith('r'):
del d[k]
d = {k: d[k] for k in d if not k.startswith('r')}
Dictionary kulcsok és értékek bejárása
d = {'matthew': 'blue', 'rachel': 'green', 'raymond': 'red'}
for k in d:
print(k, '-->', d[k])
for k, v in d.items():
print(k, '-->', v)
# Python 2:
for k, v in d.iteritems():
print k, '-->', v
Dictionary létrehozása
default értékkel
errors = dict.fromkeys(['sid', 'wsid', 'base64'], 0)
# {'errors': 0, 'wsid': 0, 'base64: 0}
Dictionary létrehozása párokból
names = ['raymond', 'rachel', 'matthew']
colors = ['red', 'green', 'blue']
d = dict(zip(names, colors))
# {'matthew': 'blue', 'rachel': 'green', 'raymond': 'red'}
# Python 2:
d = dict(izip(names, colors))
Számlálás dictionary-vel
colors = ['red', 'green', 'red', 'blue', 'green', 'red']
d = {}
for color in colors:
if color not in d:
d[color] = 0
d[color] += 1
# {'blue': 1, 'green': 2, 'red': 3}
d = {}
for color in colors:
d[color] = d.get(color, 0) + 1
from collections import defaultdict
d = defaultdict(int)
for color in colors:
d[color] += 1
Csoportosítás dictionary-vel
names = ['raymond', 'rachel', 'matthew', 'roger',
'betty', 'melissa', 'judith', 'charlie']
d = {}
for name in names:
key = len(name)
if key not in d:
d[key] = []
d[key].append(name)
# {5: ['roger', 'betty'], 6: ['rachel', 'judith'],
# 7: ['raymond', 'matthew', 'melissa', 'charlie']}
d = {}
for name in names:
key = len(name)
d.setdefault(key, []).append(name)
from collections import defaultdict
d = defaultdict(list)
for name in names:
key = len(names)
d[key].append(name)
Véletlenszeű elem atomic kinyerése dictionary-ből
names = {'matthew': 'blue', 'rachel': 'green',
'raymond': 'red'}
while d:
key, value = d.popitem()
print(key, '-->', value)
Több dictionary összekapcsolása
import os
import argparse
defaults = {'color': 'red', 'user': 'guest'}
parser = argparse.ArgumentParser()
parser.add_argument('-u', '--user')
parser.add_argument('-c', '--color')
namespace = parser.parse_args([])
command_line_args = {k: v for k, v in
vars(namespace).items() if v}
d = defaults.copy()
d.update(os.environ)
d.update(command_line_args)
import os
from collections import ChainMap
d = ChainMap(command_line_args, os.environ, defaults)
Tuple, packing, unpacking
- változók egyidejű változtatása
- kiküszöböl bizonyos hibalehetőségeket
- magasabb szinten való gondolkodást tesz lehetővé
Felsorolás kibontása
p = 'Raymond', 'Hettinger', 0x30, 'python@example.com'
first_name = p[0]
last_name = p[1]
age = p[2]
email = p[3]
first_name, last_name, age, email = p
Több változó változtatása egyszerre
def fibonacci(n):
x = 0
y = 1
for i in range(n):
print(x)
t = y
y = x + y
x = t
def fibonacci(n):
x, y = 0, 1
for i in range(n):
print(x)
x, y = y, x+y
List, dictionary, set comprehensions
Egyszerű list létrehozása
numbers = []
for number in range(10):
numbers.append(number)
numbers = [number for number in range(10)]
numbers = range(10)
dict, set comprehensions
parity = {x: x**2 for x in (2, 4, 6)}
# {2: 4, 4: 16, 6: 36}
even_numbers = {num for num in bigdata if num % 2 == 0}
results = []
for i in range(10):
s = i ** 2
resul.append(s)
print(sum(result))
print(sum([i ** 2 for i in range(10)]))
# Python 2:
print sum([i ** 2 for i in xrange(10)])
Számok összeadása
print(sum(i ** 2 for i in range(10)))
# Python 2:
print sum(i ** 2 for i in xrange(10))
Dekorátorok
Dekorátor használata
"adminisztratív logika" szétválasztására
def web_lookup(url, saved({}):
if url in saved:
return saved[url]
page = urllib.urlopen(url).read()
saved[url] = page
return page
from functools import lru_cache
@lru_cache
def web_lookup(url):
return urllib.urlopen(url).read()
Dekorátor használata
"adminisztratív logika" szétválasztására
from functools import wraps
def lru_cache(func):
saved = {}
@wraps(func)
def newfunc(*args):
if args in saved:
return newfunc(*args)
result = func(*args)
saved[args] = result
return result
return newfunc
Command line példa
@main.command(help='Download ELB logs from S3 bucket. '
'If minute is passed, it will be used, otherwise only hour '
'will be included in prefix.')
@click.argument('wsid', type=ct.WSID)
@click.argument('year', type=ct.YEAR, default='')
@click.argument('month', type=ct.MONTH, default='')
@click.argument('day', type=ct.DAY, default='')
@click.argument('hour', type=ct.HOUR, default='')
@click.argument('minute', type=ct.MINUTE, default='')
@click.pass_context
def download(ctx, wsid, year, month, day, hour, minute):
"""Download ELB logs from S3 bucket."""
Context managerek
Olvasás fileból
with open('big_data.txt') as f:
data = f.read()
f = open('big_data.txt')
try:
data = f.read()
finally:
f.close()
Multi-thread lock
import threading
lock = threading.Lock()
lock.acquire()
try:
print('Critical section 1')
print('Critical section 2')
finally:
lock.release()
import threading
lock = threading.Lock()
with lock:
print('Critical section 1')
print('Critical section 2')
Ideiglenes környezet szétválasztása 1.
from decimal import Decimal, getcontext, setcontext
old_context = getcontext().copy()
getcontext().prec = 50
print(Decimal(335) / Decimal(113))
setcontext(old_context)
from decimal import Decimal, Context, localcontext
with localcontext(Context(prec=50)):
print(Decimal(355) / Decimal(113))
Ideiglenes környezet szétválasztása 2.
with open('pow_help.txt', 'w') as f:
oldstdout = sys.stdout
sys.stdout = f
try:
help(pow)
finally:
sys.stdout = oldstdout
from contextlib import redirect_stdout
with open('pow_help.txt', 'w') as f:
with redirect_stdout(f):
help(pow)
Kivételek
- "Olcsó", vagyis gyors
- Jól látható
- Beszédes stacktrace (főleg Python 3)
- "Natív", (pl. StopIteration)
Hiányzó file probléma
try:
with open('bigdata.txt') as f:
bigdata = f.read().strip()
except FileNotFoundError:
print("File is missing, loading default values")
if os.path.exists('bigdata.txt'):
with open('bigdata.txt') as f:
bigdata = f.read().strip()
Gyakori hiba:
minden kivétel elkapása
try:
do_something()
except:
pass
try:
do_something()
except Exception:
pass
try:
do_something()
except Exception as e:
pass
Megoldás:
a kivételek explicit felsorolása
try:
do_something()
except ValueError as e:
handle_exception(e)
try:
read_file()
except FileNotFoundError as e:
handle_exception(e)
Olvashatóbb kód
- pozícionális argumentek és indexek
vs
- kulcsszavakat és nevek
Függvényhívás
keyword argumentekkel
twitter_search('@obama', retweets=False,
numtweets=20, popular=True)
twitter_search('@obama', False, 20, True)
named tuple
doctest.testmod()
TestResults(failed=0, attempted=4)
doctest.testmod()
(0, 4)
from collections import namedtuple
TestResults = namedtuple('TestResult', ['failed', 'attempted'])
Teranry conditional operator
number = count if count % 2 != 0 else count - 1
name = user.name() if user is not None else 'Guest'
from collections import Counter
colors = ['red', 'green', 'red', 'blue', 'green', 'red', 'red']
colorcount = Counter(colors)
colorcount.most_common()
# [('red', 4), ('green', 2), ('blue', 1)]
Egy jobb számláló
with open('/some/file', 'r') as f:
line_count = Counter(f)
for text in lines:
if re.search(‘[a-zA-Z]\=’, text):
some_action(text)
elif re.search(‘[a-zA-Z]\s\=’, text):
some_other_action(text)
else:
some_default_action()
Funckionális eszköz: partial
def is_grouped_together(text):
return re.search("[a-zA-Z]\s\=", text)
def is_spaced_apart(text):
return re.search(“[a-zA-Z]\s\=”, text)
def and_so_on(text):
return re.search(“pattern_188364625", text)
...
for text in lines:
if is_grouped_together(text):
some_action(text)
elif is_spaced_apart(text):
some_other_action(text)
else:
some_default_action()
def my_search_method():
is_spaced_apart = partial(re.search, '[a-zA-Z]\s\=')
is_grouped_together = partial(re.search, '[a-zA-Z]\=')
...
for text in lines:
if is_grouped_together(text):
some_action(text)
elif is_spaced_apart(text):
some_other_action(text)
else:
some_default_action()
Funckionális eszköz: partial
Hatékonyság
- Ne mozgassunk/másoljunk adatot
feleslegesen
pl. dict.copy(), deepcopy, ezek drága műveletek
Stringek összefűzése
names = ['raymond', 'rachel', 'matthew', 'roger',
'betty', 'melissa', 'judith', 'charlie']
s = names[0]
for name in names[1:]:
s += ', ' + name
print(s)
print(', '.join(names))
Beszúrás felsorolás elejére
names = ['raymond', 'rachel', 'matthew', 'roger',
'betty', 'melissa', 'judith', 'charlie']
del names[0]
names.pop(0)
names.insert(0, 'mark')
from collections import deque
names = deque(['raymond', 'rachel', 'matthew', 'roger',
'betty', 'melissa', 'judith', 'charlie'])
del names[0]
names.popleft()
names.appendleft()
e nyi
==
Idiomatikus Python
By Kiss György
Idiomatikus Python
- 306