# Programowanie obiektowe w Pythonie 3

Rozdział 8. Łączenie programowania obiektowego i funkcyjnego

## Wbudowane funkcje Pythona

### Funkcja len()

```python
>>> len([1, 2, 3, 4])
4

```

### Funkcja reversed()

```python
>>> class CustomSequence:
...     def __init__(self, args):
...         self._list = args
...     def __len__(self):
...         return 5
...     def __getitem__(self, index):
...         return f"x{index}"

>>> class FunkyBackwards(list):
...     def __reversed__(self):
...         return "WSTECZ!"

>>> generic = [1, 2, 3, 4, 5]
>>> custom = CustomSequence([6, 7, 8, 9, 10])
>>> funkadelic = FunkyBackwards([11, 12, 13, 14, 15])

>>> for sequence in generic, custom, funkadelic:
...     print(f"{sequence.__class__.__name__}: ", end="")
...     for item in reversed(sequence):
...         print(f"{item}, ", end="")
...     print()
list: 5, 4, 3, 2, 1, 
CustomSequence: x4, x3, x2, x1, x0, 
FunkyBackwards: W, S, T, E, C, Z, !, 

```

### Funkcja enumerate()

```python
>>> from pathlib import Path
>>> with Path("docs/sample_data.md").open() as source:
...     for index, line in enumerate(source, start=1):
...         print(f"{index:3d}: {line.rstrip()}")
  1: # Programowanie obiektowe w Pythonie 3
  2: 
  3: Rozdział 8. Łączenie programowania obiektowego i funkcyjnego
  4: 
  5: Proste dane przykładowe służące do pokazania działania funkcji `enumerate()`.

```

## Alternatywa dla przeciążania metod

```python
>>> def no_args():
...     return "Witaj, świecie!"

>>> no_args()
'Witaj, świecie!'

>>> def no_args() -> str:
...     return "Witaj, świecie!"

```

```python
>>> def mandatory_args(x, y, z): 
...     return f"{x=}, {y=}, {z=}"

>>> a_variable = 42
>>> mandatory_args("a string", a_variable, True)
"x='a string', y=42, z=True"

>>> from typing import Any
>>> def mandatory_args(x: Any, y: Any, z: Any) -> str: 
...     return f"{x=}, {y=}, {z=}"

```


### Domyślne wartości parametrów

```python
>>> from typing import Optional

>>> def better_function(x: Optional[int] = None) -> str:
...     if x is None:
...         x = number
...     return f"better: {x=}, {number=}"

>>> number = 5
>>> better_function(42)
'better: x=42, number=5'

>>> number = 7
>>> better_function()
'better: x=7, number=7'

```

```python
>>> def better_function_2(x: Optional[int] = None) -> str:
...     x = number if x is None else x
...     return f"better: {x=}, {number=}"

>>> number = 5
>>> better_function_2(42)
'better: x=42, number=5'

>>> number = 7
>>> better_function_2()
'better: x=7, number=7'

```

### Rozpakowywanie argumentów

```python
>>> def show_args(arg1, arg2, arg3="TRZECI"): 
...     return f"{arg1=}, {arg2=}, {arg3=}" 
 
Rozpakowywanie sekwencji

>>> some_args = range(3) 
>>> show_args(*some_args)
'arg1=0, arg2=1, arg3=2'


Rozpakowywanie słownika

>>> more_args = { 
...        "arg1": "PIERWSZY", 
...        "arg2": "DRUGI"}
>>> show_args(**more_args)
"arg1='PIERWSZY', arg2='DRUGI', arg3='TRZECI'"


```

```python

>>> x = {'a': 1, 'b': 2}
>>> y = {'b': 11, 'c': 3}
>>> z = {**x, **y}
>>> z
{'a': 1, 'b': 11, 'c': 3}

```

## Funkcje są także obiektami

Dodatkowe szczegóły znajdziesz w rozdziale 3.

```python
>>> from typing import Callable
>>> def fizz(x: int) -> bool:
...     return x % 3 == 0
>>> def buzz(x: int) -> bool:
...     return x % 5 == 0
>>> def name_or_number(number: int, *tests: Callable[[int], bool]) -> None:
...     for t in tests:
...         if t(number):
...             return t.__name__
...     return str(number)

>>> name_or_number(1, fizz)
'1'
>>> name_or_number(3, fizz)
'fizz'
>>> name_or_number(5, fizz)
'5'
>>> name_or_number(5, fizz, buzz)
'buzz'

>>> for i in range(1, 11):
...     print(name_or_number(i, fizz, buzz))
1
2
fizz
4
buzz
fizz
7
8
fizz
buzz

```


### Stosowanie funkcji do modyfikowania klas

```python

>>> class A:
...     def show_something(self):
...         print("Należę do klasy A")

>>> a_object = A()
>>> a_object.show_something()
Należę do klasy A


>>> def patched_show_something():
...     print("Ja NIE należę do klasy A")

>>> a_object.show_something = patched_show_something
>>> a_object.show_something()
Ja NIE należę do klasy A

>>> b_object = A()
>>> b_object.show_something()
Należę do klasy A


```

## Plikowe operacje wejścia-wyjścia

```python
>>> contents = "Przykładowa zawartość pliku\n"
>>> file = open("nazwapliku.txt", "w")
>>> n = file.write(contents)
>>> file.close()

>>> assert Path("nazwapliku.txt").read_text() == contents
>>> Path("nazwapliku.txt").unlink()

```

```python

>>> source_path = Path("requirements.txt")
>>> with source_path.open() as source_file:
...     for line in source_file:
...         print(line, end='')
<BLANKLINE>
beautifulsoup4==4.9.1
jsonschema==3.2.0
pyyaml==5.3.1
pillow==8.0.1


```

### Działanie w kontekście

```python
>>> class StringJoiner(list): 
...     def __enter__(self): 
...         return self 
...     def __exit__(self, type, value, tb): 
...         self.result = "".join(self) 

>>> with StringJoiner("Witaj") as sj:
...     sj.append(", ")
...     sj.extend("świecie")
...     sj.append("!")
>>> sj.result
'Witaj, świecie!'

>>> with StringJoiner("Wyniki") as sj:
...     sj.append(" ")
...     sj.extend("częściowe")
...     sj.append(str(2 / 0))
...     sj.extend("nawet jeśli zgłoszono wyjątek")
Traceback (most recent call last):
  ...
  File "<doctest examples.md[60]>", line 3, in <module>
    sj.append(str(2 / 0))
ZeroDivisionError: division by zero
>>> sj.result
'Wyniki częściowe'


```
