2020-12-29 17:38:17 +01:00
import pytest
from bs4 import BeautifulSoup
from core . parsing_utils import (
ParsingFlow ,
ReferenciaFieldInstructions ,
PrecioFieldInstructions ,
TamanoCategoricoFieldInstructions ,
M2FieldInstructions ,
TipoAnuncioFieldInstructions ,
CalleFieldInstructions ,
BarrioFieldInstructions ,
DistritoFieldInstructions ,
CiudadFieldInstructions ,
SecondaryFeaturesFieldInstructions ,
TelefonoFieldInstructions ,
)
@pytest.fixture
def real_ad_html ( ) :
html = """
< ! DOCTYPE html >
< html lang = " es " env = " es " username = " " data - userAuth = " false " > < head > < meta http - equiv = " X-UA-Compatible " content = " IE=edge,chrome=1 " > < title > Garaje en venta en Passatge de Simó , 10 , La Sagrada Família , Barcelona & #8212; idealista</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta name="description" content="garaje de 9 m², Garaje en venta en Passatge de Simó, 10, La Sagrada Família, Barcelona, barrio La Sagrada Família"> <meta name="author" content="idealista.com"> <meta http-equiv="cleartype" content="on"> <meta name="pragma" content="no-cache"/> <meta http-equiv="Pragma" content="no-cache"/> <meta http-equiv="Expires" content="Fri, 01 Jan 1990 00:00:00 GMT"/> <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate"/> <script src="https://st1.idealista.com/static/common/release/mavericks/legacy/js/didomi/didomi-consent-lang.js"></script> <script src="https://st1.idealista.com/static/common/release/mavericks/legacy/js/didomi/didomi-iab-integration.js"></script> <script src="https://st1.idealista.com/static/common/release/mavericks/legacy/js/didomi/didomi-embed.js"></script> <script src="https://st1.idealista.com/static/common/release/mavericks/legacy/js/tealium/tealium-wrapper.js?20201222-0907"></script> <link rel="shortcut icon" href="https://st1.idealista.com/favicon.ico?20201222-0907"> <link rel="apple-touch-icon" sizes="32x32" href="https://st1.idealista.com/static/common/icons/32x32.png?20201222-0907"> <link rel="apple-touch-icon-precomposed" href="https://st1.idealista.com/static/common/icons/32x32.png?20201222-0907"> <link rel="apple-touch-icon" sizes="57x57" href="https://st1.idealista.com/static/common/icons/57x57.png?20201222-0907"> <link rel="apple-touch-icon-precomposed" href="https://st1.idealista.com/static/common/icons/57x57.png?20201222-0907"> <link rel="apple-touch-icon" href="https://st1.idealista.com/static/common/icons/60x60.png?20201222-0907"> <link rel="apple-touch-icon-precomposed" href="https://st1.idealista.com/static/common/icons/60x60.png?20201222-0907"> <link rel="apple-touch-icon" sizes="76x76" href="https://st1.idealista.com/static/common/icons/76x76.png?20201222-0907"> <link rel="apple-touch-icon-precomposed" sizes="76x76" href="https://st1.idealista.com/static/common/icons/76x76.png?20201222-0907"> <link rel="apple-touch-icon" sizes="96x96" href="https://st1.idealista.com/static/common/icons/96x96.png?20201222-0907"> <link rel="apple-touch-icon-precomposed" href="https://st1.idealista.com/static/common/icons/96x96.png?20201222-0907"> <link rel="apple-touch-icon" sizes="120x120" href="https://st1.idealista.com/static/common/icons/120x120.png?20201222-0907"> <link rel="apple-touch-icon-precomposed" sizes="120x120" href="https://st1.idealista.com/static/common/icons/120x120.png?20201222-0907"> <link rel="apple-touch-icon" sizes="128x128" href="https://st1.idealista.com/static/common/icons/128x128.png?20201222-0907"> <link rel="apple-touch-icon-precomposed" href="https://st1.idealista.com/static/common/icons/128x128.png?20201222-0907"> <link rel="apple-touch-icon" sizes="144x144" href="https://st1.idealista.com/static/common/icons/144x144.png?20201222-0907"> <link rel="apple-touch-icon-precomposed" href="https://st1.idealista.com/static/common/icons/144x144.png?20201222-0907"> <link rel="apple-touch-icon" sizes="152x152" href="https://st1.idealista.com/static/common/icons/152x152.png?20201222-0907"> <link rel="apple-touch-icon-precomposed" sizes="152x152" href="https://st1.idealista.com/static/common/icons/152x152.png?20201222-0907"> <link rel="apple-touch-icon" sizes="167x167" href="https://st1.idealista.com/static/common/icons/167x167.png?20201222-0907"> <link rel="apple-touch-icon-precomposed" href="https://st1.idealista.com/static/common/icons/167x167.png?20201222-0907"> <link rel="apple-touch-icon" sizes="180x180" href="https://st1.idealista.com/static/common/icons/180x180.png?20201222-0907"> <link rel="apple-touch-icon-precomposed" href="https://st1.idealista.com/static/common/icons/180x180.png?20201222-0907"> <link rel="apple-touch
var sas = sas | | { } ;
sas . cmd = sas . cmd | | [ ] ;
sas . cmd . push ( function ( ) {
if ( sas . setup ) {
sas . setup ( { domain : ' //smart.idealista.com ' , async : true , renderMode : 2 } ) ;
}
} ) ;
< / script > < script type = " didomi/javascript " data - vendor = " iab:45 " id = " advertisementScript " >
var sas = sas | | { } ;
sas . cmd = sas . cmd | | [ ] ;
sas . cmd . push ( function ( ) {
if ( sas . call ) {
sas . call ( ' onecall ' , {
siteId : 33114 ,
pageId : 253244 ,
formatId : ' 16025,16023 ' ,
target : ' geo=0-EU-ES-08-13-001-019-02-005-XX-XX;typ=gar;geop=0-EU-ES-08;ope=s;geoc=0-EU-ES-08-13;auth=f '
} , {
onNoad : function ( data ) {
if ( data . formatId ) {
var bannerDiv = document . getElementById ( ' banners_ ' + data . formatId ) ;
if ( bannerDiv != null ) {
bannerDiv . style . display = ' none ' ;
}
}
}
} ) ;
}
} ) ;
< / script > < link rel = " alternate " hreflang = " ca " href = " https://www.idealista.com/ca/inmueble/92174198/ " / > < link rel = " alternate " hreflang = " en " href = " https://www.idealista.com/en/inmueble/92174198/ " / > < link rel = " alternate " hreflang = " fr " href = " https://www.idealista.com/fr/inmueble/92174198/ " / > < link rel = " alternate " hreflang = " de " href = " https://www.idealista.com/de/inmueble/92174198/ " / > < link rel = " alternate " hreflang = " it " href = " https://www.idealista.com/it/inmueble/92174198/ " / > < link rel = " alternate " hreflang = " pt " href = " https://www.idealista.com/pt/inmueble/92174198/ " / > < link rel = " alternate " hreflang = " da " href = " https://www.idealista.com/da/inmueble/92174198/ " / > < link rel = " alternate " hreflang = " fi " href = " https://www.idealista.com/fi/inmueble/92174198/ " / > < link rel = " alternate " hreflang = " no " href = " https://www.idealista.com/no/inmueble/92174198/ " / > < link rel = " alternate " hreflang = " nl " href = " https://www.idealista.com/nl/inmueble/92174198/ " / > < link rel = " alternate " hreflang = " pl " href = " https://www.idealista.com/pl/inmueble/92174198/ " / > < link rel = " alternate " hreflang = " ro " href = " https://www.idealista.com/ro/inmueble/92174198/ " / > < link rel = " alternate " hreflang = " ru " href = " https://www.idealista.com/ru/inmueble/92174198/ " / > < link rel = " alternate " hreflang = " sv " href = " https://www.idealista.com/sv/inmueble/92174198/ " / > < link rel = " alternate " hreflang = " el " href = " https://www.idealista.com/el/inmueble/92174198/ " / > < link rel = " alternate " hreflang = " zh " href = " https://www.idealista.com/zh/inmueble/92174198/ " / > < / head > < body id = " " > < script type = " didomi/javascript " data - vendor = " c:at-internet " >
< ! - -
xtnv = document ;
xtsd = " https://logs3 " ;
xtsite = " 352991 " ;
xtdmc = " .idealista.com " ;
xtn2 = " 7 " ;
xtpage = " detalle::home " ;
xtdi = " 0 " ;
xt_multc = " " ;
xt_orderid = " " ;
xt_roimt = " " ;
xt_an = " anonymous " ;
xt_ac = " 2 " ;
clic = " #clic# " ;
xt_tags = " [sin_tag] " ;
if ( window . xtparam != null ) {
window . xtparam + = " &tag= " + xt_tags ;
} else {
window . xtparam = " &tag= " + xt_tags ;
} ;
if ( window . xtparam != null ) {
window . xtparam + = " &cmd= " + xt_orderid + " &an= " + xt_an + " &ac= " + xt_ac + " &roimt= " + xt_roimt + xt_multc ;
} else {
window . xtparam = " &cmd= " + xt_orderid + " &an= " + xt_an + " &ac= " + xt_ac + " &roimt= " + xt_roimt + xt_multc ;
} ;
/ / - - >
< / script > < script type = ' didomi/javascript ' data - vendor = ' c:at-internet ' src = ' https://st1.idealista.com/static/common/js/ext/xiti/xiti.js ' async > < / script > < script type = " text/javascript " src = " https://st1.idealista.com/static/common/js/criteo/ld.js?20201222-0907 " async = " true " > < / script > < script type = " text/javascript " >
window . criteo_q = window . criteo_q | | [ ] ;
window . criteo_q . push (
{ event : " setAccount " , account : [ 13122 , 19089 , 31531 ] } ,
{ event : " setCustomerId " , id : " " } ,
{ event : " setSiteType " , type : " d " } ,
{ event : " viewItem " , item : " 92174198 " }
) ;
< / script > < script type = " text/javascript " >
var utag_data = { " page " : { " section " : " portal " , " subSection " : " detail " , " name " : " home " , " language " : " es " , " category " : " detail " , " subCategory " : " " , " subSubCategory " : " " , " template " : " " } , " user " : { " status " : " 2 " } , " response " : { " statusCode " : " 200 " , " loadBalancer " : " web " } , " ad " : { " id " : " 92174198 " , " operation " : " 1 " , " typology " : " 4 " , " address " : { " municipalityId " : " 0-EU-ES-08-13-001-019 " , " provinceId " : " 0-EU-ES-08 " , " locationId " : " 0-EU-ES-08-13-001-019-02-005 " , " locationLevel " : " 8 " } , " price " : " 17900.0 " , " characteristics " : { " hasLift " : " 0 " , " constructedArea " : " 9.15 " } , " condition " : { } , " media " : { " photoNumber " : " 4 " , " videoNumber " : " 0 " , " hasFloorPlan " : " 0 " , " has3Dtour " : " 0 " , " hasHomeStaging " : " 0 " } , " owner " : { " type " : " 1 " , " commercialId " : " " , " contactPreference " : " 1 " } , " origin " : " 1 " , " isRecommended " : " 0 " , " hasRecommended " : " 0 " , " numberRecommended " : " " , " originTypeRecommended " : " " } , " search " : { " operation " : " 1 " , " typology " : " 4 " , " geo " : { " type " : " OFFICIAL_ZONE " , " locationId " : " 0-EU-ES-08-13-001-019-02-005 " , " locationLevel " : " 8 " } , " orderBy " : " relevance-desc " } , " correlation " : { " id " : " 2515b437-2656-43e1-8709-86d80b5e9803 " , " session " : " e90e4a02-0586-4791-9f8c-100926262753 " } , " mustExclude " : " 1 " , " didomiVendorsConsent " : " " } ;
var dataLayerContext = utag_data ;
var toggleTealiumClicks = true
window . utag_cfg_ovrd = { noview : true } ;
window . utag_data = utag_data ;
< / script > < script type = " text/javascript " >
( function ( a , b , c , d ) {
a = ' //tags.tiqcdn.com/utag/idealista/es-portal/prod/utag.js ' ;
b = document ; c = ' script ' ; d = b . createElement ( c ) ; d . onload = function ( ) { window . idealistaUtag & & window . idealistaUtag . init ( ) } ; d . src = a ; d . type = ' text/java ' + c ; d . async = true ;
a = b . getElementsByTagName ( c ) [ 0 ] ; a . parentNode . insertBefore ( d , a ) ;
} ) ( ) ;
< / script > < script type = " text/javascript " >
var detailWithSuggestionsToggle = true
if ( ! detailWithSuggestionsToggle | | ! false ) {
function onInitIdealistaUtag ( ) {
window . utag . view ( window . utag_data ) ;
}
if ( window . idealistaUtag ) {
window . idealistaUtag . onInit ( onInitIdealistaUtag ) ;
}
}
< / script > < header id = " main-header " class = " help-center ide-header " > < div class = " col-ide-lg-12 col-ide-xl-12 content " > < figure class = " logo-container " > < a href = ' / ' class = ' id-logo ' > < img alt = ' idealista ' src = ' https://st1.idealista.com/static/common/img/idealista.svg ' title = ' idealista ' > < / img > < / a > < / figure > < nav > < div class = " lang-selector lang-responsive " > < span class = " lang-selector--lang-selected icon-arrow-dropdown " > < span class = " flag lang-es " > < / span > < span class = " lang-text " > Español < / span > < / span > < ul class = " lang-selector--lang-options " data - url - attribute = " /user/update-user-language " data - user - authenticated = " false " > < li > < a rel = " nofollow " id = " idioma-ca " value = " ca " href = " /ca/inmueble/92174198/ " data - markup = " cambio::idioma::cabecera::ca " > < span > < span class = " flag lang-ca " > < / span > Català < / span > < / a > < / li > < li > < a rel = " nofollow " id = " idioma-en " value = " en " href = " /en/inmueble/92174198/ " data - markup = " cambio::idioma::cabecera::en " > < span > < span class = " flag lang-en " > < / span > English < / span > < / a > < / li > < li > < a rel = " nofollow " id = " idioma-fr " value = " fr " href = " /fr/inmueble/92174198/ " data - markup = " cambio::idioma::cabecera::fr " > < span > < span class = " flag lang-fr " > < / span > Français < / span > < / a > < / li > < li > < a rel = " nofollow " id = " idioma-de " value = " de " href = " /de/inmueble/92174198/ " data - markup = " cambio::idioma::cabecera::de " > < span > < span class = " flag lang-de " > < / span > Deutsch < / span > < / a > < / li > < li > < a rel = " nofollow " id = " idioma-it " value = " it " href = " /it/inmueble/92174198/ " data - markup = " cambio::idioma::cabecera::it " > < span > < span class = " flag lang-it " > < / span > Italiano < / span > < / a > < / li > < li > < a rel = " nofollow " id = " idioma-pt " value = " pt " href = " /pt/inmueble/92174198/ " data - markup = " cambio::idioma::cabecera::pt " > < span > < span class = " flag lang-pt " > < / span > Português < / span > < / a > < / li > < li > < a rel = " nofollow " id = " idioma-da " value = " da " href = " /da/inmueble/92174198/ " data - markup = " cambio::idioma::cabecera::da " > < span > < span class = " flag lang-da " > < / span > Dansk < / span > < / a > < / li > < li > < a rel = " nofollow " id = " idioma-fi " value = " fi " href = " /fi/inmueble/92174198/ " data - markup = " cambio::idioma::cabecera::fi " > < span > < span class = " flag lang-fi " > < / span > Suomi < / span > < / a > < / li > < li > < a rel = " nofollow " id = " idioma-no " value = " no " href = " /no/inmueble/92174198/ " data - markup = " cambio::idioma::cabecera::no " > < span > < span class = " flag lang-no " > < / span > Norsk < / span > < / a > < / li > < li > < a rel = " nofollow " id = " idioma-nl " value = " nl " href = " /nl/inmueble/92174198/ " data - markup = " cambio::idioma::cabecera::nl " > < span > < span class = " flag lang-nl " > < / span > Nederlands < / span > < / a > < / li > < li > < a rel = " nofollow " id = " idioma-pl " value = " pl " href = " /pl/inmueble/92174198/ " data - markup = " cambio::idioma::cabecera::pl " > < span > < span class = " flag lang-pl " > < / span > Polski < / span > < / a > < / li > < li > < a rel = " nofollow " id = " idioma-ro " value = " ro " href = " /ro/inmueble/92174198/ " data - markup = " cambio::idioma::cabecera::ro " > < span > < span class = " flag lang-ro " > < / span > Română < / span > < / a > < / li > < li > < a rel = " nofollow " id = " idioma-ru " value = " ru " href = " /ru/inmueble/92174198/ " data - markup = " cambio::idioma::cabecera::ru " > < span > < span class = " flag lang-ru " > < / span > Русский < / span > < / a > < / li > < li > < a rel = " nofollow " id = " idioma-sv " value = " sv " href = " /sv/inmueble/92174198/ " data - markup = " cambio::idioma::cabecera::sv " > < span > < span class = " flag lang-sv " > < / span > Svenska < / span > < / a > < / li > < li > < a rel = " nofollow " id = " idioma-el " value = " el " href = " /el/inmueble/92174198/ " data - markup = " cambio::idioma::cabecera::el " > < span > < span class = " flag lang-el " > < / span > Ελληνικά < / span > < / a > < / li > < li > < a rel = " nofollow " id = " idioma-zh " value = " zh " href = " /zh/inmueble/92174198/ " data - markup = " cambio::idioma::cabecera::zh " > < span > < span class = " flag lang-zh " > < / span > 中文 < / span > < / a > < / li > < li class = " separator " > < a href = " https://www.idealista.it/es/ " title = " " data - markup = " cambio::pais::cabecera::it " > idealista . it < / a > < / li > < li > < a href = " https://www.idealista.pt/es/ " title = " " data - markup = " cambio::pais::cabecera::pt " > idealista . pt < / a > < / li > < / ul > < / div > < ul class = " no-logged-user-menu ide-main-menu " id = " no-login-user-bar " > < li id = " put-adv-free " class = " ide-main-menu__adv-free " > < a data -
Un saludo < / textarea > < ul class = " last-contact-messages " > < / ul > < / label > < / div > < div class = " collapsed-form " > < div class = " item-form " > < div > < label > < input type = " hidden " name = " email2 " / > < span > Tu email < / span > < input name = " contact-email " type = " email " class = " js-email-field " value = " " autocomplete = " email " data - validation = " email " data - message - email = ' Revisa el formato de tu email ' data - required - group = " contact-data " data - contact - data - error = ' Indica un email o teléfono para que te puedan contestar ;-) ' > < / label > < / div > < / div > < div class = " item-form half-items clearfix " > < div > < label > < span > Tu teléfono < / span > < input name = " contact-phone " type = " tel " class = " js-phone-field " value = " " autocomplete = " tel " data - validation = " phone " data - message - phone = ' Revisa tu teléfono ' data - required - group = " contact-data " > < / label > < / div > < div > < label > < span > Tu nombre < / span > < input name = " contact-name " type = " text " autocomplete = " name " data - validation = " name " value = " " data - message - required = " Indica tu nombre " data - message - name = " Esto no parece un nombre " > < / label > < / div > < / div > < div class = " general-errors-container feedback full-width warning icon-feedbk-alert d-none " > < / div > < div class = " service-error-container feedback full-width error icon-feedbk-ko d-none " > < / div > < div class = " captcha-miniFields d-none " data - service - prefix = " " > < div class = " captcha " > < div class = " captcha-img " > < / div > < / div > < div class = " item-form half-items clearfix " > < label > < span > Escribe los números y letras que ves arriba < / span > < div > < input type = " text " class = " _captcha " name = " contact-captcha " autocapitalize = " none " autocorrect = " off " / > < / div > < / label > < p > < a class = " captcha-reload " href = " # " > ¿ No entiendes lo que pone ? < / a > < / p > < / div > < / div > < div id = " privacy-policy-detail " class = " item-form privacy-policy-container " > < div > < label class = " input-checkbox " data - validation = " checkbox " data - message - checkbox = " Para contactar debes aceptar la política de privacidad " data - error - name = " privacy-policy-checkbox " > < input type = " checkbox " name = " privacy-policy-checkbox " / > < span > < span > Aceptar < a href = " https://www.idealista.com/ayuda/articulos/politica-de-privacidad/ " target = " _blank " > política de privacidad < / a > < / span > < / span > < / label > < / div > < / div > < div id = " recommendations-detail " class = " item-form recommendations-container " > < div > < label class = " input-checkbox " data - error - name = " recommendations-checkbox " > < input type = " checkbox " name = " recommendations-checkbox " data - markup - recommendations = " detalle::conversiones::form_contacto::recomendations- " / > < span > < span > Recibir inmuebles de idealista similares a este < / span > < / span > < / label > < / div > < / div > < input type = " hidden " name = " adId " value = " 92174198 " / > < input type = " hidden " name = " operationId " value = " 1 " / > < input type = " hidden " name = " type " id = " contact-type " value = " 1 " / > < / div > < div class = " d-none " > < input type = " text " value = " " name = " contact-mail " / > < / div > < div > < input type = " submit " class = " btn action txt-bold txt-big desktop " value = " Contactar " data - contacted = " Contactar de nuevo " / > < input type = " submit " class = " btn action txt-bold txt-big no-desktop " value = " Enviar " data - contacted = " Enviar de nuevo " / > < / div > < / form > < / div > < input type = " hidden " name = " adId " value = " 92174198 " / > < div class = " ide-box-contact advertiser-data " > < div class = " contact-phones " > < div > < div class = " phone last-phone " > < p class = " txt-bold _browserPhone icon-phone " > 660 611 218 < / p > < / div > < / div > < / div > < p class = " txt-ref " > Anuncio : 92174198 < / p > < / div > < div class = " ide-box-contact module-contact-gray " > < h2 class = " txt-big txt-bold mb-small " > Anunciante < / h2 > < div class = " professional-name " > < div class = " name " > Particular < / div > < span class = " particular " > < span class = " icon-block " > < / span > María < input type = " hidden " name = " user-name " value = " María " / > < / span > < / div > < / div > < / section > < nav id = " sticky-contact " class = " sticky-contact-detail " > < div class = " sticky-contact-user col-ide-xs-6 col-ide-sm-4 two-buttons " > < div class = " sticky-contact-user-info " > < div class = " name " > Particular < / div > < span class = " particular " > < p class = " about-advertiser-name " > María < / p > < / span > < / div > < / div > < div class = " contact-buttons col-ide-xs-12 col-ide-sm-8 two-buttons "
var sas = sas | | { } ;
sas . cmd = sas . cmd | | [ ] ;
sas . cmd . push ( function ( ) {
if ( sas . render ) {
sas . render ( ' 16025 ' ) ;
}
} ) ;
< / script > < / div > < div class = " details-property " > < div class = " details-property-feature-one " > < h2 class = " txt-medium txt-bold mb-small " > Características básicas < / h2 > < div class = " details-property_features " > < ul > < li > Plaza para coche pequeño < / li > < li > 9 , 15 m² < / li > < li > Cubierta < / li > < / ul > < / div > < / div > < div class = " details-property-feature-two " > < h2 class = " txt-medium txt-bold mb-small " > Extras < / h2 > < div class = " details-property_features " > < ul > < li > Puerta automática de garaje < / li > < li > Personal de seguridad < / li > < / ul > < / div > < / div > < div class = " details-property-feature-three " > < / div > < / div > < / section > < section class = " details-box date-update-block " > < span class = " icon-feedbk-info " > < / span > < p class = " date-update-text " > Anuncio actualizado hace 7 días < / p > < / section > < / section > < div id = " premium-multimedia-container " class = " multimedia-content " > < div id = " homestaging " role = " img " > < / div > < div id = " gallery " class = " rs-gallery " > < / div > < div id = " gallery " class = " rs-gallery " > < / div > < / div > < div id = " multimedia-container " class = " multimedia-content " > < div id = " main-multimedia " class = " images-slider " > < / div > < / div > < section id = " mortgages " class = " mortgages-content " > < div id = " helpus-improve " class = " ide-box-detail overlay-box " > < h2 class = " txt-medium txt-bold mb-small icon-flag " > ¿ Hay algún error en este anuncio ? < / h2 > < p class = " helpus-improve_txt " > Infórmanos para corregirlo y ayudar a otros usuarios . < / p > < a class = " helpus-improve_notify " href = " # " data - xiti - page = " detalle::conversiones::form-reportar-error " data - xiti - shared - n2 = " true " data - title = " Cuéntanos qué error has visto " > Cuéntanos qué error has visto < / a > < / div > < div class = " ide-box-detail overlay-box " > < div class = " toggle-price " > < article class = " price-feature clearfix " > < h2 class = " txt-medium txt-bold " > Precio < / h2 > < section class = " price-features__container " > < p class = " flex-feature " > < span class = " txt-medium " > Precio del inmueble : < / span > < strong class = " txt-medium " > 17.900 € < / strong > < / p > < p class = " flex-feature " > < span class = " txt-medium " > Gastos de comunidad 20 € / mes < / span > < / p > < div > < p > < a href = " # " class = " counter-offer " data - xiti - page = " detalle::conversiones::form_contraoferta " data - xiti - shared - n2 = " true " title = ' Hacer una contraoferta ' > Hacer una contraoferta < / a > < / p > < p > < a id = " alert-price-down-btn " class = " favorite-btn " data - role = " add " data - text - add = " Recibir aviso si baja de precio " data - text - remove = " ¡Oído cocina! te avisaremos si baja de precio " data - xiti - click = " detalle::conversiones::avisame_si_baja " data - xiti - shared - n2 = " true " data - targetId = " 13 " title = " ¿Cuánto pagarías por este inmueble? recibir aviso si baja de precio " > Recibir aviso si baja de precio < / a > < / p > < / div > < / section > < / article > < / div > < / div > < div class = " ide-box-detail overlay-box mortgages " > < article class = " simulator clearfix " > < a class = " mortgages__logo " data - markup = " detalle::mortgage-logo " href = " /hipotecas/ " > < span class = " icon-font mb-regular " > < span class = " icon-idealista-icon " > < / span > < span class = " icon-hipotecas " > < / span > < / span > < / a > < section id = " simulator-container " class = " col-ide-lg-8 " > < div class = " item-form flex-feature taxes " > < span class = " txt-medium " > Precio del inmueble : < / span > < span class = " txt-medium " > 17.900 € < / span > < / div > < div class = " item-form js-taxes taxes flex-feature " > < span class = " txt-medium " > Impuestos y gastos : < a href = " # " id = " taxes-info " data - markup = " detalle::tax-expenses " class = " icon-feedbk-info " > < / a > < / span > < span > < span class = " js-result-tmpl txt-medium " data - template = " {{ =expensesGlosary.totalExpenses}} " > - < / span > & euro ; < / span > < / div > < div class = " item-form js-price-with-expenses price-expenses flex-feature " > < span class = " txt-medium " > Precio + gastos : < / span > < span class = " txt-medium txt-bold " > < span class = " js-result-tmpl " data - template = " {{ =purchasePrice}} " > - < / span > & euro ; < / span > < / div > < div class = " item-form item-redils js-saving-slider savings " > < label > < span class = " txt-medium " > Ahorro aportado < / span > < / label > < div class = " input-container " > < span class = " symbol " > & euro ; < / span > < input type = " tel " name = " givenAmount " maxlength = " 9 " data - input / > < span class = " percentage " > % < / span > < / div > < div data - slid
var sas = sas | | { } ;
sas . cmd = sas . cmd | | [ ] ;
sas . cmd . push ( function ( ) {
if ( sas . render ) {
sas . render ( ' 16023 ' ) ;
}
} ) ;
< / script > < / div > < / div > < / div > < / div > < / div > < section class = " links-block-home " > < div class = " content " > < nav > < h3 class = " h4-simulated " > ¿ Buscas inmueble ? < / h3 > < ul > < li > < a href = " / " title = " Buscador principal " > Buscador principal < / a > < / li > < li > < a href = " /pisos-bancos " title = " Pisos de bancos " > Pisos de bancos < / a > < / li > < li > < a href = " /pisos-vpo " title = " Vivienda protegida y VPO " > Vivienda protegida y VPO < / a > < / li > < li > < a href = " /properties-for-sale-in-spain " title = " Viviendas en venta en España " > Viviendas en venta en España < / a > < / li > < li > < a href = " /long-term-rentals-in-spain " title = " Viviendas en alquiler en España " > Viviendas en alquiler en España < / a > < / li > < li > < a href = " /pisos-en-alquiler " title = " Casas y pisos en alquiler " > Casas y pisos en alquiler < / a > < / li > < li > < a href = " /pisos-compartidos " title = " Pisos compartidos " > Pisos compartidos < / a > < / li > < li > < a href = " https://es.rentalia.com/ " title = " Rentalia, alquiler vacacional " > Rentalia , alquiler vacacional < / a > < / li > < li > < a href = " https://www.idealista.com/sala-de-prensa/informes-precio-vivienda/ " title = " Informe de precios " > Informe de precios < / a > < / li > < li > < a href = " https://www.idealista.com/maps/ " title = " idealista maps " > idealista maps < / a > < / li > < li > < a href = " /luxury-homes-spain " title = " Pisos y casas de lujo en venta en España " > Pisos y casas de lujo en venta en España < / a > < / li > < li > < a href = " /luxury-rentals-spain " title = " Pisos y casas de lujo en alquiler en España " > Pisos y casas de lujo en alquiler en España < / a > < / li > < / ul > < / nav > < nav > < h3 class = " h4-simulated " > ¿ Tienes un inmueble ? < / h3 > < ul > < li > < a href = " /info/publicar-anuncio " title = " Pon tu anuncio gratis " > Pon tu anuncio gratis < / a > < / li > < li > < a href = " /particulares " title = " Servicios para anunciantes particulares " > Servicios para anunciantes particulares < / a > < / li > < li > < a href = " /info/mejorar-foto-video " title = " Mejorar las fotos de tu anuncio " > Mejorar las fotos de tu anuncio < / a > < / li > < li > < a href = " /certificadoenergetico/ " title = " Solicitar certificado energético " > Solicitar certificado energético < / a > < / li > < li > < a href = " /contratos-de-alquiler " title = " Crea gratis tu contrato de alquiler " > Crea gratis tu contrato de alquiler < / a > < / li > < li > < a href = " https://www.idealista.com/labs/cartel-anuncio-piso.htm " title = " Imprimir carteles de tu anuncio " > Imprimir carteles de tu anuncio < / a > < / li > < li > < a href = " /valoracion-de-inmuebles/ " title = " Valora gratis cualquier inmueble " > Valora gratis cualquier inmueble < / a > < / li > < li > < a href = " /que-inmobiliaria-elegir/ " title = " Vender tu piso con una agencia " > Vender tu piso con una agencia < / a > < / li > < li > < a href = " /base-datos-inquilinos-morosos/como-incluir-morosos " title = " Notificar inquilino moroso " > Notificar inquilino moroso < / a > < / li > < / ul > < / nav > < nav > < h3 class = " h4-simulated " > ¿ Eres profesional inmobiliario ? < / h3 > < ul > < li > < a href = " /nuevo-profesional " title = " Publica tus inmuebles de profesional " > Publica tus inmuebles de profesional < / a > < / li > < li > < a href = " /login " title = " ¿Ya eres cliente? Entra en tu cuenta " > ¿ Ya eres cliente ? Entra en tu cuenta < / a > < / li > < li > < a href = " https://www.idealista.com/webinars/ " title = " Webinars de idealista " > Webinars de idealista < / a > < / li > < li > < a href = " /info/publicidad " title = " Publicidad en idealista " > Publicidad en idealista < / a > < / li > < li > < a href = " /info/acuerdos-internacionales " title = " Acuerdos internacionales " > Acuerdos internacionales < / a > < / li > < li > < a href = " https://www.idealista.com/tools/servicios " title = " idealista/tools: software de gestión para inmobiliarias " > idealista / tools : software de gestión para inmobiliarias < / a > < / li > < li > < a href = " /tools/software-recomendado-para-inmobiliarias " title = " Otros software recomendados " > Otros software recomendados < / a > < / li > < li > < a href = " /base-datos-inquilinos-morosos/consultar-lista-morosos " title = " Registro inquilinos morosos " > Registro inquilinos morosos < / a > < / li > < li > < a href = " https://www.idealista.com/data/ " title = " idealista/data: tecnología para el análisis inmobiliario " > idealista / data : tecnología para el análisis inmobiliario < / a > < / li > <
var smartBannerConfig = { activated : false , locale : " es " , userAuthenticated : false , urls : { urlLegalNotice : " /info/politica-cookies " } } ;
< / script > < script type = " text/javascript " >
var communicatorConfig = { enabled : true , endPointURL : " //commt.idealista.com " , authCookieName : " cc " , refreshTokenURL : " /communicator/refresh-token.ajax " } ;
< / script > < script >
var config = { locale : " es " , userAuthenticated : false , propertyId : 92174198 , favoriteFirstTime : true , discardFirstTime : true , urlAddFavorite : " /add-favorite.htm " , urlAdNewContactInfo : " /es/ajax/listingController/adContactInfoForDetail.ajax " , addFavoriteTargetId : " 32 " , discardTargetId : " 39 " , viewStatisticsTargetId : " 40 " , urlRemoveFavorite : " /remove-favorite.htm " , urlAddDiscarded : " /add-ruled-out-detail.htm " , urlRemoveDiscarded : " /remove-ruled-out.htm " , urlAddComment : " /update-favorite-comment.htm " , urlContact : " /ajax/contact/2/sendcontact.ajax " , urlCounterOffer : " /ajax/contact/suggested/4/sendcontact.ajax " , urlContactDetailGallery : " /ajax/contact/42/sendcontact.ajax " , urlContactDetailGalleryVirtualTour : " /ajax/contact/suggested/sendcontact.ajax " , urlContactDetailGalleryFloorPlan : " /ajax/contact/45/sendcontact.ajax " , urlContactDetailGallery3DTour : " /ajax/contact/46/sendcontact.ajax " , urlContactDetailGalleryVideo : " /ajax/contact/47/sendcontact.ajax " , urlSuggestedAgencies : " /ajax/zoneexperts/suggested/2/sendcontact.ajax " , urlReloadCaptcha : " /ajax/captcha/reloadCaptcha.ajax " , urlVirtualTour : " " , urlAlertSummary : " /ajax/alertsummary.ajax/25 " , mediaTablet : " screen and (max-device-width: 1023px) and (min-device-width: 767px), screen and (max-width: 1023px) and (min-width: 767px) " , mediaMobile : " screen and (max-width: 767px) " , mediaDesktop : " screen and (min-width: 1024px) and (min-device-width: 1024px) " , mediaLandscape : " screen and (orientation: landscape) " , mortgagesMinSavingsToContact : 7 , showLightboxPosition : null , showLightboxVirtualTour : false , detailUrl : " /inmueble/92174198/ " , imageSizes : { " 1280X400 " : 80 , " 140X105 " : 80 , " 140 " : 80 , " 300 " : 85 , " 600 " : 80 , " 850 " : 80 , " 1500 " : 80 , " 250X188 " : 80 , " 500X375 " : 80 , " 300X225 " : 80 , " 600X450 " : 80 } , maxAdContactMessagesSuggested : 3 , openContactModal : false , showAdIncidenceForm : false , urlContactMortgages : " /ajax/contact-mortgages.ajax?xtatc=[detalle_solicitar_hipoteca] " , xitiClientMarkup : { detailIncidenceSendForm : " detalle::conversiones::form-reportar-error " , detailContactForm : " detalle::conversiones::form_contacto " , detailFavoritesLoginInPlace : " detalle::conversiones::login-favoritear-inplace " , detailRuledOutLoginInPlace : " detalle::conversiones::login-descartar-inplace " } , contactInGalleryLightboxTitle : " Contactar " , contactInGalleryFormTitle : " ¿Te ha gustado? " , urlCreateUserAskingStats : " /new-user-asking-stats.ajax " , showMorePhotos : { desktop : { position : 4 , threshold : 4 , } , mobile : { position : 3 , threshold : 3 , } } , phoneIntlConfig : { initialCountry : " es " , preferredCountries : [ ] , allCountries : [ ] } , urlCalculateUCISavings : " /ajax/calculate-savings-form " , urlConversations : " " , currentSearchUrl : " /venta-garajes/barcelona/eixample/la-sagrada-familia/ " , urlResultsFromComparator : " /ajax/retrieve-comparator-results.ajax " , urlResultsFromValidationStep1 : " /hipotecas/validate-mortgages-contact-step1.ajax " , urlResultsFromValidationStep2 : " /hipotecas/validate-mortgages-contact-step2.ajax " , staticsBaseUri : " https://st1.idealista.com/static " , galleryReactToggle : true , isFavourite : false , showContact : true , detailWithSuggestionsToggle : true , hasToShowRecommendations : false } ; var microsoft_key = " " ; var mapConfig = { latitude : " 41.4042378 " , longitude : " 2.1732132 " , onMapElements : false , markerType : 0 , markerVisible : 1 , i18nDetail : " " , versionId : " 3.30 " , language : " es " , clientId : " gme-idealistalibertad1 " , dataProvider : " googlev3 " , channel : " map_detail " } ; var adMultimediasInfo = { fullScreenGalleryPics : [ { height : 768 , width : 1024 , multimediaId : 832773863 , title : " Imagen Plaza de parking de garaje en Passatge de Simó, 10, La Sagrada Família, Barcelona " , imageUrl : " /inmueble/92174198/foto/1/ " , absolutePosition : 1 , shortImageUrl : false , aspectRatioId : 1 , hoverText : " Plaza de parking " , imageDataService : " https://img3.idealista.com/blur/WEB_DETAIL/0/id.pro.es.image.master/23/bc/87/832773863.jpg,WEB_DETAIL " , isPlan : false , tag : " Plaza de parking " } , { height : 768 , width : 1024 , multimediaId : 832773866 , title : " Imagen Plaza de parking de garaje en Passatge de Simó, 10, La Sagrada Família, Barcelona " , imageUrl : " /inmueble/92174198/foto/2/ " , absolutePosition : 2 , shortImageUrl : false , aspectRatioId : 1 , hoverText : " Plaza de parking " , imageDataService : " https://img3.idealista.com/blur/WEB_DETAIL/0/id.pro.es.image.master/b0/fe/e2/832773866.jpg,WEB_
< / script > < script >
var mortgagesConfig = { countries : [ ] , simulateSavingsURL : " /ajax/calculate-savings-form " , initialPrice : 17900 , savedMoney : 5370 , rateInit : 0.89 , locationId : " 0-EU-ES-08 " , defaultPercentSavings : 30 , propertyTypeId : 1 , maxYears : 40 , limitYears : 30 , yearsSupported : 30 , taxRateVariable : 0.89 , taxRateFixed : 1.9 , taxRateFixedRanges : [ ] , simulationWithExpenses : true , calculationType : " MAX_PERCENTAGE_SAVINGS_CALCULATOR " , expensesCalculationType : " MIN_FIXED_SAVINGS_CALCULATOR " , buyingType : 1 , debounceMillis : 300 , termYears : 30 , markup_params : { read_more : { " read-more " : " detail::mortgage-simulation::read-info " } , read_close : { " read-more " : " detail::mortgage-simulation::close-info " } } , interestType : 2 , simulationType : 1 , offsetPercentSavings : 0 } ; mortgagesConfig . taxRateFixedRanges . push ( { min : 0 , max : 10 , rate : 1.5 } ) ; mortgagesConfig . taxRateFixedRanges . push ( { min : 0 , max : 10 , rate : 1.5 } ) ; mortgagesConfig . taxRateFixedRanges . push ( { min : 11 , max : 15 , rate : 1.55 } ) ; mortgagesConfig . taxRateFixedRanges . push ( { min : 11 , max : 15 , rate : 1.55 } ) ; mortgagesConfig . taxRateFixedRanges . push ( { min : 16 , max : 20 , rate : 1.9 } ) ; mortgagesConfig . taxRateFixedRanges . push ( { min : 16 , max : 20 , rate : 1.9 } ) ; mortgagesConfig . taxRateFixedRanges . push ( { min : 21 , max : 25 , rate : 2.15 } ) ; mortgagesConfig . taxRateFixedRanges . push ( { min : 21 , max : 25 , rate : 2.15 } ) ; mortgagesConfig . taxRateFixedRanges . push ( { min : 26 , max : 40 , rate : 2.25 } ) ; mortgagesConfig . taxRateFixedRanges . push ( { min : 26 , max : 40 , rate : 2.25 } ) ; mortgagesConfig . taxRateFixedRanges . push ( { min : 0 , max : 10 , rate : 1.5 } ) ; mortgagesConfig . taxRateFixedRanges . push ( { min : 11 , max : 15 , rate : 1.55 } ) ; mortgagesConfig . taxRateFixedRanges . push ( { min : 16 , max : 20 , rate : 1.9 } ) ; mortgagesConfig . taxRateFixedRanges . push ( { min : 21 , max : 25 , rate : 2.15 } ) ; mortgagesConfig . taxRateFixedRanges . push ( { min : 26 , max : 40 , rate : 2.25 } ) ; mortgagesConfig . countries . push ( { id : " 0-AS-AF " , name : " Afganistán " } ) ; mortgagesConfig . countries . push ( { id : " 0-EU-AL " , name : " Albania " } ) ; mortgagesConfig . countries . push ( { id : " 0-EU-DE " , name : " Alemania " } ) ; mortgagesConfig . countries . push ( { id : " 0-EU-AD " , name : " Andorra " } ) ; mortgagesConfig . countries . push ( { id : " 0-AF-AO " , name : " Angola " } ) ; mortgagesConfig . countries . push ( { id : " 0-AM-AI " , name : " Anguila " } ) ; mortgagesConfig . countries . push ( { id : " 0-AM-AG " , name : " Antigua y Barbuda " } ) ; mortgagesConfig . countries . push ( { id : " 0-AM-AN " , name : " Antillas Neerlandesas " } ) ; mortgagesConfig . countries . push ( { id : " 0-AS-SA " , name : " Arabia Saudí " } ) ; mortgagesConfig . countries . push ( { id : " 0-AF-DZ " , name : " Argelia " } ) ; mortgagesConfig . countries . push ( { id : " 0-AM-AR " , name : " Argentina " } ) ; mortgagesConfig . countries . push ( { id : " 0-AS-AM " , name : " Armenia " } ) ; mortgagesConfig . countries . push ( { id : " 0-AM-AW " , name : " Aruba " } ) ; mortgagesConfig . countries . push ( { id : " 0-OC-AU " , name : " Australia " } ) ; mortgagesConfig . countries . push ( { id : " 0-EU-AT " , name : " Austria " } ) ; mortgagesConfig . countries . push ( { id : " 0-AS-AZ " , name : " Azerbaiján " } ) ; mortgagesConfig . countries . push ( { id : " 0-AM-BS " , name : " Bahamas " } ) ; mortgagesConfig . countries . push ( { id : " 0-AS-BD " , name : " Bangladés " } ) ; mortgagesConfig . countries . push ( { id : " 0-AM-BB " , name : " Barbados " } ) ; mortgagesConfig . countries . push ( { id : " 0-AS-BH " , name : " Baréin " } ) ; mortgagesConfig . countries . push ( { id : " 0-EU-BE " , name : " Bélgica " } ) ; mortgagesConfig . countries . push ( { id : " 0-AM-BZ " , name : " Belice " } ) ; mortgagesConfig . countries . push ( { id : " 0-AF-BJ " , name : " Benín " } ) ; mortgagesConfig . countries . push ( { id : " 0-AM-BM " , name : " Bermudas " } ) ; mortgagesConfig . countries . push ( { id : " 0-EU-BY " , name : " Bielorrusia " } ) ; mortgagesConfig . countries . push ( { id : " 0-AM-BO " , name : " Bolivia " } ) ; mortgagesConfig . countries . push ( { id : " 0-EU-BA " , name : " Bosnia y Herzegovina " } ) ; mortgagesConfig . countries . push ( { id : " 0-AF-BW " , name : " Botsuana " } ) ; mortgagesConfig . countries . push ( { id : " 0-AM-BR " , name : " Brasil " } ) ; mortgagesConfig . countries . push ( { id : " 0-AS-BN " , name : " Brunéi " } ) ; mortgagesConfig . countries . push ( { id : " 0-EU-BG " , name : " Bulgaria " } ) ; mortgagesConfig . countries . push ( { id : " 0-AF-BF " , name : " Burkina Faso " } ) ; mortgagesConfig . countries . push ( { id : " 0-AF-BI " , name : " Burundi " } ) ; mortgagesConfig . countries . push ( { id : " 0-AS-BT " , name : " Bután " } ) ; mortgagesConfig . countries . push ( { id : " 0-AF-CV " , name : " Cabo Verde " } ) ; mortgagesConfig . countries . push ( { id : " 0-AS-KH " , name : " Camboya " } ) ; mortgagesConfig . countries . push ( { id : " 0-AF-CM " , name : " Camerún " } ) ; mortgagesConfig . countries . push ( { id : " 0-AM-CA
< / script > < script type = " text/microtemplate " id = " lightbox-header-images-tmpl " >
< span id = " image-gallery-tag " class = " main-title " > < / span > < span id = " image-gallery-share-links " > < div > < a href = " # " id = " image-gallery-share-friend-link " data - xiti - page = " detalle::conversiones::form_compartir_foto " data - xiti - shared - n2 = " true " class = " icon-share " > < span > Compartir < / span > < / a > < a href = " # " id = " gallery-contact-btn " class = " icon-mail " data - markup = ' detalle::photo::contact ' > < span > Contactar < / span > < / a > < / div > < / span > < span id = " image-gallery-pager " > < / span > < span class = " lightbox-close icon-close " data - xiti - page > < / span >
< / script > < script type = " text/microtemplate " id = " lightbox-header-map-tmpl " >
< div class = " address-tag " > < span id = " map-tag " class = " main-title " > Mapa de la zona < / span > < span id = " address " > Garaje en venta en Passatge de Simó , 10 < / span > < span class = " map-links " > | < a href = " # " class = " active " data - xiti - page = " detalle::streetview " data - xiti - shared - n2 = " true " > Ver foto de la calle < / a > < a href = " # " class = " d-none " data - xiti - page = " detalle::mapa " data - xiti - shared - n2 = " true " > Ver mapa < / a > < / span > < / div > < span class = " lightbox-close icon-close " > < / span > < div id = " warning-address " class = " contextual warning icon-feedbk-alert " > El anunciante prefiere no mostrar la dirección exacta , pero más o menos por aquí . . . < / div >
< / script > < script type = " text/javascript " defer src = " https://st1.idealista.com/static/common/release/detail/detail.js?20201222-0907 " > < / script > < script type = " text/javascript " defer src = " https://st1.idealista.com/static/react/gallery/dist/loadScripts.js?20201222-0907 " > < / script > < script type = " text/javascript " defer src = " https://st1.idealista.com/static/react/homestaging/dist/loadScripts.js?20201222-0907 " > < / script > < script type = " text/javascript " defer src = " https://st1.idealista.com/static/react/detailWithSuggestions/dist/loadScripts.js?20201222-0907 " > < / script > < script type = " text/microtemplate " id = " login-box-title-fav " >
< span class = " icon-fav " >
< span class = " txt-bold " > Guardar favorito < / span >
< / span >
< / script > < script type = " text/microtemplate " id = " login-box-title-discard " >
< span class = " icon-delete " >
< span class = " txt-bold " > Descartar < / span >
< / span >
< / script > < script type = " text/microtemplate " id = " login-box-title-favfilter " >
< span class = " txt-bold " > Ver tus favoritos en esta búsqueda < / span >
< / script > < script type = " text/microtemplate " id = " login-box-title-stats " >
< span class = " txt-bold " > Ver número de visitas y contactos < / span >
< / script > < script type = " text/microtemplate " id = " login-box-content-text-fav " >
< div class = " info-txt save-ad-text " >
< p > Puedes crear una lista de tus anuncios favoritos y te avisaremos si cambian de precio , añaden nuevas fotos , o si se elimina . < / p >
< p > También podrás escribir notas personales que sólo verás tú , para recordar detalles que te interesen . < / p >
< / div >
< / script > < script type = " text/microtemplate " id = " login-box-content-text-discard " >
< div class = " info-txt " >
< p > Descarta los anuncios que no te interesan y dejarás de verlos cuando busques . < / p >
< p > Si luego te arrepientes los puedes recuperar desde tus descartados . < / p >
< / div >
< / script > < script type = " text/microtemplate " id = " login-box-content-text-favfilter " >
< div class = " info-txt " >
< p > Consulta de forma rápida los favoritos que tienes en cada búsqueda y fíltralos por los criterios que quieras < / p >
< / div >
< / script > < script type = " text/microtemplate " id = " login-box-content-text-stats " >
< div class = " info-txt " >
< p > Consulta los datos de popularidad de los anuncios que te interesan : cuántas visitas y contactos han tenido , y cuántas personas lo tienen en sus favoritos < / p >
< / div >
< / script > < script type = " text/microtemplate " id = " login-box-content-tmpl " >
< div class = " login-box-form modal-inner " > { { = contentExplanation } } < div id = " loginlicker " > < label for = " email " > < span class = " username initial " > Tu email < / span > < span class = " username named " style = " display:none; " data - username = " Hola de nuevo :-) " > < / span > < input id = " email " class = " email " type = " email " name = " email " autocomplete = " email " / > < span class = " email-feedback " > Venga , escribe un email ; - ) < / span > < / label > < p class = " spinner-inline left " > Comprobando email < / p > < div class = " item-form pass-wrapper " > < label > < span class = " username " > Tu contraseña < / span > < input type = " password " class = " password js-password-field " name = " pwd " data - password - show = " Mostrar " data - password - hide = " Ocultar " / > < span class = " password-feedback " > Venga , escribe la contraseña ; - ) < / span > < / label > < / div > < p > < a href = " /recordar-password " class = " forgotpassword " > ¿ Has olvidado tu contraseña ? < / a > < / p > < label class = " input-checkbox " > < input name = " cookie " type = " checkbox " checked = " checked " / > < span > < span > Recordar tus datos < / span > < / span > < / label > < label > < span class = " msg-repeat " > Por favor , comprueba que tu email está bien escrito < / span > < span class = " repeat " > < / span > < / label > < label > < span id = " msg-accept-privacy-policy " > Tienes que aceptar la política de privacidad para poder continuar < / span > < / label > < label class = " input-checkbox " > < input id = " privacy-policy " name = " privacy-policy " type = " checkbox " data - markup - privacy / > < span > < span > Aceptar la < a href = " https://www.idealista.com/ayuda/articulos/politica-de-privacidad/ " target = " _blank " > política de privacidad < / a > < / span > < / span > < / label > < label class = " input-checkbox " > < input id = " subscribe-mailing " name = " subscribe-mailing " type = " checkbox " data - markup - communications / > < span > < span > Recibir información de inmuebles , noticias y otras comunicaciones promocionales desde idealista , idealista / data , idealista / hipotecas o Rentalia basadas en tu perfil . < / span > < / span > < / label > < / div > < / div >
< / script > < script type = " text/microtemplate " id = " login-box-button-initial " >
< div class = " modal-inner " >
< a href = " # " class = " btn action longer accept dummy " >
¡ Vamos !
< / a >
< / div >
< / script > < script type = " text/microtemplate " id = " login-box-button-fav " >
< div class = " modal-inner " >
< a href = " # " class = " btn action longer accept " >
Guardar favorito
< / a >
< / div >
< / script > < script type = " text/microtemplate " id = " login-box-button-discard " >
< div class = " modal-inner " >
< a href = " # " class = " btn action longer accept " >
Descartar anuncio
< / a >
< / div >
< / script > < script type = " text/microtemplate " id = " login-box-button-favfilter " >
< div class = " modal-inner " >
< a href = " # " class = " btn action longer accept " >
Ver sólo favoritos
< / a >
< / div >
< / script > < script type = " text/microtemplate " id = " login-box-button-stats " >
< div class = " modal-inner " >
< a href = " # " class = " btn action longer accept " >
Ver estadísticas
< / a >
< / div >
< / script > < script type = " text/microtemplate " id = " login-box-feedback-generic-content " >
< p class = " feedback warning icon-feedbk-alert " > Sólo falta que confirmes tu email < / p >
< div class = " modal-inner " >
< p > Hemos enviado un correo a { { = email } } , no olvides revisar tu correo no deseado , por si acaso ; - ) < / p >
< / div >
< / script > < script type = " text/microtemplate " id = " login-box-feedback-generic-buttons " >
< div class = " modal-inner " >
< a href = " # " class = " btn regular longer accept " >
Entendido
< / a >
< / div >
< / script > < script type = " text/microtemplate " id = " promo-app-title-tmpl " >
< span class = " txt-bold " > Bájate nuestra app < / span >
< / script > < script type = " text/microtemplate " id = " promo-app-content-tmpl " >
< img class = " promoApp " src = " " >
< p > Recibe avisos inmediatos de tus búsquedas y favoritos en cualquier lugar con la app gratuita de idealista < / p >
< div class = " download " > < a id = " buttonDownloadApp " class = " btn action txt-bold " data - markup = ' promo-app::download ' href = " http://m.onelink.me/d181dd66 " > Descargar ahora < / a > < / div >
< div class = " cancel " > < a class = " buttonCancelDownloadApp " id = " buttonCancelDownloadApp " data - markup = " promo-app::no-thanks " > No , gracias < / a > < / div >
< / script > < script src = " https://www.google.com/recaptcha/api.js?render=6LceRo8UAAAAAFbfO3vhtiQk66j-fJEkQichNAk2 " async defer > < / script > < script >
var configTwoSteps = {
urls : {
defaultLogin : " /es/login.ajax " ,
defaultPreauthorized : " /es/login-preauthorized.ajax " ,
userStatus : " /check-identification-service-user-status.ajax " ,
emailVerifyUrl : " /ajax/alerts-ls-existence-and-active "
} ,
userStatus : " " , / / authorized , preauthorized , blocked , error
recaptchaToggle : false ,
recaptcha2slToggle : true ,
recaptcha2slPublicKey : " 6LceRo8UAAAAAFbfO3vhtiQk66j-fJEkQichNAk2 " ,
recaptcha2slActionName : " login2sl " ,
recaptchaNotifyIncidence : " notifyIncidence " ,
deviceInfoToggle : false
} ;
< / script > < script >
var configSendIncidences = {
recaptchaPublicKey : " 6LceRo8UAAAAAFbfO3vhtiQk66j-fJEkQichNAk2 " ,
recaptchaAction : " notifyIncidence " ,
} ;
< / script > < script >
var configRecaptchaSendContact = {
recaptchaPublicKey : " 6LceRo8UAAAAAFbfO3vhtiQk66j-fJEkQichNAk2 " ,
recaptchaAction : " sendContact " ,
} ;
< / script > < script type = " text/microtemplate " id = " tsa-confirm-title " >
Verificación de identidad
< / script > < script type = " text/microtemplate " id = " tsa-confirm-send-again-pls " >
< div class = " modal-inner " >
< p > ¿ No lo has recibido o no tienes el móvil contigo ? < / p >
< p class = " icon-leads " > < a href = " # " id = " sendAgain " > Enviar nuevo código al móvil < / a > < / p >
< p class = " icon-phone " >
Ponte en contacto con nuestro equipo llamando al
< a class = " phone " href = " tel://900 37 30 02 " > 900 37 30 02 < / a >
< / p >
< div class = " item-form " >
< label >
< span > Código de verificación < / span >
< input type = " text " id = " code_2sa " name = " code " data - validation = " required " data - message - required = " Venga, escribe el código ;-) " / >
< / label >
< / div >
< / div >
< / script > < script type = " text/microtemplate " id = " tsa-confirm-content " >
< p class = " feedback success icon-feedbk-ok " > Hemos enviado un código al { { = phone } } < / p > < div class = " modal-inner " > < p > Por tu seguridad te recomendamos que antes de continuar compruebes el estado de tu conexión . Para ello deberías ver este < span class = " nobreak " > icono < img src = " https://st1.idealista.com/static/common/release/user-management/resources/img/locker.png " / > < / span > junto a la dirección web de idealista . < / p > < / div > < div class = " modal-inner " > < div class = " item-form " > < label > < span > Código de verificación < / span > < input type = " text " id = " code_2sa " name = " code " data - validation = " required " data - message - required = " Venga, escribe el código ;-) " / > < / label > < a href = " # " id = " resendCode " > ¿ No lo has recibido o no tienes el móvil contigo ? < / a > < / div > < / div >
< / script > < script type = " text/microtemplate " id = " tsa-confirm-buttons " >
< div class = " modal-inner " > < a href = " # " title = " Confirmar y entrar " class = " btn action accept confirm-btn " > Confirmar y entrar < / a > < / div >
< / script > < script type = " text/microtemplate " id = " tsa-confirm-wrong-code-feedback " >
El código insertado no coincide con el que te hemos enviado .
< / script > < script type = " text/microtemplate " id = " tsa-contactus-title " >
Verificación de identidad
< / script > < script type = " text/microtemplate " id = " tsa-contactus-insistent-feedback " >
Para continuar llama al número que te mostramos y sigue nuestras indicaciones
< / script > < script type = " text/microtemplate " id = " tsa-contactus-content " >
< p class = " feedback error icon-feedbk-ko " > Parece que estamos teniendo algún problema en validar el código que intentas insertar < / p > < div class = " modal-inner " > < div class = " contactus " > < p > Ponte en contacto con nuestro equipo llamando al < a class = " phone " href = " tel://902 50 80 00 " > 902 50 80 00 < / a > y te proporcionaremos un nuevo código de verificación < / p > < / div > < / div >
< / script > < script type = " text/microtemplate " id = " tsa-contactus-buttons " >
< div class = " modal-inner " > < a href = " # " title = " Insertar nuevo código " class = " btn regular accept " > Insertar nuevo código < / a > < / div >
< / script > < script type = " text/microtemplate " id = " tsa-contactus-insert-content " >
< div class = " modal-inner " > < p > Inserta el código que te hemos facilitado durante la llamada para acceder a tu cuenta < / p > < / div > < div class = " modal-inner " > < div class = " item-form " > < label > < span > Código de verificación < / span > < input type = " text " id = " code_2sa " name = " code " data - validation = " required " data - message - required = " Venga, escribe el código ;-) " / > < / label > < / div > < / div >
< / script > < script type = " text/microtemplate " id = " tsa-contactus-insert-buttons " >
< div class = " modal-inner " > < a href = " # " title = " Confirmar y entrar " class = " btn action accept confirm-btn " > Confirmar y entrar < / a > < / div >
< / script > < script type = " text/microtemplate " id = " blockedemail-modal-title " >
Cuenta bloqueada
< / script > < script type = " text/microtemplate " id = " blockedemail-modal-content " >
< p class = " feedback warning icon-feedbk-alert " > Cuenta bloqueada de manera preventiva < / p > < div class = " modal-inner " > < p > < span class = " info1 " > Hemos bloqueado temporalmente tu cuenta { { = email } } por motivos de seguridad . < / span > < / p > < p > < span class = " info2 " > Llámanos al < strong > 917 01 40 30 < / strong > < br > < / span > < span class = " info3 " > ( de lunes a viernes de 9 : 00 a 21 : 00 ) < br > < / span > < span class = " info4 " > Si tienes un anuncio publicado , llámanos desde el teléfono que tengas en el anuncio y te indicaremos los pasos a seguir . < / span > < / p > < p > < a class = " icon-phone btn action btn-phone " href = " tel://917014030 " > 917 01 40 30 < / a > < / p > < / div >
< / script > < script type = " text/microtemplate " id = " contact-feedback-tmpl " >
< div class = " contextual success icon-thumbup " > { { normalMessage } } { { userName } } ¡ Genial ! mensaje enviado a < b > < span class = ' capitalized ' > { { = userName } } < / span > < / b > { { : userName } } ¡ Genial ! mensaje enviado { { / userName } } { { : normalMessage } } mensaje enviado a las inmobliarias { { / normalMessage } } < / div > { { saveAlert } } < div > < p class = " text-save-search " > Guarda tu búsqueda para recibir por email nuevos anuncios similares a este < / p > < a href = " # " class = " btn action expand txt-bold txt-big save-search " title = ' Guardar búsqueda ' data - summaryurl = " /recibir-alertas-gratis-primer-resumen/25 " data - saveurl = " /savealert.ajax/20 " data - saverelatedurl = " /ajax/save-related-alert-popup.ajax/29 " data - emailverifyurl = " /ajax/alerts-ls-existence-and-active/18 " data - forgotpasswordurl = " /recordar-password " data - didntgetmailurl = " /info/info-problema-email " > Guardar búsqueda < / a > { { / saveAlert } } { { suggestAgencies } } < p > Estas inmobiliarias te pueden ayudar a buscar inmuebles similares < / p > < ul > { { @suggestedAgencies } } < li > < input type = " checkbox " id = " suggest_inmo_ {{ =_key}} " data - inmo - id = " {{ =_val.commercialDataId}} " checked / > < label for = " suggest_inmo_ {{ =_key}} " > { { = _val . commercialName } } < / label > < / li > { { / @suggestedAgencies } } < / ul > < a href = " # " class = " btn regular contact-pro expand " title = ' Contactar agencias ' > Contactar agencias < / a > { { / suggestAgencies } } < / div >
< / script > < script type = " text/microtemplate " id = " unblock-feedback " >
< div class = " contact-feedback feedback success icon-thumbup " >
< span > Ya puedes volver a contactar a { { = userName } } < / span >
< / div >
< / script > < script type = " text/microtemplate " id = " unblock-error-feedback " >
< div class = " contact-error-feedback feedback error icon-feedbk-ko " >
< span > algo no ha ido bien , por favor vuelve a intentarlo < / span >
< / div >
< / script > < script type = " text/microtemplate " id = " contact-unblock-modal-title " >
Anunciante bloqueado por ti
< / script > < script type = " text/microtemplate " id = " contact-unblock-modal " >
< div class = " modal-inner " >
< p class = " contact-again-title " > Para volver a contactar , debes desbloquearle antes . < / p >
< div class = " unblock - container - info " >
< div class = " unblock-container-info-content " >
< div class = " container " >
< div class = " img-container " >
< div class = " avatar " >
{ { info . image } }
< img class = " user-image " src = " {{ =info.image}} " >
{ { : info . image } }
< span class = " icon-user " > < / span >
{ { / info . image } }
< img class = " icon-blocked-user " width = " 24 " height = " 24 " src = " https://st1.idealista.com/static/common/img/icons/ic-blocked-on-avatar.svg " >
< / div >
< / div >
< div class = " info " >
< p class = " user-name " > { { = info . userName } } < / p >
< p > { { = info . address } } { { = info . price } } < / p >
< p > Tu motivo fue : < / p >
{ { info . blockedInfo . offensiveMessages } }
< p > " Mensajes ofensivos " < / p >
{ { / info . blockedInfo . offensiveMessages } }
{ { info . blockedInfo . seemsFraud } }
< p > " Parece fraude " < / p >
{ { / info . blockedInfo . seemsFraud } }
{ { info . blockedInfo . otherReason } }
< p > " <span class= " other - reason " > {{ =info.blockedInfo.text}}</span> " < / p >
{ { / info . blockedInfo . otherReason } }
< p class = " blocking-date " > Bloqueado desde el { { = info . blockedInfo . date } } < / p >
< / div >
< / div >
< / div >
< / div >
< div class = " modal-footer " >
< button class = " btn action " > Desbloquear < / button >
< a href = " {{ =info.url}} " class = " link " > Ver tu conversación con < span class = ' capitalized ' > { { = info . userName } } < / span > < / a >
< / div >
< / div >
< / script > < script type = " text/microtemplate " id = " unblock-login-modal-title " >
Entrar en idealista
< / script > < script type = " text/microtemplate " id = " unblock-login-modal " >
< div class = " modal-inner " >
< p > Para contactar con este anunciante con el email { { = email } } , debes entrar en la cuenta de idealista asociada a este correo . < / p >
< div class = " modal-footer " >
< a class = " btn action " href = " /redirectToLogin " > Entrar en idealista < / a >
< / div >
< / div >
< / script > < script type = " text/microtemplate " id = " counteroffer-tmpl " data - modal - title = " Hacer una contraoferta " data - service - error = ' ¡Vaya! No hemos podido enviar tu mensaje ¡Prueba otra vez! ' >
< div class = " modal-inner " contact - modal - type = " counteroffer " > < form action = " " data - service - error = ' ¡Vaya! No hemos podido enviar tu mensaje ¡Prueba otra vez! ' novalidate > { { showFeedback } } < div class = " feedbacks-container " > { { = feedbackTmpl } } < / div > { { / showFeedback } } { { showForm } } < ul > < li class = " item-form " > Precio actual : 17.900 € < / li > < li class = " item-form " > < label for = " counteroffer-how " > ¿ Cuánto pagarías ? < / label > < input name = " counteroffer-how " id = " counteroffer-how " type = " text " value = " " class = " half-input " data - validation = " required " data - message - required = ' Indica cuanto estás dispuesto a pagar ' autofocus > € < span data - error - here > < / span > < / li > < li class = " item-form contacted {{ showContactTime}} show {{ /showContactTime}} " > < span class = " icon-mail " > Has contactado < / span > < / li > < li class = " item-form " > < label for = " counteroffer-comments " > Más comentarios < / label > < textarea name = " counteroffer-comments " id = " counteroffer-comments " type = " text " > < / textarea > { { showSuggestedMessages } } < ul class = " last-contact-messages " > { { suggestedMessages } } { { = suggestedMessages } } { { / suggestedMessages } } < / ul > { { / showSuggestedMessages } } < / li > < li class = " item-form " > < label for = " counteroffer-email " > Tu email < / label > < input name = " counteroffer-email " id = " counteroffer-email " type = " email " class = " js-email-field " value = " " autocomplete = " email " data - validation = " required email " data - message - email = " Revisa el formato de tu email " data - message - required = ' Revisa el formato de tu email ' > < / li > < li class = " item-form half-items clearfix " > < div > < label for = " counteroffer-phone " > Tu teléfono < / label > < input name = " counteroffer-phone " id = " counteroffer-phone " type = " tel " class = " js-phone-field " value = " " data - validation = " phone required " data - message - phone = " Revisa tu teléfono " data - message - required = ' Revisa tu teléfono ' > < / div > < div > < label for = " counteroffer-name " > Tu nombre < / label > < input name = " counteroffer-name " id = " counteroffer-name " type = " text " data - validation = " name " value = " " data - message - name = " Esto no parece un nombre " > < / div > < / li > < li > < div class = " captcha-miniFields d-none " data - service - prefix = " counterOffer " > < div class = " captcha " > < div class = " captcha-img " > < / div > < / div > < div class = " item-form half-items clearfix " > < label > < span > Escribe los números y letras que ves arriba < / span > < div > < input type = " text " class = " _captcha " name = " contact-captcha " autocapitalize = " none " autocorrect = " off " / > < / div > < / label > < p > < a class = " captcha-reload " href = " # " > ¿ No entiendes lo que pone ? < / a > < / p > < / div > < / div > < / li > < div id = " privacy-policy-detail " class = " item-form privacy-policy-container " > < label class = " input-checkbox " data - validation = " checkbox " data - message - checkbox = " Para contactar debes aceptar la política de privacidad " data - error - name = " privacy-policy-checkbox " > < input type = " checkbox " name = " privacy-policy-checkbox " / > < span > < span > Aceptar < a href = " https://www.idealista.com/ayuda/articulos/politica-de-privacidad/ " target = " _blank " > política de privacidad < / a > < / span > < / span > < / label > < / div > < div id = " recommendations-detail " class = " item-form recommendations-container " > < div > < label class = " input-checkbox " data - error - name = " recommendations-checkbox " > < input type = " checkbox " name = " recommendations-checkbox " data - markup - co - recommendations = ' detalle::conversiones::form_contraoferta::recomendations- ' / > < span > < span > Recibir inmuebles de idealista similares a este < / span > < / span > < / label > < / div > < / div > < input type = " hidden " name = " operationId " value = " 1 " / > < input type = " hidden " name = " type " id = " contact-type " value = " " / > < li class = " item-form " > < span class = " service-error-container contextual full-width error icon-feedbk-ko d-none " > < / span > < div class = " d-none " > < input type = " text " value = " " name = " counteroffer-mail " > < / div > < input type = " hidden " name = " adId " value = " 92174198 " / > < input class = " btn action txt-bold " value = ' Hacer una contraoferta ' type = " submit " id = " counteroffer-send " data - xiti - page = " contactar-mail-particular-v-detalle " > < / li > < / ul > { { / showForm } } < / form > < / div >
< / script > < script type = " text/microtemplate " id = " saveAlertsPopupTitle " >
< div class = " modal-inner " > Antes de seguir , comprueba que estos criterios son correctos < / div >
< / script > < script type = " text/microtemplate " id = " saveAlertsPopupContent " >
< div class = " modal-inner " > < ul class = " criterios " > { { @criterios } } < li > { { = _val } } < / li > { { / @criterios } } < / ul > < span class = " advice " > { { avisoNormal } } Se publican de media < strong > { { = numAnuncios } } anuncios nuevos al día < / strong > con estos criterios { { / avisoNormal } } { { avisoMinimo } } < span class = " lowFrequency " > < strong > ¡ Ojo ! Te llegarán muy pocos avisos < / strong > < / span > < br / > { { minimoOne } } < span class = " lowFrequency " > Se publica de media menos de 1 anuncio nuevo al día con estos criterios < / span > { { : minimoOne } } < span class = " lowFrequency " > Se publican de media menos de { { = numAnuncios } } anuncios nuevos al día con estos criterios < / span > { { / minimoOne } } { { / avisoMinimo } } { { avisoMaximo } } < span class = " highFrequency " > < strong > ¡ Ojo ! Te llegarán muchos avisos < / strong > < / span > < br / > < span class = " highFrequency " > Se publican de media más de { { = numAnuncios } } anuncios nuevos al día con estos criterios < / span > { { / avisoMaximo } } < / span > < / div >
< / script > < script type = " text/microtemplate " id = " saveAlertsPopupButtons " >
< div class = " modal-inner " > { { continuarSolo } } < a href = " # " class = " continuarlink accept " > Vale está bien , continuar & raquo ; < / a > { { : continuarSolo } } < a href = " # " class = " btn regular accept " > Sí son correctos , continuar < / a > < a href = " # " class = " revisarCriterios " > Revisar criterios < / a > { { / continuarSolo } } < / div >
< / script > < form method = " post " name = " suggestForm " id = " suggestForm " class = " is-hidden " action = " /seleccionar-tipo-alerta " data - popupURL = " /recibir-alertas-gratis-primer-resumen " > < input name = " mailSaveSearch " type = " hidden " value = " " / > < input type = " hidden " name = " savingAlertStartingPoint " value = " " > < script type = " text/microtemplate " class = " title " >
Guarda la búsqueda para no perderte los anuncios que te interesan
< / script > < script type = " text/microtemplate " class = " content " >
< p > Te mandamos por email los anuncios nuevos que se publiquen con tus criterios < / p > < ul class = " criterios " > { { @criterios } } < li > { { = _val } } < / li > { { / @criterios } } < / ul > < span class = " advice " > Se publican de media < strong > { { = numAnuncios } } anuncios nuevos al día < / strong > con estos criterios < / span >
< / script > < script type = " text/microtemplate " class = " buttons " >
< a href = " # " class = " btn regular accept " > Vale , guardar búsqueda < / a > < a href = " # " class = " cancel " > No , gracias < / a >
< / script > < / form > < script type = " text/microtemplate " id = " share-module-tmpl " data - title - normal = " Compartir " data - title - picture = " Enviar esta foto a un amigo " data - title - virtualtour = " Enviar Virtual Tour 360 a un amigo " >
< div class = " modal-inner " > < ul class = " copy-url__social " > < li class = " copy-url__social-whatsapp " > < a class = " share-whatsapp-link icon-whatsapp-outline " data - markup = " share-whatsapp " href = " http://api.whatsapp.com/send?text=http://www.idealista.com/inmueble/92174198/ " target = " _blank " > < / a > < / li > < / ul > < h4 class = " share-email-subtitle " > Compartir por email < / h4 > < form action = " " novalidate > < div class = " loading-layer " > < div class = " loading " > < / div > < / div > { { showMessage } } < div class = " feedback success icon-feedbk-ok " > { { picture . id } } Hemos enviado la foto a tus amigos { { : picture . id } } { { virtualtour . id } } Hemos enviado el Virtual Tour 360 a tus amigos { { : virtualtour . id } } Hemos enviado el anuncio a tus amigos { { / virtualtour . id } } { { / picture . id } } < / div > { { / showMessage } } { { showFavAdded } } < p > También hemos guardado el anuncio como favorito para tu comodidad < / p > { { / showFavAdded } } { { virtualtour . id } } < div class = " picture-container virtualtour-container " > < img src = " {{ =virtualtour.id}} " alt = " {{ =virtualtour.id}} " class = " pictureSend horizontal " / > < / div > { { / virtualtour . id } } { { picture . id } } < div class = " picture-container " > < img src = " {{ =picture.url}} " alt = " {{ =picture.url}} " class = " pictureSend {{ =picture.orientation}} " / > < / div > < input type = " hidden " name = " share-to-friend-to-picture_id " value = " {{ =picture.id}} " / > { { / picture . id } } { { virtualtour . id } } < input type = " hidden " name = " share-to-friend-to-virtualtour_id " value = " {{ =virtualtour.id}} " / > { { / virtualtour . id } } { { showForm } } < ul > < li class = " item-form " > < label for = " share-to-friend-to " > Email de tus amigos < / label > < input name = " share-to-friend-to " id = " share-to-friend-to " type = " email " value = " " data - validation = " required multiple_emails " data - message - required = " Dinos a quién se lo enviamos ;-) " data - message - multiple_emails = " Revisa el formato de los emails de tus amigos " autofocus > < span class = " helpText " > Si son varios sepáralos con una coma ( , ) < / span > < / li > < li class = " item-form " > < label for = " share-to-friend-from " > Tu email < / label > < input name = " contact-email " id = " share-to-friend-from " value = " " type = " email " autocomplete = " email " data - validation = " required email " data - message - required = " Indica tu email " data - message - email = " Revisa el formato de tu email " > < span class = " helpText " > { { picture . id } } Quien recibe la foto lo verá { { : picture . id } } { { virtualtour . id } } Quien recibe el Virtual Tour 360 lo verá { { : virtualtour . id } } Quien recibe el anuncio lo verá { { / virtualtour . id } } { { / picture . id } } < / span > < / li > < li class = " item-form " > < label for = " share-to-friend-message " > Tu mensaje < / label > < textarea rows = " 2 " name = " contact-message " maxlength = " 1000 " id = " share-to-friend-message " > < / textarea > < / li > < li > < input class = " btn action txt-bold txt-big " value = ' Enviar ' type = " submit " id = " share-friend-send " / > < / li > < / ul > < div class = " copy-url " > < h4 class = " share-email-subtitle " > Copiar URL < / h4 > < div class = " copy-url__box " > < input class = " copy-url__box-text " type = " text " readonly = " readonly " value = " www.idealista.com/92174198 " / > < span class = " icon-copy clipboard-share " data - markup = " share-copy-link " data - txt - copied = " Copiado " > copiar < / span > < span class = " copy-url__box-feedback contextual success icon-feedbk-ok " > copiado al portapapeles < / span > < / div > < / div > < input type = " hidden " id = " adId " name = " share-to-friend-adId " value = " 92174198 " / > < input type = " hidden " id = " cookieEnabled " name = " cookieEnabled " value = " true " / > < div class = " d-none " > < input type = " text " value = " " name = " contact-mail " > < / div > { { : showForm } } { { picture . id } } < p > < a href = " " data - xiti - shared - n2 = " true " data - xiti - page = " detalle::conversiones::form_compartir_foto " title = " Enviar esta foto a más gente " class = " anotherTime " > Enviar esta foto a más gente < / a > < / < p > { { : picture . id } } { { virtualtour . id } } < p > < a href = " " data - xiti - shared - n2 = " true " data - xiti - page = " detalle::conversiones::form_compartir_foto " title = " Enviar este Virtual Tour 360 a más gente " class = " anotherTime " > Enviar este Virtual Tour 360 a más gente < / a > < / p > { { : virtualtour . id } } < p > < a href = " " data - xiti - shared - n2 = " true " data - xiti - page = " detalle::conversiones::form_compartir " data - markup = " detalle::compartir::email " title = " Envía este anuncio a más gente " class = " anotherTime " > Envía
< / script > < script type = " text/microtemplate " id = " notify-incidence-step1 " >
< div class = " modal-inner " > < form action = " " method = " post " id = " ferror " name = " ferror " > < div class = " item-form help-us-modal " > < p > Anuncio : Garaje en venta en Passatge de Simó , 10 < / p > < p class = " icon-feedbk-info " > Esta información llega al equipo de calidad de idealista y al anunciante , pero él no conocerá tu identidad < / p > < ul data - validation = " radio " data - message - radio = " Por favor selecciona una de las opciones que mejor describa el tipo de error que has visto " > < li > < label class = " input-radio " > < input type = " radio " id = " iTipoComentario7 " name = " incidenceTypeId " value = " 7 " / > < span > < span > La dirección está mal < / span > < / span > < / label > < / li > < li > < label class = " input-radio " > < input type = " radio " id = " iTipoComentario4 " name = " incidenceTypeId " value = " 4 " / > < span > < span > El anunciante no es un particular < / span > < / span > < / label > < / li > < li > < label class = " input-radio " > < input type = " radio " id = " iTipoComentario6 " name = " incidenceTypeId " value = " 6 " / > < span > < span > El precio no es correcto < / span > < / span > < / label > < / li > < li > < label class = " input-radio " > < input type = " radio " id = " iTipoComentario11 " name = " incidenceTypeId " value = " 11 " / > < span > < span > Es una posible estafa < / span > < / span > < / label > < / li > < li > < label class = " input-radio " > < input type = " radio " id = " iTipoComentario13 " name = " incidenceTypeId " value = " 13 " / > < span > < span > Comentarios discriminatorios y / u ofensivos < / span > < / span > < / label > < / li > < li > < label class = " input-radio " > < input type = " radio " id = " iTipoComentario1 " name = " incidenceTypeId " value = " 1 " / > < span > < span > Error en las fotos < / span > < / span > < / label > < / li > < li > < label class = " input-radio " > < input type = " radio " id = " iTipoComentario8 " name = " incidenceTypeId " value = " 8 " / > < span > < span > El teléfono es erróneo o no lo cogen < / span > < / span > < / label > < / li > < li > < label class = " input-radio " > < input type = " radio " id = " iTipoComentario9 " name = " incidenceTypeId " value = " 9 " / > < span > < span > El email es erróneo o el anunciante no responde < / span > < / span > < / label > < / li > < li > < label class = " input-radio " > < input type = " radio " id = " iTipoComentario3 " name = " incidenceTypeId " value = " 3 " / > < span > < span > Ya está vendido < / span > < / span > < / label > < / li > < li > < label class = " input-radio " > < input type = " radio " id = " iTipoComentario5 " name = " incidenceTypeId " value = " 5 " / > < span > < span > Otro motivo < / span > < / span > < / label > < / li > < / ul > < / div > < div class = " item-form " > < label > < span > Descripción completa del error ( opcional ) < / span > < textarea name = " comments " maxlength = " 1000 " id = " ferror-text " placeholder = " Este texto también le llegará al anunciante " data - message - required = " Por favor, describe el error o errores que has visto " > < / textarea > < span id = " comment " class = " help-text block v-hidden " > Este texto también le llegará al anunciante < / span > < / label > < / div > < a href = " # " class = " btn action " id = " submitBtn " > Enviar error < / a > < / form > < / div >
< / script > < script type = " text/microtemplate " id = " notify-incidence-step2 " >
< div class = " modal-inner modal-improve-feedback " > < form action = " " id = " ferror2 " method = " post " > < input type = " hidden " name = " incidenceId " id = " incidenceId " value = " " / > < div class = " feedback success icon-feedbk-ok " id = " feedback-success-div " > < p > Gracias por ayudarnos a mejorar idealista : - ) < br > Te avisaremos cuando esté aclarado < / p > < / div > < div class = " item-form " > < h3 > ¿ Quieres que te avisemos cuando se solucione ? < / h3 > < p > Indícanos tus datos ( no los recibirá el anunciante ) < / p > < / div > < div class = " item-form-and-half " > < label for = " incidence-user-name " > Tu nombre y apellidos < / label > < input name = " name " id = " incidence-user-name " value = " " type = " text " data - validation = " required " data - message - required = " Requerido " > < / div > < div class = " item-form-and-half " > < label for = " incidence-user-email " > Tu email < / label > < input name = " mail " id = " incidence-user-email " value = " " type = " email " autocomplete = " email " data - validation = " required email " data - message - required = " Requerido " data - message - email = " No es un mail válido " > < / div > < div class = " item-form privacy-policy-container " > < label class = " input-checkbox " data - validation = " checkbox " data - message - checkbox = " Para contactar debes aceptar la política de privacidad " data - error - name = " privacy-policy-checkbox " > < input type = " checkbox " name = " privacy-policy-checkbox " / > < span > < span > Aceptar < a href = " https://www.idealista.com/ayuda/articulos/politica-de-privacidad/ " target = " _blank " > política de privacidad < / a > < / span > < / span > < / label > < / div > < a href = " # " class = " btn action " id = " submitBtn " > Enviar < / a > < a href = " # " id = " cancelBtn " > No , gracias < / a > < / form > < / div >
< / script > < script type = " text/microtemplate " id = " notify-incidence-step3 " >
< div class = " modal-inner incidence-feedback " > < div class = " help-us-modal feedback success icon-feedbk-ok " id = " user-added-success-div " > < p > Gracias por ayudarnos a mejorar idealista : - ) < br / > Te avisaremos cuando esté aclarado < / p > < / div > < a href = " # " class = " btn action " id = " closeBtn " > Cerrar < / a > < / div >
< / script > < script type = " text/microtemplate " id = " contact-mortgages-tmpl " >
< div class = " modal-inner " > < form id = " send-contact-form " action = " /enviar-contacto-hipoteca " method = " post " novalidate > < input type = " hidden " name = " adId " value = " 92174198 " > < input type = " hidden " name = " monthlyRate " value = " {{ =calculated.monthlyRate}} " > < input type = " hidden " name = " interestRate " value = " {{ =calculated.taxes}} " > < input type = " hidden " name = " years " value = " {{ =calculated.years}} " > < input type = " hidden " name = " mortgageFormType " value = " 0 " > < input type = " hidden " name = " locationId " value = " 0-EU-ES-08 " > < input type = " hidden " name = " locationName " value = " La Sagrada Família " > < input type = " hidden " name = " propertyTypeId " value = " 1 " > < input type = " hidden " name = " options " value = " simulation " > < input type = " hidden " name = " simulationType " value = " {{ =calculated.simulationType}} " > < input type = " hidden " name = " origin " value = " {{ =calculated.simulationType}} " > < input type = " hidden " name = " mortgageSource " value = " " > < input type = " hidden " name = " mortgageSourceDesc " value = " " > < input type = " hidden " name = " locale " value = " es " > < div class = " hipotecas-logo " > < a target = " _blank " href = " /hipotecas/ " > < span class = " icon-idealista-icon " > < / span > < span class = " icon-hipotecas " > < / span > < / a > < / div > < div class = " feedback-container d-none " > < div class = " feedback success icon-feedbk-ok " > Tus datos han sido enviados < span class = " info " > Si no quieres esperar llámanos al < span class = " phoneDesktop " > 912 183 169 < / span > < a href = " tel:912183169 " class = " phoneMobile " > 912 183 169 < / a > ( lunes a viernes de 09 h00 a 20 h00 ) < / span > < / div > < div > < span > En breve nos pondremos en contacto contigo para analizar tu caso . < / span > < span > Servicio gratuito y sin compromiso . < / span > < / div > < h4 > Descubre las mejores hipotecas seleccionadas por idealista : < / h4 > < a id = " comparator-link " href = " # " > Ver ofertas de hipotecas personalizadas < / a > < a href = " # " class = " btn regular closeBtn cancel " > Cerrar < / a > < / div > < div class = " form-container " > < div id = " contact-step1 " > < header > < span class = " txt-soft " > Nos peleamos con los bancos para conseguirte la mejor hipoteca . < / span > < span class = " txt-soft " > Servicio gratuito y sin compromiso < / span > < / header > < p class = " user-information txt-medium txt-bold " > Información sobre el inmueble < / p > < div class = " item-form half-items clearfix " > < div > < label > < span > Tipo de compra < / span > < select id = " buyingTypeId " name = " buyingTypeId " data - validation = " select " data - message - select = " Necesitamos conocer este dato " > < option value = " " > Seleccionar < / option > < option value = " 5 " > Vivienda habitual < / option > < option value = " 6 " > Segunda vivienda < / option > < option value = " 10 " > No residente < / option > < option value = " 13 " > Otros < / option > < / select > < / label > < / div > < div > < label > < span > ¿ Cuándo vas a comprar ? < / span > < select id = " buyingWhen " name = " buyingWhen " data - validation = " select " data - message - select = " Necesitamos conocer este dato " > < option value = " " > Seleccionar < / option > < option value = " 1 " > Ya he hecho la reserva < / option > < option value = " 2 " > Quiero hacer una oferta < / option > < option value = " 3 " > Estoy buscando vivienda < / option > < option value = " 4 " > Solo quiero asesoramiento < / option > < / select > < / label > < / div > < / div > < div class = " item-form half-items clearfix " > < div > < label > < span > Precio del inmueble < / span > < span class = " desc-container " > < input type = " tel " name = " buyingPrice " data - validation = " required number " maxlength = " 13 " value = " 17900 " data - message - required = " Necesitamos conocer este dato " data - message - number = " Debe ser un número " / > < span class = " desc " > Euros < / span > < / span > < / label > < / div > < div > < label > < span > Ahorro aportado < / span > < span class = " desc-container " > { { calculated . savings } } < input type = " tel " name = " savings " data - validation = " required number " maxlength = " 13 " value = " {{ =calculated.savings}} " data - message - required = " Necesitamos conocer este dato " data - message - number = " Debe ser un número " / > { { : calculated . savings } } < input type = " tel " name = " savings " data - validation = " required number " maxlength = " 13 " value = " " data - message - required = " Necesitamos conocer este dato " data - message - number = " Debe ser un número " / > { { / calculated . savings } } < span class = " desc " > Euros < / span > < / span > < / label > < / div > < / div > < div class = " item-form " > < button id = " next-to-step2-btn " type = " button " clas
< / script > < script type = " text/microtemplate " id = " mortgages-comparator-results-panel-tmpl " >
< p > También tenemos < span > { { = totalResults } } < / span > ofertas en nuestro comparador que coinciden con tus criterios . < a id = " mortgages-comparator-redirect " href = " {{ =redirectUrl}} " target = " _blank " > Comparar hipotecas < / a > < / p >
< / script > < script type = " text/microtemplate " id = " contact-mortgages-step1-buying-price-low-step-validation-tmpl " >
< div class = " feedback contextual full-width warning icon-feedbk-alert error-container " > < p > No es posible encontrar una hipoteca con esas condiciones < / p > < / div > < p > Los bancos con los que trabajamos fijan un < strong > valor mínimo del precio de la vivienda de { { = minBuyingPrice } } € < / strong > para analizar una solicitud de hipoteca < / p > < a href = " # " data - simulator - url = " /hipotecas/hipoteca-para-comprar-casa?adId=92174198&xtatc=INT-8-[detalle_personalizar_hipoteca_consecutivo] " class = " btn action expand go-to-simulator " target = " _blank " > Calcular gastos < / a > < div class = " back-button-mortgages " > < a class = " back-step1-btn " href = " # " > Volver al paso anterior < / a > < / div >
< / script > < script type = " text/microtemplate " id = " contact-mortgages-step1-no-savings-validation-tmpl " >
< div class = " feedback contextual full-width warning icon-feedbk-alert error-container " > < p > No es posible encontrar una hipoteca con esas condiciones < / p > < / div > < p > Los bancos suelen pedir una aportación mínima del < strong > { { = minLtvPercentage } } % del precio de compra . < / strong > < / p > < a href = " # " data - simulator - url = " /hipotecas/hipoteca-para-comprar-casa?adId=92174198&xtatc=INT-8-[detalle_personalizar_hipoteca_consecutivo] " class = " btn action expand go-to-simulator " target = " _blank " > Calcular gastos < / a > < div class = " back-button-mortgages " > < a class = " back-step1-btn " href = " # " > Volver al paso anterior < / a > < / div >
< / script > < script type = " text/microtemplate " id = " contact-mortgages-step2-contract-type-validation-tmpl " >
{ { hasNoActivityContract } } < div class = " feedback contextual full-width warning icon-feedbk-alert error-container " > < p > No es posible encontrar una hipoteca con esas condiciones < / p > < / div > { { / hasNoActivityContract } } < p > Los bancos requieren que al menos uno de los solicitantes tenga < strong > estabilidad laboral demostrable < / strong > para conceder una hipoteca . < / p > { { hasTemporalContract } } < button id = " temporal-to-step3-btn " type = " button " class = " btn action expand " > Continuar < / button > { { / hasTemporalContract } } < div class = " back-button-mortgages " > < a id = " back-step2-btn " href = " # " > Volver al paso anterior < / a > < / div >
< / script > < script type = " text/microtemplate " id = " contact-mortgages-step2-validation-tmpl " >
< div class = " feedback contextual full-width warning icon-feedbk-alert error-container " > < p > No es posible encontrar una hipoteca con esas condiciones < / p > < / div > { { showAllErrors } } < ul class = " styled-list " > < li > Los bancos no permiten que la < strong > edad < / strong > del solicitante más joven sea < strong > superior a los 65 años . < / strong > < / li > < li > Para esta casa y con tu ahorro aportado , tendrías que pagar una mensualidad de < strong > { { = monthlyRate } } € durante 30 años . < / strong > Al superar el 40 % de tus ingresos los bancos no te concederán la hipoteca . < / li > < / ul > { { : showAllErrors } } { { ageValid } } { { : ageValid } } < p > Los bancos no permiten que la < strong > edad < / strong > del solicitante más joven sea < strong > superior a los 65 años . < / strong > < / p > { { / ageValid } } { { debtRatioValid } } { { : debtRatioValid } } < p > Para esta casa y con tu ahorro aportado , tendrías que pagar una mensualidad de < strong > { { = monthlyRate } } € durante 30 años . < / strong > Al superar el 40 % de tus ingresos los bancos no te concederán la hipoteca . < / p > { { / debtRatioValid } } { { / showAllErrors } } < div class = " back-button-mortgages " > < a id = " back-step2-btn " href = " # " > Volver al paso anterior < / a > < / div >
< / script > < script type = " text/microtemplate " id = " contact-mortgages-step2-low-income-validation-tmpl " >
< div class = " feedback contextual full-width warning icon-feedbk-alert error-container " > < p > No es posible encontrar una hipoteca con esas condiciones < / p > < / div > < p > Los bancos con los que trabajamos requieren unos < strong > ingresos mínimos mensuales de 800 € < / strong > para conceder una hipoteca . < / p > < a href = " # " data - simulator - url = " " class = " btn action expand go-to-simulator " target = " _blank " > Calcular gastos < / a > < div class = " back-button-mortgages " > < a id = " back-step2-btn " href = " # " > Volver al paso anterior < / a > < / div >
< / script > < script type = " text/microtemplate " id = " contact-mortgages-step2-contract-type-validation-tmpl " >
{ { hasNoActivityContract } } < div class = " feedback contextual full-width warning icon-feedbk-alert error-container " > < p > No es posible encontrar una hipoteca con esas condiciones < / p > < / div > { { / hasNoActivityContract } } < p > Los bancos requieren que al menos uno de los solicitantes tenga < strong > estabilidad laboral demostrable < / strong > para conceder una hipoteca . < / p > { { hasTemporalContract } } < button id = " temporal-to-step3-btn " type = " button " class = " btn action expand " > Continuar < / button > { { / hasTemporalContract } } < div class = " back-button-mortgages " > < a id = " back-step2-btn " href = " # " > Volver al paso anterior < / a > < / div >
< / script > < script type = " text/microtemplate " id = " contact-mortgages-step2-country-validation-tmpl " >
< div class = " feedback contextual full-width warning icon-feedbk-alert error-container " > < p > No es posible encontrar una hipoteca con esas condiciones < / p > < / div > < p > No tenemos productos para residentes en { { = countrySelected } } < a href = " {{ =invalidCountryGuide}} " target = " _blank " > ¿ Por qué ? < / a > < / p > < div class = " back-button-mortgages " > < a id = " back-step2-btn " href = " # " > Volver al paso anterior < / a > < / div >
< / script > < script type = " text/microtemplate " id = " show-more-photos-tmpl " >
< div id = " show-more-photos-button " class = " button overlay-box " > < a class = " btn regular more-photos " data - markup = " seePhotos " > Ver { { = hiddenPhotosLeft } } fotos siguientes < / a > < / div >
< / script > < script type = " text/microtemplate " id = " saving-alert-tmpl " >
{ { lessSavingAlert } } Necesitas cubrir la aportación mínima que piden los bancos . < a id = " link-set-saving " href = " " data - markup = ' detalle::mortgages::savings-adjust ' > Ajustar < / a > . { { / lessSavingAlert } } { { bigSavingWarning } } El ahorro aportado no puede ser mayor que el precio del inmueble { { / bigSavingWarning } }
< / script > < script type = " text/microtemplate " id = " saving-warning-tmpl " >
El ahorro aportado no puede ser mayor que el precio del inmueble
< / script > < script type = " text/microtemplate " id = " expenses-glossary-tmpl " >
< div id = " expenses-glossary-content " class = " expenses-glossary " > < p class = " h3-simulated txt-bold mb-regular " > Impuestos y gastos < / p > < p > Cálculo teniendo en cuenta la localización y condición del inmueble . < / p > < table id = " buying-expenses " > < thead > < tr > < th class = " txt-bold " colspan = " 3 " > Gastos de la Compra < / th > < / tr > < / thead > < tbody > < tr > < td > Notaría : < / td > < td class = " separator " > < span > < / span > < / td > < td class = " price " > < span > { { = buyingExpenses . notaryExpenses } } & euro ; < / span > < / td > < / tr > < tr > < td > Registro : < / td > < td class = " separator " > < span > < / span > < / td > < td class = " price " > < span > { { = buyingExpenses . registryExpenses } } & euro ; < / span > < / td > < / tr > < tr > < td > Gestoría : < / td > < td class = " separator " > < span > < / span > < / td > < td class = " price " > < span > { { = buyingExpenses . processAgencyExpenses } } & euro ; < / span > < / td > < / tr > < tr > < td > Impuestos : < / td > < td class = " separator " > < span > < / span > < / td > < td class = " price " > < span > { { = buyingExpenses . taxesExpenses . total } } & euro ; < / span > < / td > < / tr > < tr class = " total " > < td class = " result " > Total : < / td > < td class = " separator " > < span > < / span > < / td > < td class = " price " > < span > { { = buyingExpenses . total } } & euro ; < / span > < / td > < / tr > < / tbody > < / table > < table id = " loan-expenses " > < thead > < tr > < th class = " txt-bold " colspan = " 3 " > Gastos de la Hipoteca < / th > < / tr > < / thead > < tbody > < tr > < td > Notaría : < / td > < td class = " separator " > < span > < / span > < / td > < td class = " price " > < span > { { = loanExpenses . notaryExpenses } } & euro ; < / span > < / td > < / tr > < tr > < td > Registro : < / td > < td class = " separator " > < span > < / span > < / td > < td class = " price " > < span > { { = loanExpenses . registryExpenses } } & euro ; < / span > < / td > < / tr > < tr > < td > Gestoría : < / td > < td class = " separator " > < span > < / span > < / td > < td class = " price " > < span > { { = loanExpenses . processAgencyExpenses } } & euro ; < / span > < / td > < / tr > < tr > < td > Impuestos : < / td > < td class = " separator " > < span > < / span > < / td > < td class = " price " > < span > { { = loanExpenses . taxesExpenses . total } } & euro ; < / span > < / td > < / tr > < tr class = " total " > < td class = " result " > Total : < / td > < td class = " separator " > < span > < / span > < / td > < td class = " price " > < span > { { = loanExpenses . total } } & euro ; < / span > < / td > < / tr > < / tbody > < / table > < table id = " results " > < tbody > < tr class = " total " > < td > < strong > Gastos totales < / strong > < / td > < td class = " separator " > < span > < / span > < / td > < td class = " price " > < strong > { { = totalExpenses } } & euro ; < / strong > < / td > < / tr > < tr > < td > Gastos de Tasación < / td > < td class = " separator " > < span > < / span > < / td > < td class = " price " > < span > { { = tasationExpenses } } & euro ; < / span > < / td > < / tr > < / tbody > < / table > < p class = " txt-result " > Este es un cálculo orientativo no vinculante . Es necesario remitir la información detallada al banco para calcular la cantidad exacta . < / p > < / div >
< / script > < script type = " text/microtemplate " id = " rate-type-info-tmpl " >
< div class = " rate-type-info-content " > < h3 class = " txt-medium txt-bold mb-small " > ¿ Cómo hemos calculado el interés ? < / h3 > < p > Este tipo de interés es una referencia media del mercado hipotecario . < / p > < p > < strong > Fijo : < / strong > es el tipo de interés que se mantiene constante durante toda la vida del préstamo . Es más bajo cuando menor sea el plazo . < / p > < p > < strong > Variable : < / strong > la cuota variará a lo largo de la vida del préstamo en función a las fluctuaciones del euribor < / p > < / div >
< / script > < script type = " text/microtemplate " id = " affida-contact-mortgages-tmpl " data - title = " " >
< section class = " affida-contact " > < div class = " modal-inner " > < form id = " send-contact-form " method = " post " novalidate > < div class = " logo-idealista " > < a href = " /hipotecas/ " > < span class = " icon-idealista-icon " > < / span > < span class = " icon-hipotecas " > < / span > < / a > < / div > < div class = " item-form affida-items " > < div class = " item-form " > < label > < span > Precio inmueble < / span > < span class = " desc-container " > < span class = " desc " > Euros < / span > < input type = " tel " class = " property-price " name = " property-price " maxlength = " 9 " data - min = " 0 " data - max = " 2000000 " data - validation = " required number " data - message - required = " Necesitamos conocer este dato " value = " 17900 " > < / span > < / label > < / div > < div class = " item-form " > < label > < span class = " show-mobile " > Ahorro aportado < / span > < span class = " show-desktop " > Ahorro aportado < / span > < span class = " desc-container " > < span class = " desc " > Euros < / span > < input type = " tel " class = " savings-input " name = " savings " maxlength = " 9 " data - min = " 0 " data - max = " 2000000 " data - validation = " required number " data - message - required = " Necesitamos conocer este dato " value = " {{ =savings}} " / > < / span > < / label > < / div > < div class = " item-form " > < label > < span > Term ( years ) < / span > < select class = " duration-in-years " name = " duration-in-years " data - validation = " select " data - message - select = " Necesitamos conocer este dato " > < option value = " " > Seleccionar < / option > < option value = " 30 " { { y30 } } selected { { / y30 } } > 30 < / option > < option value = " 25 " { { y25 } } selected { { / y25 } } > 25 < / option > < option value = " 20 " { { y20 } } selected { { / y20 } } > 20 < / option > < option value = " 15 " { { y15 } } selected { { / y15 } } > 15 < / option > < option value = " 10 " { { y10 } } selected { { / y10 } } > 10 < / option > < / select > < / label > < / div > < div class = " item-form " > < label > Tipo de interés < ul class = " horizontal-list " > < li > < label class = " input-radio " > < input type = " radio " name = " tax-type " value = " 2 " { { variableRate } } checked = " checked " { { / variableRate } } > < span > < span > Variable < / span > < / span > < / label > < / li > < li > < label class = " input-radio " > < input type = " radio " name = " tax-type " value = " 1 " { { fixedRate } } checked = " checked " { { / fixedRate } } > < span > < span > Fijo < / span > < / span > < / label > < / li > < / ul > < / label > < / div > < / div > < div class = " affida-offer " > < div class = " affida-offer__main " > < p class = " txt-big " > Nuestra mejor oferta < / p > < p class = " txt-bold txt-big affida-offer__monthly " > < span id = " js-calculated-monthly " > 3901 < / span > € / mes < / p > < / div > < div class = " affida-offer__details " > < p > < span class = " affida-offer__details__condition " > Ahorro aportado : < / span > < span > < span id = " js-calculated-savings " > < / span > € < / span > < / p > < p > < span class = " affida-offer__details__condition " > Importe hipoteca : < / span > < span > < span id = " js-calculated-loan " > < / span > € < / span > < / p > < p > < span class = " affida-offer__details__condition " > Interés : < / span > < span > < span id = " js-calculated-tax " > < / span > % < / span > < / p > < / div > < / div > < div class = " affida-offer__btn-container " > < a href = " # " id = " js-affida-form " class = " btn action fullwidth send-form " > Encuentra tu hipoteca < / a > < / div > < / form > < div class = " affida-legal " > < p > Affida S . r . l . El tipo de interés indicado es el más ventajoso disponible entre las entidades de crédito que mantienen acuerdos con Affida S . r . l . y es el resultado de un cálculo algorítmico de los préstamos hipotecarios . La cuota se publica sólo a título informativo y puede ser confirmada o recalculada tras el asesoramiento y el análisis de Affida S . r . l . y de acuerdo con los parámetros de crédito de los bancos y / u otros acreedores . Póngase en contacto con uno de los más de 200 agentes personales de Affida presentes en todo el territorio nacional para obtener asesoramiento y el cupón " La Casa en Tasca " , un documento oficial emitido por una institución financiera socia de Affida S . r . l . que le dará la certeza de obtener el préstamo hipotecario en las condiciones establecidas . Las hojas de información están disponibles en la red de agencias de Affida S . r . l . o en el sitio web de Affida . credit < / p > < / div > < / div > < / section >
< / script > < script type = " text/microtemplate " id = " comment-translation-tmpl " >
< div class = " translationWarning " > < span > { { = message } } < / span > < span class = " viewOriginalComment " > < a href = " # " data - lang = " es " > { { = messageLink } } < / a > < / span > < div class = " googleTranslateLogo " > < img src = " https://st1.idealista.com/static/common/release/detail/resources/img/google-translate.png " > < / div > < / div >
< / script > < script type = " text/microtemplate " id = " comment-tmpl " >
< div class = " adCommentsLanguage expandable " data - compressed - max - length = " 1650 " > { { = commentText } } < / div > { { expandable } } < div class = " expanderParent " > < span class = " icon-arrow-right " > < / span > < a href = " # " class = " expander " > Leer comentario completo < / a > < / div > { { / expandable } }
< / script > < script type = " text/javascript " async src = " https://st1.idealista.com/static/common/js/criteo/ld.js?20201222-0907 " > < / script > < / body > < / html >
"""
return html
@pytest.fixture
def unrelated_html ( ) :
html = """
< ! doctype html >
< html lang = " en " >
< head >
< link href = " https://files.realpython.com " rel = " preconnect " >
< title > Primer on Python Decorators – Real Python < / title >
< meta name = " author " content = " Real Python " >
< meta name = " description " content = " In this introductory tutorial, we'll look at what Python decorators are and how to create and use them. " >
< meta name = " keywords " content = " " >
< meta charset = " utf-8 " >
< meta name = " viewport " content = " width=device-width, initial-scale=1, shrink-to-fit=no " >
< link rel = " stylesheet " href = " /static/gfonts/font.32be62914940.css " >
< link rel = " stylesheet " href = " /static/realpython.min.afcaac4617ad.css " >
< link rel = " canonical " href = " https://realpython.com/primer-on-python-decorators/ " >
< meta name = " twitter:card " content = " summary_large_image " >
< meta name = " twitter:image " content = " https://files.realpython.com/media/Primer-on-Python-Decorators_Watermarked.d0da542fa3fc.jpg " >
< meta property = " og:image " content = " https://files.realpython.com/media/Primer-on-Python-Decorators_Watermarked.d0da542fa3fc.jpg " >
< meta name = " twitter:creator " content = " @realpython " >
< meta name = " twitter:site " content = " @realpython " >
< meta property = " og:title " content = " Primer on Python Decorators – Real Python " >
< meta property = " og:type " content = " article " >
< meta property = " og:url " content = " https://realpython.com/primer-on-python-decorators/ " >
< meta property = " og:description " content = " In this introductory tutorial, we'll look at what Python decorators are and how to create and use them. " >
< link href = " /static/favicon.68cbf4197b0c.png " rel = " icon " >
< link href = " https://realpython.com/atom.xml " rel = " alternate " title = " Real Python " type = " application/atom+xml " >
< link rel = " manifest " href = " /manifest.json " >
< script >
( function ( i , s , o , g , r , a , m ) { i [ ' GoogleAnalyticsObject ' ] = r ; i [ r ] = i [ r ] | | function ( ) {
( i [ r ] . q = i [ r ] . q | | [ ] ) . push ( arguments ) } , i [ r ] . l = 1 * new Date ( ) ; a = s . createElement ( o ) ,
m = s . getElementsByTagName ( o ) [ 0 ] ; a . async = 1 ; a . src = g ; m . parentNode . insertBefore ( a , m )
} ) ( window , document , ' script ' , ' //www.google-analytics.com/analytics.js ' , ' ga ' ) ;
ga ( ' create ' , ' UA-35184939-1 ' , ' auto ' , { ' allowLinker ' : true } ) ;
ga ( ' set ' , ' anonymizeIp ' , true ) ;
ga ( ' set ' , {
dimension1 : false ,
dimension2 : false
} ) ;
ga ( ' send ' , ' pageview ' ) ;
< / script >
< script async src = ' /cdn-cgi/bm/cv/669835187/api.js ' > < / script > < / head >
< body >
< nav class = " navbar fixed-top navbar-expand-lg navbar-dark flex-column nav-message " >
< div class = " container flex-row " >
< a class = " navbar-brand " href = " / " >
< img src = " /static/real-python-logo.893c30edea53.svg " height = " 40 " class = " d-inline-block align-top " alt = " Real Python " >
< / a >
< button class = " navbar-toggler " type = " button " data - toggle = " collapse " data - target = " #navbarSupportedContent " aria - controls = " navbarSupportedContent " aria - expanded = " false " aria - label = " Toggle navigation " >
< span class = " navbar-toggler-icon " > < / span >
< / button >
< div class = " collapse navbar-collapse " id = " navbarSupportedContent " >
< ul class = " navbar-nav mr-2 flex-fill " >
< li class = " nav-item " >
< a class = " nav-link " href = " /start-here/ " > Start & nbsp ; Here < / a >
< / li >
< li class = " nav-item dropdown " >
< a class = " nav-link dropdown-toggle " href = " # " id = " navbarDropdownLibrary " role = " button " data - toggle = " dropdown " aria - haspopup = " true " aria - expanded = " false " >
< span class = " fa fa-graduation-cap " > < / span > Learn Python
< / a >
< div class = " dropdown-menu " aria - labelledby = " navbarDropdownLibrary " >
< a class = " dropdown-item " href = " / " style = " color: #ff7e73; line-height: 110 % ; " > < i class = " fa fa-fw mr-1 fa-graduation-cap " > < / i > Python Tutorials → < br > < small class = " text-secondary " > In - depth articles and tutorials < / small > < / a >
< a class = " dropdown-item " href = " /courses/ " style = " color: #abe5b1; line-height: 110 % ; " > < i class = " fa fa-fw mr-1 fa-film " > < / i > Video Courses → < br > < small class = " text-secondary " > Step - by - step video lessons < / small > < / a >
< a class = " dropdown-item " href = " /quizzes/ " style = " color: #abe0e5; line-height: 110 % ; " > < i class = " fa fa-fw mr-1 fa-trophy " > < / i > Quizzes → < br > < small class = " text-secondary " > Check your learning progress < / small > < / a >
< a class = " dropdown-item " href = " /learning-paths/ " style = " color: #ffc873; line-height: 110 % ; " > < i class = " fa fa-fw mr-1 fa-map-o " > < / i > Learning Paths → < br > < small class = " text-secondary " > Guided study plans for accelerated learning < / small > < / a >
< a class = " dropdown-item " href = " /community/ " style = " color: #e5c6ab; line-height: 110 % ; " > < i class = " fa fa-fw mr-1 fa-slack " > < / i > Community → < br > < small class = " text-secondary " > Learn with other Pythonistas < / small > < / a >
< a class = " dropdown-item pb-3 " href = " /tutorials/all/ " style = " color: #b8abe5; line-height: 110 % ; " > < i class = " fa fa-fw mr-1 fa-tags " > < / i > Topics → < br > < small class = " text-secondary " > Focus on a specific area or skill level < / small > < / a >
< a class = " dropdown-item border-top " href = " /account/join/ " > < i class = " fa fa-fw fa-star text-warning " > < / i > Unlock All Content < / a >
< / div >
< / li >
< li class = " nav-item dropdown " >
< a class = " nav-link dropdown-toggle " href = " # " id = " navbarDropdownBooksCourses " role = " button " data - toggle = " dropdown " aria - haspopup = " true " aria - expanded = " false " >
Store
< / a >
< div class = " dropdown-menu " aria - labelledby = " navbarDropdownBooksCourses " >
< a class = " dropdown-item " href = " /account/join/ " > < i class = " fa fa-fw fa-star text-warning " > < / i > RP Membership < / a >
< a class = " dropdown-item " href = " /products/python-basics-book/ " > Python Basics Book < / a >
< a class = " dropdown-item " href = " /products/python-tricks-book/ " > Python Tricks Book < / a >
< a class = " dropdown-item " href = " /products/cpython-internals-book/ " > CPython Internals Book < / a >
< a class = " dropdown-item " href = " /products/real-python-course/ " > The Real Python Course < / a >
< a class = " dropdown-item " href = " /products/managing-python-dependencies/ " > Managing Python Dependencies < / a >
< a class = " dropdown-item " href = " /products/sublime-python/ " > Sublime Text + Python Setup < / a >
< a class = " dropdown-item " href = " /products/pythonic-wallpapers/ " > Pythonic Wallpapers Pack < / a >
< a class = " dropdown-item " href = " https://nerdlettering.com " target = " _blank " > Python Mugs , T - Shirts , and More < / a >
< a class = " dropdown-item " href = " https://www.pythonistacafe.com " target = " _blank " > Pythonista Cafe Community < / a >
< a class = " dropdown-item border-top " href = " /products/ " > Browse All » < / a >
< / div >
< / li >
< li class = " nav-item dropdown " >
< a class = " nav-link dropdown-toggle " href = " # " id = " navbarDropdownMore " role = " button " data - toggle = " dropdown " aria - haspopup = " true " aria - expanded = " false " >
More
< / a >
< div class = " dropdown-menu " aria - labelledby = " navbarDropdownMore " >
< a class = " dropdown-item " href = " /newsletter/ " > Python Newsletter < / a >
< a class = " dropdown-item " href = " /podcasts/rpp/ " > Python Podcast < / a >
< a class = " dropdown-item " href = " https://www.pythonjobshq.com " target = " _blank " > Python Job Board < / a >
< a class = " dropdown-item " href = " /team/ " > Meet the Team < / a >
< a class = " dropdown-item " href = " /write-for-us/ " > Become a Tutorial Author < / a >
< a class = " dropdown-item " href = " /become-an-instructor/ " > Become a Video Instructor < / a >
< / div >
< / li >
< / ul >
< div class = " d-block d-xl-none " >
< ul class = " navbar-nav " >
< li class = " nav-item " >
< a class = " nav-link " href = " /search " title = " Search " > < span class = " d-block d-lg-none " > < i class = " fa fa-search " > < / i > Search < / span > < span class = " d-none d-lg-block " > < i class = " fa fa-search " > < / i > < / span > < / a >
< / li >
< / ul >
< / div >
< div class = " d-none d-xl-flex align-items-center mr-2 " >
< form class = " form-inline " action = " /search " method = " GET " >
< a class = " position-absolute " href = " /search " title = " Search " > < i class = " fa fa-search fa-fw text-muted pl-2 " > < / i > < / a >
< input class = " search-field form-control form-control-md mr-sm-1 mr-lg-2 w-100 " style = " padding-left: 2rem; " maxlength = 50 type = " search " placeholder = " Search " aria - label = " Search " name = " q " >
< input type = " hidden " name = " _from " value = " nav " >
< / form >
< / div >
< ul class = " navbar-nav " >
< li class = " nav-item form-inline " >
< a class = " ml-2 ml-lg-0 btn btn-sm btn-primary px-3 " href = " /account/join/ " > Join < / a >
< / li >
< li class = " nav-item " >
< a class = " btn text-light " href = " /account/login/ " > Sign & #8209;In</a>
< / li >
< / ul >
< / div >
< / div >
< div class = " flex-row w-100 " style = " border-top: 1px dashed #ffc107 !important; " >
< div class = " container " >
< a class = " link-unstyled mx-auto " href = " https://realpython.com/free-courses-march-2020 " > < p class = " my-1 small " > < span class = " text-warning " > 😷 < strong > Stuck at home ? < / strong > Enjoy free courses , on & nbsp ; us & nbsp ; → < / span > < / p > < / a >
< / div >
< / div >
< / nav >
< div class = " container main-content " >
< div class = " row justify-content-center " >
< div class = " col-md-11 col-lg-8 article with-headerlinks " >
< figure class = " embed-responsive embed-responsive-16by9 " >
< img class = " card-img-top m-0 p-0 embed-responsive-item rounded " style = " object-fit: contain; " alt = " Python Decorators " src = " https://files.realpython.com/media/Primer-on-Python-Decorators_Watermarked.d0da542fa3fc.jpg " width = " 1920 " height = " 1080 " srcset = " https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/Primer-on-Python-Decorators_Watermarked.d0da542fa3fc.jpg&w=480&sig=45483afc98ba9e9ed26f1672d17d426e011761df 480w, https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/Primer-on-Python-Decorators_Watermarked.d0da542fa3fc.jpg&w=960&sig=5b4082dbad29a1c1f1fdbe7b5de93af202663c6a 960w, https://files.realpython.com/media/Primer-on-Python-Decorators_Watermarked.d0da542fa3fc.jpg 1920w " sizes = " 75vw " >
< / figure >
< h1 > Primer on Python Decorators < / h1 >
< p class = " mb-0 " >
< span class = " text-muted " > by < a class = " text-muted " href = " #author " > Geir Arne Hjelle < / a >
< span class = " ml-2 mr-1 fa fa-comments " > < / span > < a class = " text-muted " href = " #reader-comments " > < span class = " disqus-comment-count " data - disqus - identifier = " https://realpython.com/primer-on-python-decorators/ " > < / span > < / a >
< span class = " ml-2 fa fa-tags " > < / span >
< a href = " /tutorials/intermediate/ " class = " badge badge-light text-muted " > intermediate < / a >
< a href = " /tutorials/python/ " class = " badge badge-light text-muted " > python < / a >
< div class = " d-flex flex-row justify-content-between my-2 " >
< div class = " align-self-center " >
< span >
< a target = " _blank " rel = " nofollow " href = " https://twitter.com/intent/tweet/?text=Check out this % 23Python tutorial: Primer %20o n % 20Python % 20Decorators by @realpython&url=https % 3A//realpython.com/primer-on-python-decorators/ " class = " mr-1 badge badge-twitter text-light mb-1 " > < i class = " mr-1 fa fa-twitter text-light " > < / i > Tweet < / a >
< a target = " _blank " rel = " nofollow " href = " https://facebook.com/sharer/sharer.php?u=https % 3A//realpython.com/primer-on-python-decorators/ " class = " mr-1 badge badge-facebook text-light mb-1 " > < i class = " mr-1 fa fa-facebook text-light " > < / i > Share < / a >
< a target = " _blank " rel = " nofollow " href = " mailto:?subject=Python article for you&body=Check out this Python tutorial: % 0A % 0APrimer %20o n % 20Python % 20Decorators % 0A % 0Ahttps % 3A//realpython.com/primer-on-python-decorators/ " class = " mr-1 badge badge-red text-light mb-1 " > < i class = " mr-1 fa fa-envelope text-light " > < / i > Email < / a >
< / span >
< / div >
< / div >
< / p >
< div class = " article-body " >
< div class = " bg-light sidebar-module sidebar-module-inset " id = " toc " >
< p class = " h3 mb-2 text-muted " > Table of Contents < / p >
< div class = " toc " >
< ul >
< li > < a href = " #functions " > Functions < / a > < ul >
< li > < a href = " #first-class-objects " > First - Class Objects < / a > < / li >
< li > < a href = " #inner-functions " > Inner Functions < / a > < / li >
< li > < a href = " #returning-functions-from-functions " > Returning Functions From Functions < / a > < / li >
< / ul >
< / li >
< li > < a href = " #simple-decorators " > Simple Decorators < / a > < ul >
< li > < a href = " #syntactic-sugar " > Syntactic Sugar ! < / a > < / li >
< li > < a href = " #reusing-decorators " > Reusing Decorators < / a > < / li >
< li > < a href = " #decorating-functions-with-arguments " > Decorating Functions With Arguments < / a > < / li >
< li > < a href = " #returning-values-from-decorated-functions " > Returning Values From Decorated Functions < / a > < / li >
< li > < a href = " #who-are-you-really " > Who Are You , Really ? < / a > < / li >
< / ul >
< / li >
< li > < a href = " #a-few-real-world-examples " > A Few Real World Examples < / a > < ul >
< li > < a href = " #timing-functions " > Timing Functions < / a > < / li >
< li > < a href = " #debugging-code " > Debugging Code < / a > < / li >
< li > < a href = " #slowing-down-code " > Slowing Down Code < / a > < / li >
< li > < a href = " #registering-plugins " > Registering Plugins < / a > < / li >
< li > < a href = " #is-the-user-logged-in " > Is the User Logged In ? < / a > < / li >
< / ul >
< / li >
< li > < a href = " #fancy-decorators " > Fancy Decorators < / a > < ul >
< li > < a href = " #decorating-classes " > Decorating Classes < / a > < / li >
< li > < a href = " #nesting-decorators " > Nesting Decorators < / a > < / li >
< li > < a href = " #decorators-with-arguments " > Decorators With Arguments < / a > < / li >
< li > < a href = " #both-please-but-never-mind-the-bread " > Both Please , But Never Mind the Bread < / a > < / li >
< li > < a href = " #stateful-decorators " > Stateful Decorators < / a > < / li >
< li > < a href = " #classes-as-decorators " > Classes as Decorators < / a > < / li >
< / ul >
< / li >
< li > < a href = " #more-real-world-examples " > More Real World Examples < / a > < ul >
< li > < a href = " #slowing-down-code-revisited " > Slowing Down Code , Revisited < / a > < / li >
< li > < a href = " #creating-singletons " > Creating Singletons < / a > < / li >
< li > < a href = " #caching-return-values " > Caching Return Values < / a > < / li >
< li > < a href = " #adding-information-about-units " > Adding Information About Units < / a > < / li >
< li > < a href = " #validating-json " > Validating JSON < / a > < / li >
< / ul >
< / li >
< li > < a href = " #conclusion " > Conclusion < / a > < / li >
< li > < a href = " #further-reading " > Further Reading < / a > < / li >
< / ul >
< / div >
< / div >
< div class = " sidebar-module sidebar-module-inset p-0 " style = " overflow:hidden; " >
< div style = " display:block;position:relative; " >
< div style = " display:block;width:100 % ;padding-top:12.5 % ; " > < / div >
< div class = " rpad " data - unit = " 8x1 " style = " position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden; " > < / div >
< / div >
< / div >
< div class = " border rounded p-3 card mb-2 " >
< p class = " mb-0 " > < span class = " badge badge-pill badge-success " > < i class = " fa fa-play-circle " aria - hidden = " true " > < / i > Watch Now < / span > This tutorial has a related video course created by the Real Python team . Watch it together with the written tutorial to deepen your understanding : < a class = " stretched-link text-success " href = " /courses/python-decorators-101/ " > < strong > Python Decorators 101 < / strong > < / a > < / p >
< / div >
< p > In this tutorial on decorators , we & rsquo ; ll look at what they are and how to create and use them . Decorators provide a simple syntax for calling < a href = " http://en.wikipedia.org/wiki/Higher-order_function " > higher - order functions < / a > . < / p >
< p > By definition , a decorator is a function that takes another function and extends the behavior of the latter function without explicitly modifying it . < / p >
< p > This sounds confusing , but it & rsquo ; s really not , especially after you & rsquo ; ve seen a few examples of how decorators work . You can find all the examples from this article < a href = " https://github.com/realpython/materials/tree/master/primer-on-python-decorators " > here < / a > . < / p >
< div class = " alert alert-warning " role = " alert " > < p > < strong > Free Bonus : < / strong > < a href = " " class = " alert-link " data - toggle = " modal " data - target = " #modal-power-of-decorators-fixed " data - focus = " false " > Click here to get access to a free " The Power of Python Decorators " guide < / a > that shows you 3 advanced decorator patterns and techniques you can use to write to cleaner and more Pythonic programs . < / p > < / div >
< div class = " alert alert-warning " role = " alert " >
< p > < strong markdown = " 1 " > Decorators Cheat Sheet : < / strong > < a href = " https://realpython.com/bonus/decorators-cheatsheet/ " class = " alert-link " data - toggle = " modal " data - target = " #modal-decorators-cheatsheet " data - focus = " false " markdown = " 1 " > Click here to get access to a free 3 - page Python decorators cheat sheet < / a > that summarizes the techniques explained in this tutorial . < / p >
< / div >
< div class = " alert alert-warning " role = " alert " >
< p > < strong markdown = " 1 " > Decorators Q & amp ; A Transcript : < / strong > < a href = " https://realpython.com/bonus/decorators-qa-2019/ " class = " alert-link " data - toggle = " modal " data - target = " #modal-decorators-qa-2019 " data - focus = " false " markdown = " 1 " > Click here to get access to a 25 - page chat log from our recent Python decorators Q & amp ; A session < / a > in the Real Python Community Slack where we discussed common decorator questions . < / p >
< / div >
< p > < strong > Updates : < / strong > < / p >
< ul >
< li > < em > 08 / 22 / 2018 : < / em > Major update adding more examples and more advanced decorators < / li >
< li > < em > 01 / 12 / 2016 : < / em > Updated examples to Python 3 ( v3 .5 .1 ) syntax and added a new example < / li >
< li > < em > 11 / 01 / 2015 : < / em > Added a brief explanation on the < code > functools . wraps ( ) < / code > decorator < / li >
< / ul >
< div class = " w-100 text-center js-needs-scaling " style = " transform-origin: 0 0; " > < div id = " waldo-tag-4996 " > < / div > < a class = " small text-muted js-disclosure " href = " /account/join/ " rel = " nofollow " style = " display: none; " > < i aria - hidden = " true " class = " fa fa-info-circle " > < / i > Remove ads < / a > < / div > < section class = " section2 " id = " functions " > < h2 > Functions < a class = " headerlink " href = " #functions " title = " Permanent link " > < / a > < / h2 >
< p > Before you can understand decorators , you must first understand how functions work . For our purposes , < strong > a function returns a value based on the given arguments < / strong > . Here is a very simple example : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " k " > def < / span > < span class = " nf " > add_one < / span > < span class = " p " > ( < / span > < span class = " n " > number < / span > < span class = " p " > ) : < / span >
< span class = " gp " > . . . < / span > < span class = " k " > return < / span > < span class = " n " > number < / span > < span class = " o " > + < / span > < span class = " mi " > 1 < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > add_one < / span > < span class = " p " > ( < / span > < span class = " mi " > 2 < / span > < span class = " p " > ) < / span >
< span class = " go " > 3 < / span >
< / code > < / pre > < / div >
< p > In general , functions in Python may also have side effects rather than just turning an input into an output . < a href = " https://realpython.com/python-print/ " > The < code > print ( ) < / code > function < / a > is a basic example of this : it < a href = " https://realpython.com/python-return-statement/ " > returns < / a > < a href = " https://realpython.com/null-in-python/ " > < code > None < / code > < / a > while having the side effect of outputting something to the console . However , to understand decorators , it is enough to think about functions as something that turns given arguments into a value . < / p >
< div class = " alert alert-primary " role = " alert " >
< p > < strong > Note : < / strong > In < a href = " https://en.wikipedia.org/wiki/Functional_programming " > functional programming < / a > , you work ( almost ) only with pure functions without side effects . While not a purely functional language , Python supports many of the functional programming concepts , including functions as first - class objects . < / p >
< / div >
< section class = " section3 " id = " first-class-objects " > < h3 > First - Class Objects < a class = " headerlink " href = " #first-class-objects " title = " Permanent link " > < / a > < / h3 >
< p > In Python , functions are < a href = " https://dbader.org/blog/python-first-class-functions " > first - class objects < / a > . This means that < strong > functions can be passed around and used as arguments < / strong > , just like < a href = " https://realpython.com/python-data-types/ " > any other object ( string , int , float , list , and so on ) < / a > . Consider the following three functions : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " k " > def < / span > < span class = " nf " > say_hello < / span > < span class = " p " > ( < / span > < span class = " n " > name < / span > < span class = " p " > ) : < / span >
< span class = " k " > return < / span > < span class = " sa " > f < / span > < span class = " s2 " > & quot ; Hello < / span > < span class = " si " > { < / span > < span class = " n " > name < / span > < span class = " si " > } < / span > < span class = " s2 " > & quot ; < / span >
< span class = " k " > def < / span > < span class = " nf " > be_awesome < / span > < span class = " p " > ( < / span > < span class = " n " > name < / span > < span class = " p " > ) : < / span >
< span class = " k " > return < / span > < span class = " sa " > f < / span > < span class = " s2 " > & quot ; Yo < / span > < span class = " si " > { < / span > < span class = " n " > name < / span > < span class = " si " > } < / span > < span class = " s2 " > , together we are the awesomest ! & quot ; < / span >
< span class = " k " > def < / span > < span class = " nf " > greet_bob < / span > < span class = " p " > ( < / span > < span class = " n " > greeter_func < / span > < span class = " p " > ) : < / span >
< span class = " k " > return < / span > < span class = " n " > greeter_func < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; Bob & quot ; < / span > < span class = " p " > ) < / span >
< / code > < / pre > < / div >
< p > Here , < code > say_hello ( ) < / code > and < code > be_awesome ( ) < / code > are regular functions that expect a name given as a string . The < code > greet_bob ( ) < / code > function however , expects a function as its argument . We can , for instance , pass it the < code > say_hello ( ) < / code > or the < code > be_awesome ( ) < / code > function : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > greet_bob < / span > < span class = " p " > ( < / span > < span class = " n " > say_hello < / span > < span class = " p " > ) < / span >
< span class = " go " > & #39;Hello Bob'</span>
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > greet_bob < / span > < span class = " p " > ( < / span > < span class = " n " > be_awesome < / span > < span class = " p " > ) < / span >
< span class = " go " > & #39;Yo Bob, together we are the awesomest!'</span>
< / code > < / pre > < / div >
< p > Note that < code > greet_bob ( say_hello ) < / code > refers to two functions , but in different ways : < code > greet_bob ( ) < / code > and < code > say_hello < / code > . The < code > say_hello < / code > function is named without parentheses . This means that only a reference to the function is passed . The function is not executed . The < code > greet_bob ( ) < / code > function , on the other hand , is written with parentheses , so it will be called as usual . < / p >
< / section > < section class = " section3 " id = " inner-functions " > < h3 > Inner Functions < a class = " headerlink " href = " #inner-functions " title = " Permanent link " > < / a > < / h3 >
< p > It & rsquo ; s possible to < a href = " https://realpython.com/defining-your-own-python-function/ " > define functions < / a > < em > inside other functions < / em > . Such functions are called < a href = " https://realpython.com/inner-functions-what-are-they-good-for/ " > inner functions < / a > . Here & rsquo ; s an example of a function with two inner functions : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " k " > def < / span > < span class = " nf " > parent < / span > < span class = " p " > ( ) : < / span >
< span class = " nb " > print < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; Printing from the parent ( ) function & quot ; < / span > < span class = " p " > ) < / span >
< span class = " k " > def < / span > < span class = " nf " > first_child < / span > < span class = " p " > ( ) : < / span >
< span class = " nb " > print < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; Printing from the first_child ( ) function & quot ; < / span > < span class = " p " > ) < / span >
< span class = " k " > def < / span > < span class = " nf " > second_child < / span > < span class = " p " > ( ) : < / span >
< span class = " nb " > print < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; Printing from the second_child ( ) function & quot ; < / span > < span class = " p " > ) < / span >
< span class = " n " > second_child < / span > < span class = " p " > ( ) < / span >
< span class = " n " > first_child < / span > < span class = " p " > ( ) < / span >
< / code > < / pre > < / div >
< p > What happens when you call the < code > parent ( ) < / code > function ? Think about this for a minute . The output will be as follows : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > parent < / span > < span class = " p " > ( ) < / span >
< span class = " go " > Printing from the parent ( ) function < / span >
< span class = " go " > Printing from the second_child ( ) function < / span >
< span class = " go " > Printing from the first_child ( ) function < / span >
< / code > < / pre > < / div >
< p > Note that the order in which the inner functions are defined does not matter . Like with any other functions , the printing only happens when the inner functions are executed . < / p >
< p > Furthermore , the inner functions are not defined until the parent function is called . They are locally scoped to < code > parent ( ) < / code > : they only exist inside the < code > parent ( ) < / code > function as local < a href = " https://realpython.com/python-variables/ " > variables < / a > . Try calling < code > first_child ( ) < / code > . You should get an error : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " gt " > Traceback ( most recent call last ) : < / span >
File < span class = " nb " > & quot ; & lt ; stdin & gt ; & quot ; < / span > , line < span class = " m " > 1 < / span > , in < span class = " n " > & lt ; module & gt ; < / span >
< span class = " gr " > NameError < / span > : < span class = " n " > name & #39;first_child' is not defined</span>
< / code > < / pre > < / div >
< p > Whenever you call < code > parent ( ) < / code > , the inner functions < code > first_child ( ) < / code > and < code > second_child ( ) < / code > are also called . But because of their local scope , they aren & rsquo ; t available outside of the < code > parent ( ) < / code > function . < / p >
< div class = " w-100 text-center js-needs-scaling " style = " transform-origin: 0 0; " > < div id = " waldo-tag-4998 " > < / div > < a class = " small text-muted js-disclosure " href = " /account/join/ " rel = " nofollow " style = " display: none; " > < i aria - hidden = " true " class = " fa fa-info-circle " > < / i > Remove ads < / a > < / div > < / section > < section class = " section3 " id = " returning-functions-from-functions " > < h3 > Returning Functions From Functions < a class = " headerlink " href = " #returning-functions-from-functions " title = " Permanent link " > < / a > < / h3 >
< p > Python also allows you to use functions as return values . The following example returns one of the inner functions from the outer < code > parent ( ) < / code > function : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " k " > def < / span > < span class = " nf " > parent < / span > < span class = " p " > ( < / span > < span class = " n " > num < / span > < span class = " p " > ) : < / span >
< span class = " k " > def < / span > < span class = " nf " > first_child < / span > < span class = " p " > ( ) : < / span >
< span class = " k " > return < / span > < span class = " s2 " > & quot ; Hi , I am Emma & quot ; < / span >
< span class = " k " > def < / span > < span class = " nf " > second_child < / span > < span class = " p " > ( ) : < / span >
< span class = " k " > return < / span > < span class = " s2 " > & quot ; Call me Liam & quot ; < / span >
< span class = " k " > if < / span > < span class = " n " > num < / span > < span class = " o " > == < / span > < span class = " mi " > 1 < / span > < span class = " p " > : < / span >
< span class = " k " > return < / span > < span class = " n " > first_child < / span >
< span class = " k " > else < / span > < span class = " p " > : < / span >
< span class = " k " > return < / span > < span class = " n " > second_child < / span >
< / code > < / pre > < / div >
< p > Note that you are returning < code > first_child < / code > without the parentheses . Recall that this means that you are < strong > returning a reference to the function < code > first_child < / code > < / strong > . In contrast < code > first_child ( ) < / code > with parentheses refers to the result of evaluating the function . This can be seen in the following example : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > first < / span > < span class = " o " > = < / span > < span class = " n " > parent < / span > < span class = " p " > ( < / span > < span class = " mi " > 1 < / span > < span class = " p " > ) < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > second < / span > < span class = " o " > = < / span > < span class = " n " > parent < / span > < span class = " p " > ( < / span > < span class = " mi " > 2 < / span > < span class = " p " > ) < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > first < / span >
< span class = " go " > & lt ; function parent . & lt ; locals & gt ; . first_child at 0x7f599f1e2e18 & gt ; < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > second < / span >
< span class = " go " > & lt ; function parent . & lt ; locals & gt ; . second_child at 0x7f599dad5268 & gt ; < / span >
< / code > < / pre > < / div >
< p > The somewhat cryptic output simply means that the < code > first < / code > variable refers to the local < code > first_child ( ) < / code > function inside of < code > parent ( ) < / code > , while < code > second < / code > points to < code > second_child ( ) < / code > . < / p >
< p > You can now use < code > first < / code > and < code > second < / code > as if they are regular functions , even though the functions they point to can & rsquo ; t be accessed directly : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > first < / span > < span class = " p " > ( ) < / span >
< span class = " go " > & #39;Hi, I am Emma'</span>
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > second < / span > < span class = " p " > ( ) < / span >
< span class = " go " > & #39;Call me Liam'</span>
< / code > < / pre > < / div >
< p > Finally , note that in the earlier example you executed the inner functions within the parent function , for instance < code > first_child ( ) < / code > . However , in this last example , you did not add parentheses to the inner functions & mdash ; < code > first_child < / code > & mdash ; upon returning . That way , you got a reference to each function that you could call in the future . Make sense ? < / p >
< / section > < / section > < section class = " section2 " id = " simple-decorators " > < h2 > Simple Decorators < a class = " headerlink " href = " #simple-decorators " title = " Permanent link " > < / a > < / h2 >
< p > Now that you & rsquo ; ve seen that functions are just like any other object in Python , you & rsquo ; re ready to move on and see the magical beast that is the Python decorator . Let & rsquo ; s start with an example : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " k " > def < / span > < span class = " nf " > my_decorator < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) : < / span >
< span class = " k " > def < / span > < span class = " nf " > wrapper < / span > < span class = " p " > ( ) : < / span >
< span class = " nb " > print < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; Something is happening before the function is called . & quot ; < / span > < span class = " p " > ) < / span >
< span class = " n " > func < / span > < span class = " p " > ( ) < / span >
< span class = " nb " > print < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; Something is happening after the function is called . & quot ; < / span > < span class = " p " > ) < / span >
< span class = " k " > return < / span > < span class = " n " > wrapper < / span >
< span class = " k " > def < / span > < span class = " nf " > say_whee < / span > < span class = " p " > ( ) : < / span >
< span class = " nb " > print < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; Whee ! & quot ; < / span > < span class = " p " > ) < / span >
< span class = " n " > say_whee < / span > < span class = " o " > = < / span > < span class = " n " > my_decorator < / span > < span class = " p " > ( < / span > < span class = " n " > say_whee < / span > < span class = " p " > ) < / span >
< / code > < / pre > < / div >
< p > Can you guess what happens when you call < code > say_whee ( ) < / code > ? Try it : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > say_whee < / span > < span class = " p " > ( ) < / span >
< span class = " go " > Something is happening before the function is called . < / span >
< span class = " go " > Whee ! < / span >
< span class = " go " > Something is happening after the function is called . < / span >
< / code > < / pre > < / div >
< p > To understand what & rsquo ; s going on here , look back at the previous examples . We are literally just applying everything you have learned so far . < / p >
< p > The so - called decoration happens at the following line : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " n " > say_whee < / span > < span class = " o " > = < / span > < span class = " n " > my_decorator < / span > < span class = " p " > ( < / span > < span class = " n " > say_whee < / span > < span class = " p " > ) < / span >
< / code > < / pre > < / div >
< p > In effect , the name < code > say_whee < / code > now points to the < code > wrapper ( ) < / code > inner function . Remember that you return < code > wrapper < / code > as a function when you call < code > my_decorator ( say_whee ) < / code > : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > say_whee < / span >
< span class = " go " > & lt ; function my_decorator . & lt ; locals & gt ; . wrapper at 0x7f3c5dfd42f0 & gt ; < / span >
< / code > < / pre > < / div >
< p > However , < code > wrapper ( ) < / code > has a reference to the original < code > say_whee ( ) < / code > as < code > func < / code > , and calls that function between the two calls to < a href = " https://realpython.com/courses/python-print/ " > < code > print ( ) < / code > < / a > . < / p >
< p > Put simply : < strong > decorators wrap a function , modifying its behavior . < / strong > < / p >
< p > Before moving on , let & rsquo ; s have a look at a second example . Because < code > wrapper ( ) < / code > is a regular Python function , the way a decorator modifies a function can change dynamically . So as not to disturb your neighbors , the following example will only run the decorated code during the day : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " kn " > from < / span > < span class = " nn " > datetime < / span > < span class = " kn " > import < / span > < span class = " n " > datetime < / span >
< span class = " k " > def < / span > < span class = " nf " > not_during_the_night < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) : < / span >
< span class = " k " > def < / span > < span class = " nf " > wrapper < / span > < span class = " p " > ( ) : < / span >
< span class = " k " > if < / span > < span class = " mi " > 7 < / span > < span class = " o " > & lt ; = < / span > < span class = " n " > datetime < / span > < span class = " o " > . < / span > < span class = " n " > now < / span > < span class = " p " > ( ) < / span > < span class = " o " > . < / span > < span class = " n " > hour < / span > < span class = " o " > & lt ; < / span > < span class = " mi " > 22 < / span > < span class = " p " > : < / span >
< span class = " n " > func < / span > < span class = " p " > ( ) < / span >
< span class = " k " > else < / span > < span class = " p " > : < / span >
< span class = " k " > pass < / span > < span class = " c1 " > # Hush, the neighbors are asleep</span>
< span class = " k " > return < / span > < span class = " n " > wrapper < / span >
< span class = " k " > def < / span > < span class = " nf " > say_whee < / span > < span class = " p " > ( ) : < / span >
< span class = " nb " > print < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; Whee ! & quot ; < / span > < span class = " p " > ) < / span >
< span class = " n " > say_whee < / span > < span class = " o " > = < / span > < span class = " n " > not_during_the_night < / span > < span class = " p " > ( < / span > < span class = " n " > say_whee < / span > < span class = " p " > ) < / span >
< / code > < / pre > < / div >
< p > If you try to call < code > say_whee ( ) < / code > after bedtime , nothing will happen : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > say_whee < / span > < span class = " p " > ( ) < / span >
< span class = " go " > & gt ; & gt ; & gt ; < / span >
< / code > < / pre > < / div >
< div class = " w-100 text-center js-needs-scaling " style = " transform-origin: 0 0; " > < div id = " waldo-tag-5000 " > < / div > < a class = " small text-muted js-disclosure " href = " /account/join/ " rel = " nofollow " style = " display: none; " > < i aria - hidden = " true " class = " fa fa-info-circle " > < / i > Remove ads < / a > < / div > < section class = " section3 " id = " syntactic-sugar " > < h3 > Syntactic Sugar ! < a class = " headerlink " href = " #syntactic-sugar " title = " Permanent link " > < / a > < / h3 >
< p > The way you decorated < code > say_whee ( ) < / code > above is a little clunky . First of all , you end up typing the name < code > say_whee < / code > three times . In addition , the decoration gets a bit hidden away below the definition of the function . < / p >
< p > Instead , Python allows you to < strong > use decorators in a simpler way with the < code > @ < / code > symbol < / strong > , sometimes called the < a href = " https://www.python.org/dev/peps/pep-0318/#background " > & ldquo ; pie & rdquo ; syntax < / a > . The following example does the exact same thing as the first decorator example : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " k " > def < / span > < span class = " nf " > my_decorator < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) : < / span >
< span class = " k " > def < / span > < span class = " nf " > wrapper < / span > < span class = " p " > ( ) : < / span >
< span class = " nb " > print < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; Something is happening before the function is called . & quot ; < / span > < span class = " p " > ) < / span >
< span class = " n " > func < / span > < span class = " p " > ( ) < / span >
< span class = " nb " > print < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; Something is happening after the function is called . & quot ; < / span > < span class = " p " > ) < / span >
< span class = " k " > return < / span > < span class = " n " > wrapper < / span >
< span class = " nd " > @my_decorator < / span >
< span class = " k " > def < / span > < span class = " nf " > say_whee < / span > < span class = " p " > ( ) : < / span >
< span class = " nb " > print < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; Whee ! & quot ; < / span > < span class = " p " > ) < / span >
< / code > < / pre > < / div >
< p > So , < code > @my_decorator < / code > is just an easier way of saying < code > say_whee = my_decorator ( say_whee ) < / code > . It & rsquo ; s how you apply a decorator to a function . < / p >
< / section > < section class = " section3 " id = " reusing-decorators " > < h3 > Reusing Decorators < a class = " headerlink " href = " #reusing-decorators " title = " Permanent link " > < / a > < / h3 >
< p > Recall that a decorator is just a regular Python function . All the usual tools for easy reusability are available . Let & rsquo ; s move the decorator to its own < a href = " https://realpython.com/python-modules-packages/ " > module < / a > that can be used in many other functions . < / p >
< p > Create a file called < code > decorators . py < / code > with the following content : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " k " > def < / span > < span class = " nf " > do_twice < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) : < / span >
< span class = " k " > def < / span > < span class = " nf " > wrapper_do_twice < / span > < span class = " p " > ( ) : < / span >
< span class = " n " > func < / span > < span class = " p " > ( ) < / span >
< span class = " n " > func < / span > < span class = " p " > ( ) < / span >
< span class = " k " > return < / span > < span class = " n " > wrapper_do_twice < / span >
< / code > < / pre > < / div >
< div class = " alert alert-primary " role = " alert " >
< p > < strong > Note : < / strong > You can name your inner function whatever you want , and a generic name like < code > wrapper ( ) < / code > is usually okay . You & rsquo ; ll see a lot of decorators in this article . To keep them apart , we & rsquo ; ll name the inner function with the same name as the decorator but with a < code > wrapper_ < / code > prefix . < / p >
< / div >
< p > You can now use this new decorator in other files by doing a regular < a href = " https://realpython.com/absolute-vs-relative-python-imports/ " > import < / a > : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " kn " > from < / span > < span class = " nn " > decorators < / span > < span class = " kn " > import < / span > < span class = " n " > do_twice < / span >
< span class = " nd " > @do_twice < / span >
< span class = " k " > def < / span > < span class = " nf " > say_whee < / span > < span class = " p " > ( ) : < / span >
< span class = " nb " > print < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; Whee ! & quot ; < / span > < span class = " p " > ) < / span >
< / code > < / pre > < / div >
< p > When you run this example , you should see that the original < code > say_whee ( ) < / code > is executed twice : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > say_whee < / span > < span class = " p " > ( ) < / span >
< span class = " go " > Whee ! < / span >
< span class = " go " > Whee ! < / span >
< / code > < / pre > < / div >
< div class = " alert alert-warning " role = " alert " > < p > < strong > Free Bonus : < / strong > < a href = " " class = " alert-link " data - toggle = " modal " data - target = " #modal-power-of-decorators-fixed " data - focus = " false " > Click here to get access to a free " The Power of Python Decorators " guide < / a > that shows you 3 advanced decorator patterns and techniques you can use to write to cleaner and more Pythonic programs . < / p > < / div >
< / section > < section class = " section3 " id = " decorating-functions-with-arguments " > < h3 > Decorating Functions With Arguments < a class = " headerlink " href = " #decorating-functions-with-arguments " title = " Permanent link " > < / a > < / h3 >
< p > Say that you have a function that accepts some arguments . Can you still decorate it ? Let & rsquo ; s try : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " kn " > from < / span > < span class = " nn " > decorators < / span > < span class = " kn " > import < / span > < span class = " n " > do_twice < / span >
< span class = " nd " > @do_twice < / span >
< span class = " k " > def < / span > < span class = " nf " > greet < / span > < span class = " p " > ( < / span > < span class = " n " > name < / span > < span class = " p " > ) : < / span >
< span class = " nb " > print < / span > < span class = " p " > ( < / span > < span class = " sa " > f < / span > < span class = " s2 " > & quot ; Hello < / span > < span class = " si " > { < / span > < span class = " n " > name < / span > < span class = " si " > } < / span > < span class = " s2 " > & quot ; < / span > < span class = " p " > ) < / span >
< / code > < / pre > < / div >
< p > Unfortunately , running this code raises an error : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > greet < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; World & quot ; < / span > < span class = " p " > ) < / span >
< span class = " gt " > Traceback ( most recent call last ) : < / span >
File < span class = " nb " > & quot ; & lt ; stdin & gt ; & quot ; < / span > , line < span class = " m " > 1 < / span > , in < span class = " n " > & lt ; module & gt ; < / span >
< span class = " gr " > TypeError < / span > : < span class = " n " > wrapper_do_twice ( ) takes 0 positional arguments but 1 was given < / span >
< / code > < / pre > < / div >
< p > The problem is that the inner function < code > wrapper_do_twice ( ) < / code > does not take any arguments , but < code > name = " World " < / code > was passed to it . You could fix this by letting < code > wrapper_do_twice ( ) < / code > accept one argument , but then it would not work for the < code > say_whee ( ) < / code > function you created earlier . < / p >
< p > The solution is to use < a href = " https://realpython.com/python-kwargs-and-args/ " > < code > * args < / code > and < code > * * kwargs < / code > < / a > in the inner wrapper function . Then it will accept an arbitrary number of positional and keyword arguments . Rewrite < code > decorators . py < / code > as follows : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " k " > def < / span > < span class = " nf " > do_twice < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) : < / span >
< span class = " hll " > < span class = " k " > def < / span > < span class = " nf " > wrapper_do_twice < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) : < / span >
< / span > < span class = " hll " > < span class = " n " > func < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) < / span >
< / span > < span class = " hll " > < span class = " n " > func < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) < / span >
< / span > < span class = " k " > return < / span > < span class = " n " > wrapper_do_twice < / span >
< / code > < / pre > < / div >
< p > The < code > wrapper_do_twice ( ) < / code > inner function now accepts any number of arguments and passes them on to the function it decorates . Now both your < code > say_whee ( ) < / code > and < code > greet ( ) < / code > examples works : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > say_whee < / span > < span class = " p " > ( ) < / span >
< span class = " go " > Whee ! < / span >
< span class = " go " > Whee ! < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > greet < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; World & quot ; < / span > < span class = " p " > ) < / span >
< span class = " go " > Hello World < / span >
< span class = " go " > Hello World < / span >
< / code > < / pre > < / div >
< div class = " w-100 text-center js-needs-scaling " style = " transform-origin: 0 0; " > < div id = " waldo-tag-5002 " > < / div > < a class = " small text-muted js-disclosure " href = " /account/join/ " rel = " nofollow " style = " display: none; " > < i aria - hidden = " true " class = " fa fa-info-circle " > < / i > Remove ads < / a > < / div > < / section > < section class = " section3 " id = " returning-values-from-decorated-functions " > < h3 > Returning Values From Decorated Functions < a class = " headerlink " href = " #returning-values-from-decorated-functions " title = " Permanent link " > < / a > < / h3 >
< p > What happens to the return value of decorated functions ? Well , that & rsquo ; s up to the decorator to decide . Let & rsquo ; s say you decorate a simple function as follows : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " kn " > from < / span > < span class = " nn " > decorators < / span > < span class = " kn " > import < / span > < span class = " n " > do_twice < / span >
< span class = " nd " > @do_twice < / span >
< span class = " k " > def < / span > < span class = " nf " > return_greeting < / span > < span class = " p " > ( < / span > < span class = " n " > name < / span > < span class = " p " > ) : < / span >
< span class = " nb " > print < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; Creating greeting & quot ; < / span > < span class = " p " > ) < / span >
< span class = " k " > return < / span > < span class = " sa " > f < / span > < span class = " s2 " > & quot ; Hi < / span > < span class = " si " > { < / span > < span class = " n " > name < / span > < span class = " si " > } < / span > < span class = " s2 " > & quot ; < / span >
< / code > < / pre > < / div >
< p > Try to use it : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > hi_adam < / span > < span class = " o " > = < / span > < span class = " n " > return_greeting < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; Adam & quot ; < / span > < span class = " p " > ) < / span >
< span class = " go " > Creating greeting < / span >
< span class = " go " > Creating greeting < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " nb " > print < / span > < span class = " p " > ( < / span > < span class = " n " > hi_adam < / span > < span class = " p " > ) < / span >
< span class = " go " > None < / span >
< / code > < / pre > < / div >
< p > Oops , your decorator ate the return value from the function . < / p >
< p > Because the < code > do_twice_wrapper ( ) < / code > doesn & rsquo ; t explicitly return a value , the call < code > return_greeting ( " Adam " ) < / code > ended up returning < code > None < / code > . < / p >
< p > To fix this , you need to < strong > make sure the wrapper function returns the return value of the decorated function < / strong > . Change your < code > decorators . py < / code > file : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " k " > def < / span > < span class = " nf " > do_twice < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) : < / span >
< span class = " k " > def < / span > < span class = " nf " > wrapper_do_twice < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) : < / span >
< span class = " n " > func < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) < / span >
< span class = " hll " > < span class = " k " > return < / span > < span class = " n " > func < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) < / span >
< / span > < span class = " k " > return < / span > < span class = " n " > wrapper_do_twice < / span >
< / code > < / pre > < / div >
< p > The return value from the last execution of the function is returned : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > return_greeting < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; Adam & quot ; < / span > < span class = " p " > ) < / span >
< span class = " go " > Creating greeting < / span >
< span class = " go " > Creating greeting < / span >
< span class = " go " > & #39;Hi Adam'</span>
< / code > < / pre > < / div >
< / section > < section class = " section3 " id = " who-are-you-really " > < h3 > Who Are You , Really ? < a class = " headerlink " href = " #who-are-you-really " title = " Permanent link " > < / a > < / h3 >
< p > A great convenience when working with Python , especially in the interactive shell , is its powerful introspection ability . < a href = " https://en.wikipedia.org/wiki/Type_introspection " > Introspection < / a > is the ability of an object to know about its own attributes at runtime . For instance , a function knows its own name and < a href = " https://realpython.com/documenting-python-code/ " > documentation < / a > : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " nb " > print < / span >
< span class = " go " > & lt ; built - in function print & gt ; < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " nb " > print < / span > < span class = " o " > . < / span > < span class = " vm " > __name__ < / span >
< span class = " go " > & #39;print'</span>
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > help < / span > < span class = " p " > ( < / span > < span class = " nb " > print < / span > < span class = " p " > ) < / span >
< span class = " go " > Help on built - in function print in module builtins : < / span >
< span class = " go " > print ( . . . ) < / span >
< span class = " go " > & lt ; full help message & gt ; < / span >
< / code > < / pre > < / div >
< p > The introspection works for functions you define yourself as well : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > say_whee < / span >
< span class = " go " > & lt ; function do_twice . & lt ; locals & gt ; . wrapper_do_twice at 0x7f43700e52f0 & gt ; < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > say_whee < / span > < span class = " o " > . < / span > < span class = " vm " > __name__ < / span >
< span class = " go " > & #39;wrapper_do_twice'</span>
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > help < / span > < span class = " p " > ( < / span > < span class = " n " > say_whee < / span > < span class = " p " > ) < / span >
< span class = " go " > Help on function wrapper_do_twice in module decorators : < / span >
< span class = " go " > wrapper_do_twice ( ) < / span >
< / code > < / pre > < / div >
< p > However , after being decorated , < code > say_whee ( ) < / code > has gotten very confused about its identity . It now reports being the < code > wrapper_do_twice ( ) < / code > inner function inside the < code > do_twice ( ) < / code > decorator . Although technically true , this is not very useful information . < / p >
< p > To fix this , decorators should use the < a href = " https://docs.python.org/library/functools.html#functools.wraps " > < code > @functools.wraps < / code > < / a > decorator , which will preserve information about the original function . Update < code > decorators . py < / code > again : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " hll " > < span class = " kn " > import < / span > < span class = " nn " > functools < / span >
< / span >
< span class = " k " > def < / span > < span class = " nf " > do_twice < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) : < / span >
< span class = " hll " > < span class = " nd " > @functools < / span > < span class = " o " > . < / span > < span class = " n " > wraps < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) < / span >
< / span > < span class = " k " > def < / span > < span class = " nf " > wrapper_do_twice < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) : < / span >
< span class = " n " > func < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) < / span >
< span class = " k " > return < / span > < span class = " n " > func < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) < / span >
< span class = " k " > return < / span > < span class = " n " > wrapper_do_twice < / span >
< / code > < / pre > < / div >
< p > You do not need to change anything about the decorated < code > say_whee ( ) < / code > function : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > say_whee < / span >
< span class = " go " > & lt ; function say_whee at 0x7ff79a60f2f0 & gt ; < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > say_whee < / span > < span class = " o " > . < / span > < span class = " vm " > __name__ < / span >
< span class = " go " > & #39;say_whee'</span>
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > help < / span > < span class = " p " > ( < / span > < span class = " n " > say_whee < / span > < span class = " p " > ) < / span >
< span class = " go " > Help on function say_whee in module whee : < / span >
< span class = " go " > say_whee ( ) < / span >
< / code > < / pre > < / div >
< p > Much better ! Now < code > say_whee ( ) < / code > is still itself after decoration . < / p >
< div class = " alert alert-primary " role = " alert " >
< p > < strong > Technical Detail : < / strong > The < code > @functools.wraps < / code > decorator < a href = " https://github.com/python/cpython/blob/5d4cb54800966947db2e86f65fb109c5067076be/Lib/functools.py#L34 " > uses < / a > the function < code > functools . update_wrapper ( ) < / code > to update special attributes like < code > __name__ < / code > and < code > __doc__ < / code > that are used in the introspection . < / p >
< / div >
< div class = " w-100 text-center js-needs-scaling " style = " transform-origin: 0 0; " > < div id = " waldo-tag-5004 " > < / div > < a class = " small text-muted js-disclosure " href = " /account/join/ " rel = " nofollow " style = " display: none; " > < i aria - hidden = " true " class = " fa fa-info-circle " > < / i > Remove ads < / a > < / div > < / section > < / section > < section class = " section2 " id = " a-few-real-world-examples " > < h2 > A Few Real World Examples < a class = " headerlink " href = " #a-few-real-world-examples " title = " Permanent link " > < / a > < / h2 >
< p > Let & rsquo ; s look at a few more useful examples of decorators . You & rsquo ; ll notice that they & rsquo ; ll mainly follow the same pattern that you & rsquo ; ve learned so far : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " kn " > import < / span > < span class = " nn " > functools < / span >
< span class = " k " > def < / span > < span class = " nf " > decorator < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) : < / span >
< span class = " nd " > @functools < / span > < span class = " o " > . < / span > < span class = " n " > wraps < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) < / span >
< span class = " k " > def < / span > < span class = " nf " > wrapper_decorator < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) : < / span >
< span class = " c1 " > # Do something before</span>
< span class = " n " > value < / span > < span class = " o " > = < / span > < span class = " n " > func < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) < / span >
< span class = " c1 " > # Do something after</span>
< span class = " k " > return < / span > < span class = " n " > value < / span >
< span class = " k " > return < / span > < span class = " n " > wrapper_decorator < / span >
< / code > < / pre > < / div >
< p > This formula is a good boilerplate template for building more complex decorators . < / p >
< div class = " alert alert-primary " role = " alert " >
< p > < strong > Note : < / strong > In later examples , we will assume that these decorators are saved in your < code > decorators . py < / code > file as well . Recall that you can download < a href = " https://github.com/realpython/materials/tree/master/primer-on-python-decorators " > all the examples in this tutorial < / a > . < / p >
< / div >
< section class = " section3 " id = " timing-functions " > < h3 > Timing Functions < a class = " headerlink " href = " #timing-functions " title = " Permanent link " > < / a > < / h3 >
< p > Let & rsquo ; s start by creating a < code > @timer < / code > decorator . It will < a href = " https://realpython.com/python-timer/ " > measure the time a function takes to execute < / a > and print the duration to the console . Here & rsquo ; s the code : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " kn " > import < / span > < span class = " nn " > functools < / span >
< span class = " kn " > import < / span > < span class = " nn " > time < / span >
< span class = " k " > def < / span > < span class = " nf " > timer < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) : < / span >
< span class = " sd " > & quot ; & quot ; & quot ; Print the runtime of the decorated function & quot ; & quot ; & quot ; < / span >
< span class = " nd " > @functools < / span > < span class = " o " > . < / span > < span class = " n " > wraps < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) < / span >
< span class = " k " > def < / span > < span class = " nf " > wrapper_timer < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) : < / span >
< span class = " n " > start_time < / span > < span class = " o " > = < / span > < span class = " n " > time < / span > < span class = " o " > . < / span > < span class = " n " > perf_counter < / span > < span class = " p " > ( ) < / span > < span class = " c1 " > # 1</span>
< span class = " n " > value < / span > < span class = " o " > = < / span > < span class = " n " > func < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) < / span >
< span class = " n " > end_time < / span > < span class = " o " > = < / span > < span class = " n " > time < / span > < span class = " o " > . < / span > < span class = " n " > perf_counter < / span > < span class = " p " > ( ) < / span > < span class = " c1 " > # 2</span>
< span class = " n " > run_time < / span > < span class = " o " > = < / span > < span class = " n " > end_time < / span > < span class = " o " > - < / span > < span class = " n " > start_time < / span > < span class = " c1 " > # 3</span>
< span class = " nb " > print < / span > < span class = " p " > ( < / span > < span class = " sa " > f < / span > < span class = " s2 " > & quot ; Finished < / span > < span class = " si " > { < / span > < span class = " n " > func < / span > < span class = " o " > . < / span > < span class = " vm " > __name__ < / span > < span class = " si " > ! r } < / span > < span class = " s2 " > in < / span > < span class = " si " > { < / span > < span class = " n " > run_time < / span > < span class = " si " > : < / span > < span class = " s2 " > .4 f < / span > < span class = " si " > } < / span > < span class = " s2 " > secs & quot ; < / span > < span class = " p " > ) < / span >
< span class = " k " > return < / span > < span class = " n " > value < / span >
< span class = " k " > return < / span > < span class = " n " > wrapper_timer < / span >
< span class = " nd " > @timer < / span >
< span class = " k " > def < / span > < span class = " nf " > waste_some_time < / span > < span class = " p " > ( < / span > < span class = " n " > num_times < / span > < span class = " p " > ) : < / span >
< span class = " k " > for < / span > < span class = " n " > _ < / span > < span class = " ow " > in < / span > < span class = " nb " > range < / span > < span class = " p " > ( < / span > < span class = " n " > num_times < / span > < span class = " p " > ) : < / span >
< span class = " nb " > sum < / span > < span class = " p " > ( [ < / span > < span class = " n " > i < / span > < span class = " o " > * * < / span > < span class = " mi " > 2 < / span > < span class = " k " > for < / span > < span class = " n " > i < / span > < span class = " ow " > in < / span > < span class = " nb " > range < / span > < span class = " p " > ( < / span > < span class = " mi " > 10000 < / span > < span class = " p " > ) ] ) < / span >
< / code > < / pre > < / div >
< p > This decorator works by storing the time just before the function starts running ( at the line marked < code > # 1</code>) and just after the function finishes (at <code># 2</code>). The time the function takes is then the difference between the two (at <code># 3</code>). We use the <a href="https://docs.python.org/library/time.html#time.perf_counter"><code>time.perf_counter()</code></a> function, which does a good job of measuring time intervals. Here are some examples of timings:</p>
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > waste_some_time < / span > < span class = " p " > ( < / span > < span class = " mi " > 1 < / span > < span class = " p " > ) < / span >
< span class = " go " > Finished & #39;waste_some_time' in 0.0010 secs</span>
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > waste_some_time < / span > < span class = " p " > ( < / span > < span class = " mi " > 999 < / span > < span class = " p " > ) < / span >
< span class = " go " > Finished & #39;waste_some_time' in 0.3260 secs</span>
< / code > < / pre > < / div >
< p > Run it yourself . Work through the code line by line . Make sure you understand how it works . Don & rsquo ; t worry if you don & rsquo ; t get it , though . Decorators are advanced beings . Try to sleep on it or make a drawing of the program flow . < / p >
< div class = " alert alert-primary " role = " alert " >
< p > < strong > Note : < / strong > The < code > @timer < / code > decorator is great if you just want to get an idea about the runtime of your functions . If you want to do more precise measurements of code , you should instead consider the < a href = " https://docs.python.org/library/timeit.html " > < code > timeit < / code > module < / a > in the standard library . It temporarily disables < a href = " https://realpython.com/python-memory-management/#garbage-collection " > garbage collection < / a > and runs multiple trials to strip out noise from quick function calls . < / p >
< / div >
< / section > < section class = " section3 " id = " debugging-code " > < h3 > Debugging Code < a class = " headerlink " href = " #debugging-code " title = " Permanent link " > < / a > < / h3 >
< p > The following < code > @debug < / code > decorator will print the arguments a function is called with as well as its return value every time the function is called : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " kn " > import < / span > < span class = " nn " > functools < / span >
< span class = " k " > def < / span > < span class = " nf " > debug < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) : < / span >
< span class = " sd " > & quot ; & quot ; & quot ; Print the function signature and return value & quot ; & quot ; & quot ; < / span >
< span class = " nd " > @functools < / span > < span class = " o " > . < / span > < span class = " n " > wraps < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) < / span >
< span class = " k " > def < / span > < span class = " nf " > wrapper_debug < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) : < / span >
< span class = " n " > args_repr < / span > < span class = " o " > = < / span > < span class = " p " > [ < / span > < span class = " nb " > repr < / span > < span class = " p " > ( < / span > < span class = " n " > a < / span > < span class = " p " > ) < / span > < span class = " k " > for < / span > < span class = " n " > a < / span > < span class = " ow " > in < / span > < span class = " n " > args < / span > < span class = " p " > ] < / span > < span class = " c1 " > # 1</span>
< span class = " n " > kwargs_repr < / span > < span class = " o " > = < / span > < span class = " p " > [ < / span > < span class = " sa " > f < / span > < span class = " s2 " > & quot ; < / span > < span class = " si " > { < / span > < span class = " n " > k < / span > < span class = " si " > } < / span > < span class = " s2 " > = < / span > < span class = " si " > { < / span > < span class = " n " > v < / span > < span class = " si " > ! r } < / span > < span class = " s2 " > & quot ; < / span > < span class = " k " > for < / span > < span class = " n " > k < / span > < span class = " p " > , < / span > < span class = " n " > v < / span > < span class = " ow " > in < / span > < span class = " n " > kwargs < / span > < span class = " o " > . < / span > < span class = " n " > items < / span > < span class = " p " > ( ) ] < / span > < span class = " c1 " > # 2</span>
< span class = " n " > signature < / span > < span class = " o " > = < / span > < span class = " s2 " > & quot ; , & quot ; < / span > < span class = " o " > . < / span > < span class = " n " > join < / span > < span class = " p " > ( < / span > < span class = " n " > args_repr < / span > < span class = " o " > + < / span > < span class = " n " > kwargs_repr < / span > < span class = " p " > ) < / span > < span class = " c1 " > # 3</span>
< span class = " nb " > print < / span > < span class = " p " > ( < / span > < span class = " sa " > f < / span > < span class = " s2 " > & quot ; Calling < / span > < span class = " si " > { < / span > < span class = " n " > func < / span > < span class = " o " > . < / span > < span class = " vm " > __name__ < / span > < span class = " si " > } < / span > < span class = " s2 " > ( < / span > < span class = " si " > { < / span > < span class = " n " > signature < / span > < span class = " si " > } < / span > < span class = " s2 " > ) & quot ; < / span > < span class = " p " > ) < / span >
< span class = " n " > value < / span > < span class = " o " > = < / span > < span class = " n " > func < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) < / span >
< span class = " nb " > print < / span > < span class = " p " > ( < / span > < span class = " sa " > f < / span > < span class = " s2 " > & quot ; < / span > < span class = " si " > { < / span > < span class = " n " > func < / span > < span class = " o " > . < / span > < span class = " vm " > __name__ < / span > < span class = " si " > ! r } < / span > < span class = " s2 " > returned < / span > < span class = " si " > { < / span > < span class = " n " > value < / span > < span class = " si " > ! r } < / span > < span class = " s2 " > & quot ; < / span > < span class = " p " > ) < / span > < span class = " c1 " > # 4</span>
< span class = " k " > return < / span > < span class = " n " > value < / span >
< span class = " k " > return < / span > < span class = " n " > wrapper_debug < / span >
< / code > < / pre > < / div >
< p > The signature is created by joining the < a href = " https://dbader.org/blog/python-repr-vs-str " > string representations < / a > of all the arguments . The numbers in the following list correspond to the numbered comments in the code : < / p >
< ol >
< li > Create a list of the positional arguments . Use < code > repr ( ) < / code > to get a nice string representing each argument . < / li >
< li > Create a list of the keyword arguments . The < a href = " https://realpython.com/python-f-strings/ " > f - string < / a > formats each argument as < code > key = value < / code > where the < code > ! r < / code > specifier means that < code > repr ( ) < / code > is used to represent the value . < / li >
< li > The lists of positional and keyword arguments is joined together to one signature string with each argument separated by a comma . < / li >
< li > The return value is printed after the function is executed . < / li >
< / ol >
< p > Let & rsquo ; s see how the decorator works in practice by applying it to a simple function with one position and one keyword argument : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " nd " > @debug < / span >
< span class = " k " > def < / span > < span class = " nf " > make_greeting < / span > < span class = " p " > ( < / span > < span class = " n " > name < / span > < span class = " p " > , < / span > < span class = " n " > age < / span > < span class = " o " > = < / span > < span class = " kc " > None < / span > < span class = " p " > ) : < / span >
< span class = " k " > if < / span > < span class = " n " > age < / span > < span class = " ow " > is < / span > < span class = " kc " > None < / span > < span class = " p " > : < / span >
< span class = " k " > return < / span > < span class = " sa " > f < / span > < span class = " s2 " > & quot ; Howdy < / span > < span class = " si " > { < / span > < span class = " n " > name < / span > < span class = " si " > } < / span > < span class = " s2 " > ! & quot ; < / span >
< span class = " k " > else < / span > < span class = " p " > : < / span >
< span class = " k " > return < / span > < span class = " sa " > f < / span > < span class = " s2 " > & quot ; Whoa < / span > < span class = " si " > { < / span > < span class = " n " > name < / span > < span class = " si " > } < / span > < span class = " s2 " > ! < / span > < span class = " si " > { < / span > < span class = " n " > age < / span > < span class = " si " > } < / span > < span class = " s2 " > already , you are growing up ! & quot ; < / span >
< / code > < / pre > < / div >
< p > Note how the < code > @debug < / code > decorator prints the signature and return value of the < code > make_greeting ( ) < / code > function : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > make_greeting < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; Benjamin & quot ; < / span > < span class = " p " > ) < / span >
< span class = " go " > Calling make_greeting ( & #39;Benjamin')</span>
< span class = " go " > & #39;make_greeting' returned 'Howdy Benjamin!'</span>
< span class = " go " > & #39;Howdy Benjamin!'</span>
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > make_greeting < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; Richard & quot ; < / span > < span class = " p " > , < / span > < span class = " n " > age < / span > < span class = " o " > = < / span > < span class = " mi " > 112 < / span > < span class = " p " > ) < / span >
< span class = " go " > Calling make_greeting ( & #39;Richard', age=112)</span>
< span class = " go " > & #39;make_greeting' returned 'Whoa Richard! 112 already, you are growing up!'</span>
< span class = " go " > & #39;Whoa Richard! 112 already, you are growing up!'</span>
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > make_greeting < / span > < span class = " p " > ( < / span > < span class = " n " > name < / span > < span class = " o " > = < / span > < span class = " s2 " > & quot ; Dorrisile & quot ; < / span > < span class = " p " > , < / span > < span class = " n " > age < / span > < span class = " o " > = < / span > < span class = " mi " > 116 < / span > < span class = " p " > ) < / span >
< span class = " go " > Calling make_greeting ( name = & #39;Dorrisile', age=116)</span>
< span class = " go " > & #39;make_greeting' returned 'Whoa Dorrisile! 116 already, you are growing up!'</span>
< span class = " go " > & #39;Whoa Dorrisile! 116 already, you are growing up!'</span>
< / code > < / pre > < / div >
< p > This example might not seem immediately useful since the < code > @debug < / code > decorator just repeats what you just wrote . It & rsquo ; s more powerful when applied to small convenience functions that you don & rsquo ; t call directly yourself . < / p >
< p > The following example calculates an approximation to the < a href = " https://en.wikipedia.org/wiki/E_(mathematical_constant) " > mathematical constant < em > e < / em > < / a > : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " kn " > import < / span > < span class = " nn " > math < / span >
< span class = " kn " > from < / span > < span class = " nn " > decorators < / span > < span class = " kn " > import < / span > < span class = " n " > debug < / span >
< span class = " c1 " > # Apply a decorator to a standard library function</span>
< span class = " n " > math < / span > < span class = " o " > . < / span > < span class = " n " > factorial < / span > < span class = " o " > = < / span > < span class = " n " > debug < / span > < span class = " p " > ( < / span > < span class = " n " > math < / span > < span class = " o " > . < / span > < span class = " n " > factorial < / span > < span class = " p " > ) < / span >
< span class = " k " > def < / span > < span class = " nf " > approximate_e < / span > < span class = " p " > ( < / span > < span class = " n " > terms < / span > < span class = " o " > = < / span > < span class = " mi " > 18 < / span > < span class = " p " > ) : < / span >
< span class = " k " > return < / span > < span class = " nb " > sum < / span > < span class = " p " > ( < / span > < span class = " mi " > 1 < / span > < span class = " o " > / < / span > < span class = " n " > math < / span > < span class = " o " > . < / span > < span class = " n " > factorial < / span > < span class = " p " > ( < / span > < span class = " n " > n < / span > < span class = " p " > ) < / span > < span class = " k " > for < / span > < span class = " n " > n < / span > < span class = " ow " > in < / span > < span class = " nb " > range < / span > < span class = " p " > ( < / span > < span class = " n " > terms < / span > < span class = " p " > ) ) < / span >
< / code > < / pre > < / div >
< p > This example also shows how you can apply a decorator to a function that has already been defined . The approximation of < em > e < / em > is based on the following < a href = " https://en.wikipedia.org/wiki/E_(mathematical_constant) " > series expansion < / a > : < / p >
< figure > < a href = " https://files.realpython.com/media/e_series_long.7ce8d6492b4f.png " target = " _blank " > < img loading = " lazy " class = " img-fluid mx-auto d-block w-66 " src = " https://files.realpython.com/media/e_series_long.7ce8d6492b4f.png " width = " 1935 " height = " 228 " srcset = " https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/e_series_long.7ce8d6492b4f.png&w=483&sig=78370c9bf4724332d985cae21d5c7ed5de9f5397 483w, https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/e_series_long.7ce8d6492b4f.png&w=967&sig=13a780a5e9682c6ac8ee58960d9d82bb0d01284e 967w, https://files.realpython.com/media/e_series_long.7ce8d6492b4f.png 1935w " sizes = " 75vw " alt = " Series for calculating mathematical constant e " data - asset = " 636 " / > < / a > < / figure >
< p > When calling the < code > approximate_e ( ) < / code > function , you can see the < code > @debug < / code > decorator at work : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > approximate_e < / span > < span class = " p " > ( < / span > < span class = " mi " > 5 < / span > < span class = " p " > ) < / span >
< span class = " go " > Calling factorial ( 0 ) < / span >
< span class = " go " > & #39;factorial' returned 1</span>
< span class = " go " > Calling factorial ( 1 ) < / span >
< span class = " go " > & #39;factorial' returned 1</span>
< span class = " go " > Calling factorial ( 2 ) < / span >
< span class = " go " > & #39;factorial' returned 2</span>
< span class = " go " > Calling factorial ( 3 ) < / span >
< span class = " go " > & #39;factorial' returned 6</span>
< span class = " go " > Calling factorial ( 4 ) < / span >
< span class = " go " > & #39;factorial' returned 24</span>
< span class = " go " > 2.708333333333333 < / span >
< / code > < / pre > < / div >
< p > In this example , you get a decent approximation to the true value < em > e < / em > = 2.718281828 , adding only 5 terms . < / p >
< div class = " w-100 text-center js-needs-scaling " style = " transform-origin: 0 0; " > < div id = " waldo-tag-5257 " > < / div > < a class = " small text-muted js-disclosure " href = " /account/join/ " rel = " nofollow " style = " display: none; " > < i aria - hidden = " true " class = " fa fa-info-circle " > < / i > Remove ads < / a > < / div > < / section > < section class = " section3 " id = " slowing-down-code " > < h3 > Slowing Down Code < a class = " headerlink " href = " #slowing-down-code " title = " Permanent link " > < / a > < / h3 >
< p > This next example might not seem very useful . Why would you want to slow down your Python code ? Probably the most common use case is that you want to rate - limit a function that continuously checks whether a resource & mdash ; like a web page & mdash ; has changed . The < code > @slow_down < / code > decorator will sleep one second before it calls the decorated function : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " kn " > import < / span > < span class = " nn " > functools < / span >
< span class = " kn " > import < / span > < span class = " nn " > time < / span >
< span class = " k " > def < / span > < span class = " nf " > slow_down < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) : < / span >
< span class = " sd " > & quot ; & quot ; & quot ; Sleep 1 second before calling the function & quot ; & quot ; & quot ; < / span >
< span class = " nd " > @functools < / span > < span class = " o " > . < / span > < span class = " n " > wraps < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) < / span >
< span class = " k " > def < / span > < span class = " nf " > wrapper_slow_down < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) : < / span >
< span class = " n " > time < / span > < span class = " o " > . < / span > < span class = " n " > sleep < / span > < span class = " p " > ( < / span > < span class = " mi " > 1 < / span > < span class = " p " > ) < / span >
< span class = " k " > return < / span > < span class = " n " > func < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) < / span >
< span class = " k " > return < / span > < span class = " n " > wrapper_slow_down < / span >
< span class = " nd " > @slow_down < / span >
< span class = " k " > def < / span > < span class = " nf " > countdown < / span > < span class = " p " > ( < / span > < span class = " n " > from_number < / span > < span class = " p " > ) : < / span >
< span class = " k " > if < / span > < span class = " n " > from_number < / span > < span class = " o " > & lt ; < / span > < span class = " mi " > 1 < / span > < span class = " p " > : < / span >
< span class = " nb " > print < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; Liftoff ! & quot ; < / span > < span class = " p " > ) < / span >
< span class = " k " > else < / span > < span class = " p " > : < / span >
< span class = " nb " > print < / span > < span class = " p " > ( < / span > < span class = " n " > from_number < / span > < span class = " p " > ) < / span >
< span class = " n " > countdown < / span > < span class = " p " > ( < / span > < span class = " n " > from_number < / span > < span class = " o " > - < / span > < span class = " mi " > 1 < / span > < span class = " p " > ) < / span >
< / code > < / pre > < / div >
< p > To see the effect of the < code > @slow_down < / code > decorator , you really need to run the example yourself : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > countdown < / span > < span class = " p " > ( < / span > < span class = " mi " > 3 < / span > < span class = " p " > ) < / span >
< span class = " go " > 3 < / span >
< span class = " go " > 2 < / span >
< span class = " go " > 1 < / span >
< span class = " go " > Liftoff ! < / span >
< / code > < / pre > < / div >
< div class = " alert alert-primary " role = " alert " >
< p > < strong > Note : < / strong > The < code > countdown ( ) < / code > function is a recursive function . In other words , it & rsquo ; s a function calling itself . To learn more about recursive functions in Python , see our guide on < a href = " https://realpython.com/python-thinking-recursively/ " > Thinking Recursively in Python < / a > . < / p >
< / div >
< p > The < code > @slow_down < / code > decorator always sleeps for one second . < a href = " #slowing-down-code-revisited " > Later < / a > , you & rsquo ; ll see how to control the rate by passing an argument to the decorator . < / p >
< / section > < section class = " section3 " id = " registering-plugins " > < h3 > Registering Plugins < a class = " headerlink " href = " #registering-plugins " title = " Permanent link " > < / a > < / h3 >
< p > Decorators don & rsquo ; t have to wrap the function they & rsquo ; re decorating . They can also simply register that a function exists and return it unwrapped . This can be used , for instance , to create a light - weight plug - in architecture : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " kn " > import < / span > < span class = " nn " > random < / span >
< span class = " n " > PLUGINS < / span > < span class = " o " > = < / span > < span class = " nb " > dict < / span > < span class = " p " > ( ) < / span >
< span class = " k " > def < / span > < span class = " nf " > register < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) : < / span >
< span class = " sd " > & quot ; & quot ; & quot ; Register a function as a plug - in & quot ; & quot ; & quot ; < / span >
< span class = " n " > PLUGINS < / span > < span class = " p " > [ < / span > < span class = " n " > func < / span > < span class = " o " > . < / span > < span class = " vm " > __name__ < / span > < span class = " p " > ] < / span > < span class = " o " > = < / span > < span class = " n " > func < / span >
< span class = " k " > return < / span > < span class = " n " > func < / span >
< span class = " nd " > @register < / span >
< span class = " k " > def < / span > < span class = " nf " > say_hello < / span > < span class = " p " > ( < / span > < span class = " n " > name < / span > < span class = " p " > ) : < / span >
< span class = " k " > return < / span > < span class = " sa " > f < / span > < span class = " s2 " > & quot ; Hello < / span > < span class = " si " > { < / span > < span class = " n " > name < / span > < span class = " si " > } < / span > < span class = " s2 " > & quot ; < / span >
< span class = " nd " > @register < / span >
< span class = " k " > def < / span > < span class = " nf " > be_awesome < / span > < span class = " p " > ( < / span > < span class = " n " > name < / span > < span class = " p " > ) : < / span >
< span class = " k " > return < / span > < span class = " sa " > f < / span > < span class = " s2 " > & quot ; Yo < / span > < span class = " si " > { < / span > < span class = " n " > name < / span > < span class = " si " > } < / span > < span class = " s2 " > , together we are the awesomest ! & quot ; < / span >
< span class = " k " > def < / span > < span class = " nf " > randomly_greet < / span > < span class = " p " > ( < / span > < span class = " n " > name < / span > < span class = " p " > ) : < / span >
< span class = " n " > greeter < / span > < span class = " p " > , < / span > < span class = " n " > greeter_func < / span > < span class = " o " > = < / span > < span class = " n " > random < / span > < span class = " o " > . < / span > < span class = " n " > choice < / span > < span class = " p " > ( < / span > < span class = " nb " > list < / span > < span class = " p " > ( < / span > < span class = " n " > PLUGINS < / span > < span class = " o " > . < / span > < span class = " n " > items < / span > < span class = " p " > ( ) ) ) < / span >
< span class = " nb " > print < / span > < span class = " p " > ( < / span > < span class = " sa " > f < / span > < span class = " s2 " > & quot ; Using < / span > < span class = " si " > { < / span > < span class = " n " > greeter < / span > < span class = " si " > ! r } < / span > < span class = " s2 " > & quot ; < / span > < span class = " p " > ) < / span >
< span class = " k " > return < / span > < span class = " n " > greeter_func < / span > < span class = " p " > ( < / span > < span class = " n " > name < / span > < span class = " p " > ) < / span >
< / code > < / pre > < / div >
< p > The < code > @register < / code > decorator simply stores a reference to the decorated function in the global < code > PLUGINS < / code > dict . Note that you do not have to write an inner function or use < code > @functools.wraps < / code > in this example because you are returning the original function unmodified . < / p >
< p > The < code > randomly_greet ( ) < / code > function randomly chooses one of the registered functions to use . Note that the < code > PLUGINS < / code > dictionary already contains references to each function object that is registered as a plugin : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > PLUGINS < / span >
< span class = " go " > { & #39;say_hello': <function say_hello at 0x7f768eae6730>,</span>
< span class = " go " > & #39;be_awesome': <function be_awesome at 0x7f768eae67b8>}</span>
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > randomly_greet < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; Alice & quot ; < / span > < span class = " p " > ) < / span >
< span class = " go " > Using & #39;say_hello'</span>
< span class = " go " > & #39;Hello Alice'</span>
< / code > < / pre > < / div >
< p > The main benefit of this simple plugin architecture is that you do not need to maintain a list of which plugins exist . That list is created when the plugins register themselves . This makes it trivial to add a new plugin : just define the function and decorate it with < code > @register < / code > . < / p >
< p > If you are familiar with < code > globals ( ) < / code > in Python , you might see some similarities to how the plugin architecture works . < code > globals ( ) < / code > gives access to all global variables in the current scope , including your plugins : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " nb " > globals < / span > < span class = " p " > ( ) < / span >
< span class = " go " > { . . . , # Lots of variables not shown here.</span>
< span class = " go " > & #39;say_hello': <function say_hello at 0x7f768eae6730>,</span>
< span class = " go " > & #39;be_awesome': <function be_awesome at 0x7f768eae67b8>,</span>
< span class = " go " > & #39;randomly_greet': <function randomly_greet at 0x7f768eae6840>}</span>
< / code > < / pre > < / div >
< p > Using the < code > @register < / code > decorator , you can create your own curated list of interesting variables , effectively hand - picking some functions from < code > globals ( ) < / code > . < / p >
< / section > < section class = " section3 " id = " is-the-user-logged-in " > < h3 > Is the User Logged In ? < a class = " headerlink " href = " #is-the-user-logged-in " title = " Permanent link " > < / a > < / h3 >
< p > The final example before moving on to some fancier decorators is commonly used when working with a web framework . In this example , we are using < a href = " https://realpython.com/tutorials/flask/ " > Flask < / a > to set up a < code > / secret < / code > web page that should only be visible to users that are logged in or otherwise authenticated : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " kn " > from < / span > < span class = " nn " > flask < / span > < span class = " kn " > import < / span > < span class = " n " > Flask < / span > < span class = " p " > , < / span > < span class = " n " > g < / span > < span class = " p " > , < / span > < span class = " n " > request < / span > < span class = " p " > , < / span > < span class = " n " > redirect < / span > < span class = " p " > , < / span > < span class = " n " > url_for < / span >
< span class = " kn " > import < / span > < span class = " nn " > functools < / span >
< span class = " n " > app < / span > < span class = " o " > = < / span > < span class = " n " > Flask < / span > < span class = " p " > ( < / span > < span class = " vm " > __name__ < / span > < span class = " p " > ) < / span >
< span class = " k " > def < / span > < span class = " nf " > login_required < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) : < / span >
< span class = " sd " > & quot ; & quot ; & quot ; Make sure user is logged in before proceeding & quot ; & quot ; & quot ; < / span >
< span class = " nd " > @functools < / span > < span class = " o " > . < / span > < span class = " n " > wraps < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) < / span >
< span class = " k " > def < / span > < span class = " nf " > wrapper_login_required < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) : < / span >
< span class = " k " > if < / span > < span class = " n " > g < / span > < span class = " o " > . < / span > < span class = " n " > user < / span > < span class = " ow " > is < / span > < span class = " kc " > None < / span > < span class = " p " > : < / span >
< span class = " k " > return < / span > < span class = " n " > redirect < / span > < span class = " p " > ( < / span > < span class = " n " > url_for < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; login & quot ; < / span > < span class = " p " > , < / span > < span class = " nb " > next < / span > < span class = " o " > = < / span > < span class = " n " > request < / span > < span class = " o " > . < / span > < span class = " n " > url < / span > < span class = " p " > ) ) < / span >
< span class = " k " > return < / span > < span class = " n " > func < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) < / span >
< span class = " k " > return < / span > < span class = " n " > wrapper_login_required < / span >
< span class = " nd " > @app < / span > < span class = " o " > . < / span > < span class = " n " > route < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; / secret & quot ; < / span > < span class = " p " > ) < / span >
< span class = " nd " > @login_required < / span >
< span class = " k " > def < / span > < span class = " nf " > secret < / span > < span class = " p " > ( ) : < / span >
< span class = " o " > . . . < / span >
< / code > < / pre > < / div >
< p > While this gives an idea about how to add authentication to your web framework , you should usually not write these types of decorators yourself . For Flask , you can use < a href = " https://flask-login.readthedocs.io/en/latest/#flask_login.login_required " > the Flask - Login extension < / a > instead , which adds more security and functionality . < / p >
< div class = " w-100 text-center js-needs-scaling " style = " transform-origin: 0 0; " > < div id = " waldo-tag-5259 " > < / div > < a class = " small text-muted js-disclosure " href = " /account/join/ " rel = " nofollow " style = " display: none; " > < i aria - hidden = " true " class = " fa fa-info-circle " > < / i > Remove ads < / a > < / div > < / section > < / section > < section class = " section2 " id = " fancy-decorators " > < h2 > Fancy Decorators < a class = " headerlink " href = " #fancy-decorators " title = " Permanent link " > < / a > < / h2 >
< p > So far , you & rsquo ; ve seen how to create simple decorators . You already have a pretty good understanding of what decorators are and how they work . Feel free to take a break from this article to practice everything you & rsquo ; ve learned . < / p >
< p > In the second part of this tutorial , we & rsquo ; ll explore more advanced features , including how to use the following : < / p >
< ul >
< li > < a href = " #decorating-classes " > Decorators on classes < / a > < / li >
< li > < a href = " #nesting-decorators " > Several decorators on one function < / a > < / li >
< li > < a href = " #decorators-with-arguments " > Decorators with arguments < / a > < / li >
< li > < a href = " #both-please-but-never-mind-the-bread " > Decorators that can optionally take arguments < / a > < / li >
< li > < a href = " #stateful-decorators " > Stateful decorators < / a > < / li >
< li > < a href = " #classes-as-decorators " > Classes as decorators < / a > < / li >
< / ul >
< section class = " section3 " id = " decorating-classes " > < h3 > Decorating Classes < a class = " headerlink " href = " #decorating-classes " title = " Permanent link " > < / a > < / h3 >
< p > There are two different ways you can use decorators on classes . The first one is very close to what you have already done with functions : you can < strong > decorate the methods of a class < / strong > . This was < a href = " https://www.python.org/dev/peps/pep-0318/#motivation " > one of the motivations < / a > for introducing decorators back in the day . < / p >
< p > Some commonly used decorators that are even built - ins in Python are < a href = " https://realpython.com/instance-class-and-static-methods-demystified/ " > < code > @classmethod < / code > , < code > @staticmethod < / code > < / a > , and < a href = " https://docs.python.org/library/functions.html#property " > < code > @property < / code > < / a > . The < code > @classmethod < / code > and < code > @staticmethod < / code > decorators are used to define methods inside a class < a href = " https://realpython.com/python-namespaces-scope/ " > namespace < / a > that are not connected to a particular instance of that class . The < code > @property < / code > decorator is used to customize < a href = " https://docs.python.org/howto/descriptor.html#properties " > getters and setters < / a > for class attributes . Expand the box below for an example using these decorators . < / p >
< div class = " card mb-3 " id = " collapse_card0584b0 " >
< div class = " card-header border-0 " >
< p class = " m-0 " > < button class = " btn w-100 " data - toggle = " collapse " data - target = " #collapse0584b0 " aria - expanded = " false " aria - controls = " collapse0584b0 " markdown = " 1 " > < span class = " float-left " markdown = " 1 " > Example using built - in class decorators < / span > < span class = " float-right text-muted " > Show / Hide < / span > < / button > < / p >
< / div >
< div class = " collapse " data - parent = " #collapse_card0584b0 " id = " collapse0584b0 " >
< div class = " card-body " >
< p > The following definition of a < code > Circle < / code > class uses the < code > @classmethod < / code > , < code > @staticmethod < / code > , and < code > @property < / code > decorators : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " k " > class < / span > < span class = " nc " > Circle < / span > < span class = " p " > : < / span >
< span class = " k " > def < / span > < span class = " fm " > __init__ < / span > < span class = " p " > ( < / span > < span class = " bp " > self < / span > < span class = " p " > , < / span > < span class = " n " > radius < / span > < span class = " p " > ) : < / span >
< span class = " bp " > self < / span > < span class = " o " > . < / span > < span class = " n " > _radius < / span > < span class = " o " > = < / span > < span class = " n " > radius < / span >
< span class = " nd " > @property < / span >
< span class = " k " > def < / span > < span class = " nf " > radius < / span > < span class = " p " > ( < / span > < span class = " bp " > self < / span > < span class = " p " > ) : < / span >
< span class = " sd " > & quot ; & quot ; & quot ; Get value of radius & quot ; & quot ; & quot ; < / span >
< span class = " k " > return < / span > < span class = " bp " > self < / span > < span class = " o " > . < / span > < span class = " n " > _radius < / span >
< span class = " nd " > @radius < / span > < span class = " o " > . < / span > < span class = " n " > setter < / span >
< span class = " k " > def < / span > < span class = " nf " > radius < / span > < span class = " p " > ( < / span > < span class = " bp " > self < / span > < span class = " p " > , < / span > < span class = " n " > value < / span > < span class = " p " > ) : < / span >
< span class = " sd " > & quot ; & quot ; & quot ; Set radius , raise error if negative & quot ; & quot ; & quot ; < / span >
< span class = " k " > if < / span > < span class = " n " > value < / span > < span class = " o " > & gt ; = < / span > < span class = " mi " > 0 < / span > < span class = " p " > : < / span >
< span class = " bp " > self < / span > < span class = " o " > . < / span > < span class = " n " > _radius < / span > < span class = " o " > = < / span > < span class = " n " > value < / span >
< span class = " k " > else < / span > < span class = " p " > : < / span >
< span class = " k " > raise < / span > < span class = " ne " > ValueError < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; Radius must be positive & quot ; < / span > < span class = " p " > ) < / span >
< span class = " nd " > @property < / span >
< span class = " k " > def < / span > < span class = " nf " > area < / span > < span class = " p " > ( < / span > < span class = " bp " > self < / span > < span class = " p " > ) : < / span >
< span class = " sd " > & quot ; & quot ; & quot ; Calculate area inside circle & quot ; & quot ; & quot ; < / span >
< span class = " k " > return < / span > < span class = " bp " > self < / span > < span class = " o " > . < / span > < span class = " n " > pi < / span > < span class = " p " > ( ) < / span > < span class = " o " > * < / span > < span class = " bp " > self < / span > < span class = " o " > . < / span > < span class = " n " > radius < / span > < span class = " o " > * * < / span > < span class = " mi " > 2 < / span >
< span class = " k " > def < / span > < span class = " nf " > cylinder_volume < / span > < span class = " p " > ( < / span > < span class = " bp " > self < / span > < span class = " p " > , < / span > < span class = " n " > height < / span > < span class = " p " > ) : < / span >
< span class = " sd " > & quot ; & quot ; & quot ; Calculate volume of cylinder with circle as base & quot ; & quot ; & quot ; < / span >
< span class = " k " > return < / span > < span class = " bp " > self < / span > < span class = " o " > . < / span > < span class = " n " > area < / span > < span class = " o " > * < / span > < span class = " n " > height < / span >
< span class = " nd " > @classmethod < / span >
< span class = " k " > def < / span > < span class = " nf " > unit_circle < / span > < span class = " p " > ( < / span > < span class = " bp " > cls < / span > < span class = " p " > ) : < / span >
< span class = " sd " > & quot ; & quot ; & quot ; Factory method creating a circle with radius 1 & quot ; & quot ; & quot ; < / span >
< span class = " k " > return < / span > < span class = " bp " > cls < / span > < span class = " p " > ( < / span > < span class = " mi " > 1 < / span > < span class = " p " > ) < / span >
< span class = " nd " > @staticmethod < / span >
< span class = " k " > def < / span > < span class = " nf " > pi < / span > < span class = " p " > ( ) : < / span >
< span class = " sd " > & quot ; & quot ; & quot ; Value of π , could use math . pi instead though & quot ; & quot ; & quot ; < / span >
< span class = " k " > return < / span > < span class = " mf " > 3.1415926535 < / span >
< / code > < / pre > < / div >
< p > In this class : < / p >
< ul >
< li > < code > . cylinder_volume ( ) < / code > is a regular method . < / li >
< li > < code > . radius < / code > is a mutable property : it can be set to a different value . However , by defining a setter method , we can do some error testing to make sure it & rsquo ; s not set to a nonsensical negative number . Properties are accessed as attributes without parentheses . < / li >
< li > < code > . area < / code > is an immutable property : properties without < code > . setter ( ) < / code > methods can & rsquo ; t be changed . Even though it is defined as a method , it can be retrieved as an attribute without parentheses . < / li >
< li > < code > . unit_circle ( ) < / code > is a class method . It & rsquo ; s not bound to one particular instance of < code > Circle < / code > . Class methods are often used as factory methods that can create specific instances of the class . < / li >
< li > < code > . pi ( ) < / code > is a static method . It & rsquo ; s not really dependent on the < code > Circle < / code > class , except that it is part of its namespace . Static methods can be called on either an instance or the class . < / li >
< / ul >
< p > The < code > Circle < / code > class can for example be used as follows : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > c < / span > < span class = " o " > = < / span > < span class = " n " > Circle < / span > < span class = " p " > ( < / span > < span class = " mi " > 5 < / span > < span class = " p " > ) < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > c < / span > < span class = " o " > . < / span > < span class = " n " > radius < / span >
< span class = " go " > 5 < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > c < / span > < span class = " o " > . < / span > < span class = " n " > area < / span >
< span class = " go " > 78.5398163375 < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > c < / span > < span class = " o " > . < / span > < span class = " n " > radius < / span > < span class = " o " > = < / span > < span class = " mi " > 2 < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > c < / span > < span class = " o " > . < / span > < span class = " n " > area < / span >
< span class = " go " > 12.566370614 < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > c < / span > < span class = " o " > . < / span > < span class = " n " > area < / span > < span class = " o " > = < / span > < span class = " mi " > 100 < / span >
< span class = " go " > AttributeError : can & #39;t set attribute</span>
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > c < / span > < span class = " o " > . < / span > < span class = " n " > cylinder_volume < / span > < span class = " p " > ( < / span > < span class = " n " > height < / span > < span class = " o " > = < / span > < span class = " mi " > 4 < / span > < span class = " p " > ) < / span >
< span class = " go " > 50.265482456 < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > c < / span > < span class = " o " > . < / span > < span class = " n " > radius < / span > < span class = " o " > = < / span > < span class = " o " > - < / span > < span class = " mi " > 1 < / span >
< span class = " go " > ValueError : Radius must be positive < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > c < / span > < span class = " o " > = < / span > < span class = " n " > Circle < / span > < span class = " o " > . < / span > < span class = " n " > unit_circle < / span > < span class = " p " > ( ) < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > c < / span > < span class = " o " > . < / span > < span class = " n " > radius < / span >
< span class = " go " > 1 < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > c < / span > < span class = " o " > . < / span > < span class = " n " > pi < / span > < span class = " p " > ( ) < / span >
< span class = " go " > 3.1415926535 < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > Circle < / span > < span class = " o " > . < / span > < span class = " n " > pi < / span > < span class = " p " > ( ) < / span >
< span class = " go " > 3.1415926535 < / span >
< / code > < / pre > < / div >
< / div >
< / div >
< / div >
< p > Let & rsquo ; s define a class where we decorate some of its methods using the < a href = " #debugging-code " > < code > @debug < / code > < / a > and < a href = " #timing-functions " > < code > @timer < / code > < / a > decorators from < a href = " #a-few-real-world-examples " > earlier < / a > : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " kn " > from < / span > < span class = " nn " > decorators < / span > < span class = " kn " > import < / span > < span class = " n " > debug < / span > < span class = " p " > , < / span > < span class = " n " > timer < / span >
< span class = " k " > class < / span > < span class = " nc " > TimeWaster < / span > < span class = " p " > : < / span >
< span class = " nd " > @debug < / span >
< span class = " k " > def < / span > < span class = " fm " > __init__ < / span > < span class = " p " > ( < / span > < span class = " bp " > self < / span > < span class = " p " > , < / span > < span class = " n " > max_num < / span > < span class = " p " > ) : < / span >
< span class = " bp " > self < / span > < span class = " o " > . < / span > < span class = " n " > max_num < / span > < span class = " o " > = < / span > < span class = " n " > max_num < / span >
< span class = " nd " > @timer < / span >
< span class = " k " > def < / span > < span class = " nf " > waste_time < / span > < span class = " p " > ( < / span > < span class = " bp " > self < / span > < span class = " p " > , < / span > < span class = " n " > num_times < / span > < span class = " p " > ) : < / span >
< span class = " k " > for < / span > < span class = " n " > _ < / span > < span class = " ow " > in < / span > < span class = " nb " > range < / span > < span class = " p " > ( < / span > < span class = " n " > num_times < / span > < span class = " p " > ) : < / span >
< span class = " nb " > sum < / span > < span class = " p " > ( [ < / span > < span class = " n " > i < / span > < span class = " o " > * * < / span > < span class = " mi " > 2 < / span > < span class = " k " > for < / span > < span class = " n " > i < / span > < span class = " ow " > in < / span > < span class = " nb " > range < / span > < span class = " p " > ( < / span > < span class = " bp " > self < / span > < span class = " o " > . < / span > < span class = " n " > max_num < / span > < span class = " p " > ) ] ) < / span >
< / code > < / pre > < / div >
< p > Using this class , you can see the effect of the decorators : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > tw < / span > < span class = " o " > = < / span > < span class = " n " > TimeWaster < / span > < span class = " p " > ( < / span > < span class = " mi " > 1000 < / span > < span class = " p " > ) < / span >
< span class = " go " > Calling __init__ ( & lt ; time_waster . TimeWaster object at 0x7efccce03908 & gt ; , 1000 ) < / span >
< span class = " go " > & #39;__init__' returned None</span>
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > tw < / span > < span class = " o " > . < / span > < span class = " n " > waste_time < / span > < span class = " p " > ( < / span > < span class = " mi " > 999 < / span > < span class = " p " > ) < / span >
< span class = " go " > Finished & #39;waste_time' in 0.3376 secs</span>
< / code > < / pre > < / div >
< p > The other way to use decorators on classes is to < strong > decorate the whole class < / strong > . This is , for example , done in the new < a href = " https://realpython.com/python-data-classes/ " > < code > dataclasses < / code > module < / a > in < a href = " https://realpython.com/python37-new-features/ " > Python 3.7 < / a > : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " kn " > from < / span > < span class = " nn " > dataclasses < / span > < span class = " kn " > import < / span > < span class = " n " > dataclass < / span >
< span class = " nd " > @dataclass < / span >
< span class = " k " > class < / span > < span class = " nc " > PlayingCard < / span > < span class = " p " > : < / span >
< span class = " n " > rank < / span > < span class = " p " > : < / span > < span class = " nb " > str < / span >
< span class = " n " > suit < / span > < span class = " p " > : < / span > < span class = " nb " > str < / span >
< / code > < / pre > < / div >
< p > The meaning of the syntax is similar to the function decorators . In the example above , you could have done the decoration by writing < code > PlayingCard = dataclass ( PlayingCard ) < / code > . < / p >
< p > A < a href = " https://www.python.org/dev/peps/pep-3129/#rationale " > common use of class decorators < / a > is to be a simpler alternative to some use - cases of < a href = " https://realpython.com/python-metaclasses/ " > metaclasses < / a > . In both cases , you are changing the definition of a class dynamically . < / p >
< p > Writing a class decorator is very similar to writing a function decorator . The only difference is that the decorator will receive a class and not a function as an argument . In fact , all the decorators < a href = " #a-few-real-world-examples " > you saw above < / a > will work as class decorators . When you are using them on a class instead of a function , their effect might not be what you want . In the following example , the < code > @timer < / code > decorator is applied to a class : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " kn " > from < / span > < span class = " nn " > decorators < / span > < span class = " kn " > import < / span > < span class = " n " > timer < / span >
< span class = " nd " > @timer < / span >
< span class = " k " > class < / span > < span class = " nc " > TimeWaster < / span > < span class = " p " > : < / span >
< span class = " k " > def < / span > < span class = " fm " > __init__ < / span > < span class = " p " > ( < / span > < span class = " bp " > self < / span > < span class = " p " > , < / span > < span class = " n " > max_num < / span > < span class = " p " > ) : < / span >
< span class = " bp " > self < / span > < span class = " o " > . < / span > < span class = " n " > max_num < / span > < span class = " o " > = < / span > < span class = " n " > max_num < / span >
< span class = " k " > def < / span > < span class = " nf " > waste_time < / span > < span class = " p " > ( < / span > < span class = " bp " > self < / span > < span class = " p " > , < / span > < span class = " n " > num_times < / span > < span class = " p " > ) : < / span >
< span class = " k " > for < / span > < span class = " n " > _ < / span > < span class = " ow " > in < / span > < span class = " nb " > range < / span > < span class = " p " > ( < / span > < span class = " n " > num_times < / span > < span class = " p " > ) : < / span >
< span class = " nb " > sum < / span > < span class = " p " > ( [ < / span > < span class = " n " > i < / span > < span class = " o " > * * < / span > < span class = " mi " > 2 < / span > < span class = " k " > for < / span > < span class = " n " > i < / span > < span class = " ow " > in < / span > < span class = " nb " > range < / span > < span class = " p " > ( < / span > < span class = " bp " > self < / span > < span class = " o " > . < / span > < span class = " n " > max_num < / span > < span class = " p " > ) ] ) < / span >
< / code > < / pre > < / div >
< p > Decorating a class does not decorate its methods . Recall that < code > @timer < / code > is just shorthand for < code > TimeWaster = timer ( TimeWaster ) < / code > . < / p >
< p > Here , < code > @timer < / code > only measures the time it takes to instantiate the class : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > tw < / span > < span class = " o " > = < / span > < span class = " n " > TimeWaster < / span > < span class = " p " > ( < / span > < span class = " mi " > 1000 < / span > < span class = " p " > ) < / span >
< span class = " go " > Finished & #39;TimeWaster' in 0.0000 secs</span>
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > tw < / span > < span class = " o " > . < / span > < span class = " n " > waste_time < / span > < span class = " p " > ( < / span > < span class = " mi " > 999 < / span > < span class = " p " > ) < / span >
< span class = " go " > & gt ; & gt ; & gt ; < / span >
< / code > < / pre > < / div >
< p > < a href = " #creating-singletons " > Later < / a > , you will see an example defining a proper class decorator , namely < code > @singleton < / code > , which ensures that there is only one instance of a class . < / p >
< div class = " w-100 text-center js-needs-scaling " style = " transform-origin: 0 0; " > < div id = " waldo-tag-5261 " > < / div > < a class = " small text-muted js-disclosure " href = " /account/join/ " rel = " nofollow " style = " display: none; " > < i aria - hidden = " true " class = " fa fa-info-circle " > < / i > Remove ads < / a > < / div > < / section > < section class = " section3 " id = " nesting-decorators " > < h3 > Nesting Decorators < a class = " headerlink " href = " #nesting-decorators " title = " Permanent link " > < / a > < / h3 >
< p > You can < strong > apply several decorators < / strong > to a function by stacking them on top of each other : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " kn " > from < / span > < span class = " nn " > decorators < / span > < span class = " kn " > import < / span > < span class = " n " > debug < / span > < span class = " p " > , < / span > < span class = " n " > do_twice < / span >
< span class = " nd " > @debug < / span >
< span class = " nd " > @do_twice < / span >
< span class = " k " > def < / span > < span class = " nf " > greet < / span > < span class = " p " > ( < / span > < span class = " n " > name < / span > < span class = " p " > ) : < / span >
< span class = " nb " > print < / span > < span class = " p " > ( < / span > < span class = " sa " > f < / span > < span class = " s2 " > & quot ; Hello < / span > < span class = " si " > { < / span > < span class = " n " > name < / span > < span class = " si " > } < / span > < span class = " s2 " > & quot ; < / span > < span class = " p " > ) < / span >
< / code > < / pre > < / div >
< p > Think about this as the decorators being executed in the order they are listed . In other words , < code > @debug < / code > calls < code > @do_twice < / code > , which calls < code > greet ( ) < / code > , or < code > debug ( do_twice ( greet ( ) ) ) < / code > : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > greet < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; Eva & quot ; < / span > < span class = " p " > ) < / span >
< span class = " go " > Calling greet ( & #39;Eva')</span>
< span class = " go " > Hello Eva < / span >
< span class = " go " > Hello Eva < / span >
< span class = " go " > & #39;greet' returned None</span>
< / code > < / pre > < / div >
< p > Observe the difference if we change the order of < code > @debug < / code > and < code > @do_twice < / code > : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " kn " > from < / span > < span class = " nn " > decorators < / span > < span class = " kn " > import < / span > < span class = " n " > debug < / span > < span class = " p " > , < / span > < span class = " n " > do_twice < / span >
< span class = " hll " > < span class = " nd " > @do_twice < / span >
< / span > < span class = " hll " > < span class = " nd " > @debug < / span >
< / span > < span class = " k " > def < / span > < span class = " nf " > greet < / span > < span class = " p " > ( < / span > < span class = " n " > name < / span > < span class = " p " > ) : < / span >
< span class = " nb " > print < / span > < span class = " p " > ( < / span > < span class = " sa " > f < / span > < span class = " s2 " > & quot ; Hello < / span > < span class = " si " > { < / span > < span class = " n " > name < / span > < span class = " si " > } < / span > < span class = " s2 " > & quot ; < / span > < span class = " p " > ) < / span >
< / code > < / pre > < / div >
< p > In this case , < code > @do_twice < / code > will be applied to < code > @debug < / code > as well : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > greet < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; Eva & quot ; < / span > < span class = " p " > ) < / span >
< span class = " go " > Calling greet ( & #39;Eva')</span>
< span class = " go " > Hello Eva < / span >
< span class = " go " > & #39;greet' returned None</span>
< span class = " go " > Calling greet ( & #39;Eva')</span>
< span class = " go " > Hello Eva < / span >
< span class = " go " > & #39;greet' returned None</span>
< / code > < / pre > < / div >
< / section > < section class = " section3 " id = " decorators-with-arguments " > < h3 > Decorators With Arguments < a class = " headerlink " href = " #decorators-with-arguments " title = " Permanent link " > < / a > < / h3 >
< p > Sometimes , it & rsquo ; s useful to < strong > pass arguments to your decorators < / strong > . For instance , < code > @do_twice < / code > could be extended to a < code > @repeat ( num_times ) < / code > decorator . The number of times to execute the decorated function could then be given as an argument . < / p >
< p > This would allow you to do something like this : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " nd " > @repeat < / span > < span class = " p " > ( < / span > < span class = " n " > num_times < / span > < span class = " o " > = < / span > < span class = " mi " > 4 < / span > < span class = " p " > ) < / span >
< span class = " k " > def < / span > < span class = " nf " > greet < / span > < span class = " p " > ( < / span > < span class = " n " > name < / span > < span class = " p " > ) : < / span >
< span class = " nb " > print < / span > < span class = " p " > ( < / span > < span class = " sa " > f < / span > < span class = " s2 " > & quot ; Hello < / span > < span class = " si " > { < / span > < span class = " n " > name < / span > < span class = " si " > } < / span > < span class = " s2 " > & quot ; < / span > < span class = " p " > ) < / span >
< / code > < / pre > < / div >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > greet < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; World & quot ; < / span > < span class = " p " > ) < / span >
< span class = " go " > Hello World < / span >
< span class = " go " > Hello World < / span >
< span class = " go " > Hello World < / span >
< span class = " go " > Hello World < / span >
< / code > < / pre > < / div >
< p > Think about how you could achieve this . < / p >
< p > So far , the name written after the < code > @ < / code > has referred to a function object that can be called with another function . To be consistent , you then need < code > repeat ( num_times = 4 ) < / code > to return a function object that can act as a decorator . Luckily , you < a href = " #returning-functions-from-functions " > already know how to return functions < / a > ! In general , you want something like the following : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " k " > def < / span > < span class = " nf " > repeat < / span > < span class = " p " > ( < / span > < span class = " n " > num_times < / span > < span class = " p " > ) : < / span >
< span class = " k " > def < / span > < span class = " nf " > decorator_repeat < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) : < / span >
< span class = " o " > . . . < / span > < span class = " c1 " > # Create and return a wrapper function</span>
< span class = " k " > return < / span > < span class = " n " > decorator_repeat < / span >
< / code > < / pre > < / div >
< p > Typically , the decorator creates and returns an inner wrapper function , so writing the example out in full will give you an inner function within an inner function . While this might sound like the programming equivalent of the < a href = " https://en.wikipedia.org/wiki/Inception " > Inception movie < / a > , we & rsquo ; ll untangle it all in a moment : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " k " > def < / span > < span class = " nf " > repeat < / span > < span class = " p " > ( < / span > < span class = " n " > num_times < / span > < span class = " p " > ) : < / span >
< span class = " k " > def < / span > < span class = " nf " > decorator_repeat < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) : < / span >
< span class = " nd " > @functools < / span > < span class = " o " > . < / span > < span class = " n " > wraps < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) < / span >
< span class = " k " > def < / span > < span class = " nf " > wrapper_repeat < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) : < / span >
< span class = " k " > for < / span > < span class = " n " > _ < / span > < span class = " ow " > in < / span > < span class = " nb " > range < / span > < span class = " p " > ( < / span > < span class = " n " > num_times < / span > < span class = " p " > ) : < / span >
< span class = " n " > value < / span > < span class = " o " > = < / span > < span class = " n " > func < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) < / span >
< span class = " k " > return < / span > < span class = " n " > value < / span >
< span class = " k " > return < / span > < span class = " n " > wrapper_repeat < / span >
< span class = " k " > return < / span > < span class = " n " > decorator_repeat < / span >
< / code > < / pre > < / div >
< p > It looks a little messy , but we have only put the same decorator pattern you have seen many times by now inside one additional < code > def < / code > that handles the arguments to the decorator . Let & rsquo ; s start with the innermost function : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " k " > def < / span > < span class = " nf " > wrapper_repeat < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) : < / span >
< span class = " k " > for < / span > < span class = " n " > _ < / span > < span class = " ow " > in < / span > < span class = " nb " > range < / span > < span class = " p " > ( < / span > < span class = " n " > num_times < / span > < span class = " p " > ) : < / span >
< span class = " n " > value < / span > < span class = " o " > = < / span > < span class = " n " > func < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) < / span >
< span class = " k " > return < / span > < span class = " n " > value < / span >
< / code > < / pre > < / div >
< p > This < code > wrapper_repeat ( ) < / code > function takes arbitrary arguments and returns the value of the decorated function , < code > func ( ) < / code > . This wrapper function also contains the loop that calls the decorated function < code > num_times < / code > times . This is no different from the earlier wrapper functions you have seen , except that it is using the < code > num_times < / code > parameter that must be supplied from the outside . < / p >
< p > One step out , you & rsquo ; ll find the decorator function : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " k " > def < / span > < span class = " nf " > decorator_repeat < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) : < / span >
< span class = " nd " > @functools < / span > < span class = " o " > . < / span > < span class = " n " > wraps < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) < / span >
< span class = " k " > def < / span > < span class = " nf " > wrapper_repeat < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) : < / span >
< span class = " o " > . . . < / span >
< span class = " k " > return < / span > < span class = " n " > wrapper_repeat < / span >
< / code > < / pre > < / div >
< p > Again , < code > decorator_repeat ( ) < / code > looks exactly like the decorator functions you have written earlier , except that it & rsquo ; s named differently . That & rsquo ; s because we reserve the base name & mdash ; < code > repeat ( ) < / code > & mdash ; for the outermost function , which is the one the user will call . < / p >
< p > As you have already seen , the outermost function returns a reference to the decorator function : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " k " > def < / span > < span class = " nf " > repeat < / span > < span class = " p " > ( < / span > < span class = " n " > num_times < / span > < span class = " p " > ) : < / span >
< span class = " k " > def < / span > < span class = " nf " > decorator_repeat < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) : < / span >
< span class = " o " > . . . < / span >
< span class = " k " > return < / span > < span class = " n " > decorator_repeat < / span >
< / code > < / pre > < / div >
< p > There are a few subtle things happening in the < code > repeat ( ) < / code > function : < / p >
< ul >
< li > Defining < code > decorator_repeat ( ) < / code > as an inner function means that < code > repeat ( ) < / code > will refer to a function object & mdash ; < code > decorator_repeat < / code > . Earlier , we used < code > repeat < / code > without parentheses to refer to the function object . The added parentheses are necessary when defining decorators that take arguments . < / li >
< li > The < code > num_times < / code > argument is seemingly not used in < code > repeat ( ) < / code > itself . But by passing < code > num_times < / code > a < a href = " https://realpython.com/inner-functions-what-are-they-good-for/ " > closure < / a > is created where the value of < code > num_times < / code > is stored until it will be used later by < code > wrapper_repeat ( ) < / code > . < / li >
< / ul >
< p > With everything set up , let & rsquo ; s see if the results are as expected : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " nd " > @repeat < / span > < span class = " p " > ( < / span > < span class = " n " > num_times < / span > < span class = " o " > = < / span > < span class = " mi " > 4 < / span > < span class = " p " > ) < / span >
< span class = " k " > def < / span > < span class = " nf " > greet < / span > < span class = " p " > ( < / span > < span class = " n " > name < / span > < span class = " p " > ) : < / span >
< span class = " nb " > print < / span > < span class = " p " > ( < / span > < span class = " sa " > f < / span > < span class = " s2 " > & quot ; Hello < / span > < span class = " si " > { < / span > < span class = " n " > name < / span > < span class = " si " > } < / span > < span class = " s2 " > & quot ; < / span > < span class = " p " > ) < / span >
< / code > < / pre > < / div >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > greet < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; World & quot ; < / span > < span class = " p " > ) < / span >
< span class = " go " > Hello World < / span >
< span class = " go " > Hello World < / span >
< span class = " go " > Hello World < / span >
< span class = " go " > Hello World < / span >
< / code > < / pre > < / div >
< p > Just the result we were aiming for . < / p >
< div class = " w-100 text-center js-needs-scaling " style = " transform-origin: 0 0; " > < div id = " waldo-tag-5263 " > < / div > < a class = " small text-muted js-disclosure " href = " /account/join/ " rel = " nofollow " style = " display: none; " > < i aria - hidden = " true " class = " fa fa-info-circle " > < / i > Remove ads < / a > < / div > < / section > < section class = " section3 " id = " both-please-but-never-mind-the-bread " > < h3 > Both Please , But Never Mind the Bread < a class = " headerlink " href = " #both-please-but-never-mind-the-bread " title = " Permanent link " > < / a > < / h3 >
< p > With a little bit of care , you can also define < strong > decorators that can be used both with and without arguments < / strong > . Most likely , you don & rsquo ; t need this , but it is nice to have the flexibility . < / p >
< p > As you saw in the previous section , when a decorator uses arguments , you need to add an extra outer function . The challenge is for your code to figure out if the decorator has been called with or without arguments . < / p >
< p > Since the function to decorate is only passed in directly if the decorator is called without arguments , the function must be an optional argument . This means that the decorator arguments must all be specified by keyword . You can enforce this with the special < code > * < / code > syntax , which means that < a href = " https://www.python.org/dev/peps/pep-3102/ " > all following parameters are keyword - only < / a > : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " k " > def < / span > < span class = " nf " > name < / span > < span class = " p " > ( < / span > < span class = " n " > _func < / span > < span class = " o " > = < / span > < span class = " kc " > None < / span > < span class = " p " > , < / span > < span class = " o " > * < / span > < span class = " p " > , < / span > < span class = " n " > kw1 < / span > < span class = " o " > = < / span > < span class = " n " > val1 < / span > < span class = " p " > , < / span > < span class = " n " > kw2 < / span > < span class = " o " > = < / span > < span class = " n " > val2 < / span > < span class = " p " > , < / span > < span class = " o " > . . . < / span > < span class = " p " > ) : < / span > < span class = " c1 " > # 1</span>
< span class = " k " > def < / span > < span class = " nf " > decorator_name < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) : < / span >
< span class = " o " > . . . < / span > < span class = " c1 " > # Create and return a wrapper function.</span>
< span class = " k " > if < / span > < span class = " n " > _func < / span > < span class = " ow " > is < / span > < span class = " kc " > None < / span > < span class = " p " > : < / span >
< span class = " k " > return < / span > < span class = " n " > decorator_name < / span > < span class = " c1 " > # 2</span>
< span class = " k " > else < / span > < span class = " p " > : < / span >
< span class = " k " > return < / span > < span class = " n " > decorator_name < / span > < span class = " p " > ( < / span > < span class = " n " > _func < / span > < span class = " p " > ) < / span > < span class = " c1 " > # 3</span>
< / code > < / pre > < / div >
< p > Here , the < code > _func < / code > argument acts as a marker , noting whether the decorator has been called with arguments or not : < / p >
< ol >
< li > If < code > name < / code > has been called without arguments , the decorated function will be passed in as < code > _func < / code > . If it has been called with arguments , then < code > _func < / code > will be < code > None < / code > , and some of the keyword arguments may have been changed from their default values . The < code > * < / code > in the argument list means that the remaining arguments can & rsquo ; t be called as positional arguments . < / li >
< li > In this case , the decorator was called with arguments . Return a decorator function that can read and return a function . < / li >
< li > In this case , the decorator was called without arguments . Apply the decorator to the function immediately . < / li >
< / ol >
< p > Using this boilerplate on the < code > @repeat < / code > decorator in the previous section , you can write the following : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " hll " > < span class = " k " > def < / span > < span class = " nf " > repeat < / span > < span class = " p " > ( < / span > < span class = " n " > _func < / span > < span class = " o " > = < / span > < span class = " kc " > None < / span > < span class = " p " > , < / span > < span class = " o " > * < / span > < span class = " p " > , < / span > < span class = " n " > num_times < / span > < span class = " o " > = < / span > < span class = " mi " > 2 < / span > < span class = " p " > ) : < / span >
< / span > < span class = " k " > def < / span > < span class = " nf " > decorator_repeat < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) : < / span >
< span class = " nd " > @functools < / span > < span class = " o " > . < / span > < span class = " n " > wraps < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) < / span >
< span class = " k " > def < / span > < span class = " nf " > wrapper_repeat < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) : < / span >
< span class = " k " > for < / span > < span class = " n " > _ < / span > < span class = " ow " > in < / span > < span class = " nb " > range < / span > < span class = " p " > ( < / span > < span class = " n " > num_times < / span > < span class = " p " > ) : < / span >
< span class = " n " > value < / span > < span class = " o " > = < / span > < span class = " n " > func < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) < / span >
< span class = " k " > return < / span > < span class = " n " > value < / span >
< span class = " k " > return < / span > < span class = " n " > wrapper_repeat < / span >
< span class = " hll " > < span class = " k " > if < / span > < span class = " n " > _func < / span > < span class = " ow " > is < / span > < span class = " kc " > None < / span > < span class = " p " > : < / span >
< / span > < span class = " hll " > < span class = " k " > return < / span > < span class = " n " > decorator_repeat < / span >
< / span > < span class = " hll " > < span class = " k " > else < / span > < span class = " p " > : < / span >
< / span > < span class = " hll " > < span class = " k " > return < / span > < span class = " n " > decorator_repeat < / span > < span class = " p " > ( < / span > < span class = " n " > _func < / span > < span class = " p " > ) < / span >
< / span > < / code > < / pre > < / div >
< p > Compare this with the original < code > @repeat < / code > . The only changes are the added < code > _func < / code > parameter and the < code > if < / code > - < code > else < / code > at the end . < / p >
< p > < a href = " https://github.com/dabeaz/python-cookbook/blob/master/src/9/defining_a_decorator_that_takes_an_optional_argument/example.py " > Recipe 9.6 < / a > of the excellent < a href = " https://realpython.com/asins/1449340377/ " > Python Cookbook < / a > shows an alternative solution using < a href = " https://docs.python.org/library/functools.html#functools.partial " > < code > functools . partial ( ) < / code > < / a > . < / p >
< p > These examples show that < code > @repeat < / code > can now be used with or without arguments : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " nd " > @repeat < / span >
< span class = " k " > def < / span > < span class = " nf " > say_whee < / span > < span class = " p " > ( ) : < / span >
< span class = " nb " > print < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; Whee ! & quot ; < / span > < span class = " p " > ) < / span >
< span class = " nd " > @repeat < / span > < span class = " p " > ( < / span > < span class = " n " > num_times < / span > < span class = " o " > = < / span > < span class = " mi " > 3 < / span > < span class = " p " > ) < / span >
< span class = " k " > def < / span > < span class = " nf " > greet < / span > < span class = " p " > ( < / span > < span class = " n " > name < / span > < span class = " p " > ) : < / span >
< span class = " nb " > print < / span > < span class = " p " > ( < / span > < span class = " sa " > f < / span > < span class = " s2 " > & quot ; Hello < / span > < span class = " si " > { < / span > < span class = " n " > name < / span > < span class = " si " > } < / span > < span class = " s2 " > & quot ; < / span > < span class = " p " > ) < / span >
< / code > < / pre > < / div >
< p > Recall that the default value of < code > num_times < / code > is 2 : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > say_whee < / span > < span class = " p " > ( ) < / span >
< span class = " go " > Whee ! < / span >
< span class = " go " > Whee ! < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > greet < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; Penny & quot ; < / span > < span class = " p " > ) < / span >
< span class = " go " > Hello Penny < / span >
< span class = " go " > Hello Penny < / span >
< span class = " go " > Hello Penny < / span >
< / code > < / pre > < / div >
< / section > < section class = " section3 " id = " stateful-decorators " > < h3 > Stateful Decorators < a class = " headerlink " href = " #stateful-decorators " title = " Permanent link " > < / a > < / h3 >
< p > Sometimes , it & rsquo ; s useful to have < strong > a decorator that can keep track of state < / strong > . As a simple example , we will create a decorator that counts the number of times a function is called . < / p >
< div class = " alert alert-primary " role = " alert " >
< p > < strong > Note : < / strong > In < a href = " #functions " > the beginning of this guide < / a > , we talked about pure functions returning a value based on given arguments . Stateful decorators are quite the opposite , where the return value will depend on the current state , as well as the given arguments . < / p >
< / div >
< p > In the < a href = " #classes-as-decorators " > next section < / a > , you will see how to use classes to keep state . But in simple cases , you can also get away with using < a href = " https://www.python.org/dev/peps/pep-0232/ " > function attributes < / a > : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " kn " > import < / span > < span class = " nn " > functools < / span >
< span class = " k " > def < / span > < span class = " nf " > count_calls < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) : < / span >
< span class = " nd " > @functools < / span > < span class = " o " > . < / span > < span class = " n " > wraps < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) < / span >
< span class = " k " > def < / span > < span class = " nf " > wrapper_count_calls < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) : < / span >
< span class = " n " > wrapper_count_calls < / span > < span class = " o " > . < / span > < span class = " n " > num_calls < / span > < span class = " o " > + = < / span > < span class = " mi " > 1 < / span >
< span class = " nb " > print < / span > < span class = " p " > ( < / span > < span class = " sa " > f < / span > < span class = " s2 " > & quot ; Call < / span > < span class = " si " > { < / span > < span class = " n " > wrapper_count_calls < / span > < span class = " o " > . < / span > < span class = " n " > num_calls < / span > < span class = " si " > } < / span > < span class = " s2 " > of < / span > < span class = " si " > { < / span > < span class = " n " > func < / span > < span class = " o " > . < / span > < span class = " vm " > __name__ < / span > < span class = " si " > ! r } < / span > < span class = " s2 " > & quot ; < / span > < span class = " p " > ) < / span >
< span class = " k " > return < / span > < span class = " n " > func < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) < / span >
< span class = " n " > wrapper_count_calls < / span > < span class = " o " > . < / span > < span class = " n " > num_calls < / span > < span class = " o " > = < / span > < span class = " mi " > 0 < / span >
< span class = " k " > return < / span > < span class = " n " > wrapper_count_calls < / span >
< span class = " nd " > @count_calls < / span >
< span class = " k " > def < / span > < span class = " nf " > say_whee < / span > < span class = " p " > ( ) : < / span >
< span class = " nb " > print < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; Whee ! & quot ; < / span > < span class = " p " > ) < / span >
< / code > < / pre > < / div >
< p > The state & mdash ; the number of calls to the function & mdash ; is stored in the function attribute < code > . num_calls < / code > on the wrapper function . Here is the effect of using it : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > say_whee < / span > < span class = " p " > ( ) < / span >
< span class = " go " > Call 1 of & #39;say_whee'</span>
< span class = " go " > Whee ! < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > say_whee < / span > < span class = " p " > ( ) < / span >
< span class = " go " > Call 2 of & #39;say_whee'</span>
< span class = " go " > Whee ! < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > say_whee < / span > < span class = " o " > . < / span > < span class = " n " > num_calls < / span >
< span class = " go " > 2 < / span >
< / code > < / pre > < / div >
< div class = " w-100 text-center js-needs-scaling " style = " transform-origin: 0 0; " > < div id = " waldo-tag-5265 " > < / div > < a class = " small text-muted js-disclosure " href = " /account/join/ " rel = " nofollow " style = " display: none; " > < i aria - hidden = " true " class = " fa fa-info-circle " > < / i > Remove ads < / a > < / div > < / section > < section class = " section3 " id = " classes-as-decorators " > < h3 > Classes as Decorators < a class = " headerlink " href = " #classes-as-decorators " title = " Permanent link " > < / a > < / h3 >
< p > The typical way to maintain state is by < a href = " https://realpython.com/python3-object-oriented-programming/ " > using classes < / a > . In this section , you & rsquo ; ll see how to rewrite the < code > @count_calls < / code > example from the previous section < strong > using a class as a decorator < / strong > . < / p >
< p > Recall that the decorator syntax < code > @my_decorator < / code > is just an easier way of saying < code > func = my_decorator ( func ) < / code > . Therefore , if < code > my_decorator < / code > is a class , it needs to take < code > func < / code > as an argument in its < code > . __init__ ( ) < / code > method . Furthermore , the class instance needs to be < a href = " https://docs.python.org/reference/datamodel.html#emulating-callable-objects " > callable < / a > so that it can stand in for the decorated function . < / p >
< p > For a class instance to be callable , you implement the special < code > . __call__ ( ) < / code > method : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " k " > class < / span > < span class = " nc " > Counter < / span > < span class = " p " > : < / span >
< span class = " k " > def < / span > < span class = " fm " > __init__ < / span > < span class = " p " > ( < / span > < span class = " bp " > self < / span > < span class = " p " > , < / span > < span class = " n " > start < / span > < span class = " o " > = < / span > < span class = " mi " > 0 < / span > < span class = " p " > ) : < / span >
< span class = " bp " > self < / span > < span class = " o " > . < / span > < span class = " n " > count < / span > < span class = " o " > = < / span > < span class = " n " > start < / span >
< span class = " k " > def < / span > < span class = " fm " > __call__ < / span > < span class = " p " > ( < / span > < span class = " bp " > self < / span > < span class = " p " > ) : < / span >
< span class = " bp " > self < / span > < span class = " o " > . < / span > < span class = " n " > count < / span > < span class = " o " > + = < / span > < span class = " mi " > 1 < / span >
< span class = " nb " > print < / span > < span class = " p " > ( < / span > < span class = " sa " > f < / span > < span class = " s2 " > & quot ; Current count is < / span > < span class = " si " > { < / span > < span class = " bp " > self < / span > < span class = " o " > . < / span > < span class = " n " > count < / span > < span class = " si " > } < / span > < span class = " s2 " > & quot ; < / span > < span class = " p " > ) < / span >
< / code > < / pre > < / div >
< p > The < code > . __call__ ( ) < / code > method is executed each time you try to call an instance of the class : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > counter < / span > < span class = " o " > = < / span > < span class = " n " > Counter < / span > < span class = " p " > ( ) < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > counter < / span > < span class = " p " > ( ) < / span >
< span class = " go " > Current count is 1 < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > counter < / span > < span class = " p " > ( ) < / span >
< span class = " go " > Current count is 2 < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > counter < / span > < span class = " o " > . < / span > < span class = " n " > count < / span >
< span class = " go " > 2 < / span >
< / code > < / pre > < / div >
< p > Therefore , a typical implementation of a decorator class needs to implement < code > . __init__ ( ) < / code > and < code > . __call__ ( ) < / code > : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " kn " > import < / span > < span class = " nn " > functools < / span >
< span class = " k " > class < / span > < span class = " nc " > CountCalls < / span > < span class = " p " > : < / span >
< span class = " k " > def < / span > < span class = " fm " > __init__ < / span > < span class = " p " > ( < / span > < span class = " bp " > self < / span > < span class = " p " > , < / span > < span class = " n " > func < / span > < span class = " p " > ) : < / span >
< span class = " n " > functools < / span > < span class = " o " > . < / span > < span class = " n " > update_wrapper < / span > < span class = " p " > ( < / span > < span class = " bp " > self < / span > < span class = " p " > , < / span > < span class = " n " > func < / span > < span class = " p " > ) < / span >
< span class = " bp " > self < / span > < span class = " o " > . < / span > < span class = " n " > func < / span > < span class = " o " > = < / span > < span class = " n " > func < / span >
< span class = " bp " > self < / span > < span class = " o " > . < / span > < span class = " n " > num_calls < / span > < span class = " o " > = < / span > < span class = " mi " > 0 < / span >
< span class = " k " > def < / span > < span class = " fm " > __call__ < / span > < span class = " p " > ( < / span > < span class = " bp " > self < / span > < span class = " p " > , < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) : < / span >
< span class = " bp " > self < / span > < span class = " o " > . < / span > < span class = " n " > num_calls < / span > < span class = " o " > + = < / span > < span class = " mi " > 1 < / span >
< span class = " nb " > print < / span > < span class = " p " > ( < / span > < span class = " sa " > f < / span > < span class = " s2 " > & quot ; Call < / span > < span class = " si " > { < / span > < span class = " bp " > self < / span > < span class = " o " > . < / span > < span class = " n " > num_calls < / span > < span class = " si " > } < / span > < span class = " s2 " > of < / span > < span class = " si " > { < / span > < span class = " bp " > self < / span > < span class = " o " > . < / span > < span class = " n " > func < / span > < span class = " o " > . < / span > < span class = " vm " > __name__ < / span > < span class = " si " > ! r } < / span > < span class = " s2 " > & quot ; < / span > < span class = " p " > ) < / span >
< span class = " k " > return < / span > < span class = " bp " > self < / span > < span class = " o " > . < / span > < span class = " n " > func < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) < / span >
< span class = " nd " > @CountCalls < / span >
< span class = " k " > def < / span > < span class = " nf " > say_whee < / span > < span class = " p " > ( ) : < / span >
< span class = " nb " > print < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; Whee ! & quot ; < / span > < span class = " p " > ) < / span >
< / code > < / pre > < / div >
< p > The < code > . __init__ ( ) < / code > method must store a reference to the function and can do any other necessary initialization . The < code > . __call__ ( ) < / code > method will be called instead of the decorated function . It does essentially the same thing as the < code > wrapper ( ) < / code > function in our earlier examples . Note that you need to use the < a href = " https://docs.python.org/library/functools.html#functools.update_wrapper " > < code > functools . update_wrapper ( ) < / code > < / a > function instead of < code > @functools.wraps < / code > . < / p >
< p > This < code > @CountCalls < / code > decorator works the same as the one in the previous section : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > say_whee < / span > < span class = " p " > ( ) < / span >
< span class = " go " > Call 1 of & #39;say_whee'</span>
< span class = " go " > Whee ! < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > say_whee < / span > < span class = " p " > ( ) < / span >
< span class = " go " > Call 2 of & #39;say_whee'</span>
< span class = " go " > Whee ! < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > say_whee < / span > < span class = " o " > . < / span > < span class = " n " > num_calls < / span >
< span class = " go " > 2 < / span >
< / code > < / pre > < / div >
< / section > < / section > < section class = " section2 " id = " more-real-world-examples " > < h2 > More Real World Examples < a class = " headerlink " href = " #more-real-world-examples " title = " Permanent link " > < / a > < / h2 >
< p > We & rsquo ; ve come a far way now , having figured out how to create all kinds of decorators . Let & rsquo ; s wrap it up , putting our newfound knowledge into creating a few more examples that might actually be useful in the real world . < / p >
< section class = " section3 " id = " slowing-down-code-revisited " > < h3 > Slowing Down Code , Revisited < a class = " headerlink " href = " #slowing-down-code-revisited " title = " Permanent link " > < / a > < / h3 >
< p > As noted earlier , our < a href = " #slowing-down-code " > previous implementation of < code > @slow_down < / code > < / a > always sleeps for one second . Now you know how to add parameters to decorators , so let & rsquo ; s rewrite < code > @slow_down < / code > using an optional < code > rate < / code > argument that controls how long it sleeps : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " kn " > import < / span > < span class = " nn " > functools < / span >
< span class = " kn " > import < / span > < span class = " nn " > time < / span >
< span class = " k " > def < / span > < span class = " nf " > slow_down < / span > < span class = " p " > ( < / span > < span class = " n " > _func < / span > < span class = " o " > = < / span > < span class = " kc " > None < / span > < span class = " p " > , < / span > < span class = " o " > * < / span > < span class = " p " > , < / span > < span class = " n " > rate < / span > < span class = " o " > = < / span > < span class = " mi " > 1 < / span > < span class = " p " > ) : < / span >
< span class = " sd " > & quot ; & quot ; & quot ; Sleep given amount of seconds before calling the function & quot ; & quot ; & quot ; < / span >
< span class = " k " > def < / span > < span class = " nf " > decorator_slow_down < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) : < / span >
< span class = " nd " > @functools < / span > < span class = " o " > . < / span > < span class = " n " > wraps < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) < / span >
< span class = " k " > def < / span > < span class = " nf " > wrapper_slow_down < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) : < / span >
< span class = " n " > time < / span > < span class = " o " > . < / span > < span class = " n " > sleep < / span > < span class = " p " > ( < / span > < span class = " n " > rate < / span > < span class = " p " > ) < / span >
< span class = " k " > return < / span > < span class = " n " > func < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) < / span >
< span class = " k " > return < / span > < span class = " n " > wrapper_slow_down < / span >
< span class = " k " > if < / span > < span class = " n " > _func < / span > < span class = " ow " > is < / span > < span class = " kc " > None < / span > < span class = " p " > : < / span >
< span class = " k " > return < / span > < span class = " n " > decorator_slow_down < / span >
< span class = " k " > else < / span > < span class = " p " > : < / span >
< span class = " k " > return < / span > < span class = " n " > decorator_slow_down < / span > < span class = " p " > ( < / span > < span class = " n " > _func < / span > < span class = " p " > ) < / span >
< / code > < / pre > < / div >
< p > We & rsquo ; re using the boilerplate introduced in the < a href = " #both-please-but-never-mind-the-bread " > Both Please , But Never Mind the Bread < / a > section to make < code > @slow_down < / code > callable both with and without arguments . The same recursive < code > countdown ( ) < / code > function < a href = " #slowing-down-code " > as earlier < / a > now sleeps two seconds between each count : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " hll " > < span class = " nd " > @slow_down < / span > < span class = " p " > ( < / span > < span class = " n " > rate < / span > < span class = " o " > = < / span > < span class = " mi " > 2 < / span > < span class = " p " > ) < / span >
< / span > < span class = " k " > def < / span > < span class = " nf " > countdown < / span > < span class = " p " > ( < / span > < span class = " n " > from_number < / span > < span class = " p " > ) : < / span >
< span class = " k " > if < / span > < span class = " n " > from_number < / span > < span class = " o " > & lt ; < / span > < span class = " mi " > 1 < / span > < span class = " p " > : < / span >
< span class = " nb " > print < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; Liftoff ! & quot ; < / span > < span class = " p " > ) < / span >
< span class = " k " > else < / span > < span class = " p " > : < / span >
< span class = " nb " > print < / span > < span class = " p " > ( < / span > < span class = " n " > from_number < / span > < span class = " p " > ) < / span >
< span class = " n " > countdown < / span > < span class = " p " > ( < / span > < span class = " n " > from_number < / span > < span class = " o " > - < / span > < span class = " mi " > 1 < / span > < span class = " p " > ) < / span >
< / code > < / pre > < / div >
< p > As before , you must run the example yourself to see the effect of the decorator : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > countdown < / span > < span class = " p " > ( < / span > < span class = " mi " > 3 < / span > < span class = " p " > ) < / span >
< span class = " go " > 3 < / span >
< span class = " go " > 2 < / span >
< span class = " go " > 1 < / span >
< span class = " go " > Liftoff ! < / span >
< / code > < / pre > < / div >
< / section > < section class = " section3 " id = " creating-singletons " > < h3 > Creating Singletons < a class = " headerlink " href = " #creating-singletons " title = " Permanent link " > < / a > < / h3 >
< p > A singleton is a class with only one instance . There are several singletons in Python that you use frequently , including < code > None < / code > , < code > True < / code > , and < code > False < / code > . It is the fact that < code > None < / code > is a singleton that allows you to compare for < code > None < / code > using the < a href = " https://realpython.com/python-is-identity-vs-equality/ " > < code > is < / code > keyword < / a > , like you saw in the < a href = " #both-please-but-never-mind-the-bread " > Both Please < / a > section : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " k " > if < / span > < span class = " n " > _func < / span > < span class = " ow " > is < / span > < span class = " kc " > None < / span > < span class = " p " > : < / span >
< span class = " k " > return < / span > < span class = " n " > decorator_name < / span >
< span class = " k " > else < / span > < span class = " p " > : < / span >
< span class = " k " > return < / span > < span class = " n " > decorator_name < / span > < span class = " p " > ( < / span > < span class = " n " > _func < / span > < span class = " p " > ) < / span >
< / code > < / pre > < / div >
< p > Using < code > is < / code > returns < code > True < / code > only for objects that are the exact same instance . The following < code > @singleton < / code > decorator turns a class into a singleton by storing the first instance of the class as an attribute . Later attempts at creating an instance simply return the stored instance : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " kn " > import < / span > < span class = " nn " > functools < / span >
< span class = " k " > def < / span > < span class = " nf " > singleton < / span > < span class = " p " > ( < / span > < span class = " bp " > cls < / span > < span class = " p " > ) : < / span >
< span class = " sd " > & quot ; & quot ; & quot ; Make a class a Singleton class ( only one instance ) & quot ; & quot ; & quot ; < / span >
< span class = " nd " > @functools < / span > < span class = " o " > . < / span > < span class = " n " > wraps < / span > < span class = " p " > ( < / span > < span class = " bp " > cls < / span > < span class = " p " > ) < / span >
< span class = " k " > def < / span > < span class = " nf " > wrapper_singleton < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) : < / span >
< span class = " k " > if < / span > < span class = " ow " > not < / span > < span class = " n " > wrapper_singleton < / span > < span class = " o " > . < / span > < span class = " n " > instance < / span > < span class = " p " > : < / span >
< span class = " n " > wrapper_singleton < / span > < span class = " o " > . < / span > < span class = " n " > instance < / span > < span class = " o " > = < / span > < span class = " bp " > cls < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) < / span >
< span class = " k " > return < / span > < span class = " n " > wrapper_singleton < / span > < span class = " o " > . < / span > < span class = " n " > instance < / span >
< span class = " n " > wrapper_singleton < / span > < span class = " o " > . < / span > < span class = " n " > instance < / span > < span class = " o " > = < / span > < span class = " kc " > None < / span >
< span class = " k " > return < / span > < span class = " n " > wrapper_singleton < / span >
< span class = " nd " > @singleton < / span >
< span class = " k " > class < / span > < span class = " nc " > TheOne < / span > < span class = " p " > : < / span >
< span class = " k " > pass < / span >
< / code > < / pre > < / div >
< p > As you see , this class decorator follows the same template as our function decorators . The only difference is that we are using < code > cls < / code > instead of < code > func < / code > as the parameter name to indicate that it is meant to be a class decorator . < / p >
< p > Let & rsquo ; s see if it works : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > first_one < / span > < span class = " o " > = < / span > < span class = " n " > TheOne < / span > < span class = " p " > ( ) < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > another_one < / span > < span class = " o " > = < / span > < span class = " n " > TheOne < / span > < span class = " p " > ( ) < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " nb " > id < / span > < span class = " p " > ( < / span > < span class = " n " > first_one < / span > < span class = " p " > ) < / span >
< span class = " go " > 140094218762280 < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " nb " > id < / span > < span class = " p " > ( < / span > < span class = " n " > another_one < / span > < span class = " p " > ) < / span >
< span class = " go " > 140094218762280 < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > first_one < / span > < span class = " ow " > is < / span > < span class = " n " > another_one < / span >
< span class = " go " > True < / span >
< / code > < / pre > < / div >
< p > It seems clear that < code > first_one < / code > is indeed the exact same instance as < code > another_one < / code > . < / p >
< div class = " alert alert-primary " role = " alert " >
< p > < strong > Note : < / strong > Singleton classes are not really used as often in Python as in other languages . The effect of a singleton is usually better implemented as a global variable in a module . < / p >
< / div >
< / section > < section class = " section3 " id = " caching-return-values " > < h3 > Caching Return Values < a class = " headerlink " href = " #caching-return-values " title = " Permanent link " > < / a > < / h3 >
< p > Decorators can provide a nice mechanism for < a href = " https://en.wikipedia.org/wiki/Cache_ %28c omputing % 29 " > caching < / a > and < a href = " https://en.wikipedia.org/wiki/Memoization " > memoization < / a > . As an example , let & rsquo ; s look at a < a href = " https://realpython.com/python-thinking-recursively/ " > recursive < / a > definition of the < a href = " https://en.wikipedia.org/wiki/Fibonacci_number " > Fibonacci sequence < / a > : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " kn " > from < / span > < span class = " nn " > decorators < / span > < span class = " kn " > import < / span > < span class = " n " > count_calls < / span >
< span class = " nd " > @count_calls < / span >
< span class = " k " > def < / span > < span class = " nf " > fibonacci < / span > < span class = " p " > ( < / span > < span class = " n " > num < / span > < span class = " p " > ) : < / span >
< span class = " k " > if < / span > < span class = " n " > num < / span > < span class = " o " > & lt ; < / span > < span class = " mi " > 2 < / span > < span class = " p " > : < / span >
< span class = " k " > return < / span > < span class = " n " > num < / span >
< span class = " k " > return < / span > < span class = " n " > fibonacci < / span > < span class = " p " > ( < / span > < span class = " n " > num < / span > < span class = " o " > - < / span > < span class = " mi " > 1 < / span > < span class = " p " > ) < / span > < span class = " o " > + < / span > < span class = " n " > fibonacci < / span > < span class = " p " > ( < / span > < span class = " n " > num < / span > < span class = " o " > - < / span > < span class = " mi " > 2 < / span > < span class = " p " > ) < / span >
< / code > < / pre > < / div >
< p > While the implementation is simple , its runtime performance is terrible : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > fibonacci < / span > < span class = " p " > ( < / span > < span class = " mi " > 10 < / span > < span class = " p " > ) < / span >
< span class = " go " > & lt ; Lots of output from count_calls & gt ; < / span >
< span class = " go " > 55 < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > fibonacci < / span > < span class = " o " > . < / span > < span class = " n " > num_calls < / span >
< span class = " go " > 177 < / span >
< / code > < / pre > < / div >
< p > To calculate the tenth Fibonacci number , you should really only need to calculate the preceding Fibonacci numbers , but this implementation somehow needs a whopping 177 calculations . It gets worse quickly : 21891 calculations are needed for < code > fibonacci ( 20 ) < / code > and almost 2.7 million calculations for the 30 th number . This is because the code keeps recalculating Fibonacci numbers that are already known . < / p >
< p > The usual solution is to implement Fibonacci numbers using a < code > for < / code > loop and a lookup table . However , simple caching of the calculations will also do the trick : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " kn " > import < / span > < span class = " nn " > functools < / span >
< span class = " kn " > from < / span > < span class = " nn " > decorators < / span > < span class = " kn " > import < / span > < span class = " n " > count_calls < / span >
< span class = " k " > def < / span > < span class = " nf " > cache < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) : < / span >
< span class = " sd " > & quot ; & quot ; & quot ; Keep a cache of previous function calls & quot ; & quot ; & quot ; < / span >
< span class = " nd " > @functools < / span > < span class = " o " > . < / span > < span class = " n " > wraps < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) < / span >
< span class = " k " > def < / span > < span class = " nf " > wrapper_cache < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) : < / span >
< span class = " n " > cache_key < / span > < span class = " o " > = < / span > < span class = " n " > args < / span > < span class = " o " > + < / span > < span class = " nb " > tuple < / span > < span class = " p " > ( < / span > < span class = " n " > kwargs < / span > < span class = " o " > . < / span > < span class = " n " > items < / span > < span class = " p " > ( ) ) < / span >
< span class = " k " > if < / span > < span class = " n " > cache_key < / span > < span class = " ow " > not < / span > < span class = " ow " > in < / span > < span class = " n " > wrapper_cache < / span > < span class = " o " > . < / span > < span class = " n " > cache < / span > < span class = " p " > : < / span >
< span class = " n " > wrapper_cache < / span > < span class = " o " > . < / span > < span class = " n " > cache < / span > < span class = " p " > [ < / span > < span class = " n " > cache_key < / span > < span class = " p " > ] < / span > < span class = " o " > = < / span > < span class = " n " > func < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) < / span >
< span class = " k " > return < / span > < span class = " n " > wrapper_cache < / span > < span class = " o " > . < / span > < span class = " n " > cache < / span > < span class = " p " > [ < / span > < span class = " n " > cache_key < / span > < span class = " p " > ] < / span >
< span class = " n " > wrapper_cache < / span > < span class = " o " > . < / span > < span class = " n " > cache < / span > < span class = " o " > = < / span > < span class = " nb " > dict < / span > < span class = " p " > ( ) < / span >
< span class = " k " > return < / span > < span class = " n " > wrapper_cache < / span >
< span class = " hll " > < span class = " nd " > @cache < / span >
< / span > < span class = " nd " > @count_calls < / span >
< span class = " k " > def < / span > < span class = " nf " > fibonacci < / span > < span class = " p " > ( < / span > < span class = " n " > num < / span > < span class = " p " > ) : < / span >
< span class = " k " > if < / span > < span class = " n " > num < / span > < span class = " o " > & lt ; < / span > < span class = " mi " > 2 < / span > < span class = " p " > : < / span >
< span class = " k " > return < / span > < span class = " n " > num < / span >
< span class = " k " > return < / span > < span class = " n " > fibonacci < / span > < span class = " p " > ( < / span > < span class = " n " > num < / span > < span class = " o " > - < / span > < span class = " mi " > 1 < / span > < span class = " p " > ) < / span > < span class = " o " > + < / span > < span class = " n " > fibonacci < / span > < span class = " p " > ( < / span > < span class = " n " > num < / span > < span class = " o " > - < / span > < span class = " mi " > 2 < / span > < span class = " p " > ) < / span >
< / code > < / pre > < / div >
< p > The cache works as a lookup table , so now < code > fibonacci ( ) < / code > only does the necessary calculations once : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > fibonacci < / span > < span class = " p " > ( < / span > < span class = " mi " > 10 < / span > < span class = " p " > ) < / span >
< span class = " go " > Call 1 of & #39;fibonacci'</span>
< span class = " gp " > . . . < / span >
< span class = " go " > Call 11 of & #39;fibonacci'</span>
< span class = " go " > 55 < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > fibonacci < / span > < span class = " p " > ( < / span > < span class = " mi " > 8 < / span > < span class = " p " > ) < / span >
< span class = " go " > 21 < / span >
< / code > < / pre > < / div >
< p > Note that in the final call to < code > fibonacci ( 8 ) < / code > , no new calculations were needed , since the eighth Fibonacci number had already been calculated for < code > fibonacci ( 10 ) < / code > . < / p >
< p > In the standard library , a < a href = " https://realpython.com/lru-cache-python/ " > Least Recently Used ( LRU ) cache < / a > is available as < a href = " https://docs.python.org/library/functools.html#functools.lru_cache " > < code > @functools.lru_cache < / code > < / a > . < / p >
< p > This decorator has more features than the one you saw above . You should use < code > @functools.lru_cache < / code > instead of writing your own cache decorator : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " kn " > import < / span > < span class = " nn " > functools < / span >
< span class = " hll " > < span class = " nd " > @functools < / span > < span class = " o " > . < / span > < span class = " n " > lru_cache < / span > < span class = " p " > ( < / span > < span class = " n " > maxsize < / span > < span class = " o " > = < / span > < span class = " mi " > 4 < / span > < span class = " p " > ) < / span >
< / span > < span class = " k " > def < / span > < span class = " nf " > fibonacci < / span > < span class = " p " > ( < / span > < span class = " n " > num < / span > < span class = " p " > ) : < / span >
< span class = " hll " > < span class = " nb " > print < / span > < span class = " p " > ( < / span > < span class = " sa " > f < / span > < span class = " s2 " > & quot ; Calculating fibonacci ( < / span > < span class = " si " > { < / span > < span class = " n " > num < / span > < span class = " si " > } < / span > < span class = " s2 " > ) & quot ; < / span > < span class = " p " > ) < / span >
< / span > < span class = " k " > if < / span > < span class = " n " > num < / span > < span class = " o " > & lt ; < / span > < span class = " mi " > 2 < / span > < span class = " p " > : < / span >
< span class = " k " > return < / span > < span class = " n " > num < / span >
< span class = " k " > return < / span > < span class = " n " > fibonacci < / span > < span class = " p " > ( < / span > < span class = " n " > num < / span > < span class = " o " > - < / span > < span class = " mi " > 1 < / span > < span class = " p " > ) < / span > < span class = " o " > + < / span > < span class = " n " > fibonacci < / span > < span class = " p " > ( < / span > < span class = " n " > num < / span > < span class = " o " > - < / span > < span class = " mi " > 2 < / span > < span class = " p " > ) < / span >
< / code > < / pre > < / div >
< p > The < code > maxsize < / code > parameter specifies how many recent calls are cached . The default value is 128 , but you can specify < code > maxsize = None < / code > to cache all function calls . However , be aware that this can cause memory problems if you are caching many large objects . < / p >
< p > You can use the < code > . cache_info ( ) < / code > method to see how the cache performs , and you can tune it if needed . In our example , we used an artificially small < code > maxsize < / code > to see the effect of elements being removed from the cache : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > fibonacci < / span > < span class = " p " > ( < / span > < span class = " mi " > 10 < / span > < span class = " p " > ) < / span >
< span class = " go " > Calculating fibonacci ( 10 ) < / span >
< span class = " go " > Calculating fibonacci ( 9 ) < / span >
< span class = " go " > Calculating fibonacci ( 8 ) < / span >
< span class = " go " > Calculating fibonacci ( 7 ) < / span >
< span class = " go " > Calculating fibonacci ( 6 ) < / span >
< span class = " go " > Calculating fibonacci ( 5 ) < / span >
< span class = " go " > Calculating fibonacci ( 4 ) < / span >
< span class = " go " > Calculating fibonacci ( 3 ) < / span >
< span class = " go " > Calculating fibonacci ( 2 ) < / span >
< span class = " go " > Calculating fibonacci ( 1 ) < / span >
< span class = " go " > Calculating fibonacci ( 0 ) < / span >
< span class = " go " > 55 < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > fibonacci < / span > < span class = " p " > ( < / span > < span class = " mi " > 8 < / span > < span class = " p " > ) < / span >
< span class = " go " > 21 < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > fibonacci < / span > < span class = " p " > ( < / span > < span class = " mi " > 5 < / span > < span class = " p " > ) < / span >
< span class = " go " > Calculating fibonacci ( 5 ) < / span >
< span class = " go " > Calculating fibonacci ( 4 ) < / span >
< span class = " go " > Calculating fibonacci ( 3 ) < / span >
< span class = " go " > Calculating fibonacci ( 2 ) < / span >
< span class = " go " > Calculating fibonacci ( 1 ) < / span >
< span class = " go " > Calculating fibonacci ( 0 ) < / span >
< span class = " go " > 5 < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > fibonacci < / span > < span class = " p " > ( < / span > < span class = " mi " > 8 < / span > < span class = " p " > ) < / span >
< span class = " go " > Calculating fibonacci ( 8 ) < / span >
< span class = " go " > Calculating fibonacci ( 7 ) < / span >
< span class = " go " > Calculating fibonacci ( 6 ) < / span >
< span class = " go " > 21 < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > fibonacci < / span > < span class = " p " > ( < / span > < span class = " mi " > 5 < / span > < span class = " p " > ) < / span >
< span class = " go " > 5 < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > fibonacci < / span > < span class = " o " > . < / span > < span class = " n " > cache_info < / span > < span class = " p " > ( ) < / span >
< span class = " go " > CacheInfo ( hits = 17 , misses = 20 , maxsize = 4 , currsize = 4 ) < / span >
< / code > < / pre > < / div >
< / section > < section class = " section3 " id = " adding-information-about-units " > < h3 > Adding Information About Units < a class = " headerlink " href = " #adding-information-about-units " title = " Permanent link " > < / a > < / h3 >
< p > The following example is somewhat similar to the < a href = " #registering-plugins " > Registering Plugins < / a > example from earlier , in that it does not really change the behavior of the decorated function . Instead , it simply adds < code > unit < / code > as a function attribute : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " k " > def < / span > < span class = " nf " > set_unit < / span > < span class = " p " > ( < / span > < span class = " n " > unit < / span > < span class = " p " > ) : < / span >
< span class = " sd " > & quot ; & quot ; & quot ; Register a unit on a function & quot ; & quot ; & quot ; < / span >
< span class = " k " > def < / span > < span class = " nf " > decorator_set_unit < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) : < / span >
< span class = " n " > func < / span > < span class = " o " > . < / span > < span class = " n " > unit < / span > < span class = " o " > = < / span > < span class = " n " > unit < / span >
< span class = " k " > return < / span > < span class = " n " > func < / span >
< span class = " k " > return < / span > < span class = " n " > decorator_set_unit < / span >
< / code > < / pre > < / div >
< p > The following example calculates the volume of a cylinder based on its radius and height in centimeters : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " kn " > import < / span > < span class = " nn " > math < / span >
< span class = " nd " > @set_unit < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; cm ^ 3 & quot ; < / span > < span class = " p " > ) < / span >
< span class = " k " > def < / span > < span class = " nf " > volume < / span > < span class = " p " > ( < / span > < span class = " n " > radius < / span > < span class = " p " > , < / span > < span class = " n " > height < / span > < span class = " p " > ) : < / span >
< span class = " k " > return < / span > < span class = " n " > math < / span > < span class = " o " > . < / span > < span class = " n " > pi < / span > < span class = " o " > * < / span > < span class = " n " > radius < / span > < span class = " o " > * * < / span > < span class = " mi " > 2 < / span > < span class = " o " > * < / span > < span class = " n " > height < / span >
< / code > < / pre > < / div >
< p > This < code > . unit < / code > function attribute can later be accessed when needed : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > volume < / span > < span class = " p " > ( < / span > < span class = " mi " > 3 < / span > < span class = " p " > , < / span > < span class = " mi " > 5 < / span > < span class = " p " > ) < / span >
< span class = " go " > 141.3716694115407 < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > volume < / span > < span class = " o " > . < / span > < span class = " n " > unit < / span >
< span class = " go " > & #39;cm^3'</span>
< / code > < / pre > < / div >
< p > Note that you could have achieved something similar using < a href = " https://www.python.org/dev/peps/pep-3107/ " > function annotations < / a > : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " kn " > import < / span > < span class = " nn " > math < / span >
< span class = " hll " > < span class = " k " > def < / span > < span class = " nf " > volume < / span > < span class = " p " > ( < / span > < span class = " n " > radius < / span > < span class = " p " > , < / span > < span class = " n " > height < / span > < span class = " p " > ) < / span > < span class = " o " > - & gt ; < / span > < span class = " s2 " > & quot ; cm ^ 3 & quot ; < / span > < span class = " p " > : < / span >
< / span > < span class = " k " > return < / span > < span class = " n " > math < / span > < span class = " o " > . < / span > < span class = " n " > pi < / span > < span class = " o " > * < / span > < span class = " n " > radius < / span > < span class = " o " > * * < / span > < span class = " mi " > 2 < / span > < span class = " o " > * < / span > < span class = " n " > height < / span >
< / code > < / pre > < / div >
< p > However , since annotations are < a href = " https://www.python.org/dev/peps/pep-0484/ " > used for type hints < / a > , it would be hard to combine such units as annotations with < a href = " https://realpython.com/python-type-checking/#static-type-checking " > static type checking < / a > . < / p >
< p > Units become even more powerful and fun when connected with a library that can convert between units . One such library is < a href = " http://pint.readthedocs.io/ " > < code > pint < / code > < / a > . With < code > pint < / code > installed ( < a href = " https://pypi.org/project/Pint/ " > < code > pip install Pint < / code > < / a > ) , you can for instance convert the volume to cubic inches or gallons : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " kn " > import < / span > < span class = " nn " > pint < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > ureg < / span > < span class = " o " > = < / span > < span class = " n " > pint < / span > < span class = " o " > . < / span > < span class = " n " > UnitRegistry < / span > < span class = " p " > ( ) < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > vol < / span > < span class = " o " > = < / span > < span class = " n " > volume < / span > < span class = " p " > ( < / span > < span class = " mi " > 3 < / span > < span class = " p " > , < / span > < span class = " mi " > 5 < / span > < span class = " p " > ) < / span > < span class = " o " > * < / span > < span class = " n " > ureg < / span > < span class = " p " > ( < / span > < span class = " n " > volume < / span > < span class = " o " > . < / span > < span class = " n " > unit < / span > < span class = " p " > ) < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > vol < / span >
< span class = " go " > & lt ; Quantity ( 141.3716694115407 , & #39;centimeter ** 3')></span>
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > vol < / span > < span class = " o " > . < / span > < span class = " n " > to < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; cubic inches & quot ; < / span > < span class = " p " > ) < / span >
< span class = " go " > & lt ; Quantity ( 8.627028576414954 , & #39;inch ** 3')></span>
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > vol < / span > < span class = " o " > . < / span > < span class = " n " > to < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; gallons & quot ; < / span > < span class = " p " > ) < / span > < span class = " o " > . < / span > < span class = " n " > m < / span > < span class = " c1 " > # Magnitude</span>
< span class = " go " > 0.0373464440537444 < / span >
< / code > < / pre > < / div >
< p > You could also modify the decorator to return a < code > pint < / code > < a href = " https://pint.readthedocs.io/en/latest/tutorial.html " > < code > Quantity < / code > < / a > directly . Such a < code > Quantity < / code > is made by multiplying a value with the unit . In < code > pint < / code > , units must be looked up in a < code > UnitRegistry < / code > . The registry is stored as a function attribute to avoid cluttering the namespace : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " k " > def < / span > < span class = " nf " > use_unit < / span > < span class = " p " > ( < / span > < span class = " n " > unit < / span > < span class = " p " > ) : < / span >
< span class = " sd " > & quot ; & quot ; & quot ; Have a function return a Quantity with given unit & quot ; & quot ; & quot ; < / span >
< span class = " n " > use_unit < / span > < span class = " o " > . < / span > < span class = " n " > ureg < / span > < span class = " o " > = < / span > < span class = " n " > pint < / span > < span class = " o " > . < / span > < span class = " n " > UnitRegistry < / span > < span class = " p " > ( ) < / span >
< span class = " k " > def < / span > < span class = " nf " > decorator_use_unit < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) : < / span >
< span class = " nd " > @functools < / span > < span class = " o " > . < / span > < span class = " n " > wraps < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) < / span >
< span class = " k " > def < / span > < span class = " nf " > wrapper_use_unit < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) : < / span >
< span class = " n " > value < / span > < span class = " o " > = < / span > < span class = " n " > func < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) < / span >
< span class = " k " > return < / span > < span class = " n " > value < / span > < span class = " o " > * < / span > < span class = " n " > use_unit < / span > < span class = " o " > . < / span > < span class = " n " > ureg < / span > < span class = " p " > ( < / span > < span class = " n " > unit < / span > < span class = " p " > ) < / span >
< span class = " k " > return < / span > < span class = " n " > wrapper_use_unit < / span >
< span class = " k " > return < / span > < span class = " n " > decorator_use_unit < / span >
< span class = " nd " > @use_unit < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; meters per second & quot ; < / span > < span class = " p " > ) < / span >
< span class = " k " > def < / span > < span class = " nf " > average_speed < / span > < span class = " p " > ( < / span > < span class = " n " > distance < / span > < span class = " p " > , < / span > < span class = " n " > duration < / span > < span class = " p " > ) : < / span >
< span class = " k " > return < / span > < span class = " n " > distance < / span > < span class = " o " > / < / span > < span class = " n " > duration < / span >
< / code > < / pre > < / div >
< p > With the < code > @use_unit < / code > decorator , converting units is practically effortless : < / p >
< div class = " highlight python repl " > < span class = " repl-toggle " title = " Toggle REPL prompts and output " > & gt ; & gt ; & gt ; < / span > < pre > < span > < / span > < code > < span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > bolt < / span > < span class = " o " > = < / span > < span class = " n " > average_speed < / span > < span class = " p " > ( < / span > < span class = " mi " > 100 < / span > < span class = " p " > , < / span > < span class = " mf " > 9.58 < / span > < span class = " p " > ) < / span >
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > bolt < / span >
< span class = " go " > & lt ; Quantity ( 10.438413361169102 , & #39;meter / second')></span>
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > bolt < / span > < span class = " o " > . < / span > < span class = " n " > to < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; km per hour & quot ; < / span > < span class = " p " > ) < / span >
< span class = " go " > & lt ; Quantity ( 37.578288100208766 , & #39;kilometer / hour')></span>
< span class = " gp " > & gt ; & gt ; & gt ; < / span > < span class = " n " > bolt < / span > < span class = " o " > . < / span > < span class = " n " > to < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; mph & quot ; < / span > < span class = " p " > ) < / span > < span class = " o " > . < / span > < span class = " n " > m < / span > < span class = " c1 " > # Magnitude</span>
< span class = " go " > 23.350065679064745 < / span >
< / code > < / pre > < / div >
< / section > < section class = " section3 " id = " validating-json " > < h3 > Validating JSON < a class = " headerlink " href = " #validating-json " title = " Permanent link " > < / a > < / h3 >
< p > Let & rsquo ; s look at one last use case . Take a quick look at the following < a href = " https://realpython.com/tutorials/flask/ " > Flask < / a > route handler : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " nd " > @app < / span > < span class = " o " > . < / span > < span class = " n " > route < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; / grade & quot ; < / span > < span class = " p " > , < / span > < span class = " n " > methods < / span > < span class = " o " > = < / span > < span class = " p " > [ < / span > < span class = " s2 " > & quot ; POST & quot ; < / span > < span class = " p " > ] ) < / span >
< span class = " k " > def < / span > < span class = " nf " > update_grade < / span > < span class = " p " > ( ) : < / span >
< span class = " n " > json_data < / span > < span class = " o " > = < / span > < span class = " n " > request < / span > < span class = " o " > . < / span > < span class = " n " > get_json < / span > < span class = " p " > ( ) < / span >
< span class = " k " > if < / span > < span class = " s2 " > & quot ; student_id & quot ; < / span > < span class = " ow " > not < / span > < span class = " ow " > in < / span > < span class = " n " > json_data < / span > < span class = " p " > : < / span >
< span class = " n " > abort < / span > < span class = " p " > ( < / span > < span class = " mi " > 400 < / span > < span class = " p " > ) < / span >
< span class = " c1 " > # Update database</span>
< span class = " k " > return < / span > < span class = " s2 " > & quot ; success ! & quot ; < / span >
< / code > < / pre > < / div >
< p > Here we ensure that the key < code > student_id < / code > is part of the request . Although this validation works , it really does not belong in the function itself . Plus , perhaps there are other routes that use the exact same validation . So , let & rsquo ; s keep it < a href = " https://en.wikipedia.org/wiki/Don % 27t_repeat_yourself " > DRY < / a > and abstract out any unnecessary logic with a decorator . The following < code > @validate_json < / code > decorator will do the job : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " kn " > from < / span > < span class = " nn " > flask < / span > < span class = " kn " > import < / span > < span class = " n " > Flask < / span > < span class = " p " > , < / span > < span class = " n " > request < / span > < span class = " p " > , < / span > < span class = " n " > abort < / span >
< span class = " kn " > import < / span > < span class = " nn " > functools < / span >
< span class = " n " > app < / span > < span class = " o " > = < / span > < span class = " n " > Flask < / span > < span class = " p " > ( < / span > < span class = " vm " > __name__ < / span > < span class = " p " > ) < / span >
< span class = " k " > def < / span > < span class = " nf " > validate_json < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > expected_args < / span > < span class = " p " > ) : < / span > < span class = " c1 " > # 1</span>
< span class = " k " > def < / span > < span class = " nf " > decorator_validate_json < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) : < / span >
< span class = " nd " > @functools < / span > < span class = " o " > . < / span > < span class = " n " > wraps < / span > < span class = " p " > ( < / span > < span class = " n " > func < / span > < span class = " p " > ) < / span >
< span class = " k " > def < / span > < span class = " nf " > wrapper_validate_json < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) : < / span >
< span class = " n " > json_object < / span > < span class = " o " > = < / span > < span class = " n " > request < / span > < span class = " o " > . < / span > < span class = " n " > get_json < / span > < span class = " p " > ( ) < / span >
< span class = " k " > for < / span > < span class = " n " > expected_arg < / span > < span class = " ow " > in < / span > < span class = " n " > expected_args < / span > < span class = " p " > : < / span > < span class = " c1 " > # 2</span>
< span class = " k " > if < / span > < span class = " n " > expected_arg < / span > < span class = " ow " > not < / span > < span class = " ow " > in < / span > < span class = " n " > json_object < / span > < span class = " p " > : < / span >
< span class = " n " > abort < / span > < span class = " p " > ( < / span > < span class = " mi " > 400 < / span > < span class = " p " > ) < / span >
< span class = " k " > return < / span > < span class = " n " > func < / span > < span class = " p " > ( < / span > < span class = " o " > * < / span > < span class = " n " > args < / span > < span class = " p " > , < / span > < span class = " o " > * * < / span > < span class = " n " > kwargs < / span > < span class = " p " > ) < / span >
< span class = " k " > return < / span > < span class = " n " > wrapper_validate_json < / span >
< span class = " k " > return < / span > < span class = " n " > decorator_validate_json < / span >
< / code > < / pre > < / div >
< p > In the above code , the decorator takes a variable length list as an argument so that we can pass in as many string arguments as necessary , each representing a key used to validate the < a href = " https://realpython.com/python-json/ " > JSON < / a > data : < / p >
< ol >
< li > The list of keys that must be present in the JSON is given as arguments to the decorator . < / li >
< li > The wrapper function validates that each expected key is present in the JSON data . < / li >
< / ol >
< p > The route handler can then focus on its real job & mdash ; updating grades & mdash ; as it can safely assume that JSON data are valid : < / p >
< div class = " highlight python " > < pre > < span > < / span > < code > < span class = " nd " > @app < / span > < span class = " o " > . < / span > < span class = " n " > route < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; / grade & quot ; < / span > < span class = " p " > , < / span > < span class = " n " > methods < / span > < span class = " o " > = < / span > < span class = " p " > [ < / span > < span class = " s2 " > & quot ; POST & quot ; < / span > < span class = " p " > ] ) < / span >
< span class = " nd " > @validate_json < / span > < span class = " p " > ( < / span > < span class = " s2 " > & quot ; student_id & quot ; < / span > < span class = " p " > ) < / span >
< span class = " k " > def < / span > < span class = " nf " > update_grade < / span > < span class = " p " > ( ) : < / span >
< span class = " n " > json_data < / span > < span class = " o " > = < / span > < span class = " n " > request < / span > < span class = " o " > . < / span > < span class = " n " > get_json < / span > < span class = " p " > ( ) < / span >
< span class = " c1 " > # Update database.</span>
< span class = " k " > return < / span > < span class = " s2 " > & quot ; success ! & quot ; < / span >
< / code > < / pre > < / div >
< / section > < / section > < section class = " section2 " id = " conclusion " > < h2 > Conclusion < a class = " headerlink " href = " #conclusion " title = " Permanent link " > < / a > < / h2 >
< p > This has been quite a journey ! You started this tutorial by looking a little closer at functions , particularly how they can be defined inside other functions and passed around just like any other Python object . Then you learned about decorators and how to write them such that : < / p >
< ul >
< li > They can be reused . < / li >
< li > They can decorate functions with arguments and return values . < / li >
< li > They can use < code > @functools.wraps < / code > to look more like the decorated function . < / li >
< / ul >
< p > In the second part of the tutorial , you saw more advanced decorators and learned how to : < / p >
< ul >
< li > Decorate classes < / li >
< li > Nest decorators < / li >
< li > Add arguments to decorators < / li >
< li > Keep state within decorators < / li >
< li > Use classes as decorators < / li >
< / ul >
< p > You saw that , to define a decorator , you typically define a function returning a wrapper function . The wrapper function uses < code > * args < / code > and < code > * * kwargs < / code > to pass on arguments to the decorated function . If you want your decorator to also take arguments , you need to nest the wrapper function inside another function . In this case , you usually end up with three < code > return < / code > statements . < / p >
< p > You can find the < a href = " https://github.com/realpython/materials/tree/master/primer-on-python-decorators " > code from this tutorial online < / a > . < / p >
< / section > < section class = " section2 " id = " further-reading " > < h2 > Further Reading < a class = " headerlink " href = " #further-reading " title = " Permanent link " > < / a > < / h2 >
< p > If you are still looking for more , our book < a href = " https://realpython.com/products/python-tricks-book/ " > Python Tricks < / a > has a section on decorators , as does the < a href = " https://realpython.com/asins/1449340377/ " > Python Cookbook < / a > by David Beazley and Brian K . Jones . < / p >
< p > For a deep dive into the historical discussion on how decorators should be implemented in Python , see < a href = " https://www.python.org/dev/peps/pep-0318/ " > PEP 318 < / a > as well as the < a href = " https://wiki.python.org/moin/PythonDecorators " > Python Decorator Wiki < / a > . More examples of decorators can be found in the < a href = " https://wiki.python.org/moin/PythonDecoratorLibrary " > Python Decorator Library < / a > . The < a href = " https://github.com/micheles/decorator " > < code > decorator < / code > module < / a > can simplify creating your own decorators , and its < a href = " https://decorator.readthedocs.io " > documentation < / a > contains further decorator examples . < / p >
< p > Also , we & rsquo ; ve put together a short & amp ; sweet Python decorators cheat sheet for you : < / p >
< div class = " alert alert-warning " role = " alert " >
< p > < strong markdown = " 1 " > Decorators Cheat Sheet : < / strong > < a href = " https://realpython.com/bonus/decorators-cheatsheet/ " class = " alert-link " data - toggle = " modal " data - target = " #modal-decorators-cheatsheet " data - focus = " false " markdown = " 1 " > Click here to get access to a free 3 - page Python decorators cheat sheet < / a > that summarizes the techniques explained in this tutorial . < / p >
< / div >
< / section >
< div class = " border rounded p-3 card mb-2 " >
< p class = " mb-0 " > < span class = " badge badge-pill badge-success " > < i class = " fa fa-play-circle " aria - hidden = " true " > < / i > Watch Now < / span > This tutorial has a related video course created by the Real Python team . Watch it together with the written tutorial to deepen your understanding : < a class = " stretched-link text-success " href = " /courses/python-decorators-101/ " > < strong > Python Decorators 101 < / strong > < / a > < / p >
< / div >
< / div >
< div class = " card mt-4 mb-4 bg-secondary " >
< p class = " card-header h3 text-center bg-light " > 🐍 Python Tricks 💌 < / p >
< div class = " card-body " >
< div class = " container " >
< div class = " row " >
< div class = " col-xs-12 col-sm-7 " >
< p > Get a short & amp ; sweet < strong > Python Trick < / strong > delivered to your inbox every couple of days . No spam ever . Unsubscribe any time . Curated by the Real Python team . < / p >
< / div >
< div class = " col-xs-12 col-sm-5 " >
< img class = " img-fluid rounded mb-3 " src = " /static/pytrick-dict-merge.4201a0125a5e.png " width = " 738 " height = " 490 " alt = " Python Tricks Dictionary Merge " >
< / div >
< / div >
< div class = " row mb-3 " >
< form class = " col-12 " action = " /optins/process/ " method = " post " >
< input type = " hidden " name = " csrfmiddlewaretoken " value = " APHcibHwrBuqt3ULEi1fpqoAksKrzZcjzkcOoObHwIKXPQcmqbJWzjIZjTh8JogZ " >
< input type = " hidden " name = " slug " value = " static-python-tricks-footer " >
< div class = " form-group " >
< input name = " email " type = " email " class = " form-control form-control-lg " placeholder = " Email Address " required >
< / div >
< button name = " submit " type = " submit " class = " btn btn-primary btn-lg btn-block " > Send Me Python Tricks » < / button >
< / form >
< / div >
< / div >
< / div >
< / div >
< div class = " card mt-3 " id = " author " >
< p class = " card-header h3 " > About < strong > Geir Arne Hjelle < / strong > < / p >
< div class = " card-body " >
< div class = " container p-0 " >
< div class = " row " >
< div class = " col-12 col-md-3 align-self-center " >
< a href = " /team/gahjelle/ " > < img loading = " lazy " src = " https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/gahjelle.470149ee709e.jpg&w=800&h=800&mode=crop&sig=e9b761c6cf1359953014dba05554f5424eb116e1 " srcset = " https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/gahjelle.470149ee709e.jpg&w=200&h=200&mode=crop&sig=c6390201e73d3e09429d73da5bb29c17ab10403a 200w, https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/gahjelle.470149ee709e.jpg&w=400&h=400&mode=crop&sig=fcea459ee24a7b320573cadee324cf75509dc1d6 400w, https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/gahjelle.470149ee709e.jpg&w=800&h=800&mode=crop&sig=e9b761c6cf1359953014dba05554f5424eb116e1 800w " sizes = " 25vw " width = " 800 " height = " 800 " class = " d-block d-md-none rounded-circle img-fluid w-33 mb-0 mx-auto " alt = " Geir Arne Hjelle " > < / a >
< a href = " /team/gahjelle/ " > < img loading = " lazy " src = " https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/gahjelle.470149ee709e.jpg&w=800&h=800&mode=crop&sig=e9b761c6cf1359953014dba05554f5424eb116e1 " srcset = " https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/gahjelle.470149ee709e.jpg&w=200&h=200&mode=crop&sig=c6390201e73d3e09429d73da5bb29c17ab10403a 200w, https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/gahjelle.470149ee709e.jpg&w=400&h=400&mode=crop&sig=fcea459ee24a7b320573cadee324cf75509dc1d6 400w, https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/gahjelle.470149ee709e.jpg&w=800&h=800&mode=crop&sig=e9b761c6cf1359953014dba05554f5424eb116e1 800w " sizes = " 25vw " width = " 800 " height = " 800 " class = " d-none d-md-block rounded-circle img-fluid w-100 mb-0 " alt = " Geir Arne Hjelle " > < / a >
< / div >
< div class = " col mt-3 " >
< p > Geir Arne is an avid Pythonista and a member of the Real Python tutorial team . < / p >
< a href = " /team/gahjelle/ " class = " card-link " > » More about Geir Arne < / a >
< / div >
< / div >
< / div >
< / div >
< hr class = " my-0 " >
< div class = " card-body pb-0 " >
< div class = " container " >
< div class = " row " >
< p > < em > Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards . The team members who worked on this tutorial are : < / em > < / p >
< / div >
< div class = " row align-items-center w-100 mx-auto " >
< div class = " col-4 col-sm-2 align-self-center " >
< a href = " /team/asantos/ " > < img loading = " lazy " src = " https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/asantos-avatar.888c78fffab3.jpg&w=700&h=700&mode=crop&sig=be17609cd7f6a4cd249ff61e186b3ad1aece949c " srcset = " https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/asantos-avatar.888c78fffab3.jpg&w=175&h=175&mode=crop&sig=30253487c673fd67bb45ea6bef95b3c57e115e46 175w, https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/asantos-avatar.888c78fffab3.jpg&w=350&h=350&mode=crop&sig=a43bd9ea30a6f61a4fc2451ab9abfc6faec8a2c0 350w, https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/asantos-avatar.888c78fffab3.jpg&w=700&h=700&mode=crop&sig=be17609cd7f6a4cd249ff61e186b3ad1aece949c 700w " sizes = " 10vw " width = " 800 " height = " 800 " class = " rounded-circle img-fluid w-100 " alt = " Aldren Santos " > < / a >
< / div >
< div class = " col pl-0 d-none d-sm-block " >
< a href = " /team/asantos/ " class = " card-link small " > < p > Aldren < / p > < / a >
< / div >
< div class = " col-4 col-sm-2 align-self-center " >
< a href = " /team/bsolomon/ " > < img loading = " lazy " src = " https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/bsolomon.2a5a05833ac0.jpg&w=938&h=938&mode=crop&sig=a9beb357e2cfca40c0fd24da0d7543253f7fb10e " srcset = " https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/bsolomon.2a5a05833ac0.jpg&w=234&h=234&mode=crop&sig=745b89b17845841a5c4d667e619518a6aa22d0a4 234w, https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/bsolomon.2a5a05833ac0.jpg&w=469&h=469&mode=crop&sig=af2598277ff415557b3331061f0d41462d29a48f 469w, https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/bsolomon.2a5a05833ac0.jpg&w=938&h=938&mode=crop&sig=a9beb357e2cfca40c0fd24da0d7543253f7fb10e 938w " sizes = " 10vw " width = " 800 " height = " 800 " class = " rounded-circle img-fluid w-100 " alt = " Brad Solomon " > < / a >
< / div >
< div class = " col pl-0 d-none d-sm-block " >
< a href = " /team/bsolomon/ " class = " card-link small " > < p > Brad < / p > < / a >
< / div >
< div class = " col-4 col-sm-2 align-self-center " >
< a href = " /team/dbader/ " > < img loading = " lazy " src = " https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/daniel-square.d58bf4388750.jpg&w=1000&h=1000&mode=crop&sig=304f5f568993310d5e87b2ca3c504260c018effa " srcset = " https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/daniel-square.d58bf4388750.jpg&w=250&h=250&mode=crop&sig=981634b4528584f2e9c7ee663477173599e1781a 250w, https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/daniel-square.d58bf4388750.jpg&w=500&h=500&mode=crop&sig=841025b97d25f05c1b90802032e477020462fe01 500w, https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/daniel-square.d58bf4388750.jpg&w=1000&h=1000&mode=crop&sig=304f5f568993310d5e87b2ca3c504260c018effa 1000w " sizes = " 10vw " width = " 800 " height = " 800 " class = " rounded-circle img-fluid w-100 " alt = " Dan Bader " > < / a >
< / div >
< div class = " col pl-0 d-none d-sm-block " >
< a href = " /team/dbader/ " class = " card-link small " > < p > Dan < / p > < / a >
< / div >
< / div >
< div class = " row align-items-center w-100 mx-auto " >
< div class = " col-4 col-sm-2 align-self-center " >
< a href = " /team/jjablonski/ " > < img loading = " lazy " src = " https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/jjablonksi-avatar.e37c4f83308e.jpg&w=800&h=800&mode=crop&sig=c363b704eeccb35f2247db13baff3d4383459858 " srcset = " https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/jjablonksi-avatar.e37c4f83308e.jpg&w=200&h=200&mode=crop&sig=706b16de3cb88a8f353f4a98d7c7bc7234229bd0 200w, https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/jjablonksi-avatar.e37c4f83308e.jpg&w=400&h=400&mode=crop&sig=6d7aa672ca3f1ac5f7cd62ed1641b60f98d04d8b 400w, https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/jjablonksi-avatar.e37c4f83308e.jpg&w=800&h=800&mode=crop&sig=c363b704eeccb35f2247db13baff3d4383459858 800w " sizes = " 10vw " width = " 800 " height = " 800 " class = " rounded-circle img-fluid w-100 " alt = " Joanna Jablonski " > < / a >
< / div >
< div class = " col pl-0 d-none d-sm-block " >
< a href = " /team/jjablonski/ " class = " card-link small " > < p > Joanna < / p > < / a >
< / div >
< div class = " col-4 col-sm-2 align-self-center " >
< a href = " /team/mherman/ " > < img loading = " lazy " src = " https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/mike.fa94729a1e81.jpg&w=160&h=160&mode=crop&sig=edbc2adbb770d6a7dbb53b24a4cb3c0924927a9c " srcset = " https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/mike.fa94729a1e81.jpg&w=40&h=40&mode=crop&sig=d205b98ae5f7d6680f3f2f3a1cd74a15eeee9f17 40w, https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/mike.fa94729a1e81.jpg&w=80&h=80&mode=crop&sig=8191692eba0f009f0beb44a7bd789533c0a5c213 80w, https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/mike.fa94729a1e81.jpg&w=160&h=160&mode=crop&sig=edbc2adbb770d6a7dbb53b24a4cb3c0924927a9c 160w " sizes = " 10vw " width = " 800 " height = " 800 " class = " rounded-circle img-fluid w-100 " alt = " Michael Herman " > < / a >
< / div >
< div class = " col pl-0 d-none d-sm-block " >
< a href = " /team/mherman/ " class = " card-link small " > < p > Michael < / p > < / a >
< / div >
< div class = " col-4 col-sm-2 align-self-center " > < / div >
< div class = " col pl-0 d-none d-sm-block " > < / div >
< / div >
< / div >
< / div >
< / div >
< div class = " bg-light rounded py-4 my-4 shadow shadow-sm mx-n2 " >
< div class = " col-12 text-center d-block d-md-none " >
< p class = " h2 mb-3 " > Master < u > < span class = " marker-highlight " > Real - World Python Skills < / mark > < / u > With Unlimited Access to Real & nbsp ; Python < / p >
< p class = " mb-1 " > < img class = " w-75 " src = " /static/videos/lesson-locked.f5105cfd26db.svg " > < / p >
< p class = " mx-auto w-75 mb-3 small " > < strong > Join us and get access to hundreds of tutorials , hands - on video courses , and a community of expert & nbsp ; Pythonistas : < / strong > < / p >
< p class = " mb-0 " > < a href = " /account/join/?utm_source=rp_article_footer&utm_content=primer-on-python-decorators " class = " btn btn-primary btn-sm px-4 mb-0 " > Level Up Your Python Skills » < / a >
< / div >
< div class = " col-12 text-center d-none d-md-block " >
< p class = " h2 mb-2 " > Master < u > < span class = " marker-highlight " > Real - World Python Skills < / span > < / u > < br > With Unlimited Access to Real & nbsp ; Python < / p >
< p class = " mb-2 " > < img class = " w-50 mb-2 " src = " /static/videos/lesson-locked.f5105cfd26db.svg " > < / p >
< p class = " mx-auto w-50 mb-3 " > < strong > Join us and get access to hundreds of tutorials , hands - on video courses , and a community of expert Pythonistas : < / strong > < / p >
< p > < a href = " /account/join/?utm_source=rp_article_footer&utm_content=primer-on-python-decorators " class = " btn btn-primary btn-lg px-4 " > Level Up Your Python Skills » < / a >
< / div >
< / div >
< div class = " card mt-4 " id = " reader-comments " >
< p class = " card-header h3 " > What Do You Think ? < / p >
< div class = " text-center mt-3 mb-0 p-0 " >
< span >
< a target = " _blank " rel = " nofollow " href = " https://twitter.com/intent/tweet/?text=Check out this % 23Python tutorial: Primer %20o n % 20Python % 20Decorators by @realpython&url=https % 3A//realpython.com/primer-on-python-decorators/ " class = " mr-1 badge badge-twitter text-light mb-1 " > < i class = " mr-1 fa fa-twitter text-light " > < / i > Tweet < / a >
< a target = " _blank " rel = " nofollow " href = " https://facebook.com/sharer/sharer.php?u=https % 3A//realpython.com/primer-on-python-decorators/ " class = " mr-1 badge badge-facebook text-light mb-1 " > < i class = " mr-1 fa fa-facebook text-light " > < / i > Share < / a >
< a target = " _blank " rel = " nofollow " href = " mailto:?subject=Python article for you&body=Check out this Python tutorial: % 0A % 0APrimer %20o n % 20Python % 20Decorators % 0A % 0Ahttps % 3A//realpython.com/primer-on-python-decorators/ " class = " mr-1 badge badge-red text-light mb-1 " > < i class = " mr-1 fa fa-envelope text-light " > < / i > Email < / a >
< / span >
< / div >
< div class = " card-body " >
< div class = " alert alert-dark " >
< p class = " mb-0 " > < strong > Real Python Comment Policy : < / strong > The most useful comments are those written with the goal of learning from or helping out other readers — after reading the whole article and all the earlier comments . Complaints and insults generally won ’ t make the cut here . < / p >
< / div >
< p > What ’ s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.</p>
< div class = " mb-4 " id = " disqus_thread " >
< / div >
< / div >
< / div >
< div class = " card mt-4 mb-4 " >
< p class = " card-header h3 " > Keep Learning < / p >
< div class = " card-body " >
< p class = " mb-0 " > Related Tutorial Categories :
< a href = " /tutorials/intermediate/ " class = " badge badge-light text-muted " > intermediate < / a >
< a href = " /tutorials/python/ " class = " badge badge-light text-muted " > python < / a >
< / p >
< p class = " mt-3 mb-0 " > Recommended Video Course : < a class = " text-success " href = " /courses/python-decorators-101/ " > Python Decorators 101 < / a > < / p >
< / div >
< / div >
< div class = " modal fade " tabindex = " -1 " role = " dialog " id = " rpvc " >
< div class = " modal-dialog modal-lg modal-dialog-centered " role = " document " >
< div class = " modal-content " >
< div class = " modal-header border-0 mt-3 " >
< div class = " col-12 modal-title text-center " >
< h2 class = " my-0 mx-5 " > Master < u > Real - World Python Skills < / u > With Unlimited Access to Real Python < / h2 >
< p class = " text-center text-muted mt-2 mb-1 " > Already a member ? < a href = " /account/login/ " > Sign - In < / a > < / p >
< / div >
< / div >
< div class = " modal-body bg-light " >
< div class = " col-12 text-center " >
< p class = " mb-2 mt-3 " > < a href = " /account/join/?utm_source=rp&utm_medium=web&utm_campaign=pwn&utm_content=v2 " > < img class = " w-50 mb-2 " src = " /static/videos/lesson-locked.f5105cfd26db.svg " > < / a > < / p >
< p class = " mx-auto w-66 mb-3 " > < strong > Join us and get access to hundreds of tutorials , hands - on video courses , and a community of expert Pythonistas : < / strong > < / p >
< p > < a href = " /account/join/?utm_source=rp&utm_medium=web&utm_campaign=pwn&utm_content=v2 " class = " btn btn-primary btn-lg px-4 " > < i class = " fa fa-unlock mr-2 " > < / i > Unlock All Content » < / a > < / a >
< / div >
< / div >
< div class = " modal-footer border-0 " >
< button class = " text-muted btn " data - dismiss = " modal " > Close < / button >
< / div >
< / div >
< / div >
< / div >
< / div >
< aside class = " col-md-7 col-lg-4 " >
< div class = " card mb-3 bg-secondary " >
< form class = " card-body " action = " /optins/process/ " method = " post " >
< div class = " form-group " >
< p class = " h5 text-muted text-center " > — FREE Email Series — < / p >
< p class = " h3 text-center " > 🐍 Python Tricks 💌 < / p >
< p > < img class = " img-fluid rounded " src = " /static/pytrick-dict-merge.4201a0125a5e.png " width = " 738 " height = " 490 " alt = " Python Tricks Dictionary Merge " > < / p >
< / div >
< div class = " form-group " >
< input type = " hidden " name = " csrfmiddlewaretoken " value = " APHcibHwrBuqt3ULEi1fpqoAksKrzZcjzkcOoObHwIKXPQcmqbJWzjIZjTh8JogZ " >
< input type = " hidden " name = " slug " value = " static-python-tricks-sidebar " >
< input type = " email " class = " form-control form-control-md " name = " email " placeholder = " Email… " required >
< / div >
< button type = " submit " name = " submit " class = " btn btn-primary btn-md btn-block " > Get Python Tricks » < / button >
< p class = " mb-0 mt-2 text-muted text-center " > 🔒 No spam . Unsubscribe any time . < / p >
< / form >
< / div >
< div class = " sidebar-module sidebar-module-inset border " >
< p class = " h4 " > < a class = " link-unstyled " href = " /tutorials/all/ " > All Tutorial Topics < / a > < / p >
< a href = " /tutorials/advanced/ " class = " badge badge-light text-muted " > advanced < / a >
< a href = " /tutorials/api/ " class = " badge badge-light text-muted " > api < / a >
< a href = " /tutorials/basics/ " class = " badge badge-light text-muted " > basics < / a >
< a href = " /tutorials/best-practices/ " class = " badge badge-light text-muted " > best - practices < / a >
< a href = " /tutorials/community/ " class = " badge badge-light text-muted " > community < / a >
< a href = " /tutorials/databases/ " class = " badge badge-light text-muted " > databases < / a >
< a href = " /tutorials/data-science/ " class = " badge badge-light text-muted " > data - science < / a >
< a href = " /tutorials/devops/ " class = " badge badge-light text-muted " > devops < / a >
< a href = " /tutorials/django/ " class = " badge badge-light text-muted " > django < / a >
< a href = " /tutorials/docker/ " class = " badge badge-light text-muted " > docker < / a >
< a href = " /tutorials/flask/ " class = " badge badge-light text-muted " > flask < / a >
< a href = " /tutorials/front-end/ " class = " badge badge-light text-muted " > front - end < / a >
< a href = " /tutorials/gui/ " class = " badge badge-light text-muted " > gui < / a >
< a href = " /tutorials/intermediate/ " class = " badge badge-light text-muted " > intermediate < / a >
< a href = " /tutorials/machine-learning/ " class = " badge badge-light text-muted " > machine - learning < / a >
< a href = " /tutorials/projects/ " class = " badge badge-light text-muted " > projects < / a >
< a href = " /tutorials/python/ " class = " badge badge-light text-muted " > python < / a >
< a href = " /tutorials/testing/ " class = " badge badge-light text-muted " > testing < / a >
< a href = " /tutorials/tools/ " class = " badge badge-light text-muted " > tools < / a >
< a href = " /tutorials/web-dev/ " class = " badge badge-light text-muted " > web - dev < / a >
< a href = " /tutorials/web-scraping/ " class = " badge badge-light text-muted " > web - scraping < / a >
< / div >
< div class = " sidebar-module sidebar-module-inset p-0 " style = " overflow:hidden; " >
< div style = " display:block;position:relative; " >
< div style = " display:block;width:100 % ;padding-top:100 % ; " > < / div >
< div class = " rpad " data - unit = " 1x1 " style = " position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden; " > < / div >
< / div >
< / div >
< div class = " sidebar-sticky sidebar-nav-message " >
< div class = " bg-light sidebar-module sidebar-module-inset " style = " max-height: 80vh; overflow-y: scroll; " id = " sidebar-toc " >
< p class = " h4 text-muted " > < a class = " link-unstyled " href = " #toc " > Table of Contents < / a > < / p >
< div class = " toc " >
< ul >
< li > < a href = " #functions " > Functions < / a > < ul >
< li > < a href = " #first-class-objects " > First - Class Objects < / a > < / li >
< li > < a href = " #inner-functions " > Inner Functions < / a > < / li >
< li > < a href = " #returning-functions-from-functions " > Returning Functions From Functions < / a > < / li >
< / ul >
< / li >
< li > < a href = " #simple-decorators " > Simple Decorators < / a > < ul >
< li > < a href = " #syntactic-sugar " > Syntactic Sugar ! < / a > < / li >
< li > < a href = " #reusing-decorators " > Reusing Decorators < / a > < / li >
< li > < a href = " #decorating-functions-with-arguments " > Decorating Functions With Arguments < / a > < / li >
< li > < a href = " #returning-values-from-decorated-functions " > Returning Values From Decorated Functions < / a > < / li >
< li > < a href = " #who-are-you-really " > Who Are You , Really ? < / a > < / li >
< / ul >
< / li >
< li > < a href = " #a-few-real-world-examples " > A Few Real World Examples < / a > < ul >
< li > < a href = " #timing-functions " > Timing Functions < / a > < / li >
< li > < a href = " #debugging-code " > Debugging Code < / a > < / li >
< li > < a href = " #slowing-down-code " > Slowing Down Code < / a > < / li >
< li > < a href = " #registering-plugins " > Registering Plugins < / a > < / li >
< li > < a href = " #is-the-user-logged-in " > Is the User Logged In ? < / a > < / li >
< / ul >
< / li >
< li > < a href = " #fancy-decorators " > Fancy Decorators < / a > < ul >
< li > < a href = " #decorating-classes " > Decorating Classes < / a > < / li >
< li > < a href = " #nesting-decorators " > Nesting Decorators < / a > < / li >
< li > < a href = " #decorators-with-arguments " > Decorators With Arguments < / a > < / li >
< li > < a href = " #both-please-but-never-mind-the-bread " > Both Please , But Never Mind the Bread < / a > < / li >
< li > < a href = " #stateful-decorators " > Stateful Decorators < / a > < / li >
< li > < a href = " #classes-as-decorators " > Classes as Decorators < / a > < / li >
< / ul >
< / li >
< li > < a href = " #more-real-world-examples " > More Real World Examples < / a > < ul >
< li > < a href = " #slowing-down-code-revisited " > Slowing Down Code , Revisited < / a > < / li >
< li > < a href = " #creating-singletons " > Creating Singletons < / a > < / li >
< li > < a href = " #caching-return-values " > Caching Return Values < / a > < / li >
< li > < a href = " #adding-information-about-units " > Adding Information About Units < / a > < / li >
< li > < a href = " #validating-json " > Validating JSON < / a > < / li >
< / ul >
< / li >
< li > < a href = " #conclusion " > Conclusion < / a > < / li >
< li > < a href = " #further-reading " > Further Reading < / a > < / li >
< / ul >
< / div >
< / div >
< div class = " sidebar-module sidebar-module-inset text-center my-3 py-0 " >
< span >
< a target = " _blank " rel = " nofollow " href = " https://twitter.com/intent/tweet/?text=Check out this % 23Python tutorial: Primer %20o n % 20Python % 20Decorators by @realpython&url=https % 3A//realpython.com/primer-on-python-decorators/ " class = " mr-1 badge badge-twitter text-light mb-1 " > < i class = " mr-1 fa fa-twitter text-light " > < / i > Tweet < / a >
< a target = " _blank " rel = " nofollow " href = " https://facebook.com/sharer/sharer.php?u=https % 3A//realpython.com/primer-on-python-decorators/ " class = " mr-1 badge badge-facebook text-light mb-1 " > < i class = " mr-1 fa fa-facebook text-light " > < / i > Share < / a >
< a target = " _blank " rel = " nofollow " href = " mailto:?subject=Python article for you&body=Check out this Python tutorial: % 0A % 0APrimer %20o n % 20Python % 20Decorators % 0A % 0Ahttps % 3A//realpython.com/primer-on-python-decorators/ " class = " mr-1 badge badge-red text-light mb-1 " > < i class = " mr-1 fa fa-envelope text-light " > < / i > Email < / a >
< / span >
< / div >
< div class = " sidebar-module sidebar-module-inset border card " >
< p > < span class = " badge badge-pill badge-success " > < i class = " fa fa-play-circle mr-1 " aria - hidden = " true " > < / i > Recommended Video Course < / span > < br > < a class = " stretched-link text-success " href = " /courses/python-decorators-101/ " > Python Decorators 101 < / a > < / p >
< / div >
< div class = " sidebar-module sidebar-module-inset p-0 " style = " overflow:hidden; " >
< div style = " display:block;position:relative; " >
< div style = " display:block;width:100 % ;padding-top:25 % ; " > < / div >
< div class = " rpad " data - unit = " 4x1 " style = " position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden; " > < / div >
< / div >
< / div >
< / div >
< / aside >
< / div >
< / div >
< div class = " modal fade " id = " modal-decorators-cheatsheet " tabindex = " -1 " role = " dialog " aria - hidden = " true " >
< div class = " modal-dialog modal-dialog-centered modal-lg " role = " document " >
< div class = " modal-content " >
< div class = " modal-header bg-light pt-3 pb-2 " >
< div class = " container-fluid " >
< div class = " row " >
< div class = " col-12 " >
< div class = " progress " style = " height: .5rem; " >
< div class = " progress-bar progress-bar-striped progress-bar-animated w-50 " role = " progressbar " > < / div >
< / div >
< / div >
< div class = " col-12 " >
< p class = " text-muted text-center mb-0 mt-2 " > Almost there ! Complete this form and click the button below to gain instant access : < / p >
< / div >
< / div >
< / div >
< button type = " button " class = " close " data - dismiss = " modal " aria - label = " Close " >
< span aria - hidden = " true " > & times ; < / span >
< / button >
< / div >
< div class = " modal-body m-4 " >
< div class = " container-fluid " >
< div class = " row align-items-center " >
< div class = " col-12 col-lg-4 mb-4 " >
< img class = " img-fluid rounded " src = " https://files.realpython.com/media/Screenshot_2018-08-22_08.53.54.c0da91f23a6a.png " width = " 762 " height = " 592 " srcset = " https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/Screenshot_2018-08-22_08.53.54.c0da91f23a6a.png&w=190&sig=fd11e4309a0a7d8a9ad825bc79de6bda4569c888 190w, https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/Screenshot_2018-08-22_08.53.54.c0da91f23a6a.png&w=381&sig=8a76902dfe13cd83dc30e1aefe5213bc139bbecd 381w, https://files.realpython.com/media/Screenshot_2018-08-22_08.53.54.c0da91f23a6a.png 762w " sizes = " 50vw " alt = " Python Decorators Cheatsheet " >
< / div >
< div class = " col " >
< p class = " text-center h3 mb-4 " > Python Decorators Cheat Sheet ( PDF ) < / p >
< form class = " col-12 " action = " /optins/process/ " method = " post " >
< input type = " hidden " name = " csrfmiddlewaretoken " value = " APHcibHwrBuqt3ULEi1fpqoAksKrzZcjzkcOoObHwIKXPQcmqbJWzjIZjTh8JogZ " >
< input type = " hidden " name = " slug " value = " decorators-cheatsheet " >
< div class = " form-group " >
< input type = " email " name = " email " class = " form-control " placeholder = " Email Address " required autofocus >
< / div >
< button name = " submit " type = " submit " class = " btn btn-primary btn-block text-wrap " > Send Cheat Sheet » < / button >
< / form >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< div class = " modal fade " id = " modal-power-of-decorators-fixed " tabindex = " -1 " role = " dialog " aria - hidden = " true " >
< div class = " modal-dialog modal-dialog-centered modal-lg " role = " document " >
< div class = " modal-content " >
< div class = " modal-header bg-light pt-3 pb-2 " >
< div class = " container-fluid " >
< div class = " row " >
< div class = " col-12 " >
< div class = " progress " style = " height: .5rem; " >
< div class = " progress-bar progress-bar-striped progress-bar-animated w-50 " role = " progressbar " > < / div >
< / div >
< / div >
< div class = " col-12 " >
< p class = " text-muted text-center mb-0 mt-2 " > Almost there ! Complete this form and click the button below to gain instant access : < / p >
< / div >
< / div >
< / div >
< button type = " button " class = " close " data - dismiss = " modal " aria - label = " Close " >
< span aria - hidden = " true " > & times ; < / span >
< / button >
< / div >
< div class = " modal-body m-4 " >
< div class = " container-fluid " >
< div class = " row align-items-center " >
< div class = " col-12 col-lg-4 mb-4 " >
< img class = " img-fluid rounded " src = " https://files.realpython.com/media/python-logo.8eb72ea6927b.png " width = " 1000 " height = " 1000 " srcset = " https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/python-logo.8eb72ea6927b.png&w=250&sig=3922247197a4a17add6954dd288123a4df30e55a 250w, https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/python-logo.8eb72ea6927b.png&w=500&sig=40c3b1b474ae94aec528188714ba5878a77362ea 500w, https://files.realpython.com/media/python-logo.8eb72ea6927b.png 1000w " sizes = " 50vw " alt = " Python Logo " >
< / div >
< div class = " col " >
< p class = " text-center h3 mb-4 " > The Power of Python Decorators : Advanced Patterns & amp ; Techniques ( PDF Guide ) < / p >
< form class = " col-12 " action = " /optins/process/ " method = " post " >
< input type = " hidden " name = " csrfmiddlewaretoken " value = " APHcibHwrBuqt3ULEi1fpqoAksKrzZcjzkcOoObHwIKXPQcmqbJWzjIZjTh8JogZ " >
< input type = " hidden " name = " slug " value = " power-of-decorators-fixed " >
< div class = " form-group " >
< input type = " email " name = " email " class = " form-control " placeholder = " Email Address " required autofocus >
< / div >
< button name = " submit " type = " submit " class = " btn btn-primary btn-block text-wrap " > Get the Python Decorators Guide » < / button >
< / form >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< div class = " modal fade " id = " modal-decorators-qa-2019 " tabindex = " -1 " role = " dialog " aria - hidden = " true " >
< div class = " modal-dialog modal-dialog-centered modal-lg " role = " document " >
< div class = " modal-content " >
< div class = " modal-header bg-light pt-3 pb-2 " >
< div class = " container-fluid " >
< div class = " row " >
< div class = " col-12 " >
< div class = " progress " style = " height: .5rem; " >
< div class = " progress-bar progress-bar-striped progress-bar-animated w-50 " role = " progressbar " > < / div >
< / div >
< / div >
< div class = " col-12 " >
< p class = " text-muted text-center mb-0 mt-2 " > Almost there ! Complete this form and click the button below to gain instant access : < / p >
< / div >
< / div >
< / div >
< button type = " button " class = " close " data - dismiss = " modal " aria - label = " Close " >
< span aria - hidden = " true " > & times ; < / span >
< / button >
< / div >
< div class = " modal-body m-4 " >
< div class = " container-fluid " >
< div class = " row align-items-center " >
< div class = " col-12 col-lg-4 mb-4 " >
< img class = " img-fluid rounded " src = " https://files.realpython.com/media/Primer-on-Python-Decorators_Watermarked.d0da542fa3fc.jpg " width = " 1920 " height = " 1080 " srcset = " https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/Primer-on-Python-Decorators_Watermarked.d0da542fa3fc.jpg&w=480&sig=45483afc98ba9e9ed26f1672d17d426e011761df 480w, https://robocrop.realpython.net/?url=https % 3A//files.realpython.com/media/Primer-on-Python-Decorators_Watermarked.d0da542fa3fc.jpg&w=960&sig=5b4082dbad29a1c1f1fdbe7b5de93af202663c6a 960w, https://files.realpython.com/media/Primer-on-Python-Decorators_Watermarked.d0da542fa3fc.jpg 1920w " sizes = " 50vw " alt = " Python Decorators " >
< / div >
< div class = " col " >
< p class = " text-center h3 mb-4 " > Python Decorators Q & amp ; A Transcript ( PDF ) < / p >
< form class = " col-12 " action = " /optins/process/ " method = " post " >
< input type = " hidden " name = " csrfmiddlewaretoken " value = " APHcibHwrBuqt3ULEi1fpqoAksKrzZcjzkcOoObHwIKXPQcmqbJWzjIZjTh8JogZ " >
< input type = " hidden " name = " slug " value = " decorators-qa-2019 " >
< div class = " form-group " >
< input type = " email " name = " email " class = " form-control " placeholder = " Email Address " required autofocus >
< / div >
< button name = " submit " type = " submit " class = " btn btn-primary btn-block text-wrap " > Send My PDF » < / button >
< / form >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< footer class = " footer " >
< div class = " container " >
< div class = " w-75 mx-auto my-0 " >
< div style = " display:block;position:relative; " >
< div style = " display:block;width:100 % ;padding-top:12.5 % ; " > < / div >
< div class = " rpad " data - unit = " 8x1 " style = " position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden; " > < / div >
< / div >
< / div >
< p class = " text-center text-muted w-75 mx-auto " > © 2012 – 2020 Real Python & nbsp ; ⋅ < a href = " /newsletter/ " > Newsletter < / a > & nbsp ; ⋅ < a href = " /podcasts/rpp/ " > Podcast < / a > & nbsp ; ⋅ < a href = " https://www.youtube.com/realpython " > YouTube < / a > & nbsp ; ⋅ < a href = " https://twitter.com/realpython " > Twitter < / a > & nbsp ; ⋅ < a href = " https://facebook.com/LearnRealPython " > Facebook < / a > & nbsp ; ⋅ < a href = " https://www.instagram.com/realpython/ " > Instagram < / a > & nbsp ; ⋅ < a href = " / " > Python & nbsp ; Tutorials < / a > & nbsp ; ⋅ < a href = " /search " > Search < / a > & nbsp ; ⋅ < a href = " /privacy-policy/ " > Privacy Policy < / a > & nbsp ; ⋅ < a href = " /energy-policy/ " class = " text-success active " > Energy Policy < / a > & nbsp ; ⋅ < a href = " /sponsorships/ " > Advertise < / a > & nbsp ; ⋅ < a href = " /contact/ " > Contact < / a > < br > ❤ ️ Happy Pythoning ! < / p >
< / div >
< / footer >
< script >
( function ( document , history , location ) {
var HISTORY_SUPPORT = ! ! ( history & & history . pushState ) ;
var anchorScrolls = {
ANCHOR_REGEX : / ^ #[^ ]+$/,
OFFSET_HEIGHT_PX : 120 ,
/ * *
* Establish events , and fix initial scroll position if a hash is provided .
* /
init : function ( ) {
this . scrollToCurrent ( ) ;
window . addEventListener ( ' hashchange ' , this . scrollToCurrent . bind ( this ) ) ;
document . body . addEventListener ( ' click ' , this . delegateAnchors . bind ( this ) ) ;
} ,
/ * *
* Return the offset amount to deduct from the normal scroll position .
* Modify as appropriate to allow for dynamic calculations
* /
getFixedOffset : function ( ) {
return this . OFFSET_HEIGHT_PX ;
} ,
/ * *
* If the provided href is an anchor which resolves to an element on the
* page , scroll to it .
* @param { String } href
* @return { Boolean } - Was the href an anchor .
* /
scrollIfAnchor : function ( href , pushToHistory ) {
var match , rect , anchorOffset ;
if ( ! this . ANCHOR_REGEX . test ( href ) ) {
return false ;
}
match = document . getElementById ( href . slice ( 1 ) ) ;
if ( match ) {
rect = match . getBoundingClientRect ( ) ;
anchorOffset = window . pageYOffset + rect . top - this . getFixedOffset ( ) ;
window . scrollTo ( window . pageXOffset , anchorOffset ) ;
/ / Add the state to history as - per normal anchor links
if ( HISTORY_SUPPORT & & pushToHistory ) {
history . pushState ( { } , document . title , location . pathname + href ) ;
}
}
return ! ! match ;
} ,
/ * *
* Attempt to scroll to the current location ' s hash.
* /
scrollToCurrent : function ( ) {
this . scrollIfAnchor ( window . location . hash ) ;
} ,
/ * *
* If the click event ' s target was an anchor, fix the scroll position.
* /
delegateAnchors : function ( e ) {
var elem = e . target ;
if (
elem . nodeName == = ' A ' & &
this . scrollIfAnchor ( elem . getAttribute ( ' href ' ) , true )
) {
e . preventDefault ( ) ;
}
}
} ;
window . addEventListener (
' DOMContentLoaded ' , anchorScrolls . init . bind ( anchorScrolls )
) ;
} ) ( window . document , window . history , window . location ) ;
< / script >
< script src = " /static/jquery.min.dc5e7f18c8d3.js " > < / script >
< script src = " /static/popper.min.1022eaf388cc.js " > < / script >
< script src = " /static/bootstrap.min.f20fa8b102f2.js " > < / script >
< script src = " /static/repl-toggle.366cb6d72340.js " > < / script >
< script > window . rp_prop_id = ' 58946116052 ' ; < / script >
< script src = " https://srv.realpython.net/tag.js " async > < / script >
< script src = " /static/toc-refresh.412f6e102282.js " async > < / script >
< script id = " dsq-count-scr " src = " https://realpython.disqus.com/count.js " async > < / script >
< script >
var disqus_config = function ( ) {
this . page . url = ' https://realpython.com/primer-on-python-decorators/ ' ;
this . page . identifier = ' https://realpython.com/primer-on-python-decorators/ ' ;
this . callbacks . onReady = [ function ( ) {
if ( window . onDisqusReady ) {
window . onDisqusReady ( ) ;
}
} ] ;
} ;
var disqus_script_url = ' https://realpython.disqus.com/embed.js ' ;
< / script >
< script src = " /static/lazydisqus.3a3e39583b32.js " defer > < / script >
< script src = " /static/articlevc.11f8c3a3f08d.js " defer > < / script >
< script type = " application/ld+json " >
{
" @context " : " http://schema.org " ,
" @type " : " Article " ,
" headline " : " Primer on Python Decorators " ,
" image " : {
" @type " : " ImageObject " ,
" url " : " https://files.realpython.com/media/Primer-on-Python-Decorators_Watermarked.d0da542fa3fc.jpg " ,
" width " : 1920 ,
" height " : 1080
} ,
" mainEntityOfPage " : {
" @type " : " WebPage " ,
" @id " : " https://realpython.com/primer-on-python-decorators/ "
} ,
" datePublished " : " 2018-08-22T16:00:00+00:00 " ,
" dateModified " : " 2020-12-18T23:55:42.878208+00:00 " ,
" publisher " : {
" @type " : " Organization " ,
" name " : " Real Python " ,
" logo " : {
" @type " : " ImageObject " ,
" url " : " https://realpython.com/static/real-python-logo-square-tiny.b2452b6d3823.png " ,
" width " : 60 ,
" height " : 60
}
} ,
" author " : {
" @type " : " Organization " ,
" name " : " Real Python " ,
" url " : " https://realpython.com " ,
" logo " : " https://realpython.com/static/real-python-logo-square.146e987bf77c.png "
} ,
" description " : " In this introductory tutorial, we'll look at what Python decorators are and how to create and use them. "
}
< / script >
< script >
var _dcq = _dcq | | [ ] ;
var _dcs = _dcs | | { } ;
_dcs . account = ' 6214500 ' ;
( function ( ) {
var dc = document . createElement ( ' script ' ) ;
dc . type = ' text/javascript ' ; dc . async = true ;
dc . src = ' //tag.getdrip.com/6214500.js ' ;
var s = document . getElementsByTagName ( ' script ' ) [ 0 ] ;
s . parentNode . insertBefore ( dc , s ) ;
} ) ( ) ;
< / script >
< script type = " text/javascript " > ( function ( ) { window [ ' __CF$cv$params ' ] = { r : ' 6085d4d7d9cc6179 ' , m : ' 2dbf24d5eea10ee95a641a479e5bc0e51ce81e44-1609101362-1800-AWokc7ylH13jHI1gyNrUE+y/V0pBFC+rJ3uM/qHDnLkKAY2mwS2b2AibOnGUHQEHCLgsURHkxpQOsvs92px2cTZt6+abEOAMPKKSXarkF7bkN+DLxH42JBsqzP89QVsH8xDW5WZmF5rtGphdIlsZSQ8= ' , s : [ 0x9e017daa80 , 0x5627e559d8 ] , } } ) ( ) ; < / script > < / body >
< / html >
"""
return html
def test_referencia_instructions_extract_correctly ( real_ad_html ) :
soup = BeautifulSoup ( real_ad_html , " html5lib " )
referencia_instructions = ReferenciaFieldInstructions ( )
referencia_instructions . scrape ( soup )
referencia_instructions . validate ( )
assert (
referencia_instructions . found == True
and referencia_instructions . valid == True
and referencia_instructions . value is not None
and referencia_instructions . search_issue is None
)
def test_referencia_instructions_find_nothing_in_unrelated_html ( unrelated_html ) :
soup = BeautifulSoup ( unrelated_html , " html5lib " )
referencia_instructions = ReferenciaFieldInstructions ( )
referencia_instructions . scrape ( soup )
referencia_instructions . validate ( )
assert (
referencia_instructions . found == False
and referencia_instructions . valid is None
and referencia_instructions . value is None
and referencia_instructions . search_issue is not None
)
def test_all_instructions_extract_correctly ( real_ad_html ) :
soup = BeautifulSoup ( real_ad_html , " html5lib " )
all_instructions = [
ReferenciaFieldInstructions ( ) ,
PrecioFieldInstructions ( ) ,
TamanoCategoricoFieldInstructions ( ) ,
M2FieldInstructions ( ) ,
TipoAnuncioFieldInstructions ( ) ,
CalleFieldInstructions ( ) ,
BarrioFieldInstructions ( ) ,
DistritoFieldInstructions ( ) ,
CiudadFieldInstructions ( ) ,
SecondaryFeaturesFieldInstructions (
field_name = " cubierta " , search_keyword = " Cubierta "
) ,
SecondaryFeaturesFieldInstructions (
field_name = " puerta_auto " , search_keyword = " Puerta "
) ,
SecondaryFeaturesFieldInstructions (
field_name = " ascensor " , search_keyword = " ascensor "
) ,
SecondaryFeaturesFieldInstructions (
field_name = " alarma " , search_keyword = " Alarma "
) ,
SecondaryFeaturesFieldInstructions (
field_name = " circuito " , search_keyword = " Cámaras "
) ,
SecondaryFeaturesFieldInstructions (
field_name = " personal " , search_keyword = " Personal "
) ,
TelefonoFieldInstructions ( ) ,
]
for instruction in all_instructions :
instruction . scrape ( soup ) . validate ( )
assert all (
[
instruction . found == True
and instruction . valid == True
and instruction . value is not None
and instruction . search_issue is None
for instruction in all_instructions
]
)
def test_all_instructions_fail_on_unrelated_html ( unrelated_html ) :
soup = BeautifulSoup ( unrelated_html , " html5lib " )
all_instructions = [
ReferenciaFieldInstructions ( ) ,
PrecioFieldInstructions ( ) ,
TamanoCategoricoFieldInstructions ( ) ,
M2FieldInstructions ( ) ,
TipoAnuncioFieldInstructions ( ) ,
CalleFieldInstructions ( ) ,
BarrioFieldInstructions ( ) ,
DistritoFieldInstructions ( ) ,
CiudadFieldInstructions ( ) ,
SecondaryFeaturesFieldInstructions (
field_name = " cubierta " , search_keyword = " Cubierta "
) ,
SecondaryFeaturesFieldInstructions (
field_name = " puerta_auto " , search_keyword = " Puerta "
) ,
SecondaryFeaturesFieldInstructions (
field_name = " ascensor " , search_keyword = " ascensor "
) ,
SecondaryFeaturesFieldInstructions (
field_name = " alarma " , search_keyword = " Alarma "
) ,
SecondaryFeaturesFieldInstructions (
field_name = " circuito " , search_keyword = " Cámaras "
) ,
SecondaryFeaturesFieldInstructions (
field_name = " personal " , search_keyword = " Personal "
) ,
TelefonoFieldInstructions ( ) ,
]
for instruction in all_instructions :
instruction . scrape ( soup ) . validate ( )
assert all (
[
instruction . found == False
and ( instruction . valid == False or instruction . valid == None )
and instruction . value is None
for instruction in all_instructions
]
)
def test_parsing_flow_works_for_ad_html ( real_ad_html ) :
soup = BeautifulSoup ( real_ad_html , " html5lib " )
parsing_flow = ParsingFlow ( )
all_instructions = [
ReferenciaFieldInstructions ( ) ,
PrecioFieldInstructions ( ) ,
TamanoCategoricoFieldInstructions ( ) ,
M2FieldInstructions ( ) ,
TipoAnuncioFieldInstructions ( ) ,
CalleFieldInstructions ( ) ,
BarrioFieldInstructions ( ) ,
DistritoFieldInstructions ( ) ,
CiudadFieldInstructions ( ) ,
SecondaryFeaturesFieldInstructions (
field_name = " cubierta " , search_keyword = " Cubierta "
) ,
SecondaryFeaturesFieldInstructions (
field_name = " puerta_auto " , search_keyword = " Puerta "
) ,
SecondaryFeaturesFieldInstructions (
field_name = " ascensor " , search_keyword = " ascensor "
) ,
SecondaryFeaturesFieldInstructions (
field_name = " alarma " , search_keyword = " Alarma "
) ,
SecondaryFeaturesFieldInstructions (
field_name = " circuito " , search_keyword = " Cámaras "
) ,
SecondaryFeaturesFieldInstructions (
field_name = " personal " , search_keyword = " Personal "
) ,
TelefonoFieldInstructions ( ) ,
]
parsing_flow . add_instructions ( all_instructions )
parsing_flow . execute_flow ( soup )
assert (
parsing_flow . all_non_optional_fields_were_found
and parsing_flow . all_found_fields_are_valid
2020-12-29 20:42:21 +01:00
and len ( parsing_flow . field_values ) == len ( all_instructions )
2020-12-29 17:38:17 +01:00
)
def test_parsing_flow_fails_for_unrelated_html ( unrelated_html ) :
soup = BeautifulSoup ( unrelated_html , " html5lib " )
parsing_flow = ParsingFlow ( )
all_instructions = [
ReferenciaFieldInstructions ( ) ,
PrecioFieldInstructions ( ) ,
TamanoCategoricoFieldInstructions ( ) ,
M2FieldInstructions ( ) ,
TipoAnuncioFieldInstructions ( ) ,
CalleFieldInstructions ( ) ,
BarrioFieldInstructions ( ) ,
DistritoFieldInstructions ( ) ,
CiudadFieldInstructions ( ) ,
SecondaryFeaturesFieldInstructions (
field_name = " cubierta " , search_keyword = " Cubierta "
) ,
SecondaryFeaturesFieldInstructions (
field_name = " puerta_auto " , search_keyword = " Puerta "
) ,
SecondaryFeaturesFieldInstructions (
field_name = " ascensor " , search_keyword = " ascensor "
) ,
SecondaryFeaturesFieldInstructions (
field_name = " alarma " , search_keyword = " Alarma "
) ,
SecondaryFeaturesFieldInstructions (
field_name = " circuito " , search_keyword = " Cámaras "
) ,
SecondaryFeaturesFieldInstructions (
field_name = " personal " , search_keyword = " Personal "
) ,
TelefonoFieldInstructions ( ) ,
]
parsing_flow . add_instructions ( all_instructions )
parsing_flow . execute_flow ( soup )
assert not parsing_flow . all_non_optional_fields_were_found and len (
parsing_flow . issues
) == len ( all_instructions )