Logo MockIT na ciemnym tle.
Umów rozmowęBlog
Zaloguj się
Logo MockIT na ciemnym tle.
Logo MockIT.

Masz wrażenie, że nikt nie czyta Twojego CV? Nie dostajesz zaproszeń na rozmowy rekrutacyjne?

Sprawdź swoje umiejętności techniczne a MockIT pomoże Ci znaleźć pracę w IT!

Zapisz się na rozmowę
Powrót

Python Developer - zaawansowane pytania i zadania rekrutacyjne

W tym artykule przyjrzymy się pytaniom których możesz się spodziewać podczas rozmowy rekrutacyjnej na stanowiska, które wymagają już pewnego doświadczenia w Pythonie.

Michał Kandybowicz

Backend Team Lead - Rebell Pay


24 czerwca 2024

Intro

Python to bardzo popularny język programowania, znany ze swojej prostoty, czytelności i szerokiego zastosowania. Niezależnie od tego, czy jesteś początkującym programistą, który dopiero zaczyna swoją przygodę z kodowaniem, czy doświadczonym deweloperem szukającym odpowiedzi na specyficzne pytania techniczne - w tym artykule znajdziesz coś dla siebie.

Odpowiem w nim na dziewięć technicznych pytań pojawiających się na rozmowach rekrutacyjnych na stanowisko Python Developera. Rekruterzy uwielbiają zadawać podchwytliwe pytania. Dlatego, zapoznanie się z tymi pytaniami pomoże Ci przygotować się do rozmów rekrutacyjnych. Co więcej, odpowiedzi na nie powinien znać każdy obecny i przyszły programista języka Python.

Jakie są różnice między list comprehension a generator expressions?

List comprehensions i generator expressions to dwie konstrukcje w Pythonie do tworzenia sekwencji danych. Chociaż mają bardzo podobną składnię, różnią się pod względem wydajności, szybkości i zachowania.

Tworzenie Obiektów

List Comprehensions: Tworzą pełną listę w pamięci. Oznacza to, że wszystkie elementy są generowane od razu i przechowywane w pamięci.

Generator Expressions: Tworzą generator, który produkuje elementy na bieżąco, jeden po drugim, gdy są potrzebne. Nie tworzy pełnej listy w pamięci.

Zarządzanie Pamięcią

List Comprehensions: Może być mniej wydajne, jeśli lista jest duża, ponieważ cała lista jest przechowywana w pamięci.

Generator Expressions: Bardziej efektywne pamięciowo, ponieważ elementy są generowane 'leniwie' (lazy evaluation) i nie są przechowywane wszystkie naraz w pamięci.

Czas Wykonania

List Comprehensions: Mają tendencję do bycia szybszymi, jeśli potrzebujesz przetworzyć wszystkie elementy, ponieważ cała lista jest dostępna od razu.

Generator Expressions: Mogą być wolniejsze w przetwarzaniu wszystkich elementów, ponieważ elementy są generowane na żądanie, co dodaje narzut czasowy dla każdej operacji 'next'.

Kiedy wykorzystywać

List Comprehensions: Są idealne, gdy potrzebujesz dostępu do wszystkich elementów naraz lub gdy wynikowa lista ma być użyta wielokrotnie.

Generator Expressions: Są lepsze, gdy pracujesz z dużymi zbiorami danych lub strumieniami danych, których nie można załadować w całości do pamięci lub gdy chcesz przetworzyć elementy tylko raz.

# List comprehension list_comp = [x * 2 for x in range(10)] print(list_comp) # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18] # Iterating over List comprehension for x in list_comp: print(x) # 0, 2, 4, 6, 8, 10, 12, 14, 16, 18 (one by one) # Generator expression gen_exp = (x * 2 for x in range(10)) print(gen_exp) # <generator object <genexpr> at 0x...> # Iterating over generator for x in gen_exp: print(x) # 0, 2, 4, 6, 8, 10, 12, 14, 16, 18 (one by one)

Jak działa mechanizm zarządzania pamięcią w Pythonie. Jak działa garbage collection (GC) i kiedy może być wywołana ręcznie?

Mechanizm zarządzania pamięcią w Pythonie odpowiada za przydzielanie i zwalnianie pamięci w aplikacjach napisanych w tym języku. Oto, jak działa w prostych słowach.

Przydzielanie pamięci

Kiedy tworzysz nowy obiekt (np. liczbę, listę, słownik), Python automatycznie przydziela dla niego pamięć. Nie musisz się martwić o zarządzanie tą pamięcią ręcznie.

Zwalnianie pamięci

