Le mot yuan.,Vous pourriez penser aux métadonnées,Les métadonnées sont des données qui décrivent les données elles - mêmes,Les métaclasses sont des classes de classes,La Métaprogrammation correspondante est le Code qui décrit le Code lui - même,La Métaprogrammation consiste à créer le code source de l'opération(Comme la modification、Générer ou emballer le code original)Fonctions et classes pour.La technique principale est l'utilisation de décorateurs、Métaclasse、Classe descripteur.
Le but principal de cet article est de vous présenter ces techniques de Métaprogrammation,Et donner des exemples pour montrer comment ils personnalisent le comportement du code source.
Décorateur
Le décorateur est une fonction de la fonction,Il prend une fonction comme argument et renvoie une nouvelle fonction,Ajouter une nouvelle fonctionnalité sans modifier le Code de fonction original,Comme les décorateurs de minuterie les plus couramment utilisés:
from functools import wraps
def timeit(logger=None):
"""
Compteur de temps décorateur,En secondes,Réserve 4 Décimale
"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
if logger:
logger.info(f"{func.__name__} cost {end - start :.4f} seconds")
else:
print(f"{func.__name__} cost {end - start :.4f} seconds")
return result
return wrapper
return decorator
(Note::Comme l'utilisation ci - dessus @wraps(func) Les notes sont importantes , Il conserve les métadonnées de la fonction originale ) Il suffit d'ajouter @timeit() De nouvelles fonctionnalités peuvent être ajoutées :
@timeit()
def test_timeit():
time.sleep(1)
test_timeit()
#test_timeit cost 1.0026 seconds
Le code ci - dessus a le même effet que celui écrit ci - dessous :
test_timeit = timeit(test_timeit)
test_timeit()
Séquence d'exécution des garnitures
Quand il y a plusieurs décorateurs, Quel est leur ordre d'appel ?
S'il y a un tel code, S'il vous plaît, imprimez d'abord Decorator1 Toujours Decorator2 ?
from functools import wraps
def decorator1(func):
@wraps(func)
def wrapper(*args, **kwargs):
print('Decorator 1')
return func(*args, **kwargs)
return wrapper
def decorator2(func):
@wraps(func)
def wrapper(*args, **kwargs):
print('Decorator 2')
return func(*args, **kwargs)
return wrapper
@decorator1
@decorator2
def add(x, y):
return x + y
add(1,2)
# Decorator 1
# Decorator 2
Avant de répondre à cette question, Je vais d'abord vous donner une métaphore de l'image , Les décorateurs sont comme des fonctions qui s'habillent , Le premier à le porter le plus près , La dernière chose à porter loin ,Dans l'exemple ci - dessus decorator1 C'est un manteau. ,decorator2 C'est des sous - vêtements .
add = decorator1(decorator2(add))
Quand la fonction est appelée, C'est comme se déshabiller , Relâchez d'abord le decorator1, C'est - à - dire imprimer d'abord Decorator1,Mise en œuvre return func(
args, kwargs) Et quand ça arrivera decorator2,Puis imprimez Decorator2,Encore une fois, passez à return func(
args, kwargs) Quand il est vraiment exécuté add() Fonctions.
Notez l'emplacement de l'impression , Si le Code de la chaîne d'impression suit la fonction d'appel ,Comme ci - dessous, Le résultat de cette sortie est exactement le contraire :
def decorator1(func):
@wraps(func)
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
print('Decorator 1')
return result
return wrapper
def decorator2(func):
@wraps(func)
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
print('Decorator 2')
return result
return wrapper
Les décorateurs ne peuvent pas seulement être définis comme des fonctions , Peut également être défini comme une classe , Tant que tu t'assures que ça arrive __call__() Et __get__() Méthodes.
Métaclasse
Python Toutes les classes(object)Métaclasse de,C'est type Catégorie,C'est - à - dire Python Le comportement de création de classe est défini par défaut par type Contrôle de classe,Une métaphore.,type Les classes sont les ancêtres de toutes les classes. Nous pouvons implémenter programmatiquement certains comportements de création d'objets personnalisés .
Hériter d'une classe type Catégorie A, Ensuite, laissez les métaclasses des autres classes pointer vers A,On peut le contrôler. A Comportement de création de. Un exemple typique est l'utilisation de métaclasses pour implémenter un seul exemple :
class Singleton(type):
def __init__(self, *args, **kwargs):
self._instance = None
super().__init__(*args, **kwargs)
def __call__(self, *args, **kwargs):
if self._instance is None:
self._instance = super().__call__(*args, **kwargs)
return self._instance
else:
return self._instance
class Spam(metaclass=Singleton):
def __init__(self):
print("Spam!!!")
Métaclasse Singleton De__init__Et__new__ La méthode définit Spam La période de ,Et __call__ La méthode instanciera Spam Quand il est exécuté.
Pour mieux comprendre les métaclasses ,Peut être luPython La Magie Noire metaclass
descriptor Catégorie(Classe descripteur)
descriptor C'est n'importe quelle définition __get__(),__set__()Ou __delete__()Objet de,Le descripteur permet aux objets de personnaliser la recherche d'attributs、Opérations de stockage et de suppression. Voici les documents officiels [1] Un exemple de validateur personnalisé .
Définir la classe de validateur , C'est une classe de descripteurs , C'est aussi une classe abstraite :
from abc import ABC, abstractmethod
class Validator(ABC):
def __set_name__(self, owner, name):
self.private_name = '_' + name
def __get__(self, obj, objtype=None):
return getattr(obj, self.private_name)
def __set__(self, obj, value):
self.validate(value)
setattr(obj, self.private_name, value)
@abstractmethod
def validate(self, value):
pass
Le validateur personnalisé doit partir de Validator Succession, Et doit fournir validate() Méthodes pour tester les diverses contraintes au besoin .
Il s'agit de trois outils pratiques de validation des données :
OneOf La valeur de validation est l'une des options restreintes .
class OneOf(Validator):
def __init__(self, *options):
self.options = set(options)
def validate(self, value):
if value not in self.options:
raise ValueError(f'Expected {value!r} to be one of {self.options!r}')
Number Vérifier que la valeur est int Ou float. Selon les paramètres optionnels , Il peut également vérifier que la valeur se situe entre un minimum ou un maximum donné .
class Number(Validator):
def __init__(self, minvalue=None, maxvalue=None):
self.minvalue = minvalue
self.maxvalue = maxvalue
def validate(self, value):
if not isinstance(value, (int, float)):
raise TypeError(f'Expected {value!r} to be an int or float')
if self.minvalue is not None and value < self.minvalue:
raise ValueError(
f'Expected {value!r} to be at least {self.minvalue!r}'
)
if self.maxvalue is not None and value > self.maxvalue:
raise ValueError(
f'Expected {value!r} to be no more than {self.maxvalue!r}'
)
String Vérifier que la valeur est str. Selon les paramètres optionnels , Il peut vérifier une longueur minimale ou maximale donnée . Il peut également valider les predicate.
class String(Validator):
def __init__(self, minsize=None, maxsize=None, predicate=None):
self.minsize = minsize
self.maxsize = maxsize
self.predicate = predicate
def validate(self, value):
if not isinstance(value, str):
raise TypeError(f'Expected {value!r} to be an str')
if self.minsize is not None and len(value) < self.minsize:
raise ValueError(
f'Expected {value!r} to be no smaller than {self.minsize!r}'
)
if self.maxsize is not None and len(value) > self.maxsize:
raise ValueError(
f'Expected {value!r} to be no bigger than {self.maxsize!r}'
)
if self.predicate is not None and not self.predicate(value):
raise ValueError(
f'Expected {self.predicate} to be true for {value!r}'
)
Écrivez ceci lorsqu'il est appliqué :
class Component:
name = String(minsize=3, maxsize=10, predicate=str.isupper)
kind = OneOf('wood', 'metal', 'plastic')
quantity = Number(minvalue=0)
def __init__(self, name, kind, quantity):
self.name = name
self.kind = kind
self.quantity = quantity
Le descripteur bloque la création d'instances invalides :
>>> Component('Widget', 'metal', 5) # Blocked: 'Widget' is not all uppercase
Traceback (most recent call last):
...
ValueError: Expected <method 'isupper' of 'str' objects> to be true for 'Widget'
>>> Component('WIDGET', 'metle', 5) # Blocked: 'metle' is misspelled
Traceback (most recent call last):
...
ValueError: Expected 'metle' to be one of {'metal', 'plastic', 'wood'}
>>> Component('WIDGET', 'metal', -5) # Blocked: -5 is negative
Traceback (most recent call last):
...
ValueError: Expected -5 to be at least 0
>>> Component('WIDGET', 'metal', 'V') # Blocked: 'V' isn't a number
Traceback (most recent call last):
...
TypeError: Expected 'V' to be an int or float
>>> c = Component('WIDGET', 'metal', 5) # Allowed: The inputs are valid
Les derniers mots
À propos de Python Métaprogrammation de,Résumé ci - après:
Si vous voulez que certaines fonctions aient la même fonctionnalité , J'espère que vous ne changerez pas la façon dont vous appelez 、Ne pas écrire de code dupliqué、Facile à entretenir, Les décorateurs peuvent être utilisés pour .
Si vous voulez que certaines classes aient certaines des mêmes caractéristiques , Ou mettre en œuvre le contrôle de la classe dans la définition de la classe , Nous pouvons personnaliser une métaclasse , Puis laissez la métaclasse de sa classe pointer vers cette classe .
Si vous voulez que les propriétés de l'instance aient certaines caractéristiques communes , Vous pouvez personnaliser une classe de descripteurs .
C'est tout ce que j'ai partagé,Pour en savoir plus python Knowledge Welcome to the public number :Python Cercle d'apprentissage de la programmation ,Envoyer “J” Disponible gratuitement,Partage quotidien des marchandises sèches