6. Modüller

Python yorumlayıcısını kapatıp tekrar açarsanız yaptığınız tanımlar (işlevler ve değişkenler) kaybolur. Uzunca bir yazılım yazmak isterseniz bunun için yazılımınızı bir metin düzenleyici ile hazırlayıp yazdığınız dosyayı yorumlayıcı girişi olarak kullanırsanız daha iyi olur. Bu işleme betik yazmak denir. Programınız uzadıkça bunu daha kolay idare etmek için birkaç dosyaya bölmek isteyebilirsiniz. Yazdığınız bir işlev tanımını kopyalamaya ihtiyaç duymaksızın birkaç yazılımda kullanmayı da isteyebilirsiniz.

Bu iş için Python'da modül denen dosyalar var. Bunlara yazılan tanımlar diğer modüllere ya da etkileşimli kipteki yorumlayıcıya import deyimi ile yüklenebilir.

Modüller .py uzantılı metin dosyalarıdır ve içlerinde Python deyimleri ve tanımları bulur. Bir modül içerisinde __name__ global değişkeninin değeri (bir dizge) o modülün adını verir. Örneğin, favori metin düzenleyiciniz ile fibo.py adlı bir dosya yaratıp Python yorumlayıcısının bulabileceği bir dizine kaydedin. Dosyanın içeriği de şu olsun:

# Fibonacci sayıları modülü

def fib(n):    # n e kadar Fibonacci serisini yazdır
    a, b = 0, 1
    while b < n:
        print b,
        a, b = b, a+b

def fib2(n): # n e kadar Fibonacci serisi geri döndürür
    sonuc = []
    a, b = 0, 1
    while b < n:
        sonuc.append(b)
        a, b = b, a+b
    return sonuc

Yorumlayıcıyı açıp bu modülü şu komut ile yükleyin:

>>> import fibo

Bu fibo içindeki işlev tanımlarını yürürlükte olan simge tablosuna eklemez; sadece modül adı fibo tabloya eklenir. İşlevlere modül adı kullanarak erişilebilir:

>>> fibo.fib(1000)
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
>>> fibo.fib2(100)
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
>>> fibo.__name__
'fibo'

Bir işlevi sık sık kullanmak isterseniz bunu yerel bir isme atayabilirsiniz:

>>> fib = fibo.fib
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377

6.1. Modüller Üzerine Daha Fazla Bilgi

İşlev tanımlarının yanısıra modül içinde çalıştırılabilir ifadeler de olabilir. Bu ifadeler modülün ilk kullanıma hazırlanması için kullanılabilirler ve sadece modülün ilk yüklenişinde çalışır.[70]

Her modülün o modül içindeki bütün işlevler tarafından global simge tablosu olarak kullanılan kendi simge tablosu vardır. Bu özellik sayesinde modülü yazan kişi rahatlıkla modül içnde global değişkenler kullanabilir. Modülü kullanan diğer kişilerin global değişkenleri ile isim çakışması olmaz. Modül içindeki global değişkenlere de modulAdi.degiskenAdi şeklinde ulaşmak ve istenirse bunları değiştirmek mümkündür.

Modüller diğer modülleri yükleyebilir. Bütün import ifadelerinin modülün (ya da betiğin) başına konması gelenektendir; ancak şart değildir. Yüklenen modüller kendilerini yükleyen modülün global simge tablosuna eklenir.

import deyiminin bir modüldeki isimleri doğrudan yükleyen modülün simge tablosuna ekleyen kullanım şekli var. Örnek:

>>> from fibo import fib, fib2
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377

Bu kullanım şeklinde yüklemenin yapıldığı modül adı yerel simge tablosuna eklenmez (yani örnekteki fibo tanımlı değildir).

Bir modülde tanımlanmış bütün isimleri de yüklemek şu şekilde mümkündür:

>>> from fibo import *
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377

Bu altçizgi ( _ ) ile başlayanlar dışındaki bütün isimleri yükler.

6.1.1. Modül Arama Yolu