Gdy obiekt nie jest już potrzebny (czyli nie ma żadnej zmiennej, która by się do niego odwoływała), pamięć, którą zajmował, powinna być zwolniona, aby mogła być użyta przez inne obiekty.

Garbage Collection

Zbieranie Śmieci Garbage collection to mechanizm, który automatycznie zwalnia pamięć zajmowaną przez obiekty, które nie są już używane. W Pythonie działa to na dwa główne sposoby

Zliczanie referencji

Każdy obiekt ma licznik, który mówi, ile zmiennych (referencji) się do niego odwołuje. Kiedy licznik spada do zera, obiekt jest usuwany, a pamięć zwalniana.

Cykliczne zależności

Python ma mechanizm wykrywania i usuwania cyklicznych zależności (np. dwa obiekty, które odwołują się do siebie nawzajem, ale nie są używane w kodzie). Do tego używa tzw. generacyjnego garbage collectora, który okresowo sprawdza obiekty w poszukiwaniu cykli.

Ręczne wywołanie garbage collection

Choć Python automatycznie zarządza pamięcią, możliwe jest wywołanie go ręcznie, można to zrobić, używając modułu gc.

import gc # Function to create a cycle of references def create_cycle(): list_a = [] list_b = [list_a] list_a.append(list_b) # After the end of the function, list_a and list_b still exist in memory as a cycle of references create_cycle() # Before running GC print(f"Objects in memory: {len(gc.get_objects())}") # Running GC manually gc.collect() # After running GC print(f"Objects in memory: {len(gc.get_objects())}")

Jak implementowane są dekoratory w Pythonie i w jakich przypadkach są szczególnie użyteczne?

Dekoratory w Pythonie są specjalnymi funkcjami, które modyfikują zachowanie innych funkcji lub metod. Pozwalają one na 'opakowanie' jednej funkcji przez inną, co daje możliwość dodania dodatkowej funkcjonalności przed lub po wykonaniu oryginalnej funkcji, bez zmiany jej kodu źródłowego.

Dekoratory są szczególnie użyteczne w następujących przypadkach.

Logowanie i monitorowanie

Umożliwiają śledzenie wywołań funkcji, co jest przydatne przy debugowaniu i monitorowaniu działania aplikacji.

def simple_decorator(func): def wrapper(): print("Before running func") func() print("After running func") return wrapper @simple_decorator def say_hello(): print("Hello!") say_hello()

Autoryzacja i uwierzytelnianie

Mogą sprawdzić, czy użytkownik ma odpowiednie uprawnienia przed wykonaniem określonej funkcji.

def requires_auth(func): def wrapper(user, *args, **kwargs): if not user.get('is_authenticated'): raise PermissionError("User is not authenticated") return func(user, *args, **kwargs) return wrapper @requires_auth def access_secure_data(user): return "Secure Data" user = {'is_authenticated': True} print(access_secure_data(user)) user = {'is_authenticated': False} try: print(access_secure_data(user)) except PermissionError as e: print(e)

Modyfikacja wejścia/wyjścia

Pozwalają na modyfikowanie argumentów przekazywanych do funkcji oraz wyników zwracanych przez funkcję.

def uppercase_output(func): def wrapper(*args, **kwargs): result = func(*args, **kwargs) return result.upper() return wrapper @uppercase_output def greet(name): return f"Hello, {name}" print(greet("world"))

Cachowanie wyników

Przyspieszają działanie funkcji przez zapamiętywanie wyników dla już obliczonych argumentów.

def cache(func): storage = {} def wrapper(*args): if args in storage: return storage[args] result = func(*args) storage[args] = result return result return wrapper @cache def fibonacci(n): if n < 2: return n return fibonacci(n-1) + fibonacci(n-2) print(fibonacci(10))

Kontrola dostępu

Mogą ograniczać dostęp do funkcji, np. ze względu na czas (limity wywołań).

import time def rate_limit(func): last_called = [0] # We use a list to store a variable in a closure def wrapper(*args, **kwargs): now = time.time() if now - last_called[0] < 1: # Limits calls to 1 per second raise RuntimeError("Function called too frequently") last_called[0] = now return func(*args, **kwargs) return wrapper @rate_limit def do_something(): print("Function executed") try: do_something() time.sleep(0.5) do_something() except RuntimeError as e: print(e) time.sleep(1) do_something()

Jakie techniki stosujemy do profilowania kodu Pythona w celu zidentyfikowania wąskich gardeł?

Profilowanie kodu Pythona jest kluczowe do identyfikowania wąskich gardeł wydajnościowych i optymalizacji aplikacji. Na rynku dostępnych jest wiele rozwiazań, najprostzrym sposobem jest wykonanie 'stopera' dzięki timeit, nadaje się on doskonale do mikrobenchmarków, czyli do mierzenia czasu wykonywania małych fragmentów kodu.

