Import ifadesi kullanımında karşılaştığım bir sıkıntı

Merhaba arkadaşlar,
Fırat Özgül Bey’in hazırlamış olduğu bu kaynaktaki konular hakkında sormak istediğimiz sorularla alakalı bir başlık olsun istedim.

Benim sormak istediğim soru indeks numarası 45.4. olan Kendi Oluşturduğumuz Paketler konusuyla alakalı.

Önce o konuda işlenen, paket mantığının şablonunu aktarmak istiyorum:

  • paket
    | __ modul1. py
    | __ modul2. py
    | __ modul3. py
  • altdizin
    | __altmodul1. py
    | __altmodul2. py
  1. sayfada örnek üzerinden gidilen şöyle bir açıklama var:

Buradaki temel mantığı kavradığınızı zannediyorum. Standart modülleri incelerken bahsettiğimiz içe aktarma yöntemlerini tek tek yukarıdaki yapıya uygulayarak, buraya kadar
anlattıklarımızı anlayıp anlamadığınızı test edebilirsiniz. Dilerseniz pratik yapmak açısından
bir de altdizin içindeki modüllere uzanalım.

Öncelikle altdizin‘i içe aktaralım:

>> import paket.altdizin

Bu şekilde paket adlı paketin altdizin adlı alt dizinini içe aktarmış olduk. Artık bu alt dizin
içindeki modüllere ve onların niteliklerine erişebiliriz. Mesela paket adlı paketin altdizin adlı
alt dizini içindeki altmodul1 adlı modülün altisim1 niteliğini alalım:

' altmodul1'

Gördüğünüz gibi, altisim1 niteliğine erişmek için uzun bir yol gitmemiz gerekiyor…

Sormak istediğim soru ise bu örneği çalışırken ortaya çıktı:

Adım 1. Aşağıdaki kodları yazdım:

import paket
print(dir(paket))

Olması gerektiği gibi şöyle bir çıktı verdi:

['__doc__', '__loader__', '__name__', '__package__', '__path__', '__spec__']

Adım 2. Modüle ulaşmak için şöyle bir kod yazdım:

from paket import modül1

Bu da olması gerektiği gibi aşağıdaki çıktıyı verdi:

modül1

Adım 3. Sonra kılavuzda verilen yöntemi denedim:

import paket.altdizin
paket.altdizin.altmodul1.altisim1

Ancak bu yöntem aşağıdaki çıktıyı verdi:

AttributeError: module 'paket.altdizin' has no attribute 'altmodul1'

Adım 4: Daha sonra aşağıdaki kodu yazdım:

from paket.altdizin import altmodül1
altmodül1.altisim1

Bu sefer aşağıdaki çıktıyı verdi.

altmodül1

dir(paket) yazdığımızda madem ki sadece özel fonksiyonlar gözüküyor, modüllere ulaşmak için from paket import modül yapmak zorundayız, aynı durum altdizin için de geçerli değil mi?

Yani şöyle bir komut yazıyorum:

import paket.altdizin
print(dir(paket.altdizin))

Aşağıdaki çıktıyı alıyorum:

['__doc__', '__loader__', '__name__', '__package__', '__path__', '__spec__']

O halde import paket.altdizin yazdıktan sonra paket.altdizin.altmodul1.altisim1 yazdığımız zaman altmodül1 ismini almamız imkansız gözüküyor. Ancak kılavuzun 780. sayfasında aynen böyle gösterilmiş. Ya benim henüz daha öğrenmediğim bir durum söz konusu burada ya da muhtemelen yanlış yazılmış diye düşünüyorum.

1 Beğeni

Aşağıdaki gibi bir klasör yapımız var diyelim:

"""+
   |__klasör_1+
   |          |__klasör_1_1+
   |          |            |__test_1_1.py
   |          |
   |          |__klasör_1_2+
   |          |            |__test_1_2.py
   |          |
   |          |__test_1.py
   |
   |__klasör_2+
   |          |__klasör_2_1+
   |          |            |__test_2_1.py
   |          |
   |          |__klasör_2_2+
   |          |            |__test_2_2.py
   |          |
   |          |__test_2.py
   |
   |__test.py"""