spam isimli bir modül yüklenmek istendiğinde yorumlayıcı önce çalıştırıldığı dizinde ve sonra PYTHONPATH ortam değişkenince tanımlanan dizinler içinde spam.py isimli bir dosya arar. PYTHONPATH dizin isimlerinden oluşan bir listedir (PATH gibi). Aranan dosya bulunmazsa arama, kuruluma bağlı başka bir yolda da aranabilir. Genelde bu /usr/local/lib/python dizinidir.

Aslında modüller sys.path değişkeninde bulunan dizin listesinde aranır. Bu değişken değerini betiğin alıştırıldığı dizin, PYTHONPATH ve kuruluma bağlı diğer dizinlerden alır. sys.path değişkeni sayesinde Python yazılımları modül arama yolunu değiştirebilir.

6.1.2. "Derlenmiş" Python Dosyaları

Derlenmiş Python dosyaları yazılımların çalışmaya başlaması için gereken süreyi kısaltır. Örneğin spam.py adlı dosyanın bulunduğu dizinde spam.pyc adlı bir dosya varsa bu modul, spam modülünün ikilik derlenmiş halidir. spam.py dosyasının son değiştirilme tarihi spam.pyc dosyasının içinde de kayıtlıdır ve bu tarihler aynı değil ise .pyc dosyası dikkate alınmaz.

spam.pyc dosyasının oluşması için bir şey yapmanız gerekmez. spam.py her ne zaman başarılı olarak derlenirse yazılımın derlenmiş hali spam.pyc dosyasına kaydedilir. Bunun yapılamaması bir hata değildir; herhangi bir nedenle .pyc dosyası tam olarak yazılamazsa geçersiz sayılır ve dikkate alınmaz. .pyc dosyalarının içeriği platformdan bağımsızdır. Bu sayede bir Python modülü dizini farklı mimarideki makineler tarafından paylaşılabilir.

Uzmanlar için birkaç ip ucu:

  • Python yorumlayıcısı -O bağımsız değişkeni ile çalıştırıldığında eniyileştirilmiş (optimized) kod üretilir ve .pyo uzantılı dosyalarda saklanır. Eniyileştircinin (optimizer) şu anda pek bir yararı olmuyor; sadece assert deyimlerini siliyor. -O bağımsız değişkeni kullanıldığında tüm ikilik kod eniyileştirilir, .pyc dosyaları göz ardı edilir ve .py dosyaları eniyileştirilmiş ikilik kod olarak derlenir.

  • Yorumlayıcıya iki tane -O bağımsız değişkeni (-OO) vermek derleyicinin bazı ender durumlarda doğru çalışmayan yazılımlara neden olan eniyileştirmeler yapmasına neden olur. Şu anda sadece __doc__ dizgeleri silinerek daha küçük .pyo dosyaları üretilmektedir. Bazı yazılımların çalışması bunların varlığına bağımlı olabileceğinden bu bağımsız değişkeni kullanırken dikkatli olun.

  • Bir yazılım .pyc ya da .pyo dosyasından okunduğunda .py dosyasından okunan halinden daha hızlı çalışmaz; sadece yüklenme süresi kısalır.

  • Bir betik komut satırından ismi verilerek çalıştırıldığında bunun ikilik kodu asla bir .pyc ya da .pyo dosyasına yazılmaz. Bu yüzden betiğin başlama süresini kısaltmak için bunun bir kısmı bir modüle aktarılarak ve bu modülü yükleyen küçük bir başlatıcı betik kullanılarak kısaltılabilir. Komut satırından bir .pyc ya da .pyo dosyası da ismi verilerek doğrudan çalıştırılabilir.

  • spam.py dosyası olmadan da spam.pyc (ya da -O kullanıldığında spam.pyo) dosyası kullanılabilir. Bunlar bir Python kodu kütüphanesinin tersine mühendisliği zorlaştıran şekilde dağıtılmasında kullanılabilir.

  • compileall modülü bir dizindeki bütün dosyalar için spam.pyc (ya da -O kullanıldığında spam.pyo) dosyaları yaratabilir.

6.2. Standart Modüller