import timeit def test_func(): sum = 0 for i in range(10000): sum += i return sum time = timeit.timeit('test_func()', globals=globals(), number=1000) print(f"Execution time: {time:.5f} seconds")

cProfile to wbudowane narzędzie w Pythonie do profilowania. Można go użyć do mierzenia czasu wykonywania funkcji i identyfikowania najbardziej kosztownych fragmentów kodu.

import cProfile import pstats def profiling_func(): # Example profiling function sum = 0 for i in range(10000): sum += i return sum cProfile.run('profiling_func()', 'profil_output') # Displaying results in readable form p = pstats.Stats('profil_output') p.sort_stats('cumulative').print_stats(10)

Poza tym można skorzystać z bardziej zaawansowanych narzędzi.

line_profiler

To narzędzie do profilowania kodu linia po linii. Jest to szczególnie przydatne narzędzie do dokładnej analizy czasu wykonania poszczególnych linii kodu. Dzięki niemu możesz zidentyfikować konkretne linie kodu, które są najbardziej kosztowne pod względem czasu wykonania.

memory_profiler

Pozwala dokładnie monitorować zużycie pamięci w trakcie wykonywania programu, co jest niezbędne do identyfikacji problemów związanych z zarządzaniem pamięcią i optymalizacją wydajności.

Py-Spy

Umożliwia monitorowanie zużycia procesora przez aplikację oraz identyfikowanie gorących punktów, czyli fragmentów kodu, które zużywają najwięcej zasobów procesora. Jest to przydatne narzędzie do diagnostyki wydajnościowej i identyfikacji wąskich gardeł w aplikacji.

Opisz, jak działa getitem, setitem i delitem w kontekście tworzenia niestandardowych typów kontenerów.

__getitem__, __setitem__ i __delitem__ to specjalne metody w Pythonie, które pozwalają na dostęp, ustawienie i usunięcie elementów z niestandardowych typów kontenerów, takich jak klasy, które zachowują się jak listy, słowniki lub inne kontenery.

__getitem__(self, key)

Metoda ta jest wywoływana, gdy próbujemy uzyskać dostęp do elementu kontenera za pomocą operatora indeksowania []. Akceptuje ona jeden argument, którym jest klucz lub indeks, za pomocą którego chcemy uzyskać dostęp do elementu.

class MyList: def __init__(self, data): self.data = data def __getitem__(self, index): return self.data[index] my_list = MyList([1, 2, 3, 4, 5]) print(my_list[2]) # Displays: 3

__setitem__(self, key, value)

Metoda ta jest wywoływana, gdy próbujemy przypisać wartość do elementu kontenera za pomocą operatora indeksowania []. Akceptuje dwa argumenty: klucz lub indeks oraz wartość, którą chcemy przypisać.

class MyList: def __init__(self, data): self.data = data def __setitem__(self, index, value): self.data[index] = value my_list = MyList([1, 2, 3, 4, 5]) my_list[2] = 10 print(my_list.data) # Displays: [1, 2, 10, 4, 5]

__delitem__(self, key)

Metoda ta jest wywoływana, gdy próbujemy usunąć element z kontenera za pomocą operatora del i indeksowania []. Akceptuje tylko jeden argument: klucz lub indeks elementu, który chcemy usunąć.

class MyList: def __init__(self, data): self.data = data def __delitem__(self, index): del self.data[index] my_list = MyList([1, 2, 3, 4, 5]) del my_list[2] print(my_list.data) # Displays: [1, 2, 4, 5]

Dzięki tym specjalnym metodom możliwe jest tworzenie niestandardowych typów kontenerów w Pythonie, które mogą zachowywać się jak wbudowane typy, takie jak listy czy słowniki. Pozwalają one na dostosowanie zachowania kontenera do indywidualnych potrzeb, na przykład poprzez niestandardowe operacje podczas uzyskiwania, ustawiania lub usuwania elementów.

Jak działają wyrażenia regularne w Pythonie? Jakie są najczęściej używane funkcje w module re?

Wyrażenia regularne w Pythonie są narzędziem do manipulowania i przetwarzania tekstów za pomocą wzorców. Moduł re w Pythonie dostarcza funkcji i obiektów do obsługi wyrażeń regularnych.

Jak działają wyrażenia regularne?

Wyrażenia regularne opisują wzorce znaków, które są używane do wyszukiwania i manipulowania tekstami. Mogą zawierać specjalne znaki oraz zwykłe znaki, które definiują, jakie wzorce są dopasowywane w tekście.

