Vajon mi történik, ha ezt lefuttatjuk?
def test(n):
if n>1:
return test(n-1)+1
if n==1:
return 1
print(test(30))
print(test(300))
print(test(3000))
import sys
sys.setrecursionlimit(3500)
print(test(3000))
Milyen gyorsan fut le a következő kód?
%%time
def fibo(n):
if n>2:
return fibo(n-1)+fibo(n-2)
if n==1 or n==2:
return 1
print(fibo(10))
print(fibo(11))
print(fibo(38))
Fibo javítás:
my_cache={}
def fibo(n):
if n in my_cache:
#print("reading cache")
return my_cache[n]
if n>2:
ans=fibo(n-1)+fibo(n-2)
if n==1 or n==2:
ans=1
my_cache[n]=ans
return ans
%%time
fibo(38)
Még mindig nem az igazi. Miért nem menti el a python magától az eredményeket?
Egy függvény kimenetele feltétlen csak a bemenetektől függ! Így nem menthetők el az eredmények. Viszont megkérhetjük a pythont, hogy mentsen.
from functools import lru_cache
@lru_cache(10000)
def fibo(n):
if n>2:
return fibo(n-1)+fibo(n-2)
if n==1 or n==2:
return 1
%%time
fibo(1000)
fibo_called=0
@lru_cache(10000)
def fibo(n):
global fibo_called
fibo_called=fibo_called+1
if n>2:
return fibo(n-1)+fibo(n-2)
if n==1 or n==2:
return 1
fibo(10)
fibo_called
Mentsük el azokat az eredményeket, melyek csak a bemenettől függenek. A gond, hogy ezekből túl sok van, ezért néha törölni kell.
Különböző stratégiák léteznek arra, hogy mit dobjunk ki, ha már túl sokat mentettünk el:
név | törölni |
---|---|
First-In/First-Out (FIFO) | A legrégebben elmentett elemet |
Last-In/First-Out (LIFO) | A legfrissebben elmentett elemet |
Least Recently Used (LRU) | A legrégebben használt elmentett elemet |
Most Recently Used (MRU) | A legfrissebben használt elmentett elemet |
Least Frequently Used (LFU) | A legkevésbé gyakran használt elemet |
És természetesen ezek mindenféle kombinációja is hasznos lehet. Például idő és tárhely közös figyelembevételével való törlés.
Python a függvények "first class citizen"-ek. Ez azt jelenti, hogy ugyanolyan objektumok, mint bármi más.
def negyzet(n):
return(n**2)
print(negyzet(10))
ez_is_negyzet=negyzet
ez_is_negyzet(11) # ez is meghívató függvényként
def map_to_list(f,lista): # át lehet adni egy függvényt
ans=[]
for i in lista:
ans.append(f(i))
return ans
map_to_list(negyzet,[1,2,3,4])
Egy függvény definíciója (a def
utasítás) két dolgot csinál: létrehoz egy függvény objektumot és azt eltárolja olyan néven, amit megadtunk. A dekorátorok lehetővé teszik, hogy valamit „beszúrjunk” eközé a két lépés közé: létrejön a függvény objektum, meghívódik a dekorátor és megkapja paraméterként az éppen létrejött függvény objektumot, majd a dekorátor visszatérési értéke eltárolódik olyan néven, amit a függvény definíciójánál megadtunk.
def first_decorator(func):
def inner(x, y):
print("< Függvényhívás előtt")
func(x, y)
print("< Függvényhívás után")
return inner
def foo(x, y):
print("A paraméterek: ", x, y)
method = first_decorator(foo)
method("Run", 120)
def first_decorator(func):
def inner(x, y):
print("< Függvényhívás előtt")
func(x, y)
print("< Függvényhívás után")
return inner
@first_decorator
def foo(x, y):
print("A paraméterek: ", x, y)
def foo2(x, y):
print("A paraméterek: ", x, y)
# Dekoralt funkcio meghivasa
foo("First run", 100)
method = first_decorator(foo2)
method("Second run", 120)
A dekorátorokat kukac karakterrel kell bevezetni:
@callable_used_as_decorator
def new_function(arguments):
#... function body
Ahogyan fentebb leírtuk, ez nagyjából annak felel meg, mintha azt írtuk volna, hogy:
def _temporary_function_object(arguments):
#... function body
new_function = callable_used_as_decorator(_temporary_function_object)
Példaképpen ez a (gyakorlatban nem túl hasznos) függvény meghívja a megkapott függvény objektumot, majd módosítás nélkül visszakapja azt:
def run_immediately(func):
func()
return func
@run_immediately
def greet():
print("Üdvözöllek, dicső lovag!")
print("spam, spam, spam")
greet()
greet()
Általában azonban olyan dolgokat akarunk dekorátorként használni, amelyek valahogy módosítják az éppen definiált függvényt.
def cached(func):
cache = {}
def wrapper(arg):
try:
return cache[arg]
except KeyError:
result = func(arg)
cache[arg] = result
return result
return wrapper
@cached
def ask_for_value(name):
return input(name+" értéke? ")
print(ask_for_value)
results = []
results.append(ask_for_value("x"))
results.append(ask_for_value("y"))
results.append(ask_for_value("x"))
print(results)
Vegyük észre, hogy a függvény metaadatai (például neve) nem stimmelnek.
Ennek az esztétikai problémának a korrigálására lehet importálni a functools.wraps
függvényt, ami helyreteszi a metaadatokat:
import functools
def cached(func):
cache = {}
@functools.wraps(func)
def wrapper(arg):
try:
return cache[arg]
except KeyError:
result = cache[arg] = func(arg)
return result
return wrapper
@cached
def ask_for_value(name):
'''Így szokás dokumentációt írni Pythonban.'''
return input(name+" értéke? ")
print(ask_for_value)
print("Fontos metaadatok:")
print("Név:", ask_for_value.__name__)
print("Dokumentáció:", ask_for_value.__doc__)
#futtatás kihagyva, ugyanúgy működne, mint előbb
Ez a példa illusztrálja, hogy a dekorátor kijelölésekor lehet adattag-elérést (pont operátor) és függvényhívást alkalmazni.
Pontosabban fogalmazva functools.wraps
nem egy dekorátor, hanem egy dekorátor factory: paraméterül kap egy függvényt (ahonnan veszi a metaadatok értékeit) és a visszatérési értékét fogjuk dekorátorként használni.
Mi is tudunk ilyen dekorátor factory-t írni:
from functools import wraps
def logged(file, msg):
def decorator(func):
@wraps(func)
def wrapper(*args, **kw):
result = func(*args, **kw)
file.write(msg + str(result) + "\n")
return result
return wrapper
return decorator
import sys
@logged(sys.stderr, "Osztás eredménye: ")
def divide(x, y):
return x/y
[divide(2,2), divide(16,-8), divide(1,8)]
Itt sys.stderr a sztenderd hiba kimenet, amit békés rózsaszín háttérrel jelenít meg a Jupyter rendszer.
Dekorátorokat nem csak függvényekre, hanem osztályokra is lehet alkalmazni. Például a rendezési operátorok definícióját megcsinálja nekünk a functools.total_ordering
dekorátor (csak az egyenlőséget és egy egyenlőtlenséget kell nekünk definiálnunk):
import functools
@functools.total_ordering
class Results:
def __init__(self, win, loss):
self.win = win
self.loss = loss
def adventage(self):
return self.win-self.loss
def __eq__(self, oth):
""" operator==() """
return self.win == oth.win and self.loss == oth.loss
def __lt__(self, oth):
""" operator<() """
return (self.adventage(), self.win) < (oth.adventage(), oth.win)
x = Results(6,3)
y = Results(4,2)
z = Results(4,1)
w = Results(3,0)
print(x>=y, x<=z, x!=w, w<x, x<x)
import math
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
@property
def angle(self):
return math.atan2(self.x, self.y)
@property
def r(self):
return math.sqrt(self.x**2 + self.y**2)
p = Point(3,4)
print(p.angle, p.r)
Ezek most csak olvasható adattagként viselkednek:
p.r = 10
... de definiálhatóak hozzájuk setterek is:
import math
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
@property
def angle(self):
return math.atan2(self.x, self.y)
@angle.setter
def angle(self, value):
r = self.r
self.x = math.cos(value)*r
self.y = math.sin(value)*r
@property
def r(self):
return math.sqrt(self.x**2 + self.y**2)
@r.setter
def r(self, value):
angle = self.angle
self.x = math.cos(angle)*value
self.y = math.sin(angle)*value
p = Point(3,4)
p.r = 10
print(p.x, p.y)
Ahogyan látható, bármilyen számításokat elrejthetünk a property mögött, ennek persze az az ára, hogy a Python rendszer nem tudja és nem akarja ellenőrizni azt, hogy a property valóban kulturált adattagként viselkedik-e (például ha beleírunk egy értéket, akkor utána ugyanaz az érték lesz-e kiolvasható).
Mire jó ez az egész?
A propertyk létezésének nagy előnye, hogy nekik köszönhetően Pythonban egy osztály „publikus” interfészében nyugodtan lehetnek publikus adattagok.
Ha egy adattaghoz később extra funkcionalitást akarunk csatolni (például egy beállítás-fájlból akarjuk kiolvasni vagy ellenőrizni akarjuk, hogy csak megfelelő értéket lehessen beleírni stb.), akkor bármikor lecserélhetjük egy property-re. (Az adattagok többségénél viszont ez sohasem fog bekövetkezni és azoknál élvezhetjük, hogy nem hígítják fel getter-setter metódusok a kódunkat.)
Típus ellenőrzés
def myMethod(ID, name):
if not (myIsType(ID, 'uint') and myIsType(name, 'utf8string')):
raise BlaBlaException() ...
@accepts(uint, utf8string)
def myMethod(ID, name):
Discord bot
@client.event
async def on_ready():
guild = discord.utils.get(client.guilds, name=GUILD)
print(
f'{client.user} is connected to the following guild:\n'
f'{guild.name}(id: {guild.id})'
)
bot = commands.Bot(command_prefix='!')
@bot.command(name='99')
async def nine_nine(ctx):
brooklyn_99_quotes = [
'I\'m the human form of the 💯 emoji.',
'Bingpot!',
(
'Cool. Cool cool cool cool cool cool cool, '
'no doubt no doubt no doubt no doubt.'
),
]
response = random.choice(brooklyn_99_quotes)
await ctx.send(response)
@bot.command(name='roll_dice', help='Simulates rolling dice.')
async def roll(ctx, number_of_dice, number_of_sides):
dice = [
str(random.choice(range(1, number_of_sides + 1)))
for _ in range(number_of_dice)
]
await ctx.send(', '.join(dice))
@bot.command(name='create-channel')
@commands.has_role('admin')
async def create_channel(ctx, channel_name='real-python'):
guild = ctx.guild
existing_channel = discord.utils.get(guild.channels, name=channel_name)
if not existing_channel:
print(f'Creating a new channel: {channel_name}')
await guild.create_text_channel(channel_name)
import functools
def log(logger, level='info'):
def log_decorator(fn):
@functools.wraps(fn)
def wrapper(*a, **kwa):
getattr(logger, level)(fn.__name__)
return fn(*a, **kwa)
return wrapper
return log_decorator
# later that day ...
@log(logging.getLogger('main'), level='warning')
def potentially_dangerous_function(times):
for _ in xrange(times): rockets.get_rocket(NUCLEAR=True).fire()
Mennyi ideig fut egy függvény? Mennyi memóriát használunk? Stb...
from functools import wraps
import tracemalloc
from time import perf_counter
def measure_performance(func):
'''Measure performance of a function'''
@wraps(func)
def wrapper(*args, **kwargs):
tracemalloc.start()
start_time = perf_counter()
func(*args, **kwargs)
current, peak = tracemalloc.get_traced_memory()
finish_time = perf_counter()
print(f'Function: {func.__name__}')
print(f'Method: {func.__doc__}')
print(f'Memory usage:\t\t {current / 10**6:.6f} MB \n'
f'Peak memory usage:\t {peak / 10**6:.6f} MB ')
print(f'Time elapsed is seconds: {finish_time - start_time:.6f}')
print(f'{"-"*40}')
tracemalloc.stop()
return wrapper
@measure_performance
def make_list1():
'''Range'''
my_list = list(range(100000))
@measure_performance
def make_list2():
'''List comprehension'''
my_list = [l for l in range(100000)]
@measure_performance
def make_list3():
'''Append'''
my_list = []
for item in range(100000):
my_list.append(item)
@measure_performance
def make_list4():
'''Concatenation'''
my_list = []
for item in range(100000):
my_list = my_list + [item]
print(make_list1())
print(make_list2())
print(make_list3())
print(make_list4())
Saját függvény definiálható egy osztállyal is:
class MyFunc:
def __call__(self, *args, **kwargs):
return 12
f=MyFunc()
f()
import requests
class LimitQuery:
def __init__(self, func):
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.limit = args[0]
if self.count < self.limit:
self.count += 1
return self.func(*args, **kwargs)
else:
print(f'No queries left. All {self.count} queries used.')
return
@LimitQuery
def get_coin_price(limit):
'''View the Bitcoin Price Index (BPI)'''
url = requests.get('https://api.coindesk.com/v1/bpi/currentprice.json')
if url.status_code == 200:
text = url.json()
return f"${float(text['bpi']['USD']['rate_float']):.2f}"
print(get_coin_price(5))
print(get_coin_price(5))
print(get_coin_price(5))
print(get_coin_price(5))
print(get_coin_price(5))
print(get_coin_price(5))