Python zengin bir standart modül kütüphanesine sahiptir. Bazı modüller yorumlayıcı ile bütünleşiktir. Bu modüller dilin parçası olmadıkları halde verimlerini artırmak ya da sistem çağrıları gibi işletim sistemine ait özelliklere erişim için yorumlayıcı içine dahil edilmişlerdir. Bunlara iyi bir örnek her Python yorumlayıcısına dahil edilen sys modülüdür. sys.ps1 ve sys.ps2 değişkenleri de birincil ve ikincil komut satırı olarak kullanılan dizgeleri belirlerler:

>>> import sys
>>> sys.ps1
'>>> '
>>> sys.ps2
'... '
>>> sys.ps1 = 'C> '
C> print 'Böö !'
Böö !
C>

Bu iki değişken yorumlayıcı sadece etkileşimli kipte iken tanımlıdır.

sys.path değişkeni de yorumlayıcının modül arama yolunu belirler. Bu değerini ortam değişkeni PYTHONPATH belirler. PYTHONPATH değişkenine değer atanmadıysa sys.path öntanımlı değerini alır. Bunun değeri listelere uygulana işlemler ile değiştirilebilir:

>>> import sys
>>> sys.path.append('/ufs/guido/lib/python')

6.3. dir() İşlevi

Yerleşik işlev dir() bir modülün hangi isimleri tanımladığını bulmak içik kullanılır. Bu işlev dizgelerden oluşan bir liste geri döndürür:

>>> import fibo, sys
>>> dir(fibo)
['__name__', 'fib', 'fib2']
>>> dir(sys)
['__displayhook__', '__doc__', '__excepthook__', '__name__', '__stderr__',
 '__stdin__', '__stdout__', '_getframe', 'argv', 'builtin_module_names',
 'byteorder', 'copyright', 'displayhook', 'exc_info', 'exc_type',
 'excepthook', 'exec_prefix', 'executable', 'exit', 'getdefaultencoding',
 'getdlopenflags', 'getrecursionlimit', 'getrefcount', 'hexversion',
 'maxint', 'maxunicode', 'modules', 'path', 'platform', 'prefix', 'ps1',
 'ps2', 'setcheckinterval', 'setdlopenflags', 'setprofile',
 'setrecursionlimit', 'settrace', 'stderr', 'stdin', 'stdout', 'version',
 'version_info', 'warnoptions']

Bağımsız değişken kullanmadan çağırılan dir() işlevi o anda tanımlamış olduğunuz isimleri geri döndürür:

>>> a = [1, 2, 3, 4, 5]
>>> import fibo, sys
>>> fib = fibo.fib
>>> dir()
['__name__', 'a', 'fib', 'fibo', 'sys']

Bunun değişken, modül, işlev vs. gibi her tür ismini listelediğine dikkat ediniz.

dir() yerleşik işlev ve değişkenlerin isimlerini listelemez. Bunların bir listesini isterseniz, standart modül __builtin__ içinde bulabilirsiniz:

>>> import __builtin__
>>> dir(__builtin__)
['ArithmeticError', 'AssertionError', 'AttributeError',
 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError',
 'Exception', 'False', 'FloatingPointError', 'IOError', 'ImportError',
 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt',
 'LookupError', 'MemoryError', 'NameError', 'None', 'NotImplemented',
 'NotImplementedError', 'OSError', 'OverflowError', 'OverflowWarning',
 'PendingDeprecationWarning', 'ReferenceError',
 'RuntimeError', 'RuntimeWarning', 'StandardError', 'StopIteration',
 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError',
 'True', 'TypeError', 'UnboundLocalError', 'UnicodeError', 'UserWarning',
 'ValueError', 'Warning', 'ZeroDivisionError', '__debug__', '__doc__',
 '__import__', '__name__', 'abs', 'apply', 'bool', 'buffer',
 'callable', 'chr', 'classmethod', 'cmp', 'coerce', 'compile', 'complex',
 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod',
 'enumerate', 'eval', 'execfile', 'exit', 'file', 'filter', 'float',
 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id',
 'input', 'int', 'intern', 'isinstance', 'issubclass', 'iter',
 'len', 'license', 'list', 'locals', 'long', 'map', 'max', 'min',
 'object', 'oct', 'open', 'ord', 'pow', 'property', 'quit',
 'range', 'raw_input', 'reduce', 'reload', 'repr', 'round',
 'setattr', 'slice', 'staticmethod', 'str', 'string', 'super',
 'tuple', 'type', 'unichr', 'unicode', 'vars', 'xrange', 'zip']

