Python - Kendi Fonksiyonlarımızı Farklı Klasörlerden Çağırmak

Merhaba, daha öncesinde kendi foksiyonlarımızı nasıl kullanacağımıza dair bir soru sormuş ve bunu öğrenmiştim. Şimdi sırada şunu öğrenmek istiyorum.

programımda kullandığım fonksiyonlar türleri ve çeşitleri bakımından fazla olduğu için düzenli görünmesini daha anlaşılır, okunabilir ve takip edilebilir olması için klasörlemeyi istiyorum.

Fonksiyonlar klasörümün latındaki login klasörünün altındaki login.py fonksiyonunu nasıl import ederim?

normal de from fonksiyonlar import login -tek klasör altında olunca işimi görüyor-

klasör altında klasördeki fonksiyonları nasıl çağıracağım?

Denediğim şeyler;

from fonksiyonlar/login import login
from fonksiyonlar\login import login
--
from fonksiyonlar/login import fonksiyonlar/login
from fonksiyonlar\login import fonksiyonlar\login

Soru 1 = Klasör altındaki klasör altındaki fonksiyonu nasıl çalıştıracağım?
Soru 2 = import ederken nerede hata yapıyorum?
Soru 3 = import ettikten sonra fonksiyonum içerisinde nasıl çağıracağım?

Yardımcı olabilirseniz teşekkür ederim.

Merhaba.

Dosya ve sınıflar içerisinden modül veya nesne import etmek için . kullanılıyor. Yani şunun gibi bir şey yapmanız lazım:

from fonksiyonlar.login import login

Daha ayrıntılı (Türkçe) bilgi için buraya bakabilirsiniz.

2 Beğeni

Aşağıdakine benzer bir paket oluşturun:

.
├── dir1
│   ├── __init__.py
│   ├── modul1.py
│   └── modul2.py
├── dir2
│   ├── __init__.py
│   ├── modul3.py
│   └── modul4.py
├── __init__.py
└── __main__.py

Dosyanın içinde şunlar yer alsın:

modul1.py

from .modul2 import fonksiyon2
from ..dir2.modul3 import fonksiyon3, fonksiyon4

fonksiyon2()
fonksiyon3()
fonksiyon4()

modul2.py

def fonksiyon2(): print("fonksiyon2")

modul3.py

from .modul4 import fonksiyon4


def fonksiyon3(): print("fonksiyon3")

modul4.py

def fonksiyon4(): print("fonksiyon4")

__main__.py

if __name__ == "__main__":
    from .dir1 import modul1

Diyelim paketimizin ismi paket. paket'in bulunduğu dizinde konsolu açıyoruz ve aşağıdaki komutu yazıyoruz.

python3 -m paket

Bu yolla kuzen dosyalar olan modul1 ile modul3 ve modul4 arasında bağlantı kurabilirsiniz.

1 Beğeni

Hatta ağaç yapısını biraz daha dallandıralım:

.
├── dir1
│   ├── __init__.py
│   ├── subdir1
│   │   ├── __init__.py
│   │   └── modul1.py
│   └── subdir2
│       ├── __init__.py
│       └── modul2.py
├── dir2
│   ├── __init__.py
│   ├── subdir3
│   │   ├── __init__.py
│   │   └── modul3.py
│   └── subdir4
│       ├── __init__.py
│       └── modul4.py
├── __init__.py
└── __main__.py

Dosyaların içlerinde de şunlar yazıyor:

modul1.py

from ..subdir2.modul2 import fonksiyon2
from ...dir2.subdir3.modul3 import fonksiyon3, fonksiyon4


fonksiyon2()
fonksiyon3()
fonksiyon4()

modul2.py

def fonksiyon2(): print("fonksiyon2")

modul3.py

from ..subdir4.modul4 import fonksiyon4


def fonksiyon3(): print("fonksiyon3")

modul4.py

def fonksiyon4(): print("fonksiyon4")

__main__.py

if __name__ == "__main__":
    from .dir1.subdir1 import modul1

Çalıştırma şeklimiz yine aynı:

python3 -m paket
1 Beğeni

tamamdır teşekkürler en basit şekilde bu işimi gördü sağolun

1 Beğeni

@dildeolupbiten üstat ben bunları okuyamadım ve anlam veremedim. biraz karmaşık geldi bana. işin içinden pek çıkamadım :frowning:

1 Beğeni

Şöyle anlatmaya çalışayım. Ve bu anlattıklarımı adım adım uygulayarak anlamaya çalışın.

Bir klasör oluşturacağız. Bu klasör bizim belli bir amaç için bir araya getireceğimiz python dosyalarını içerecek. Python’da aynı dizindeki python dosyaları birbirini görür. Yani a.py ile b.py aynı dizindeyse, a.py dosyasını b.py dosyasının içine aktarabilirsiniz. Bunun için bir kaç farklı içe aktarma yöntemi var. Hangisini kullanacağınız size kalmış. Bazen sadece bir modüldeki bir veya bir kaç nesneyi diğer programa aktarmak isteyebilirsiniz, bazen de modülde bulunan bütün nesneleri.

Eğer bir dizinde bir tane python dosyası, bir tane klasör varsa ve bu klasörün içinde de bir python dosyası varsa, klasörle aynı dizinde olan python dosyası, klasör içindeki python dosyasını sorunsuz bir şekilde içe aktarabilir.

Benim bir önceki mesajda bahsettiğim, birbirini görmeyen dosyalar ile alakalı. İzin verin bir örnek ile açıklamaya çalışayım.

Bu örneği Linux’e göre anlatıyorum.

İsmi ust_dizin olan bir klasör oluşturalım. Bu ust_dizin klasöründe dizin1 ve dizin2 isminde iki tane alt klasör olsun.

mkdir -p ust_dizin/dizin1 ust_dizin/dizin2

Bu örnekte klasör yapımız bu şekilde olursa, yapısı şuna benzeyecektir:

.
├── dizin1
└── dizin2

Buradaki . işareti şu anda bulunduğumuz dizini temsil eder.

Bu dizin1 ve dizin2 klasörlerinin her birinde bir tane python dosyası oluşturalım.

Önce dizin1'in içine girelim:

cd ust_dizin/dizin1

Şimdi modul1.py isminde içinde print(f"Bu modülün adı {__file__}") ifadesi bulunan bir Python dosyası oluşturalım.

echo "print(f'Bu modülün adı {__file__}')" > modul1.py 

Şimdi de dizin1'den dizin2'ye geçelim ve ismi modul2.py olan bir dosya oluşturalım. Bunun içine de modul1.py'yi aktaralım.

Önce dizin2'ye geçelim:

cd ../dizin2

Dosyamızı oluşturup içine modul1.py'yi aktaralım.

echo "from ..dizin1 import modul1" > modul2.py

Şimdi tekrar ust_dizin klasörümüze geçelim.

cd ..

Klasör yapımızın nasıl gözüktüğüne bakalım:

.
├── dizin1
│   └── modul1.py
└── dizin2
    └── modul2.py

Burada dikkatinizi çekmek istediğim bir durum var. Normalde modul2.py'yi çalıştırarak modul1.py'yi modul2.py'nin içine aktaramayız. Yani aktarırız ama bunun için sys.path.append fonksiyonunu kullanarak modul1.py'yi sys.path listesine eklememiz gerekir. Ama biz burada bunu yapmayacağız, modul2.py'yi ust_dizin'de bir python dosyasının içine aktaracağız. Bu dosyanın adı __main__.py olacak. Öncelikle __main__.py dosyamızı oluşturalım ve modul2.py'yi bu dosyanın içine aktarmaya çalışalım.

Burada iki satır kullanacağım için, bu sefer text dosyası oluşturmak için cat komutunu kullanıyorum. Bunları elle de yapabilirsiniz. Ama ben burada komut satırını kullanmayı tercih edeceğim.

cat > __main__.py