Örnek-1: test.py dosyasını çalıştırıyorum. test_py ile klasör_1 ve klasör_2 kardeş dosyalar oldukları için (çünkü aynı dizindeler) aşağıdaki gibi bir komut yazabiliyorum:

import klasör_1, klasör_2

Ve bu kodu çalıştırdığım zaman konsol ekranı olması gerektiği gibi ne hata veriyor ne de ekrana bir şey yazdırıyor.
Bu kez kardeş dosyaların çocuklarına yani yeğenlerine ulaşmak için aşağıdaki komutları yazıyorum:

from klasör_1 import test_1 from klasör_2 import test_2
Dosyaların içinde, kendi dosya isimlerini ekrana bastıran bir print() fonksiyonu tanımlandığı için ekrana bu modüllerin isimleri bastırılıyor. Yani yeğen modüllere de sorunsuz bir şekilde ulaşabiliyoruz.

test.py için klasör_1_1, klasör_1_2, klasör_2_1 ve klasör_2_2 klasörleri ‘‘yeğen’’ konumunda oldukları için, bu ‘‘yeğenlerden birinin çocuğuna’’ ulaşalım diyoruz ve aşağıdaki komutları yazıyoruz:

from klasör_1.klasör_1_1 import test_1_1

Yukarıda kodla yeğenin çocuğu olan test_1_1'e ulaşabiliyoruz.

Bu yapıyı bir soy ağacı gibi düşünürsek aynı yöntemle çok daha ileri gidebileceğimize dair bir sonuç çıkartıyorum. Aynı durum test_1 veya diğer test dosyaları için de geçerli. Kendileriyle kardeş olan dosyaları (yani aynı dizinde bulunan dosyaları), yeğenleri (yani bir alt klasörde bulunan dosyaları) ve kendilerinden çok daha genç nesilleri (daha da alt konumda bulunan klasörleri) import etmekte hiç bir sıkıntı yok.

Ancak benim henüz anlayamadığım bir durum var. Bunu da müsadenizle Örnek-2’de açıklamak istiyorum:

Örnek-2: Bu kez, test_1 dosyasında denemeler yapmak geliyor içimden ve kuzen dosyaya erişmeye çalışıyorum. test_1 için kuzen dosya test_2. Onu import edebilmek için aşağıdaki kodu yazıyorum:

from . import test_2 #SystemError: Parent module '' not loaded, cannot perform relative import
Bu yöntemle kuzen modüle erişemedim. Ama kardeş veya yeğen modüllere erişmekte hala bir sıkıntı yok. Şimdi benim sormak istediğim soru da tam olarak aşağıdaki durumla alakalı:

from . import modül_ismi

Nasıl bir durumda bu ‘.’ ifadesi hata vermeden başarıyla modülü import eder merak etmekteyim.
Python dökümanının şu makalesini okuyorum, diyorum ki aynen burada tarif edildiği gibi yapmaya çalış:

The __init__.py files are required to make Python treat the directories as containing packages; this is done to prevent directories with a common name, such as string, from unintentionally hiding valid modules that occur later on the module search path. In the simplest case, __init__.py can just be an empty file, but it can also execute initialization code for the package or set the __all__ variable, described later.
(Bu arada init.py'nin yazımında sağındaki ve solundaki iki tane alt çizgi görünmüyor)

Yukarıdaki Python makalesine ve birkaç kullanıcının yorumuna göre, her bir dizinin içinde init .py adlı bir dosya bulunması gerekiyormuş. Bu init dosyası en basit durumda boş da olabilir. Dökümandaki açıklamaya göre init.py dosyası Python derleyicisinin dizine bir paket olarak davranmasını sağlıyormuş.

StackOverflow adresinde konuyla ilgili bir başlık açtım. Gelen cevaplardan bir tanesi sistem yoluna (sys.path) bu modülün içinde bulunduğu dizini eklemek:

Diyelim klasör_1 içerisinde bulunan test_1.py dosyamızı çalıştırdığımızda, klasör_2’deki kuzen dosya olan test_2.py modülünü import etmek istiyoruz, bu işlemi yapabilmek için klasör_2 dizinini sistem yoluna aşağıdaki gibi eklememiz gerekiyor:

import sys sys.path.append(“C:\\Users\\TCK\\Desktop\\örnek\\klasör_2”)
Yukarıdaki işlemden sonra test_1.py dosyamıza test_2.py dosyamızı aşağıdaki gibi import edebiliriz.

import test_2
#veya
from klasör_2_2 import test_2_2
#ancak şöyle bir kod hata verecektir.
from klasör_2 import test_2
#ImportError: No module named 'klasör_2'
#sys.path'e klasör_2'yi eklediğimiz için from klasör_2 import  test_2 hata verdiren bir deyimdir.

Biz yukarıdaki kodla sistem yoluna geçici olarak bir yol atamış oluyoruz. Evet, yukarıdaki kod test_1.py dosyasına, o andaki oturum için, sistem yoluna klasör_2’yi ekleyip test_2'yi ve test_2_2'yi yukarıda yazıldıkları gibi import etme imkanı veriyor. Ancak bu ekleme kodunu silip oturumu yeniden başlattığımızda ve print(sys.path) yazdığımızda sanki az önce yapılan ekleme işlemi hiç olmamış gibi bir sonuç alıyoruz. Dolayısıyla her bir oturumda tekrar tekrar import etmek istediğimiz dizinleri yazmak zorundayız. Aynı durum test_2.py dosyası için de, diğer alt test dosyaları için de geçerli. Yani üst dizinde olan bir modüle alt dizinlerdeki modülü import etmek istediğimizde sistem yoluna ekleme yapmamız gerekmiyor. Ancak alt dizindeki bir modüle üst dizinlerdeki bir modülü eklemek istediğimizde sistem yoluna ekleme yapmamız gerekiyor.
Bu yöntem, sorunsuz bir şekilde istediğimiz modülü import etmemize yarar ancak şunu da unutmamak gerekiyor: Dosya dizinlerinde hata verdirecek bir dizin değişikliği yaparsak, hata almamak için, yaptığımız değişikliği kodlarımıza yansıtmamız gerekir.

Konuya gelen cevaplardan diğeri ise oldukça ilginç bulduğum ve henüz tam olarak nasıl yapılacağını çözemediğim bir cevap:
Mesajda bahsedilen yöntem, yazımında ‘.’ işaretini içeren import deyimiyle alakalı. Önerilen kod aşağıdaki gibi bir kod:

from …klasör_1.klasör_1_1 import test_1_1

Bu kodu çalıştırdığım zaman aşağıdaki gibi bir hata alıyorum:

SystemError: Parent module '' not loaded, cannot perform relative import

Bu mesajda daha önce de bahsettiğim hatayla aynı hata bu. Ne denediysem, bir türlü bu yöntemi kullanarak üst dizindeki bir modülü import edemedim. Belki bir yerlerde hata yapıyorumdur veya eksik bir şeyler yazıyorumdur diye düşünüyorum. Mesela bu klasör şemasının, her bir dizinine init .py adlı boş bir dosya ekledim. Yine aynı sonucu elde ettim. Bu konuyla alakalı bilgisi olan arkadaşlar varsa, yardımcı olurlarsa sevinirim. Sorunun çözümüyle alakalı bilgiler edinirsem burada paylaşırım. Son mesaj Python 3 için Türkçe Kılavuz ile pek alakalı değil gibi görünüyor, ancak diğer mesajla bağlantılı olduğunu düşünüyorum. Yine de istenirse, bu konu ayrı bir başlığa da taşınabilir. Saygılar.

2 Beğeni

O zamanlar sormuş olduğum bu soruyu tecrübe eksikliğime bağlamak gerekiyor. modülleri import ederken dikkat edilmesi gereken bazı durumlar var. Yani hangi script bir başka scripti çalıştırırken import ifadesine tek nokta konulur, hangi script için iki veya daha fazla nokta konulur bunlarla alakalı bir örnek varsa bile, bilgilerimizi bir gözden geçirelim istedim.

Bir paket yapmak için öncelikle aşağıdakine benzer bir ağaç hiyerarşisi oluşturmamız gerekiyor.

2019-08-05%2023-13-16%20ekran%20g%C3%B6r%C3%BCnt%C3%BCs%C3%BC

Alt dizin ve dosya sayısını kendi isteğinize göre değiştirebilirsiniz. Bu ağaç hiyerarşisi örneğinde, her bir py dosyası bir başka modülü çağırıyor olacak. hangi modül hangi modülü çağırıyor onu birazdan anlatacağım ama burada, her bir paketin içinde bir tane __init__.py dosyası olduğuna dikkat etmişsinizdir. Onları da size anlatmaya çalışacağım.

Şimdi öncelikle resimdeki her bir parçayı hiyerarşiye uygun bir şekilde anlatmaya çalışayım.

paket isimli dizinde çalıştır.py isimli bir dosya olduğunu görüyorsunuz. Adı ne olursa olsun, bir __main__.py dosyasıdır bu. Dolayısıyla import ifadelerinde . işareti kullanamayacağımız tek dosya bu. Test etmek için şöyle bir şey yapacağım. çalıştır.py isimli dosyanın yanında, test.py isminde bir dosya oluşturuyorum ve içine şunları yazıyorum:

from . import çalıştır

Uç birimde test.py dosyasını çalıştırıyorum. Ve şöyle bir hata alıyorum.

Traceback (most recent call last):
  File "./test.py", line 4, in <module>
    from . import çalıştır
ImportError: cannot import name 'çalıştır'

Peki ne yapmak lazım? Eğer bu dosyada çalıştır.py çağrılacaksa o zaman ifadeyi şöyle değiştirmek gerekiyor.

import çalıştır

test.py'yi yukarıdaki gibi düzenledikten sonra, paket yapısı çalışır bir hale gelecektir. Paketin yaptığı şey aslında çok basit. Ekrana sadece bir tane "hello" yazısı yazdırılacak. Basit bir işlem, ama, burada değişik import deyimleri kullanarak bir dosyanın içinde bulunan print("hello") kodunu çalıştıracağız.

çalıştır.py'nin içinde şu kodlar yazılı:

from paket_1 import paket_1_2

Demek ki çalıştır.py dosyası bir paketi import ediyor. Bir paketi import etmek ile bir modülü import etmek arasında ne fark var? Eğer paketin içinde bir __init__.py dosyası var ise, bu dosya, paketi import ettiğinizde çalıştırılır. Yani bu dosya, paketi temsil eden bir modüldür de aynı zamanda.

Bu __init__.py içinde neler varmış bir bakalım:

from . import mod121

Bu dosyada . işaretini import deyiminde kullanabiliriz.

mod121 dosyamızda ne varmış bir bakalım:

from .. import paket_1_1

Modül bize diyor ki, bir üst dizine çık, oradan paket_1_1'i import et. İki üst dizine çıkmamız gerekseydi ... kullanmamız gerekirdi. Ama burada önemli olan şu, bu dosyaların hiçbiri __main__.py olarak çalıştırılmadıkları için . işaretlerini import ifadelerinde kullanabiliyoruz.

mod121 bizi paket_1_1'e gönderdi. Demek ki bir __init__.py dosyası çalıştırılacak. Hemen o dosyaya bakalım:

from .paket_1_1_1 import mod111

. farklı bir şekilde yine kullanılabiliyor.

Komşu paketin içindeki mod111 dosyasına bakalım:

from . import mod112

O da bizi mod112'ye gönderdi.

mod112'ye bakalım.

print("hello")

Bu import ifadelerine dikkat etmekte fayda var, yoksa hatalarla karşılaşabiliyoruz. Bir hatam varsa lütfen belirtin. Herkese iyi günler dilerim.

1 Beğeni