Ostatnio eksperymentuje sobie z pythonowym typowaniem.
W przypadku pythona jestem fanem podejścia funkcyjnego a sam język dostarcza multum narzędzi do tego typu zabawa (Bogate moduły dostarczające mechanizmy aka higher order functions
).
Jestem też fanem sensownego typowania które dostarcza chociażby TS i takowe też próbowałem odzwierciedlić w pythonie w przypadku funkcji tworzonych w locie. Jak się okazało, można uzyskać bardzo podobny efekt przy użyciu takich klas jak TypedDict
oraz Protocol
.
TypedDict
to nic innego jak typowana relacja klucz-wartość. Natomiast Protocol
to taka pythonowa wersja interfejsu.
Z zasady, w pythonie wszystko jest obiektem i każdy z tych obiektów jako meta class dziedziczy po type
. Kolejno, bazowa definicja type
dostarcza z automatu definicje metody magicznej __call__
, która to określa, czy obiekt jest callable
a więc, czy jest może działać jako funkcja.
Przykładowy (bardzo ogólny) kod:
class Brands(Enum):
MERCEDES = "mercedes"
class Car(NamedTuple):
brand: Brands
model: str
def drive_car(car: Car, broken: bool = False) -> None:
if not broken:
print(f"I'm driving {car.brand.value} {car.model}")
return
print(f"This {car.brand.value} is broken :(")
if __name__ == "__main__":
mercedes_amg = Car(Brands.MERCEDES, "amg")
drive_mercedes_amg = partial(drive_car, car=mercedes_amg)
drive_broken_mercedes_amg = partial(drive_mercedes_amg, broken=True)
No dobra, stworzyliśmy dwie nowe funkcje "w locie", ale jakiego typowania użyć? Teoretycznie typing
dostarcza typ Callable
określający funkcje i to było by teoretycznie okej. Limitacją takiego podejścia jest fakt, że definiujemy tam jedynie typ argumenty, pomijając jego nazwę. W przypadku obiektów, które potrzebują wiele argumentów, takie podejście może wprowadzić pewien chaos.
Na szczęście mając na uwadze fakt istnienia Protocol
i __call__
i TypedDict
, możemy zrobić pewien sprytny fikołek i uzyskać oczekiwany efekt.
class Brands(Enum):
MERCEDES = "mercedes"
class Car(NamedTuple):
brand: Brands
model: str
class _DriveCarBase(TypedDict):
car: Car
class DriveCarFunc(Protocol):
def __call__(self, **car_base: _DriveCarBase) -> Any:
pass
class DriveBrokenCarFunc(Protocol):
def __call__(self, broken: bool = True, **car_base: _DriveCarBase) -> Any:
pass
def drive_car(car: Car, broken: bool = False) -> None:
if not broken:
print(f"I'm driving {car.brand.value} {car.model}")
return
print(f"This {car.brand.value} is broken :(")
if __name__ == "__main__":
mercedes_amg = Car(Brands.MERCEDES, "amg")
drive_mercedes_amg: DriveCarFunc = partial(drive_car, car=mercedes_amg)
drive_broken_mercedes_amg: DriveBrokenCarFunc = partial(drive_mercedes_amg, broken=True)
Mimo, że z założenia python jest dynamiczny pod względem typowania, to jednak samo typowanie ciągle ewoluuje i dostarcza co raz to nowszych funkcjonalności. Powoli skręca w kierunku TS co w mojej ocenie jest czymś pozytywnym. W wersji 3.12 zaimplementowano mechanizm obsługi json
na wzór tego z js
czyli bezpośrednie wywołanie atrybutu (obiekt.atrybut). Istniało to oczywiście wcześniej, ale trzeba było instalować 3rd party lib, natomiast teraz mamy to w standardzie :)
Standardowo odpowiem - Wyszło w praniu :D Lubię wykorzystywać właściwości języka w którym piszę na 100%. A takie funkcyjne podejście w wielu sytuacjach jest IMO bardziej sensowne niż ładowanie wszędzie standardowego OOP.
Właśnie muszę spróbować funkcyjnego pisania bo nie robiłem nigdy nic w nim. Co polecasz na start? Widzę że niektóre języki maja elementy FP ale może zacznę ogarniać coś z pure fp żeby oop nie kusił.
Czy tylko mi ten kod wydaje siętrubo nieczytelny? Poleganie na wcięciach, brak jawnych deklaracji blokó kodu za pomocą {}, jakieś super metody call gdzie bez pomocy myszki ciężko stwierdzić czy jest tam jedna podłoga czy więcej i ten wszechobecny snake_case :)
@RequiredNickname: Takie uroki PY. Wystarczy w nim chwile popisać to byś poczuł ten vibe :D
Nie ma jednej, lepszej składni, czepianie się syntaxu Pythona to jak przywalanie się do Lispa, że za dużo nawiasów jest albo do Haskella, że call funkcji to f x
a nie f(x)
.
to już lepiej na kotlin przepisać, będzie śmigać 200 razy szybciej.
Skąd pomysł na taką zabawę? Zawsze mnie to ciekawi jak ktoś wrzuca coś takiego - skąd się bierze pomysł.