İçeriğe geç →

Bilmeniz Gereken 11 Python Mülakat Sorusu

Bu içeriğin orijinaline Toptal üzerinden erişebilirsiniz (Kesinlikle tavsiye ederim çünkü daha anlaşılır ve çeviri hatası/eksiği gibi bir sorun için endişelenmenize gerek kalmaz)

Not: Bu soruların amacı sizlere ufakta olsa rehberlik sağlamasıdır. Burada hileli teknik sorulardan ziyade mülakat soruları var. Bu soruların hepsini bilen aday, işe alınmaya değer olmayabilir ya da soruların hepsini bilmeniz size işi garantilemez.

1. Aşağıdaki kodun çıktısı ne olur?

def extendList(val, list=[]):
    list.append(val)
    return list

list1 = extendList(10)
list2 = extendList(123,[])
list3 = extendList('a')

print "list1 = %s" % list1
print "list2 = %s" % list2
print "list3 = %s" % list3

Kodun çıktısı şöyle olacaktır:

list1 = [10, 'a']
list2 = [123]
list3 = [10, 'a']

Birçok kişi hataya düşüp, list argümanının extendList her çağrıldığı zaman varsayılan değeri olan [] olarak ayarlanacağını düşünüp, list1‘in 10‘a ve list3‘ün ['a'] eşit olacağını bekleyecektir.

Ancak, gerçekte olan yeni varsayılan listenin fonksiyon tanımlandığı zaman, sadece tek seferlik oluşturulduğudur, ve aynı listenin extendList, list argümanı belirtilmeden çağrıldığı her seferde tekrar tekrar kullanıldığıdır.

Bu yüzden, list1 ve list3 aynı varsayılan liste üzerinden işlem görürken, list2 farklı liste üzerinden işlem görüyor (list parametresine değer olarak kendi boş liste geçiyor).

list argümanı belirtilmediği zaman, her zaman için yeni listeyle başlamak için, ki bu daha çok olasılıkla beklenen bir davranıştır, extendList fonksiyonu tanımlaması şöyle düzenlenebilir:

def extendList(val, list=None):
  if list is None:
    list = []
  list.append(val)
  return list

# Çıktı:

list1 = [10]
list2 = [123]
list3 = ['a']

2. Kodun çıktısı ne olur ve multipliers‘ı daha çok olasılıkla beklenen davranışı sergilemesi için nasıl değiştirirsiniz?

def multipliers():
  return [lambda x : i * x for i in range(4)]
    
print [m(2) for m in multipliers()]

Kodun çıktısı [6, 6, 6, 6] olur, [0, 2, 4, 6] değil.

Bunun sebebi Python “closure”nın “late binding” olmasıdır. Çok kısaca;

“closure”, bellekte bulunmasa bile çevrelediği scope içindeki değerleri hatırlayan fonksiyon nesnesidir. Daha fazla detay ve örnek için
buraya tıklayabilirsiniz.

“late binding”, closure içindeki değişkenlerin değerlerinin, içteki fonksiyon çalıştığı zaman değerlendirilmesidir.
Şurada daha detaylı ve örneklerle beraber bir anlatım mevcut.

Böylece sonuç olarak, multipliers() tarafından döndürülen herhangi bir fonksiyon çağrıldığı zaman, i‘nin değerine o andaki çevrelendiği scope içinde bakılır. O zamana kadar, hangi döndürülmüş fonksiyonun çağrıldığından bağımsız olarak, for döngüsü tamamlanmış olur ve i son değeri olan 3 olarak kalır. Bu yüzden, döndürülen her fonksiyon ona geçilen değeri 3 ile çarpar, yukarıdaki örnekte 2 değeri geçildiğinden dolayı, fonksiyonların hepsi 6 değerini döndürür (3 x 2).

( The Hitchhiker’s Guide to Python‘da da bahsi geçtiği üzere, bunun lambda‘larla alakalı olduğuna dair bir şekilde yayılmış yanlış bir algı var, ki durum böyle değildir. lambda ile oluşturulan fonksiyonlar özel değildir ve def ile oluşturulan fonksiyonlar da aynı davranışı sergiler.)