Bu komutu yazdıktan sonra, bir alt satıra geçilir ve bizim dosyanın içine yazılacak metni yazmamız gerekir. Daha sonra da CTRL+D tuşuna basarak cat komutundan çıkarız. __main__.py içine şunları yazacağız.

if __name__ == "__main__":
    from .dizin2 import modul2

Dilerseniz klasörümüzün nasıl göründüğüne tekrar bakalım.

.
├── dizin1
│   └── modul1.py
├── dizin2
│   └── modul2.py
└── __main__.py

Buradaki if __name__ == "__main__" ifadesi dosyamızın komut satırından çağrıldığı zamanki durumu temsil eder. Size kısaca şöyle kodla göstermeye çalışayım:

if __name__ == "__main__":
    print("Dosya komut satırından çalıştırıldı.")
else:
    print("Dosya bir başka dosyaya import edildi.")

İşte yukarıda da __main__.py komut satırından çalıştırılacağı için if __name__ == "__main__": deyimini kullandık.

Bu blokun altında dizin2 klasörünün başında bir adet . görüyorsunuz. Bu tek nokta şuanda bulunduğumuz dizini ifade eder demiştim. __main__.py, dizin2 ile aynı klasörde bulundukları için dizin2'nin başına . getiriyorum ve dizin2'de bulunan modul2 dosyasını __main__.py dosyasına aktarıyorum.

Son durum nedir?
modul1.py dosyası kuzen dizindeki modul2.py dosyasının içine from ..dizin1 import modul1 şeklinde aktarılıyor ve modul2.py dosyası da __main__.py dosyasının içine from .dizin2 import modul2 şeklinde aktarılıyor.

modul1 ile modul2 birbirlerini görmedikleri için iki tane nokta kullanarak bir üst dizine geçmiş oluyoruz, bu durumda from ..dizin1 ifadesi şu demek oluyor, bir üst dizindeki dizin1 klasörüne git, import modul1 ifadesi de bu dizindeki modul1 dosyasını programa aktar demek oluyor.

Bulunduğumuz dizin için tek bir nokta . işareti kullanıyoruz, tıpkı __main__.py dosyasına modul2'yi aktarırken kullandığımız gibi. Bir üst dizin için iki tane nokta .. işareti kullanıyouz, tıpkı modul2.py dosyasına modul1.py dosyasına aktarırken kullandığımız gibi. Şayet daha üst bir dizinden bir dosya aktarmak isteseydik bu sefer üç nokta ... kullanacaktık. Buradaki mantık bu şekilde.

__main__.py dosyasını doğrudan çalıştırmıyoruz. Bizim daha üst bir dizine geçip paketi çalıştırmamız lazım. Bir paketi komut satırından bir script gibi çalıştırmak için -m parametresi kullanılır. Ve Python bu paketin __main__.py dosyasını çalıştırır.
Dolayısıyla ust_dizin'in yer aldığı klasöre geçmemiz gerekiyor.

cd ..

Gördüğünüz gibi komut satırında da üst dizin iki nokta ile temsil ediliyor. Ve burada komut satırını açıp şu komutu yazıyoruz:

python3 -m ust_dizin

Bu komut bizim __main__.py dosyamızı çalıştırıyor. __main__.py dosyası dizin2'de yer alan modul2.py dosyasını içe aktarıyor, modul2.py dosyası da dizin1'in içinde bulunan modul1.py dosyasını içe aktarıyor. Ve sonuç olarak ekrana modul1.py dosyasındaki print ifadesi yazdırılıyor. O da benim bilgisayarımda şu şekilde bir çıktı verdi.

Bu modülün adı /home/tanberk/ust_dizin/dizin1/modul1.py

Bu örnekte göstermedim ama bir önceki mesajlarımda her bir dizinin içinde __init__.py dosyalarının yer aldığını gördünüz. Aslında bu örnekte de her bir dizin için __init__.py dosyası oluşturabilirdik, ama tıpkı yukarıdaki örneklerde olduğu gibi burada da pek bir işimize yaramayacaktı. Peki __init__.py ne yapar, bundan size biraz bahsedeyim.