6.4. Paketler

Paketler "noktalı modül isimleri" kullanarak Python'un modül isim alanının düzenlenmesinde kullanılır. Örneğin modül adı A.B adı A olan bir paket içindeki B adlı alt modülü gösterir. Nasıl modüller farklı modül yazarlarını birbirlerinin kullandığı global değişkenleri dert etmekten kurtarıyorsa, paketler de NumPy ya da PyOpenGL gibi çok sayıda modül içeren paketlerin birbirlerinin modül isimlerinin çakışması tehlikesinden kurtarır.

Ses dosyaları ve ses verisi üzerinde işlem yapacak bir modül kolleksiyonu (bir "paket") geliştirmek istediğinizi düşünelim. Farklı biçemlerdeki ses dosyalarını (.wav, .aiff, .au gibi dosya uzantıları olan) birbirine dönüştürmek, seslere efektler uygulamak veya sesleri filtrelemek için pek çok modüle ihtiyacınız olacak. Paketinizin muhtemel dizin yapısı şöyle olabilir:

Sound/                          Paketin en üst seviyesi
      __init__.py               paketi ilk kullanıma hazırlama
      Formats/                  Farklı dosya biçemleri için alt paket
              __init__.py
              wavread.py
              wavwrite.py
              aiffread.py
              aiffwrite.py
              auread.py
              auwrite.py
              ...
      Effects/                  ses efektleri alt paketi
              __init__.py
              echo.py
              surround.py
              reverse.py
              ...
      Filters/                  filtre alt paketi
              __init__.py
              equalizer.py
              vocoder.py
              karaoke.py
              ...

__init__.py dosyaları Python'un bu dizinleri paket içeren dizinler olarak algılaması için gereklidir. Bunlar aynı isimli dizinlerin modül arama yolunda bulunacak diğer geçerli modülleri istemdışı saklamasını engeller. __init__.py boş bir dosya olabileceği gibi paketi ilk çalışmaya hazırlayabilir ya da daha sonra açıklanacak olan __all__ değişkenine değer atıyor olabilir.

Paketin kullanıcısı paketten dilediği bir modülü yükleyebilir.

import Sound.Effects.echo

Bu Sound.Effects.echo modülünü yükler. Modüle tüm ismi ile atıfta bulunulmalı:

Sound.Effects.echo.echofilter(input, output, delay=0.7, atten=4)

Aynı modülü yüklemenin bir diğer yolu:

from Sound.Effects import echo

Bu da echo alt modülünü yükler; ancak bunu paket adı verilmeden erişilebilir kılar ve modül şu şekilde kullanılabilir:

echo.echofilter(input, output, delay=0.7, atten=4)

Bir diğer yol da istenen işlev ya da değişkeni doğrudan yüklemektir:

from Sound.Effects.echo import echofilter

Bu da echo modülünü yükler; ancak echofilter() işlevini doğrudan erişilebilir kılar:

echofilter(input, output, delay=0.7, atten=4)

from PAKET import İSİM kullanılırken İSİM bir alt modül, alt paket ya da paket içinde tanımlı bir işlev, sınıf veya değişken ifade eden herhangi bir isim olabilir. import deyimi önce ismin pakette tanımlı olup olmadığına bakar; tanımlı değil ise bunun bir modül olduğunu varsayar ve bunu yüklemeye teşebbüs eder. Modülü bulamaz ise ImportError istisnası oluşur.

import ÖĞE.ALTÖĞE.ALTALTÖĞE ifadesinde ise son ismin dışındaki isimler paket olmalıdır. Son isim bir modül veya paket olabilir; ancak bir önceki ismin içinde tanımlanan bir işlev ya da değişken olamaz.