Bu konunun üstesinden gelmek için aşağıda birkaç örnek var.

Çözümlerden birisi, generator kullanmak:

def multipliers():
  for i in range(4): yield lambda x : i * x 

Diğer bir çözüm, varsayılan argümanları kullanarak, hemen o anda argümanlarıyla ilişkilendirilmiş “closure” oluşturmaktır:

def multipliers():
  return [lambda x, i=i : i * x for i in range(4)]

Ya da alternatif olarak, functools.partial fonskiyonunu kullanabilirsiniz:

from functools import partial
from operator import mul

def multipliers():
  return [partial(mul, i) for i in range(4)]

Son olarak, en basit çözüm dönen değerdeki [] yerine ()kullanmak olabilir:

def multipliers():
  return (lambda x : i * x for i in range(4))

3. Aşağıdaki kodun çıktısı nedir?

class Parent(object):
    x = 1

class Child1(Parent):
    pass

class Child2(Parent):
    pass

print Parent.x, Child1.x, Child2.x
Child1.x = 2
print Parent.x, Child1.x, Child2.x
Parent.x = 3
print Parent.x, Child1.x, Child2.x

Kodun çıktısı şöyle olacaktır:

1 1 1
1 2 1
3 2 3

Birçoğunun kafasını karıştıran ya da şaşırtan şey, son satırın 3 2 1 olmasından ziyade 3 2 3 olmasıdır. Neden Parent.x‘in değerinin değişmesi, ayrıca Child2.x‘in değeri değiştirdi, ama aynı zamanda Child1.x‘in değeri değişmedi?

Cevap, Python’da sınıf değişkenlerinin özünde sözlük (dictionary) olarak işlenmesidir. Eğer değişken adı geçerli sınıfın sözlüğünde bulunmazsa, sınıf hiyerarşisi (örneğin, parent sınıflar…) değişken adı bulunana kadar aranır (eğer değişken adı geçerli sınıfta ya da hiyerarşi içinde bulunmazsa, AttributeError hatası meydana gelir).

Bu yüzden, Parentsınıfında x=1 ayarlamak, x sınıf değişkenini (1 değeriyle birlikte) o sınıfta ve o sınıfın herhangi bir çocuğunda (children) referans gösterilebilir ( referenceable) kılar. Bu sebepten ilk print ifadesi 1 1 1 çıktısı verir.

Ardından, eğer herhangi bir çocuk sınıf o değeri ezerse (örneğin, Child1.x = 2 ifadesini çalıştırdığımız zaman), değer sadece o çocuk sınıfta değişir. Bundan dolayı ikinci printifadesi 1 2 1 çıktısı verir.

Son olarak, eğer değer Parent içinde değişirse (örneğin, Parent.x = 3 ifadesini çalıştırdığımız zaman), bu değişiklik değeri ezmemiş herhangi bir çocuk sınıfa da ayrıca yansır (bu örnekte Child2olacaktır). Üçüncü print ifadesinin 3 2 3 çıktısı vermesinin sebebi budur.

4. Aşağıdaki kodun Python 2’deki çıktısı ne olur? Ayrıca, cevap Python 3’de nasıl değişiklik gösterir? (printifadelerinin Python 3’e göre yazıldığını varsayıyoruz elbette)

def div1(x,y):
    print "%s/%s = %s" % (x, y, x/y)
    