Mesela tkinter kütüphanesini python betik dosyamızın içine aktarmak istediğimizde şunu yazıyoruz.

import tkinter

Tkinter’i programa aktardıktan sonra, içe aktardığımız dosyanın ne olduğunu öğrenmek için şöyle bir yol izleriz.

print(tkinter.__file__)

Bu yukarıdaki yöntem her modül için geçerli değil. Özellikle pyd uzantılı python dosyaları için bu ifade olumlu sonuç vermiyor. Ama tkinter için bu söz konusu değil. Bu yukarıdaki kodun bilgisayarımdaki ekrana yazdırdığı yazı şu şekilde:

/usr/lib/python3.8/tkinter/__init__.py

Demek ki içe aktardığım dosya aslında __init__.py dosyası. Buradaki mantık şu. Bir klasörün içinde __init__.py oluşturursanız ve o klasörü daha üst bir dizinden python3 klasor_ismi şeklinde çağırırsanız bu __init__.py dosyası çalıştırılır.

Basit bir örnek yapalım:
Bir tane ismi kutuphane olan bir dosya oluşturalım, bu klasörün içinde de __init__.py isminde bir dosya oluşturalım. Dosyanın içine de şunları yazalım:

print(f"Bu dosyanın adı {__file__}")

Sonra kutuphane klasörünün bulunduğu dizinde bir tane python oturumu açalım ve import kutuphane yazalım. Programı içe aktarır aktarmaz alacağımız çıktı şuna benzer olacaktır:

Bu dosyanın adı /home/tanberk/kutuphane/__init__.py

O halde;

python3 -m klasor_ismi, __main__.py dosyasını çalıştırır.

Eğer bu klasor_ismi bir python oturumunun içine import klasor_ismi şeklinde aktarılırsa __init__.py dosyası çalıştırılır.

Umarım anlatabilmişimdir. Burada anlattıklarımı eğer anladıysanız, yukarıdaki kodları bir daha incelemeye çalışın.

5 Beğeni

şimdi okuma imkanım oldu satır satır okuyarak her berini işleyip öğreneceğim

1 Beğeni

Bu konuyla bağlantılı bir şey anlatacağım, onu da incelersiniz.

1 Beğeni

Bir başka örnek daha yapalım. Bu örnekte __main__.py kullanmayacağız. Bunun yerine run.py isminde bir script oluşturacağız ve python3 - m ust_dizin yerine python3 run.py yazarak programı çalıştıracağız.

Sırayla adımlara geçelim:

Programımızın bir ismi olacak. Bu isim diyelim Satem olsun. İsmi Satem olan bir dizin oluşturalım ve bu dizinin içinde ismi Scripts olan bir tane alt dizin olsun. Programımızda birden fazla betik dosyası olacak ve bu betik dosyalarını Scripts isimli klasörde toplamak istiyoruz.

mkdir -p Satem/Scripts

Dizin yapımız oluştu ve şimdilik nasıl göründüğüne bir bakalım isterseniz. Bunun için Linux konsoluna şunu yazıyorum:

tree Satem

Bu komut bana aşağıdaki çıktıyı veriyor:

Satem
└── Scripts

Şimdi Scripts klasörünün içine gidelim ve dosyalarımızı oluşturmaya başlayalım. İki tane dosya oluşturmayı düşündüm. Bunlardan birisinin ismi functions.py, diğeri de examples.py olsun. functions.py dosyasında fonksiyonlar bulunacak, examples.py dosyasında da bu yazacağımız fonksiyonlara argüman atayarak örnekler yapacağız.

Dosyalarımızı oluşturmaya başlayalım:

functions.py

# -*- coding: utf-8 -*-


def cikar(*args):
    """
    Girilen argümanların hepsini çıkaran fonksiyon.
    Çıkarma işlemi soldan sağa doğru yapılır.
    """
    if all(isinstance(i, int) for i in args) or \
            all(isinstance(i, float) for i in args):
        sonuc = args[0]
        args = [*args[1:]]
        while len(args) != 0:
            sonuc -= args[0]
            args.pop(0)
        return sonuc
    else:
        raise ValueError("Demet verisinde sayı olmayan değişkenler var.")