6.4.1. Bir paketten * yüklemek

Kullanıcı from Sound.Effects import * yazdığında ne olur? Dosya sistemine ulaşılıp paketin içinde hangi alt paketlerin olduğunun bulunması ve hepsinin yüklenmesi beklenir. Bu uzun zaman alabilir ve alt modüllerin içe aktarılmasının, yalnızca alt modül açıkça içe aktarıldığında ortaya çıkan istenmeyen yan etkileri olabilir.

Tek çözüm paket yazarının açık bir paket indeksi hazırlamasıdır. Bir paketin __init__.py dosyası __all__ adlı bir liste tanımlıyorsa bu liste from PAKET import * ifadesi kullanıldığında yüklenecek modül isimlerinin listesi olarak kullanılır. Paketin yeni bir sürümü hazırlandığında bu listenin uygun şekilde güncellenmesi paket yazarının sorumluğundadır. Eğer paketten * yüklemeye ihtiyaç duyulmayacağına karar verilirse bu özellik kullanılmayabilir. Örneğin Sounds/Effects/__init__.py dosyasının içeriği şöyle olabilir:

__all__ = ["echo", "surround", "reverse"]

Bu from Sound.Effects import * ifadesinin Sound paketinden isimleri __all__ içinde geçen üç modülün yüklemesini sağlar.

__all__ tanımlanmamış ise from Sound.Effects import * ifadesi Sound.Effects paketindeki bütün alt modülleri yürürlükte olan isim alanına yüklemez; sadece Sound.Effects paketinin ve içindeki isimlerin yüklenmesini sağlar (muhtemelen __init__.py) dosyasını çalıştırdıktan sonra). Bundan önceki import deyimlerince yüklenen alt paketler de yüklenir. Şu koda bir bakalım:

import Sound.Effects.echo
import Sound.Effects.surround
from Sound.Effects import *

Bu örnekte echo ve surround modülleri from...import... ifadesi çalıştırıldığında Sound.Effects paketinde tanımlı oldukları için yürürlükte olan isim alanına yüklenir. Bu __all__ tanımlı olduğunda da bu çalışır.

Genel olarak bir modül ya da paketten * yüklemek hoş karşılanmaz; çünkü çoğunlukla zor okunan koda neden olur. Bunun etkileşimli kipte kullanılmasının bir sakıncası yoktur. Ayrıca bazı modüller sadece belirli bir kalıba uyan isimleri verecek şekilde tasarlanmışlardır.

from PAKET import GEREKLİ_ALTMODÜL ifadesini kullanmanın hiç bir kötü tarafı yoktur. Yükleyen modül farklı paketlerden aynı isimli modüller yüklemeye gereksinim duymadığı sürece tavsiye edilen kullanım şekli de budur.

6.4.2. Birbirlerini Yükleyen Modüller

Alt modüller çoğu kez birbirlerine atıfta bulunur. Örneğin surround modülü echo modülüne ihtiyaç duyabilir. Aslında bu türden atıflar öyle yaygındır ki import deyimi standart modül arama yoluna bakmadan önce çağrıldığı paketin içinde arama yapar. Bu şekilde surround modülü import echo veya from echo import echofilter ifadeleri ile kolayca echo modülüne kavuşabilir. Yüklenmek istenen modül içinde bulunan pakette (yükleme yapmaya çalışan modülün bulunduğu paket) bulunamaz ise import deyimi aynı isimli üst seviyeli bir modül arar.

Paketler Sound paketindeki gibi alt paketler şeklinde düzenlenmişler ise farklı alt paketler içindeki modüllerin birbirilerine atıfta bulunmasının kısa bir yolu yoktur; paketin tam adı kullanılmalıdır. Örneğin, Sound.Filters.vocoder modülünün echo modülünü kullanması gerekiyor ise from Sound.Effects import echo ifadesi ile buna erişebilir.



[70] Aslında işlev tanımları da `çalıştırılan' ifadelerdir; işlev adını modülün global simge tablosuna ekler.