Najczęściej używane funkcje w module re

re.search(pattern, string, flags=0): Szuka pierwszego dopasowania wzorca w całym tekście.

import re result = re.search(r'is', 'This is a test string.') print(result) # <re.Match object; span=(2, 4), match='is'>

re.match(pattern, string, flags=0): Sprawdza, czy wzorzec pasuje do początku tekstu.

import re result = re.match(r'This', 'This is a test string.') print(result) # <re.Match object; span=(0, 4), match='This'>

re.findall(pattern, string, flags=0): Znajduje wszystkie dopasowania wzorca w tekście i zwraca listę wyników.

import re result = re.findall(r'd+', 'There are 10 apples and 20 oranges.') print(result) # ['10', '20']

re.sub(pattern, repl, string, count=0, flags=0): Zastępuje wszystkie wystąpienia wzorca w tekście danym łańcuchem.

import re result = re.sub(r's+', '_', 'This is a test string.') print(result) # This_is_a_test_string.

Te funkcje są podstawowymi narzędziami w pracy z wyrażeniami regularnymi w Pythonie. Pozwalają one na przeszukiwanie, dopasowywanie, podział i zamianę tekstu zgodnie z określonymi wzorcami.

Opisz różnice między podejściem proceduralnym, obiektowym i funkcyjnym w programowaniu w Pythonie.

Podejście proceduralne

W podejściu proceduralnym program jest strukturalnie zorganizowany wokół procedur (funkcji), które wykonują konkretne zadania na danych. Kod jest sekwencyjny i wykonywany od góry do dołu. Główne cechy podejścia proceduralnego to:

Procedury: Podstawowymi elementami są procedury (funkcje), które wykonują konkretne zadania na danych.

Zmienne globalne: Dane są zwykle przechowywane jako zmienne globalne, do których procedury mają dostęp.

Proste struktury danych: Używane są proste struktury danych, takie jak listy, krotki, słowniki itp.

Prostota: Podejście, które jest łatwe do zrozumienia i stosowania w małych projektach.

def count_sum(a, b): return a + b a = 10 b = 20 sum = count_sum(a, b) print(sum)

Podejście obiektowe

W podejściu obiektowym program jest zorganizowany wokół obiektów, które są instancjami klas. Obiekty te przechowują dane (atrybuty) i metody (funkcje), które mogą operować na tych danych. Główne cechy podejścia obiektowego to:

Klasy i obiekty: Kod jest zorganizowany wokół klas, które definiują strukturę i zachowanie obiektów.

Hermetyzacja: Klasy mogą ukrywać swoje wewnętrzne implementacje, a dostęp do danych może być kontrolowany przez metody dostępowe (getter i setter).

Dziedziczenie: Klasy mogą dziedziczyć cechy i metody po innych klasach.

PolimorfizmObiekty różnych klas mogą być traktowane jednolicie, co umożliwia wykonywanie tych samych operacji na różnych typach danych.

class Calculator: def __init__(self, a, b): self.a = a self.b = b def count_sum(self): return self.a + self.b calculator = Calculator(10, 20) sum = calculator.count_sum() print(sum)

Podejście funkcyjne

W podejściu funkcyjnym program jest zorganizowany wokół funkcji, które są traktowane jako pierwszorzędne obiekty. Funkcje te mogą być przekazywane jako argumenty do innych funkcji, zwracane jako wartości z funkcji i przechowywane jako zmienne. Główne cechy podejścia funkcyjnego to:

Funkcje pierwszego rzędu: Funkcje mogą być przypisywane do zmiennych, przekazywane jako argumenty i zwracane jako wartości.

Bezstanowość: Funkcje nie mają stanu wewnętrznego i operują tylko na swoich argumentach.

Unikanie efektów ubocznych: Stara się unikać efektów ubocznych, co oznacza, że funkcje nie powinny modyfikować danych poza swoim zakresem.

Rekurencja: Często używa rekurencji do iteracji i przetwarzania danych.

W Pythonie można łączyć te trzy podejścia w jednym programie w zależności od potrzeb i preferencji projektowych. Jednak zwykle Python zachęca do używania podejścia obiektowego ze względu na jego elastyczność i możliwość ponownego użycia kodu.

Jakie są różnice między @property a bezpośrednim dostępem do atrybutów klasy? Jakie są zalety używania @property?

@property jest dekoratorem w Pythonie, który umożliwia definiowanie metod dostępowych do atrybutów klasy. Pozwala to na kontrolę dostępu do atrybutów oraz na wykonywanie dodatkowych działań podczas odczytu lub zapisu wartości atrybutu. Oto różnice między użyciem @property a bezpośrednim dostępem do atrybutów klasy:

Bezpośredni dostęp do atrybutów klasy

class MyClass: def __init__(self): self._attribute = None my_object = MyClass() my_object._attribute = "value" print(my_object._attribute)

Użycie @property

class MyClass: def __init__(self): self._attribute = None @property def attribute(self): return self._attribute my_object = MyClass() my_object.attribute = "wartość" # Throws error AttributeError print(my_object.attribute)

Zalety używania @property

Kontrola dostępu: Pozwala na kontrolowanie dostępu do atrybutów klasy, umożliwiając definiowanie niestandardowych działań podczas odczytu i zapisu wartości atrybutu.

Enkapsulacja: Pomaga w enkapsulacji danych, co oznacza, że można ukryć wewnętrzną implementację atrybutów i zapewnić bezpieczny dostęp do nich.

Dostosowanie interfejsu: Umożliwia definiowanie interfejsu klasy w sposób bardziej zrozumiały i przyjazny dla użytkownika.

Łatwa do zrozumienia i utrzymania: Pomaga w tworzeniu bardziej zrozumiałego i elastycznego kodu, który jest łatwiejszy do utrzymania i rozszerzania w przyszłości.

Jak działają itertools i jakie są najczęściej używane funkcje tego modułu?

Itertools jest modułem w Pythonie, który zawiera zestaw narzędzi do tworzenia efektywnych iteratorów. Iteratory są obiektami, które umożliwiają przeglądanie sekwencji danych, jednocześnie zajmując minimalną ilość pamięci. itertools oferuje wiele przydatnych funkcji do manipulowania iteratorami i generowania iterowalnych sekwencji danych.

Kilka najczęściej używanych funkcji z modułu itertools

itertools.chain(*iterables): Funkcja chain łączy wiele iterowalnych obiektów w jeden długi iterator.

import itertools list1 = [1, 2, 3] list2 = ['a', 'b', 'c'] chain = itertools.chain(list1, list2) for element in chain: print(element)

itertools.cycle(iterable): Funkcja cycle tworzy nieskończony iterator, który cyklicznie powtarza elementy z danego iterowalnego obiektu.

import itertools cycle = itertools.cycle([1, 2, 3]) for _ in range(5): print(next(cycle))

itertools.count(start=0, step=1): Funkcja count tworzy nieskończony iterator, który generuje liczby zaczynając od wartości start z określonym krokiem step.

import itertools counter = itertools.count(start=1, step=2) for _ in range(5): print(next(counter))

itertools.product(*iterables, repeat=1): Funkcja product tworzy iterator zawierający iloczyny kartezjańskie elementów z podanych iterowalnych obiektów.

import itertools products = itertools.product('AB', repeat=2) for product in products: print(product)

itertools.permutations(iterable, r=None): Funkcja permutations generuje wszystkie możliwe permutacje elementów z danego iterowalnego obiektu.

import itertools perms = itertools.permutations([1, 2, 3], 2) for perm in perms: print(perm)

itertools.groupby(iterable, key=None): Funkcja groupby grupuje elementy z danego iterowalnego obiektu na podstawie klucza, który jest funkcją określającą klucz grupowania.

import itertools data = [('a', 1), ('b', 2), ('a', 3), ('b', 4)] groups = itertools.groupby(data, key=lambda x: x[0]) for key, group in groups: print(key, list(group))

Podsumowanie

Mam nadzieję, że odpowiedzi na te pytania techniczne związane z programowaniem w Pythonie okazały się pomocne i rozjaśniły niektóre z bardziej skomplikowanych aspektów tego języka. Python, dzięki elastyczności, pozostaje jednym z najważniejszych narzędzi w arsenale programistów na całym świecie.

Pamiętaj, że nauka programowania to proces ciągły, a każda nowa informacja i każde rozwiązane wyzwanie przybliża Cię do stania się bardziej biegłym i efektywnym programistą Python.

Techniczna rozmowa kwalifikacyjna na stanowisko Python Developera w branży IT to kluczowy etap w procesie rekrutacyjnym. Aby zwiększyć swoje szanse na sukces, warto dobrze przygotować się do rozmowy kwalifikacyjnej, szczególnie jeśli aspirujesz na stanowisko Junior Python Developera. Przykładowe pytania rekrutacyjne mogą obejmować jeszcze wiele innych zagadnień związanych z Pythonem, takie jak sortowanie bąbelkowe Python, operacje na plikach czy użycie funkcji anonimowej lambda w Pythonie.


Mogą Cię też zainteresować

© 2024 MockIT