def topla(*args):
    """
    Girilen argümanların hepsini toplayan fonksiyon.
    """
    if all(isinstance(i, int) for i in args) or \
            all(isinstance(i, float) for i in args):
        return sum(args)
    else:
        raise ValueError("Demet verisinde sayı olmayan değişkenler var.")

examples.py dosyamızı oluşturalım.

examples.py

# -*- coding: utf-8 -*-

from .functions import topla, cikar

print(topla(*range(10)))
print(cikar(*range(10)))

Gördüğünüz gibi bu tip modül import etmelerde from ifadesinden hemen sonra bir veya duruma göre bir kaç tane . işareti geliyor. functions.py, examples.py ile aynı dizinde olduğu için sadece bir adet nokta koyduk.

Script klasörünün içini hazır hale getirdik. Şimdi Satem dizinine geri dönelim. Ve programı çalıştıracak ana python dosyamızı yazalım. Bu dosyanın ismi run.py olsun. Siz dilerseniz başka bir isim de seçebilirsiniz.

Ama önce Satem dizinin dizin yapısına bir bakalım.

tree

Not: Satem dizinindeyken tree yazmamız yeterli, şayet dizin dışında olsaydık tree Satem yazmamız gerekirdi.

Bu komut bize şu çıktıyı verdi:

.
└── Scripts
    ├── examples.py
    └── functions.py

run.py dosyamızı oluşturalım.
run.py

#!/usr/bin/python3
# -*- coding: utf-8 -*-

from Scripts import examples

Son kez dizin yapımızın neye benzediğine bakalım:

tree
.
├── run.py
└── Scripts
    ├── examples.py
    └── functions.py

run.py dosyasının kodlarına dikkat etmenizi rica ediyorum. Gördüğünüz gibi burada Scripts klasöründeki examples.py dosyasını import ederken . işaretini kullanmadık. Üstelik run.py ve Scripts klasörü aynı dizinde yer alıyor. Peki neden examples.py içine functions.py dosyasını aktarırken . kullandık da, run.py dosyasına Scripts klasörünü import ederken . kullanmadık?

Çünkü burada __main__.py isminde bir paket dosyamız yok ve bu dosyayı yukarıdaki örnekte olduğu gibi python3 -m Satem şeklinde çalıştırmayacağımız için bir tane run.py oluşturduk ve bu run.py içindeki import etme şekli @EkremDincel’in sizinle paylaştığı import etme şekline benzedi.

O halde;

  1. Doğrudan çalıştıracağımız dosyanın içinde from kutuphane.varsa_bir_alt_dizin import x yapısı kullanılır.
  2. Doğrudan çalıştırmayacağımız dosyanın içinde from .kutuphane.varsa_bir_alt_dizin import x yapısı kullanılır.

Bu son söylediklerimi test etmek için mesela examples.py dosyası içindeki from .functions import topla, cikar ifadesindeki . işaretini kaldırın veya run.py dosyasındaki from Scripts import examples ifadesindeki Scripts'in önüne . koymaya çalışın ve nasıl bir sonuç alıyorsunuz bir bakın.

Son olarak şundan da bahsedeyim:

examples.py dosyasının içine functions modülünü şöyle de import edebilirdik:

from . import functions

print(functions.topla(*range(20)))
print(functions.cikar(*range(20)))

İlkinde sadece .functions konumundan topla ve cikar şeklinde belirli fonksiyonları examples.py dosyasına import ettik.

Son paylaştığım kodlarda ise . konumundan functions dosyasının kendisini examples.py dosyasına import ettik.

from . import kutuphane ifadesi de ancak doğrudan çalıştırmayacağınız scriptler için kullanılabilir. Yani doğrudan çalıştıracağımız run.py dosyasında bu ifadeyi kullanamayız.

Neyse, benim anlatacaklarım bu kadar. Umarım faydalı olmuştur.

2 Beğeni