Nie upodabniaj się do komputera, lecz staraj się upodabniać komputer do siebie.
Dla przykładu nie należy polegać na implementacji łączenia ciągów “w miejscu” (in-place) w formie a+=b lub a=a+b. Takie deklaracje wykonują się dużo wolniej w Jython. W częściach biblioteki gdzie wydajność jest szczególnie ważna w zamian powinna być użyta konstrukcja ”.join(). Taka konstrukcja daje pewność, że połączenie zdarzy się liniowo w czasie pośród różnych implementacji.
Wystrzegać należy się pisania if x kiedy ma się na myśli if x is not None — np. kiedy testuje się czy danej zmiennej czy argumentowi, który domyślnie przyjmuje wartość None została przypisana inna wartość. Inne wartości mogą mieć taki typ (tak jak kontener), że jest on fałszywy w kontekście booleanowskim (?).
Wyjątki typu string w nowym kodzie są zabronione, ponieważ ta właściwość języka zostanie usunięta w Pythonie 2.6.
Moduły i pakiety (packages) powinny definiować własne specyficzne dla siebie klasy wyjątków (exception class), które powinny dziedziczyć z wbudowanej (built-in) klasy wyjątków. Zawsze należy zawierać docstringa klasy.
Mają tutaj również zastosowanie konwencje nazewnictwa klas, chociaż powinno dodawać się sufiks Error do nazwy klasy wyjątku, jeśli wyjątek jest błędem. Wyjątki “nie-błędy” nie potrzebuję specjalnego sufiksu.
class MessageError(Exception): """Base class for errors in the email package."""
Forma z nawiasami jest preferowana, ponieważ kiedy argumenty wyjątku są długie lub zawierają formatowanie ciągów nie zachodzi potrzeba używania znaku kontynuacji linii dzięki otoczeniu nawiasami. Starsza forma będzie niedostępna w Pythonie 3000.
Na przykład:
try: import platform_specific_module except ImportError: platform_specific_module = None
Samotna klauzula except przechwyci także wyjątki SystemExit i KeyboardInterrupt, co uczyni trudniejszym przerwanie działania programu przy pomocy
Dobrą zasadą jest ograniczyć użycie czystego except do dwóch przypadków:
Tak:
try: value = collection[key] except KeyError: return key_not_found(key) else: return handle_value(value)
Nie:
try: # Too broad! return handle_value(collection[key]) except KeyError: # Will also catch KeyError raised by handle_value() return key_not_found(key)
Metody stringów są zawsze dużo szybsze i współdzielą to samo API ze stringami unikod.
startswith() i endswith() są czystsze i powodują mniej błędów. Na przykład:
Tak:
if foo.startswith('bar'):
Nie:
if foo[:3] == 'bar':
Tak:
if isinstance(obj, int):
Nie:
if type(obj) is type(1):
Podczas sprawdzania czy obiekt jest stringiem, należy mieć na uwadze, że to również może być string unikodowy! W Pythonie 2.3 string i unikod mają wspólną klasę bazową, basestring, tak więc można zrobić:
if isinstance(obj, basestring):
Tak:
if not seq: if seq:
Nie:
if len(seq) if not len(seq)
Tak:
if greeting:Nie:
if greeting == True:
Jeszcze gorzej:
if greeting is True:
Napisane na podstawie PEP 8 . Nie jestem profesjonalistą, ale staram się. Tekst jest pomocą przede wszystkim dla mnie. Jeśli komuś jeszcze będzie on pomocny ucieszę się. Jeszcze bardziej będę zadowolony jeśli ktoś doświadczony tu zajrzy i zechce skomentować to, i tamto.
Istnieją pewne rekomendowane konwencje nazewnictwa w Pythonie. Konwencje te powinny być stosowane do nowo pisanych modułów. Nowo pisane pakiety i moduły powinny być utrzymane w tych konwencjach. Kiedy starsza biblioteka reprezentuje inny styl to przy jej rozszerzaniu powinien być zachowany pierwotny styl w celu wewnętrznej spójności.
Nie używać jako nazw zmiennych liter l (L jak Leszek), O (O jak Olga) oraz I (i jak Irena). W przypadku pewnych krojów czcionek litery te są nieodróżnialne od liczby jeden oraz zero.
Moduły powinny mieć krótką nazwę wyłącznie z małych liter. Podkreślenia mogą być używane w nazwach modułów jeśli poprawi to czytelność. Podobnie jest w przypadku pakietów (ang. package) jednak nie zaleca się stosować podkreśleń.
Ponieważ nazwy modułów są mapowane do nazw plików powinny one być odpowiednio krótkie. Zbyt długie nazwy mogą być problemem na starszych wersjach Mac, Windows oraz w DOS.
Kiedy rozszerzeniu napisanym w C/C++ towarzyszy moduł Pythona dodający interfejs wyższego poziomu (bardzo zorientowany obiektowo), nazwa rozszerzenia w C/C++ powinna rozpoczynać pojedynczym podkreśleniem (np. _socket)
Prawie że bez wyjątków, nazwy klas powinny mieć nazwę w konwencji CapsWords. Klasy do użytku wewnętrznego powinno poprzedzać pojedyncze podkreślenie (_CapsWord).
Nazwy funkcyj powinny być pisane małymi literami ze słowami rozdzielanymi przez podkreślenia, jeśli to konieczne aby zwiększyć czytelność (nazwa, nazwa_funkcji)
Zawsze należy używać self jako pierwszego argumentu do metody instancji. Zawsze należy używać cls jako pierwszego argumentu do metody klasy.
Jeśli nazwa argumentu funkcji koliduje z nazwą słowa kluczowego Pythona, zazwyczaj lepiej jest dołączyć pojedyncze podkreślenie na końcu nazwy niż tworzyć nieczytelne skróty lub celowe literówki. W związku z tym print_ jest lepsze niż prnt. Dobrze jeśli uda się uniknąć konfliktu przez użycie synonimu.
Nazwy zmiennych globalnych podlegają tym samym konwencjom co nazwy funkcyj. (Zaleca się aby używać zmiennych globalnych tylko wewnątrz jednego modułu)
Moduły, które są zaprojektowane aby używać ich przez from module import * powinny zawierać mechanizm __all__ aby zapobiec eksportowaniu zmiennych globalnych lub stosować starszą konwencję dołączania przedrostka – pojedynczego podkreślenia (co można chcieć zrobić by wskazać te zmienne globalne jako “niepubliczne”).
Stosować zasady dotyczące nazw funkcyj.
Poprzedzające pojedyncze podkreślenie dla niepublicznych metod i zmiennych instancji.
Unikać konfliktu nazw z podklasą przez użycie podwójnego poprzedzającego podkreślenia aby wywołać mechanizm dekorowania nazw Pythona (Python’s mangling rules).
Stałe są deklarowane na poziomie modułu i pisane wielkimi literami z pokreśleniami jako separatorami słów. Np. MAX_OVERFLOW i TOTAL.
Zawsze należy zdecydować czy metody i zmienne instancji (wspólne określenie to atrybuty) mają być publiczne czy niepubliczne. W razie wątpliwości należy uczynić atrybut niepublicznym, później łatwiej zrobić z niego atrybut publiczny niż na odwrót.
Publiczne atrybuty to te, co do których należy się spodziewać, że będą użyte przez niezwiązane klienty, ze “zobowiązaniem”, że zmiany w module/pakiecie nie będą powodowały niekompatybilności wstecz. Niepubliczne atrybuty to te, które nie są tworzone w zamierzeniu do użycia przez programy trzecie. Nie ma potrzeby zapewniać, że niepubliczne atrybuty nie zmienią się lub nawet nie będą usunięte.
Używa się terminu “niepubliczny” gdyż tak na prawdę żaden atrybut w Pythonie nie jest prywatny.
Kolejna kategoria atrybutów to te, które są częścią “podklasowego API” (”subclass API”) – nazywane w innych językach programowania – protected. Niektóre klasy są przeznaczone do dziedziczenia aby rozszerzać lub modyfikować różne aspekty zachowania klasy. Podczas projektowania takiej klasy należy podjąć wyraźne decyzje dotyczące tego jakie atrybuty mają być publiczne, a które są częścią “podklasowego API”, i które będą na prawdę używane tylko przez klasę bazową.
Biorąc pod uwagę powyższe, oto Pythonowe wskazówki.
Zobacz powyższe rekomendacje dla nazewnictwa metod klas.
PEP 8 to jest podręcznik z wytycznymi na temat stylu kodu w Pythonie, a powyższy tekst to moja ściąga z PEP 8, napisana na poziomie mojej wiedzy z języka Python. Ma służyć dla mnie jako pomoc w opanowaniu języka.
Do późniejszego uzupełnienia.
Wcięcia:
Spacje czy tabulatory:
Maksymalna długość linii:
def __init__(self, width, height, color='black', emphasis=None, highlight=0): if width == 0 and height == 0 and \ color == 'red' and emphasis == 'strong' or \ highlight > 100: raise ValueError("sorry, you lose") if width == 0 and height == 0 and (color == 'red' or emphasis is None): raise ValueError("I don't think so -- values are %s, %s" % (width, height)) Blob.__init__(self, width, height, color, emphasis, highlight)
Puste linie:
import os import sys
Źle:
import sys, os
ale poniższe jest w porządku:
from subprocess import Popen, PIPE
Powinno się umieszczać pustą linię pomiędzy powyższymi trzema grupami importów
from myclass import MyClass from foo.bar.yourclass import YourClass
lecz jeśli występuje konflikt nazw z lokalnymi klasami należy użyć:
import myclass import foo.bar.yourclass
i używa się “myclass.MyClass” oraz “foo.bar.yourclass.YourClass”
spam(ham[1], {eggs: 2}) if x == 4: print x, y; x, y = y, x spam(1) dict['key'] = list[index] x = 1 y = 2 long_variable = 3
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)
if foo == 'blah': do_blah_thing() do_one() do_two() do_three()
if foo == 'blah': do_blah_thing() for x in lst: total += x wile t < 10: t = delay()
Absolutnie nie:
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()
Bloki komentarzy.
Komentarze w linii z kodem
x = x + 1 # increment x by 1
Teksty dokumentacji (Docstrings)
"""Return a foobang
Optional plotz says to frobnicate the bizbaz first.
""" """Optional plotz says to frobnicate the bizbaz first."""Version Bookkeeping (?)
Napisane na podstawie PEP 8 . Nie jestem profesjonalistą, ale staram się. Tekst jest pomocą przede wszystkim dla mnie. Jeśli komuś jeszcze będzie on pomocny ucieszę się. Jeszcze bardziej będę zadowolony jeśli ktoś doświadczony tu zajrzy i zechce skomentować to, i tamto.
Jeśli posiadamy konto na flickr.com – systemu zarządzania zdjęciami online, to nie tylko możemy zarządzać fotkami przez WWW. Flickr udostępnia API służące wykorzystaniu usług serwisu w tworzeniu własnych aplikacji. Dostępne jest API w wielu językach programowania takich jak PHP, Python, C, Java, Perl i wielu innych.
Oto kilka “sztuczek” na wykorzystanie flickr.com w swojej aplikacji przy pomocy języka Python. Ja korzystam z jednej z kilku implementacji w/w api w Pythonie – flickrapi. Będziemy też potrzebować klucz api_key, który możemy zdobyć bez większych formalności aby korzystać z możliwości usług flickr.com do celów niekomercyjnych. Do pewnych celów może się także przydać ID użytkownika flickr.
Po zainstalowaniu możemy rozpocząć pracę. Wynik powyższego kodu zwróci odpowiedź parsowaną przez XMLNode.
import flickrapi # import zainstalowanego API
from re import match
api_key="nasze_flickr_api_key" # api_key
flickr = flickrapi.FlickrAPI(api_key, format='xmlnode')
#columns_num = 2
#number_of_photos='8'
user_id='30569663@N05'
def getLastPhotos(id, no_photos=8):
"""
get last photos form my flicrk public galleries
id - string (id użytkownika flickr)
no_photos - string (ilosc ostatnio załadowanych fotek)
"""
last_photos = flickr.photos_search(user_id='30569663@N05',per_page=no_photos).photos[0].photo
photo_urls = []
counter = 0
for photo in last_photos:
counter += 1
if counter % columns_num == 0:
end_row = 1
else:
end_row = 0
photo_urls.append(('http://farm' + photo['farm']+'.static.flickr.com/' + photo['server']+'/' + photo['id']+'_' + photo['secret']+'_s.jpg',
'http://farm' + photo['farm']+'.static.flickr.com/' + photo['server']+'/' + photo['id']+'_' + photo['secret']+'.jpg',end_row))
return photo_urls
Wykonanie powyższej funkcjo zwróci poniższy wynik. Dodam, że flickr udostępnia także dokumentację dotyczącą źródłowych linków do obrazów.
[(u'http://farm4.static.flickr.com/3087/3109827158_a4044631d6_s.jpg', u'http://farm4.static.flickr.com/3087/3109827158_a4044631d6.jpg', 0), (u'http://farm4.static.flickr.com/3138/3108995151_5fc5427650_s.jpg', u'http://farm4.static.flickr.com/3138/3108995151_5fc5427650.jpg', 1), (u'http://farm4.static.flickr.com/3098/3109825840_edc94d758b_s.jpg', u'http://farm4.static.flickr.com/3098/3109825840_edc94d758b.jpg', 0), (u'http://farm4.static.flickr.com/3287/3109825280_a5ebf14139_s.jpg', u'http://farm4.static.flickr.com/3287/3109825280_a5ebf14139.jpg', 1), (u'http://farm4.static.flickr.com/3050/3109824650_a2a524f4af_s.jpg', u'http://farm4.static.flickr.com/3050/3109824650_a2a524f4af.jpg', 0), (u'http://farm4.static.flickr.com/3067/3108992495_682ee07c6e_s.jpg', u'http://farm4.static.flickr.com/3067/3108992495_682ee07c6e.jpg', 1), (u'http://farm4.static.flickr.com/3094/3108991759_bb18c2b791_s.jpg', u'http://farm4.static.flickr.com/3094/3108991759_bb18c2b791.jpg', 0), (u'http://farm4.static.flickr.com/3157/3109822390_929b038782_s.jpg', u'http://farm4.static.flickr.com/3157/3109822390_929b038782.jpg', 1)]
Kolejną przydatną funkcjonalnością może być wyciągnięcie informacji exif o zdjęciu: Zmieniamy parser odpowiedzi na ElementTree. W pythonie 2.4 moduł elementtree należy zainstalować we własnym zakresie.
# teraz format zwracanych danych będzie inny.
flickr = flickrapi.FlickrAPI(api_key, format='etree')
def get_exif(id)
"""
get photo information for flickr photo with given id
return dict with all exif and url info
id - (str) flickr photo id
"""
photo = flickr_etree.photos_getExif(photo_id = id)[0]
exif_info = photo.attrib
for i in photo.getchildren():
if match(r'EXIF|TIFF',i.attrib['tagspace']):
exiftag = i.getiterator()
for j in exiftag:
if j.tag == 'clean':
exif_info.update({i.attrib['label']:j.text})
elif j.tag == 'raw':
hasKey = exif_info.has_key(i.attrib['label'])
if hasKey == False:
exif_info.update({i.attrib['label']:j.text})
return exif_info
Oto przykładowy wynik:
{'Aperture': 'f/3.6',
'Color Space': 'sRGB',
'Compressed Bits per Pixel': '4 bits',
'Compression': 'JPEG',
'Date and Time': '2007:12:22 08:10:07',
'Date and Time (Digitized)': '2007:12:22 08:10:07',
'Date and Time (Original)': '2007:12:22 08:10:07',
'Digital Zoom Ratio': '0/10',
'Exposure': '0.01 sec (1/100)',
'Exposure Bias': '0/100 EV',
'Exposure Program': 'Normal',
'Flash': 'Flash did not fire',
'Focal Length': '6 mm',
'Focal Length In 35mm Film': '36',
'Gain Control': 'Low gain up',
'ISO Speed': '200',
'Light Source': 'Fine weather',
'Make': 'Panasonic',
'Maximum Lens Aperture': '30/10',
'Metering Mode': 'Pattern',
'Model': 'DMC-FZ8',
'Orientation': 'Horizontal (normal)',
'Pixel X-Dimension': '3072',
'Pixel Y-Dimension': '2304',
'Resolution Unit': 'Inches',
'Sensing Method': 'One-chip colour area sensor',
'Software': 'Ver.1.0',
'White Balance': 'Manual',
'X-Resolution': '72',
'Y-Resolution': '72',
'YCbCr Positioning': 'Co-Sited',
'farm': '4',
'id': '3068551408',
'secret': 'a8f6fe491b',
'server': '3030'}
I jeszcze dwie proste funkcje:
def get_info(id):
"""
get photo information for flickr photo with given id
return dict with all information needed to get photo url
id - (str) flickr photo id
"""
photo = flickr_etree.photos_getExif(photo_id = id)[0]
photo_info = photo.attrib
return photo_info
def get_sizes(id):
"""
get url to all sizes of photo on flickr with given photo id
return dict with all information needed to get photo url
id - (str) flickr photo id
"""
photo = flickr_etree.photos_getSizes(photo_id = id)[0]
tmp_sizes = photo.getchildren()
sizes = dict()
for i in tmp_sizes:
sizes.update({i.attrib['label'].lower():i.attrib})
return sizes
Dzięki tym dwóm ostatnim funkcjom zdobędziemy informacje o zdjęciu. Pierwsza z nich zwróci informacje potrzebne do konstruowania źródłowego URL do zdjęcia, natomiast druga zwróci pythonowy słownik (dict) zawierający linki źródłowe do zdjęć oraz URL do strony flickr ze zdjęciem.
Kilka dni temu tłukłem się ze znalezieniem błędu w kodzie aplikacji mojego sajtu. Przez dłuższy czas nie wiedziałem co jest nie tak. Problem był dosyć dziwny. Problem dotyczył aplikacji FlatPages w Django, tak myślałem na początku. Ukończyłem pewien etap i wystawiłem nowa wersję aplikacji aby serwowała moje strony WWW. Oczywiście przy publicznym dostępnie do strony przełączyłem ustawiłem zmienną DEBUG=False w settings.py. Ku mojemu zaskoczeniu wszystkie próby otwarcia stron statycznych, obsługiwanych przez FlatPages kończyły się błędem Http 500. Przy ustawionej zmiennej DEBUG=True wszystko działało jak zakładałem.
W związku z powyższym znalezienie błędu było trochę uciążliwe. Najpierw wypróbowałem sposób podsunięty przez DIrk Ye. Logowanie “traceback’a” (jeśli można tak napisać) do bazy danych jest wygodnym rozwiązanie, lecz nie dawało efektów ponieważ przy obu ustawieniach zmiennej DEBUG logowany był cały czas ten sam błąd. Dopiero po przejrzeniu spisu zmiennych Django znalazłem jedną (DEBUG_PROPAGATE_EXCEPTIONS), która wyglądała interesująco w powiązaniu z moim przypadkiem. Dopisałem linijkę do settings.py mojej aplikacji:
DEBUG_PROPAGATE_EXCEPTIONS = TrueZmiennej należy używać tylko w celach testowych, nigdy w produkcji. Powoduje ona jakby wypchnięcie “tracebakca” na sam wierzch, np. może zostać wyświetlony na stronie. Zmienna została wprowadzona w wersji 1.0 framworka Django. Dzięki temu ustawieniu okazało się, że miałem błąd w szablonie statycznych stron. Usunąłem ten błąd i wszystko jest ok.
Dodatkowa podpowiedź może się przydać. Jeśli chcemy kontrolować treść wyświetlaną na stronie z poziomu szablonu w zależności od ustawienia zmiennej DEBUG to powinniśmy wprowadzić kilka zmian w settings.py.
'django.core.context_processors.debug'czyli np.:
TEMPLATE_CONTEXT_PROCESSORS = ( 'django.core.context_processors.i18n', 'django.core.context_processors.auth', 'django.core.context_processors.debug', )
from django.template import RequestContext
return render_to_response( 'template.html', {'context':context}, context_instance=RequestContext(request) )
W takim wypadku mamy do dyspozycji w szablonie dwie zmienne debug i sql_queries.
Więcej w dokumentacji Django.