{ "cells": [ { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "# Klasy i obiekty - elementy zaawansowane\n", "\n", "## Python jako język obiektowy - podstawowe informacje\n", "\n", "### Wszystko jest obiektem\n", "\n", "Python jest językiem w pełni obiektowym - wszystko jest obiektem, także wartości prymitywne i funkcje." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "8" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(5).__add__(3)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### Dynamiczne typowanie\n", "\n", "Python jest językiem z **dynamicznym typowaniem**.\n", "Zmienne przechowują referencję do obiektu, a dopiero obiekty posiadają typ.\n", "Dlatego raz zdefiniowana zmienna może \"zmienić typ\"." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'text'" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var = 'text'\n", "var" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var = 1\n", "var" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Dynamiczne typowanie znacznie ułatwia programowanie, ponieważ nie narzuca ograniczeń na typ zmiennych.\n", "\n", "Dzięki temu programista może skupić się na bardziej istotnych aspektach, takich jak poprawność kodu, albo po prostu napisać kod szybciej.\n", "\n", "Dzięki dynamicznemu typowaniu dostępna jest funkcja ``eval``, która pozwala na wykonanie dowolnego, dynamicznie utworzonego wyrażenia:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "2" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x = 1\n", "eval('x+1')" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Dynamiczne typowanie umożliwia także łatwe użycie technik metaprogramowania, takich jak metaklasy, które zostaną omówione później.\n", "Pozwala to np. na dynamiczne tworzenie nowych typów.\n", "Dzięki temu implementacje ORM (*Object-Relational Mapping*, mapowanie obiektowo-relacyjne) są bardziej naturalne." ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### Tożsamość, typ a wartość\n", "\n", "Każdy obiekt posiada:\n", "* tożsamość (*identity*) - wskazuje na lokalizację obiektu w pamięci i można ją sprawdzić wywołując wbudowaną funkcję ``id``;\n", "* typ (*type*) opisuje reprezentację obiektu dla Pythona;\n", "* wartość (*value*) to dane przechowywane w obiekcie." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "140498501036736" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "numbers = [1, 2, 3]\n", "id(numbers)" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "list" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "type(numbers)" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[1, 2, 3]" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "numbers" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Definiowanie klasy\n", "\n", "Klasę definiujemy za pomocą słowa kluczowego *class*:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "class MyClass:\n", " def method(self):\n", " pass" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "W Pythonie wszystko jest obiektem, także klasa, dlatego możemy mówić o *obiekcie klasy*.\n", "Taki obiekt również ma swój typ:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "__main__.MyClass" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "MyClass" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "type" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "type(MyClass)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Metody specjalne\n", "\n", "Metodami specjalnymi nazywane są metody zaczynające i kończące się dwoma podkreślnikami.\n", "Implementują one operacje, które wywołujemy przy użyciu specjalnej składni (np. porównanie dwóch obiektów ``a < b``, dostęp do atrybutów ``obj.attribute`` lub składnia ``obj[key]``).\n", "\n", "### Najważniejsze metody specjalne\n", "\n", "| Metoda specjalna | Opis |\n", "|-------------------------|----------------------------------------------------------------------------------------------------------|\n", "| `__new__(cls[, ...])` | Stworzenie nowej instancji klasy. Dokładne omówienie tej metody znajduje się w rozdziale o metaklasach. |\n", "| `__init__(self[, ...])` | Konstruktor. Metoda wywoływana przy tworzeniu nowej instancji danej klasy. |\n", "| `__del__(self)` | Destruktor. Ta metoda powinna być przeciążana tylko w szczególnych przypadkach. W większości przypadków lepiej jest utworzyć osobną metodę (np. ``close``), która powinna być jawnie wywołana. |\n", "| ``__repr__(self)`` | Wywoływana przez funkcję `repr()` i konwersję do ciągów znaków. |\n", "| ``__str__(self)`` | Wywoływana przez funkcję `str()` i `print()` |\n", "| ``__lt__(self, other)``, ``__le__``, ``__eq__``, ``__ne__``, ``__gt__``, ``__ge__`` | Operatory porównania wywoływane przez wyrażenia, takie jak ``a < b``, ``a <= b`` itd. |\n", "| ``__cmp__(self, other)``| Funkcja porównania. Wywoływana, gdy nie zdefiniowano powyższych operatorów. |\n", "| ``__hash__(self)`` | Wywoływana przez funkcję `hash()`, używana w kolekcjach. |\n", "| ``__nonzero__(self)`` | Wywoływana w trakcie sprawdzania wartości `bool`. |" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### Dostęp do atrybutów\n", "\n", "| Metoda specjalna | Opis |\n", "|---|---|\n", "| `__getattr__(self, name)` | Wywoływana, gdy obiekt nie ma atrybutu `name` |\n", "| `__setattr__(self, name, value)` | Wywoływana podczas przypisywania atrybutów |\n", "| `__delattr__(self, name)` | Wywoływana przy usuwaniu atrybutu (`del obj.attr`) |" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Przykład słownika wykorzystującego składnię ``dict.key`` zamiast ``dict[key]``:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "class Record:\n", " def __init__(self):\n", " # Nie możemy użyć poniższego kodu:\n", " # self._d = {}\n", " # ponieważ zakończyłby się on rekurencyjnym wywoływaniem metody __setattr__\n", " super().__setattr__('_dict', {})\n", "\n", " def __getattr__(self, name):\n", " print('getting', name)\n", " return self._dict[name]\n", " \n", " def __setattr__(self, name, value):\n", " print('setting', name, 'to', value)\n", " self._dict[name] = value\n", " \n", " def __delattr__(self, name):\n", " print('deleting', name)\n", " del self._dict[name]" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "setting first_name to John\n", "getting first_name\n" ] }, { "data": { "text/plain": [ "'John'" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "person = Record()\n", "person.first_name = \"John\"\n", "person.first_name" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "deleting first_name\n" ] } ], "source": [ "del person.first_name" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "| Metoda specjalna | Opis |\n", "|---|---|\n", "| `__getattribute__(self, name)` | Wywoływana bezwarunkowo przy dostępie do atrybutów klasy, nawet jeśli dany atrybut istnieje. |" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": [ "class Person:\n", " def __init__(self, first_name):\n", " self.first_name = first_name\n", " \n", " def __getattribute__(self, name):\n", " print('getattribute', name)\n", " return object.__getattribute__(self, name)" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "getattribute first_name\n" ] }, { "data": { "text/plain": [ "'John'" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "p = Person('John')\n", "p.first_name" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Przykład ilustrujący różnicę między ``__getattr__`` a ``__getattribute__``:" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [], "source": [ "class Foo:\n", " def __init__(self):\n", " self.a = \"a\"\n", "\n", " def __getattr__(self,attribute):\n", " return f\"You asked for {attribute}, but I'm giving you default\"\n", "\n", "\n", "class Bar:\n", " def __init__(self):\n", " self.a = \"a\"\n", "\n", " def __getattribute__(self,attribute):\n", " return f\"You asked for {attribute}, but I'm giving you default\"" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'a'" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "foo = Foo()\n", "foo.a" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"You asked for b, but I'm giving you default\"" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "foo.b" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'a'" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "getattr(foo, \"a\")" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"You asked for b, but I'm giving you default\"" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "getattr(foo, \"b\")" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [], "source": [ "bar = Bar()" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"You asked for a, but I'm giving you default\"" ] }, "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], "source": [ "bar.a" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"You asked for b, but I'm giving you default\"" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "bar.b" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"You asked for a, but I'm giving you default\"" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "getattr(bar, \"a\")" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"You asked for b, but I'm giving you default\"" ] }, "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ "getattr(bar, \"b\")" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Składowe chronione i prywatne\n", "\n", "### Składowe chronione\n", "\n", "Składowe, które powinny być modyfikowane tylko przez klasę, powinny zaczynać się od podkreślnika.\n", "Jest to powszechnie przyjęta konwencja oznaczająca, że dana składowa nie powinna być modyfikowana z zewnątrz.\n", "Jest to jednak tylko konwencja - Python nie posiada mechanizmu ukrywającego takie składowe.\n", "Wciąż można je modyfikować spoza klasy." ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [], "source": [ "class BankAccount:\n", " def __init__(self, initial_balance):\n", " self._balance = initial_balance\n", " \n", " @property\n", " def balance(self):\n", " return self._balance" ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "100.0\n", "100.0\n" ] } ], "source": [ "account = BankAccount(100.0)\n", "print(account.balance)\n", "print(account._balance) # # składowe chronione są wciąż dostępne z zewnątrz klas" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### Składowe prywatne\n", "\n", "Aby ukryć atrybut lub metodę przed dostępem spoza klasy (składowa *private*), należy jej nazwę poprzedzić dwoma podkreślnikami (np. `__atrybut`).\n", "Taka składowa jest dostępna tylko wewnątrz tej klasy.\n", "Składowe zaczynające się od dwóch podkreślników (nie będące metodami specjalnymi) są traktowane w szczególny sposób - ich nazwa zostaje zmieniona na `_NazwaKlasy__atrybut`.\n", "Do tej składowej można się wciąż odwołać z zewnątrz klasy, ale tylko używając zmienionej nazwy." ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [], "source": [ "class BankAccount:\n", " def __init__(self, owner, balance=0):\n", " self.owner = owner\n", " self.__balance = balance\n", " \n", " def withdraw(self, amount):\n", " self.__balance -= amount\n", " \n", " def deposit(self, amount):\n", " self.__balance += amount\n", " \n", " def info(self):\n", " print(\"owner:\", self.owner, \"; balance:\", self.__balance)" ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [ { "ename": "AttributeError", "evalue": "'BankAccount' object has no attribute '__balance'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0mjk\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mBankAccount\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Jan Kowalski\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1000\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mjk\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__balance\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# błąd!\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mAttributeError\u001b[0m: 'BankAccount' object has no attribute '__balance'" ] } ], "source": [ "jk = BankAccount(\"Jan Kowalski\", 1000)\n", "print(jk.__balance) # błąd!" ] }, { "cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1000\n" ] } ], "source": [ "print(jk._BankAccount__balance) # ok" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Atrybuty klasy i metody statyczne\n", "\n", "Składowe statyczne są wspólne dla wszystkich instancji klasy.\n", "\n", "Z kolei metoda statyczna to po prostu funkcja w przestrzeni nazw klasy.\n", "Taką funkcję należy udekorować ``@staticmethod``.\n", "Taka funkcja nie przyjmuje instancji klasy ``self``." ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [], "source": [ "class CountedObject(object):\n", " count = 0 # statyczna składowa\n", " \n", " def __init__(self):\n", " CountedObject.count += 1\n", " \n", " @staticmethod # statyczna metoda\n", " def get_count():\n", " return CountedObject.count" ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0" ] }, "execution_count": 46, "metadata": {}, "output_type": "execute_result" } ], "source": [ "CountedObject.get_count()" ] }, { "cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4" ] }, "execution_count": 47, "metadata": {}, "output_type": "execute_result" } ], "source": [ "c1 = CountedObject()\n", "c2 = CountedObject()\n", "cs = [CountedObject(), CountedObject()]\n", "\n", "CountedObject.get_count()" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Czasami atrybutów klasy używa się, aby zainicjalizować domyślną wartość dla pewnych atrybutów instancji.\n", "Należy jednak być ostrożnym:" ] }, { "cell_type": "code", "execution_count": 52, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "John\n", "John\n" ] } ], "source": [ "class PersonWithDefaultAttributes:\n", " first_name = 'John'\n", " last_name = 'Smith'\n", " phones = []\n", "\n", "p1 = PersonWithDefaultAttributes()\n", "p2 = PersonWithDefaultAttributes()\n", "\n", "print(p1.first_name)\n", "print(p2.first_name)" ] }, { "cell_type": "code", "execution_count": 53, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Bob\n", "John\n" ] } ], "source": [ "p1.first_name = 'Bob'\n", "print(p1.first_name)\n", "print(p2.first_name)" ] }, { "cell_type": "code", "execution_count": 54, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Williams\n", "Williams\n" ] } ], "source": [ "PersonWithDefaultAttributes.last_name = 'Williams'\n", "print(p1.last_name)\n", "print(p2.last_name)" ] }, { "cell_type": "code", "execution_count": 55, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['+48123456789']\n", "['+48123456789']\n" ] } ], "source": [ "p1.phones.append('+48123456789')\n", "print(p1.phones)\n", "print(p2.phones)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Metody klasy\n", "\n", "Zwykła metoda ma dostęp do instancji klasy (poprzez parametr `self`).\n", "Z kolei metoda klasy ma dostęp do klasy, z której została wywołana, lub do **klasy instacji**, z której została wywołana.\n", "\n", "Metody klasy są przydatne, jeżeli chcemy pozwolić na więcej niż jeden sposób tworzenia instancji." ] }, { "cell_type": "code", "execution_count": 56, "metadata": {}, "outputs": [], "source": [ "class Date:\n", " def __init__(self, day, month, year):\n", " self.day = day\n", " self.month = month\n", " self.year = year\n", " \n", " @classmethod\n", " def from_string(cls, date_as_string):\n", " day, month, year = date_as_string.split('-')\n", " return cls(int(day), int(month), int(year)) # utworzenie instancji klasy cls" ] }, { "cell_type": "code", "execution_count": 59, "metadata": {}, "outputs": [], "source": [ "d1 = Date(20, 1, 2016)\n", "d2 = Date.from_string('20-01-2016')" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Warto zwrócić uwagę na to, że wewnątrz metody ``from_string`` tworzona jest nowa instancja klasy ``cls``.\n", "Nie musi być to klasa ``Date``.\n", "Tak będzie w przypadku klas dziedziczących po ``Date``." ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Deskryptory\n", "\n", "Deskryptorem jest dowolny obiekt, który zawiera przynajmniej jedną z trzech poniższych metod specjalnych.\n", "Taki obiekt musi pojawić się jako atrybut w obiekcie - \"właścicielu\".\n", "W momencie dostępu do takiego atrybutu wywoływane są odpowiednie metody deskryptora:\n", "\n", "| Metody specjalne deskryptora | Opis |\n", "|----------------------------------|--------------------------------------------------------------|\n", "| `__get__(self, instance, owner)` | Wywoływana do pobrania atrybutu z klasy - \"właściciela\" |\n", "| `__set__(self, instance, value)` | Wywoływana do ustawienia atrybutu z klasy - \"właściciela\" |\n", "| `__delete__(self, instance)` | Wywoływana w czasie usuwania atrybutu w klasie - \"właścicielu\" |\n", "\n", "### Przykład deskryptora\n" ] }, { "cell_type": "code", "execution_count": 60, "metadata": {}, "outputs": [], "source": [ "import random\n", "\n", "class Die:\n", " def __init__(self, sides=6):\n", " self.sides = sides\n", " \n", " def __get__(self, instance, owner):\n", " print('self =', self)\n", " print('instance =', instance)\n", " print('owner =', owner)\n", " return int(random.random() * self.sides) + 1\n", " \n", " def __set__(self, instance, value):\n", " print('self =', self)\n", " print('instance =', instance)\n", " print('value =', value)\n", " \n", " def __delete__(self, instance):\n", " print('self =', self)\n", " print('instance =', instance)\n", "\n", "class Game:\n", " d6 = Die()\n", " d10 = Die(sides=10)\n", " d20 = Die(sides=20)" ] }, { "cell_type": "code", "execution_count": 61, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "self = <__main__.Die object at 0x7fc85b329460>\n", "instance = None\n", "owner = \n" ] }, { "data": { "text/plain": [ "4" ] }, "execution_count": 61, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Game.d6" ] }, { "cell_type": "code", "execution_count": 62, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "self = <__main__.Die object at 0x7fc85b33b070>\n", "instance = <__main__.Game object at 0x7fc85b3815b0>\n", "owner = \n" ] }, { "data": { "text/plain": [ "15" ] }, "execution_count": 62, "metadata": {}, "output_type": "execute_result" } ], "source": [ "game = Game()\n", "game.d20" ] }, { "cell_type": "code", "execution_count": 63, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "self = <__main__.Die object at 0x7fc85b33b070>\n", "instance = <__main__.Game object at 0x7fc85b3815b0>\n", "value = 42\n" ] } ], "source": [ "game.d20 = 42" ] }, { "cell_type": "code", "execution_count": 64, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "self = <__main__.Die object at 0x7fc85b33b070>\n", "instance = <__main__.Game object at 0x7fc85b3815b0>\n" ] } ], "source": [ "del game.d20" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### Właściwości (properties)\n", "\n", "Przykładem deskryptora jest wbudowany dekorator ``@property`` pozwalający na enkapsulację obiektu, to znaczy kontrolę dostępu do atrybutów przy użyciu metod dostępowych." ] }, { "cell_type": "code", "execution_count": 70, "metadata": {}, "outputs": [], "source": [ "class BankAccount:\n", " def __init__(self, daily_limit):\n", " self.__daily_limit = daily_limit\n", " \n", " @property\n", " def daily_limit(self):\n", " print('getting daily_limit')\n", " return self.__daily_limit\n", " \n", " @daily_limit.setter\n", " def daily_limit(self, value):\n", " if value < 0:\n", " raise ValueError('Value must be >= 0')\n", " self.__daily_limit = value" ] }, { "cell_type": "code", "execution_count": 71, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "getting daily_limit\n" ] }, { "data": { "text/plain": [ "100.0" ] }, "execution_count": 71, "metadata": {}, "output_type": "execute_result" } ], "source": [ "account = BankAccount(100.0)\n", "account.daily_limit" ] }, { "cell_type": "code", "execution_count": 72, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "getting daily_limit\n" ] }, { "data": { "text/plain": [ "200.0" ] }, "execution_count": 72, "metadata": {}, "output_type": "execute_result" } ], "source": [ "account.daily_limit = 200.0\n", "account.daily_limit" ] }, { "cell_type": "code", "execution_count": 73, "metadata": {}, "outputs": [ { "ename": "ValueError", "evalue": "Value must be >= 0", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0maccount\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdaily_limit\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m-\u001b[0m\u001b[0;36m100.0\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;32m\u001b[0m in \u001b[0;36mdaily_limit\u001b[0;34m(self, value)\u001b[0m\n\u001b[1;32m 11\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mdaily_limit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 12\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mvalue\u001b[0m \u001b[0;34m<\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 13\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'Value must be >= 0'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 14\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__daily_limit\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mValueError\u001b[0m: Value must be >= 0" ] } ], "source": [ "account.daily_limit = -100.0" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Gdyby nie został zdefiniowany setter, to znaczy w kodzie nie pojawiłby się `@daily_limit.setter`, wówczas ``daily_limit`` byłaby właściwością tylko do odczytu.\n", "Próby zmiany jej wartości kończyłyby się błędem." ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Sloty\n", "\n", "Każdy obiekt pythona posiada __dict__ - słownik atrybutów.\n", "\n", "Powoduje to spory narzut na pamięć.\n", "Jeśli nasza klasa nie będzie korzystała z dynamicznej natury takiego słownika, to w klasie można zdefiniować ``__slots__`` i podać listę wszystkich składowych." ] }, { "cell_type": "code", "execution_count": 74, "metadata": {}, "outputs": [], "source": [ "class Point:\n", " __slots__ = ['x', 'y']" ] }, { "cell_type": "code", "execution_count": 75, "metadata": {}, "outputs": [ { "ename": "AttributeError", "evalue": "'Point' object has no attribute 'z'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0mp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mx\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m10\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0mp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0my\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m20\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m \u001b[0mp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mz\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m30\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mAttributeError\u001b[0m: 'Point' object has no attribute 'z'" ] } ], "source": [ "p = Point()\n", "p.x = 10\n", "p.y = 20\n", "p.z = 30" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Dziedziczenie\n", "\n", "### Podstawy\n", "\n", "Dziedziczenie definiowane jest za pomocą składni:" ] }, { "cell_type": "code", "execution_count": 76, "metadata": {}, "outputs": [], "source": [ "class Base:\n", " def base_method(self):\n", " pass\n", "\n", "class Derived(Base):\n", " def derived_method(self):\n", " pass" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### Method Resolution Order (MRO)\n", "\n", "Wszystkie metody zdefiniowane bezpośrednio w klasie ``C`` są przechowywane w słowniku ``C.__dict__``:" ] }, { "cell_type": "code", "execution_count": 77, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "mappingproxy({'__module__': '__main__',\n", " 'base_method': ,\n", " '__dict__': ,\n", " '__weakref__': ,\n", " '__doc__': None})" ] }, "execution_count": 77, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Base.__dict__" ] }, { "cell_type": "code", "execution_count": 78, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "mappingproxy({'__module__': '__main__',\n", " 'derived_method': ,\n", " '__doc__': None})" ] }, "execution_count": 78, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Derived.__dict__" ] }, { "cell_type": "code", "execution_count": 79, "metadata": {}, "outputs": [], "source": [ "d = Derived()\n", "d.base_method() # it works" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "W klasie potomnej ``Derived`` dostępne są metody zdefiniowane w klasie bazowej ``Base`` (takie jak ``base_method``), mimo że nie występują one bezpośrednio w słowniku potomka.\n", "W momencie wywoływania metody ``d.base_method()``, metoda ``base_method`` jest poszukiwana w słowniku klasy ``Derived``. Ponieważ ten słownik nie ma tej metody, przeszukiwany jest słownik klasy ``Base``.\n", "\n", "W ogólnym przypadku, przeszukiwane są słowniki wszystkich klas określonych w ``C.__mro__``.\n", "Ta krotka zawiera klasę ``C``, jej klasy nadrzędne, itd." ] }, { "cell_type": "code", "execution_count": 80, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(__main__.Derived, __main__.Base, object)" ] }, "execution_count": 80, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Derived.__mro__" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### Dziedziczenie wielobazowe\n", "\n", "W Pythonie możliwe jest dziedziczenie po więcej niż jednej klasie.\n", "\n", "Przeciążając metody (w szczególności konstruktor ``__init__``) należy pamiętać, aby wywołać także wersję rodzica przy użyciu funkcji ``super``, która zwraca obiekt proxy służący do wywoływania metod rodzica.\n", "Obiekt jest wybierany zgodnie z MRO." ] }, { "cell_type": "code", "execution_count": 81, "metadata": {}, "outputs": [], "source": [ "class A:\n", " def __init__(self):\n", " print(\"A\")\n", "\n", "class B(A):\n", " def __init__(self):\n", " super().__init__()\n", " print(\"B\")\n", "\n", "class C(A):\n", " def __init__(self):\n", " super().__init__()\n", " print(\"C\")\n", "\n", "class D(B, C):\n", " def __init__(self):\n", " super().__init__()\n", " print(\"D\")" ] }, { "cell_type": "code", "execution_count": 82, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(__main__.D, __main__.B, __main__.C, __main__.A, object)" ] }, "execution_count": 82, "metadata": {}, "output_type": "execute_result" } ], "source": [ "D.__mro__" ] }, { "cell_type": "code", "execution_count": 83, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "A\n", "C\n", "B\n", "D\n" ] }, { "data": { "text/plain": [ "<__main__.D at 0x7fc85b329ee0>" ] }, "execution_count": 83, "metadata": {}, "output_type": "execute_result" } ], "source": [ "D()" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Czasami nie jest możliwe utworzenie sensownego MRO:" ] }, { "cell_type": "code", "execution_count": 86, "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "Cannot create a consistent method resolution\norder (MRO) for bases A, B", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;32mclass\u001b[0m \u001b[0mC\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mA\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mB\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;32mpass\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;32mclass\u001b[0m \u001b[0mD\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mB\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mA\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;32mpass\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 5\u001b[0;31m \u001b[0;32mclass\u001b[0m \u001b[0mE\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mC\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mD\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;32mpass\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mTypeError\u001b[0m: Cannot create a consistent method resolution\norder (MRO) for bases A, B" ] } ], "source": [ "class A: pass\n", "class B: pass\n", "class C(A, B): pass\n", "class D(B, A): pass\n", "class E(C, D): pass" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### Mixins (klasy domieszkowe)\n", "\n", "Klasy domieszkowe (*mixins*) to klasy, które dostarczają określoną funkcjonalność innym klasom (poprzez mechanizm wielokrotnego dziedziczenia).\n", "Nie są samodzielnymi klasami i, w związku z tym, nie tworzy się instancji klas domieszkowych.\n", "Zazwyczaj nazwa takiej klasy kończy się sufixem ``Mixin`` (np. ``ComparableMixin``), ale nie jest to bezwględnie obowiązująca konwencja.\n", "\n", "Definiując klasę i wymieniając jej rodziców należy pamiętać, aby najpierw wymienić wszystkie klasy domieszkowe, a dopiero na końcu podać klasę bazową (chyba że jest nią ``object``).\n", "\n", "W przypadku porównywania obiektów wywoływana jest jedna z sześciu specjalnych metod (``__lt__``, ``__le__``, ``__eq__``, ``__ne__``, ``__gt__`` lub ``__ge__``).\n", "Wystarczy jednak zdefiniować dwie z nich (np. ``__le__`` i ``__eq__``), a pozostałe porównania to odpowiednia kombinacja tych dwóch metod.\n", "\n", "Poniżej przedstawiono klasę domieszkową ``ComparableMixin``.\n", "Jej użycie powoduje, że wystarczy zdefiniować metody ``__le__`` i ``__eq__``, aby obiekty klasy dziedziczącej po ``ComparableMixin`` mogły być porównywane.\n" ] }, { "cell_type": "code", "execution_count": 87, "metadata": {}, "outputs": [], "source": [ "class ComparableMixin:\n", " def __ne__(self, other):\n", " return not (self == other)\n", " def __lt__(self, other):\n", " return self <= other and (self != other)\n", " def __gt__(self, other):\n", " return not self <= other\n", " def __ge__(self, other):\n", " return self == other or self > other\n", "\n", "class MyInteger(ComparableMixin): # klasą bazową jest \"object\"\n", " def __init__(self, i):\n", " self.i = i\n", " def __le__(self, other):\n", " return self.i <= other.i\n", " def __eq__(self, other):\n", " return self.i == other.i" ] }, { "cell_type": "code", "execution_count": 88, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 88, "metadata": {}, "output_type": "execute_result" } ], "source": [ "MyInteger(1) > MyInteger(0)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### Dziedziczenie po typach wbudowanych\n", "\n", "Od Pythona 2.2 można dziedziczyć po wszystkich typach wbudowanych." ] }, { "cell_type": "code", "execution_count": 89, "metadata": {}, "outputs": [], "source": [ "class CountDict(dict):\n", " def __getitem__(self, key):\n", " if key in self:\n", " return super(CountDict, self).__getitem__(key)\n", " else:\n", " return 0" ] }, { "cell_type": "code", "execution_count": 90, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0" ] }, "execution_count": 90, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cd = CountDict()\n", "cd['unknown-key']" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### Dziedziczenie po typach niezmiennych\n", "\n", "Dla typów niezmiennych *(immutable)* nie działa przeciążanie konstruktora ``__init__``.\n", "Po utworzeniu obiektu jest już za późno na jakąkolwiek modyfikację." ] }, { "cell_type": "code", "execution_count": 92, "metadata": {}, "outputs": [], "source": [ "class PositiveInt(int):\n", " def __new__(cls, value):\n", " print('__new__')\n", " obj = int.__new__(cls, value)\n", " return obj if obj > 0 else -obj" ] }, { "cell_type": "code", "execution_count": 93, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "__new__\n" ] }, { "data": { "text/plain": [ "7" ] }, "execution_count": 93, "metadata": {}, "output_type": "execute_result" } ], "source": [ "PositiveInt(-7)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Przykład ilustrujący gdzie przekazywane są argumenty:" ] }, { "cell_type": "code", "execution_count": 94, "metadata": {}, "outputs": [], "source": [ "class Test:\n", " def __new__(cls, *args):\n", " print('__new__', args)\n", " obj = object.__new__(cls)\n", " obj.new_attr = \"test\"\n", " return obj\n", " \n", " def __init__(self, *args):\n", " print('__init__', args)\n", " self.args = args" ] }, { "cell_type": "code", "execution_count": 95, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "__new__ ('gadget', 42)\n", "__init__ ('gadget', 42)\n" ] } ], "source": [ "t = Test(\"gadget\", 42)" ] }, { "cell_type": "code", "execution_count": 97, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "('gadget', 42)" ] }, "execution_count": 97, "metadata": {}, "output_type": "execute_result" } ], "source": [ "t.args" ] }, { "cell_type": "code", "execution_count": 96, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'test'" ] }, "execution_count": 96, "metadata": {}, "output_type": "execute_result" } ], "source": [ "t.new_attr" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### Abstract Base Classes\n", "\n", "W Pythonie stosowany jest duck-typing, w związku z tym nie ma potrzeby definiowania abstrakcyjnych klas lub interfejsów określających kontrakt.\n", "Warto jednak czasami jawnie określić kontrakt, to znaczy stworzyć abstrakcyjną klasę bazową i określić, jakie metody powinny zostać zdefiniowane.\n", "Nie tworzy się instancji takiej klasy - służy ona jedynie jako dokumentacja." ] }, { "cell_type": "code", "execution_count": 98, "metadata": {}, "outputs": [], "source": [ "import abc\n", "\n", "class BaseCalculator(abc.ABC):\n", " @abc.abstractmethod\n", " def process(self, expr):\n", " pass\n", "\n", "class Calculator(BaseCalculator):\n", " def process(self, expr):\n", " return eval(expr)\n" ] }, { "cell_type": "code", "execution_count": 99, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4" ] }, "execution_count": 99, "metadata": {}, "output_type": "execute_result" } ], "source": [ "c = Calculator()\n", "c.process('2 + 2')" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Jeżeli w klasie potomnej nie zostanie zdefiniowana wymagana metoda, zostanie rzucony wyjątek." ] }, { "cell_type": "code", "execution_count": 100, "metadata": {}, "outputs": [], "source": [ "class InvalidCalculator(BaseCalculator):\n", " pass" ] }, { "cell_type": "code", "execution_count": 101, "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "Can't instantiate abstract class InvalidCalculator with abstract methods process", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mic\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mInvalidCalculator\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mTypeError\u001b[0m: Can't instantiate abstract class InvalidCalculator with abstract methods process" ] } ], "source": [ "ic = InvalidCalculator()" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Klasa jako obiekt\n", "\n", "W Pythonie wszystko jest obiektem, także klasa.\n", "Dlatego możemy mówić o *obiekcie klasy*.\n", "*obiekt klasy*, tak samo jak każdy obiekt, może być modyfikowany po jego utworzeniu." ] }, { "cell_type": "code", "execution_count": 102, "metadata": {}, "outputs": [], "source": [ "class Record:\n", " pass\n", "\n", "Record.name = \"John\"" ] }, { "cell_type": "code", "execution_count": 103, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'John'" ] }, "execution_count": 103, "metadata": {}, "output_type": "execute_result" } ], "source": [ "person = Record()\n", "person.name" ] }, { "cell_type": "code", "execution_count": 104, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "42" ] }, "execution_count": 104, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Record.age = 42\n", "person.age" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Ma to wpływ na wszystkie instancje." ] } ], "metadata": { "kernelspec": { "display_name": "jb", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.8" }, "orig_nbformat": 4 }, "nbformat": 4, "nbformat_minor": 2 }