Metaprogrammazione

Metaprogrammazione Capacità di un programma di trattare un programma (altro o se stesso) come dato. Un programma quindi può essere implementato in modo da poter leggere, analizzare, generare o trasformare un altro programma. Ci sono molte parti di python che sono forme di metaprogramming, tra cui [[globals e locals|locals() e globals()]] e [[#call|call]]

__call__()

import random
 
class RandomNumberGenerator:
	def __call__(self):
		return random.random()
		
randomGenerator = RandomNumberGenerator()
n = randomGenerator()
# o
n = randomGenerator.__call__()

Ma come funziona?

  1. quando si chiama object(...) l’interprete cerca un metodo __call__ partendo dalla classe di cui object è istanza, ed eventualmente cerca nelle superclassi
  2. __call__ viene chiamato con l’oggetto passato come primo parametro (bound)
  3. quindi object.__call__() e object() non sono propriamente uguali, perché il primo è unbound mentre il seconodo è bound.

Attenzione! Se la classe avesse un metodo __call__, chiamare object.__call__() porterebbe ad un altro risultato.

randomGenerator.__call__ = lambda self: self.__class__.__name__
 
# si comporta normalmente e ritorna un numero random
randomGenerator()
 
# esegue la funzione lambda e ritorna da errore 
# perchè non viene passato l'oggetto self (unbound)
randomGenerator.__call__()
 
# esegue la funzione lambda e ritorna il nome della classe
randomGenerator.__call__(randomGenerator)

Quindi randomGenerator() è equivalente a chiamare type(randomGenerator).__call__(randomGenerator) - o - RandomNumberGenerator.__call__(randomGenerator)

type, la classe di singolarità, dal momento che può essere utilizzato come funzione per avere in ritorno la classe di un oggetto, utilizza il metodo __call__ appena visto. type(O) ≡≡ type.__call__(type,O)

type(3)
type(type).__call__(type, 3)
type.__call__(type, 3)

Istanziare le classi tramite __call__

La scrittura A() deve essere equivalente a type(A).__call__(A). Infatti, quando si istanzia un oggetto, è il metodo __call__ di type che viene chiamato.

numGenerator = type(RandomNumberGenerator).__call__(RandomNumberGenerator, <eventuali parametri per init>)

Infatti, il tipo default di ogni classe è type

type(2)		# int
type(int)	# type

Questo comportamento può essere modificato tramite l’utilizzo di Metaclass

python_book6