def div2(x,y):
    print "%s//%s = %s" % (x, y, x//y)

div1(5,2)
div1(5.,2)
div2(5,2)
div2(5.,2.)

Python 2 için çıktı:

5/2 = 2
5.0/2 = 2.5
5//2 = 2
5.0//2.0 = 2.0

Python 2 varsayılan olarak, eğer işlenen iki değer tam sayıysa, otomatik olarak tam sayı olarak işlem yapar. Sonuç olarak, 5/2 2 verirken, 5./2 2.5 verir.

Python 2’deki bu davranışı değiştirmek için şu import ifadesini kullanabilirsiniz:

from __future__ import division

Ayrıca, çift bölme işareti (//) operatörünün, işlenenlerin tipinden bağımsız her zaman için tam sayıya bölme işlemi gerçekleştireceğine dikkat edin. Python 2’de 5.0//2.0‘ın 2.0 vermesinin nedeni budur.

Ancak Python 3 bu davranışa sahip değildir; örneğin eğer işlenen iki değer tam sayıysa, Python 3 tam sayı olarak işlem yapmaz. Bu yüzden, Python 3’de çıktı şöyle olur:

5/2 = 2.5
5.0/2 = 2.5
5//2 = 2
5.0//2.0 = 2.0

5. Aşağıdaki kodun çıktısı ne olur?

list = ['a', 'b', 'c', 'd', 'e']
print list[10:]

IndexError fırlatmayacak ve çıktı[] olacaktır.

Bekleneceği üzere, listenin eleman sayısını aşan bir index değeri ile bir listenin elemanına ulaşmayı denemek (örn., list[10]‘a erişmeyi denemek ) IndexError ile sonuçlanır.

Ancak, listenin bir parçasına erişmeye çalışırken, başlangıç index değeri olarak listenin eleman sayısını aşan bir index değeri kullanmak IndexErrorolarak sonuçlanmaz ve boş liste döndürür.

Bu durumu özellikle can sıkıcı yapan, “runtime” esnasında hata fırlatılmadığı için bulması zor hatalara yol açabilmesidir.

6. 2, 4, 6 ve 8. satırların çıktısı ne olur?

1. list = [ [ ] ] * 5
2. list  # output?
3. list[0].append(10)
4. list  # output?
5. list[1].append(20)
6. list  # output?
7. list.append(30)
8. list  # output?

Çıktı:

[[], [], [], [], []]
[[10], [10], [10], [10], [10]]
[[10, 20], [10, 20], [10, 20], [10, 20], [10, 20]]
[[10, 20], [10, 20], [10, 20], [10, 20], [10, 20], 30]

Sebebine gelince;

İlk satırın çıktısı muhtemelen tahmin edilebilir ve anlaması kolaydır; list = [ [ ] ] * 5 5 adet listeye sahip bir liste oluşturur.

Ancak, list = [ [ ] ] * 5 ifadesinin anahtar noktası, 5 farklı liste içeren liste oluşturmadığı, aksine, aynı listeyi referans gösteren 5 adet listeyi içeren liste oluşturduğudur. Bu bilgi ile, geri kalan çıktıları daha iyi anlayabiliriz.

list[0].append(10) ilk listeye 10 ekler. Ama tüm 5 liste aynı listeyi referans gösterdiğinden, çıktı şöyledir:

[[10], [10], [10], [10], [10]]

Benzer şekilde, list[1].append(20) ikinci listeye 20 ekler. Ama yine, tüm 5 liste aynı listeyi referans gösterdiğinden, şimdiki çıktı:

[[10, 20], [10, 20], [10, 20], [10, 20], [10, 20]]

Bunlara karşın, list.append(30) “dıştaki” listeye tamamen yeni bir eleman ekler, ki bundan dolayı çıktı:

[[10, 20], [10, 20], [10, 20], [10, 20], [10, 20], 30]

7. N adet sayıya sahip bir listeden, “single list comprehension” kullanarak sadece şu değerleri içeren yeni bir liste üret:

a- çift sayılar ve
b- orijinal listedeki çift indekse sahip elemanlar

Örneğin, eğer list[2] değeri çift sayı ise, bu değer yeni listeye dahil edilmeli, ayrıca orijinal listedeki indeks numarası (2) çift sayı olduğundan dolayı. Ancak, list[3] değeri çift sayı ise, bu değer yeni listeye eklenmemeli çünkü indeks numarası (3) tek sayı.

Bu soru için basit bir çözüm şöyle olabilir:

[x for x in list[::2] if x%2 == 0]

Örneğin, şu listeyi ele alalım:

#        0   1   2   3    4    5    6    7    8
list = [ 1 , 3 , 5 , 8 , 10 , 13 , 18 , 36 , 78 ]

“list comprehension”, [x for x in list[::2] if x%2 == 0], şöyle sonuç verir:

[10, 18, 78]

Yazdığımız “list comprehension” ifadesi, önce çift indeksteki numaraları alarak, sonra tüm tek sayıları eleyerek çalışır.

8. Sözlükten (Dict) türemiş alt sınıf veriliyor:

class DefaultDict(dict):
  def __missing__(self, key):
    return []

Aşağıdaki kod çalışır mı? Neden çalışır ya da çalışmaz?

d = DefaultDict()
d['florp'] = 127

Aslında, Python 2 ya da 3’de, gösterilen kod standart sözlük nesnesi ile çalışır – normal olan davranış budur. Sözlükten alt sınıf yapmak gereksiz. Ancak, alt sınıf gösterilen kod ile çalışmaz, çünkü __missing__ bir değer döndürüyor ama sözlüğün kendisini değiştirmiyor:

d = DefaultDict()
print d
{}
print d['foo']
[]
print d
{}

Yani, “çalışacak”. Çünkü herhangi bir hata üretmeyecek, ama gözüktüğü amaç neyse onu yapmaz.

İşte, sözlüğü güncellediği gibi bir değer de döndüren, __missing__ methodu:

class DefaultDict(dict):
    def __missing__(self, key):
        newval = []
        self[key] = newval
        return newval

Ama 2.5 versiyonundan beri, defaultdict nesnesi collections modülü içinde kullanılabilir durumdadır (standart kütüphane içinde).

9. Aşağıdaki kodu nasıl unit-test yaparsınız?

async def logs(cont, name):
    conn = aiohttp.UnixConnector(path="/var/run/docker.sock")
    async with aiohttp.ClientSession(connector=conn) as session:
        async with session.get(f"http://xx/containers/{cont}/logs?follow=1&stdout=1") as resp:
            async for line in resp.content:
                print(name, line)

İyi bir cevap, belirli bir async mock kütüphanesi ve async test yaklaşımı önerecektir, kesinlikle sonlandırılacak kısa ömürlü bir olay döngüsünü de dahil ederek (örneğin, süre aşımına uğramadan önce maksimum adım sayısı olan)

Çok iyi bir cevap, senkronizasyon problemlerinin senkron ve asenkron kod için temelde aynı olduğunu, farkın “preemption granularity” olduğuna dikkat çekecektir.

Güzel bir cevap, yukarıdaki kodun, kod akışlarının karıştığı ( örn., iki yayımın (stream) tek yayımda birleştirilmesi, sıralama, vs…) kodlara kıyasla sadece tek akışa (kolay) sahip olduğunu hesaba katar. Yukarıdaki kodu şöyle geliştirdiğimizi düşünün:

keep_running = True

async def logs(cont, name):
    conn = aiohttp.UnixConnector(path="/var/run/docker.sock")
    async with aiohttp.ClientSession(connector=conn) as session:
        async with session.get(f"http://xx/containers/{cont}/logs?follow=1&stdout=1") as resp:
            async for line in resp.content:
                if not keep_running:
                    break
                print(name, line)

Global keep_runnin‘in değişmesinin yan etkisi, async ifadelerden herhangi birisini etkilemiş olur.

10. Bir modül içindeki fonksiyonları nasıl listelersiniz?

dir() methodu kullanılarak modül içindeki fonksiyonlar listelenebilir. Örneğin;

import some_module
print dir(some_module)

11. Verilen liste içinde olmayan, ve liste içindeki elemanların toplamı olmayan, en küçük tam sayıyı yazdıran bir fonksiyon yazınız.

Örn, a = [1,2,5,7] için, listede olmayan ya da listenin bir parçasının toplamı ile bulunmayan en küçük sayı 4’dür.

Eğer a = [1,2,2,5,7] ise, o zaman en küçük tam sayı 18’dir.

import  itertools
sum_list = []
stuff = [1,2, 5, 7]
for L in range(0, len(stuff)+1):
    for subset in itertools.combinations(stuff, L):
        sum_list.append(sum(subset))

new_list = list(set(sum_list))
new_list.sort()
for each in range(0,new_list[-1]+2):
    if each not in new_list:
        print(each)
        break

Kategori: Python

Yorumlar

Siz de düşüncelerinizi paylaşın

Bu site, istenmeyenleri azaltmak için Akismet kullanıyor. Yorum verilerinizin nasıl işlendiği hakkında daha fazla bilgi edinin.