Decorating Classes¶
In the Decorating methods and Decorating classes sections of the
Quick Start chapter we already introduced the use of log_calls to decorate
methods and properties of classes. As shown in the latter section,
if you want to decorate every callable of a class, you don’t have to decorate each one
individually: you can simply decorate the class. As that section also shows, this convenience
isn’t an all or nothing affair: you can use the omit
and only
keyword
parameters to a log_calls class decorator for more fine-grained control over which
callables get decorated. The first sections of this chapter detail the use of those parameters.
The remaining sections discuss other topics pertinent to class decoration.
The omit
and only
keyword parameters (default: ()
)¶
These parameters let you concisely specify which methods and properties of a decorated class
get decorated. log_calls ignores omit
and only
when decorating a function. When not empty,
the value of each of these parameters specifies one or more methods or properties of a decorated class.
If you provide just omit
, all callables of the class will be decorated except for those
specified by omit
. If you provide just only
, only the callables it specifies will be
decorated. If you provide both, the callables decorated will be those specified by only
,
excepting any specified by omit
.
log_calls allows considerable flexibility in the format of values provided for the omit
and only
parameters. First, we give the general definition of what those values can be, and then several examples
illustrating their use.
Values of the omit
and only
parameters¶
For this section only, it’s convenient to define the following term:
- callable designator
(That’s designator of callables, not a “designator that can be called”, whatever that might be.)
A string which is one of the following:
the name of a method
the name of a property, possibly followed by one of the qualifiers
.setter
,.getter
,.deleter
a glob (Unix-style shell pattern) — a string possibly containing
- wildcard characters
*
,?
- character sets
[
s1 s2 ... sn]
where each sk can be either a character or a character range sk,1-
sk,2 (e.g.[acr-tx]
, which denotesacrstx
)- complements of character sets
[!
s1 s2 ... sn]
— all characters except those denoted by[
s1 s2 ... sn]
Matching of globs against method and property names is case-sensitive.
A value of the omit
or only
parameter can be:
- A single callable designator,
- a string consisting of multiple callable designators separated by spaces or by commas and spaces, or
- a sequence (list, tuple, or other iterable) of callable designators.
Examples of callable designators¶
Given the following class
X
:>>> class X(): ... def fn(self): pass ... def gn(self): pass ... def hn(self): pass ... @property ... def pr(self): pass ... @pr.setter ... def pr(self, val): pass ... @pr.deleter ... def pr(self): pass ... ... def pdg(self): pass ... def pds(self, val): pass ... pd = property(pdg, pds, None) ... class I(): ... def i1(self): pass ... def i2(self): passthe following table shows several callable designators for
X
and what they designate. To reduce clutter, we’ve omitted initialX.
from the literals in the righthand column:
Callable designator designates these methods and/or propertiesfn
fn
pr.getter
pr
getterpr.setter
pr
setterpr.deleter
pr
deleterpr
{pr
getter,pr
setter,pr
deleter}pr.?etter
{pr
getter,pr
setter}p*
{pr
getter,pr
setter,pr
deleter,pds
,pdg
}?n
{fn
,gn
,hn
}[fg]n
{fn
,gn
}[f-h]n
{fn
,gn
,hn
}pd
{pdg
,pds
}pdg
,pd.getter
pdg
pds
,pd.setter
pds
pd.deleter
nothing (pd
has no deleter)no_such_*
nothing (there are no matches)[f-i]*
{fn
,gn
,hn
,I.i1
,I.i2
}X.I.*
,X.[!f-hp]*
{I.i1
,I.i2
}X.[!f-ip]*
{I.i1
,I.i2
},because[!f-ip]
matchesI
[!f-hp]*
,?[!n]*
every callable in classesX
andX.I
,because these matchX.
+ anything*
every callable in classesX
andX.I
Warning
Be aware that:
- wildcards can match the dot
'.'
in qualified names;- both qualified and unqualified method and property names are matched — e.g. for a method
mymethod
in a classC
, each callable designator is checked for a match against bothmymethod
andC.mymethod
.As the second and third to last examples in the above table illustrate, these matching rules can lead to surprises, especially when using complements of character sets.
omit
and only
— Examples¶
A useful settings dict for the examples of this chapter:
>>> MINIMAL = dict(
... log_args=False,
... log_exit=False
... )
Basic examples¶
First, simple examples for methods, without wildcards, illustrating possible values
for omit
and only
and the interaction of those parameters.
In class A
, only f
is decorated:
>>> @log_calls(only='f', settings=MINIMAL)
... class A():
... def f(self): pass
... def g(self): pass
>>> a = A(); a.f(); a.g()
A.f <== called by <module>
In class B
, f
and g
are omitted, so only h
is decorated (and so, gives output):
>>> @log_calls(omit='f g', settings=MINIMAL)
... class B():
... def f(self): pass
... def g(self): pass
... def h(self): pass
>>> b = B(); b.f(); b.g(); b.h()
B.h <== called by <module>
In class C
, only f
and h
are decorated:
>>> @log_calls(only='f, h', settings=MINIMAL)
... class C():
... def f(self): pass
... def g(self): pass
... def h(self): pass
>>> c = C(); c.f(); c.g(); c.h()
C.f <== called by <module>
C.h <== called by <module>
In class D
, only f
is decorated:
>>> @log_calls(only=['f', 'g'], omit=('g',), settings=MINIMAL)
... class D():
... def f(self): pass
... def g(self): pass
... def h(self): pass
>>> d = D(); d.f(); d.g(); d.h()
D.f <== called by <module>
Precedence of inner decorators over outer decorators¶
By default, the explicitly given settings of a callable’s decorator take precedence over those of the decorator of its class:
>>> @log_calls(settings=MINIMAL)
... class E():
... def f(self): pass
... @log_calls(log_exit=True)
... def g(self): pass
>>> E().f(); E().g()
E.f <== called by <module>
E.g <== called by <module>
E.g ==> returning to <module>
The same holds for inner classes: settings provided explicitly to the decorator
of an inner class take precedence over the corresponding settings of the outer class.
To give the outer settings priority, supply override=True
to the outer decorator:
>>> @log_calls(settings=MINIMAL, override=True)
... class E():
... def f(self): pass
... @log_calls(log_exit=True)
... def g(self): pass
>>> E().f(); E().g()
E.f <== called by <module>
E.g <== called by <module>
Decorating properties¶
There are two ways to specify properties: using property
as a decorator,
and using it as a function, as described in the Python documentation for
property.
log_calls handles both approaches. The name of the property alone, with no appended qualifier,
designates all of the property’s existing callables — the getter, setter, and deleter.
Decorating properties specified with the @property
decorator¶
Python lets you define properties using decorators. You decorate the getter property prop
with @property
, and then any corresponding setter and deleter methods
with @
prop.setter
and @
prop.deleter
respectively.
Using only
to decorate just the getter:
>>> @log_calls(only='prop.getter', settings=MINIMAL)
... class A():
... @property
... def prop(self): pass
... @prop.setter
... def prop(self, val): pass
>>> A().prop; A().prop = 17
A.prop <== called by <module>
Using only
with the property name — all property methods are decorated:
>>> @log_calls(only='prop', settings=MINIMAL)
... class A():
... @property
... def prop(self): pass
... @prop.setter
... def prop(self, val): pass
... @prop.deleter
... def prop(self): pass
>>> A().prop; A().prop = 17; del A().prop
A.prop <== called by <module>
A.prop <== called by <module>
A.prop <== called by <module>
Using the name
parameter with setter and deleter property methods
As the previous example shows, log_calls cannot presently give distinct
display names to the different callables of a property defined by decorators.
However, you can use the name
parameter to overcome this limitation,
as shown in the following example. (The log_calls decorators come after
the property decorators.)
>>> @log_calls(settings=MINIMAL)
... class A():
... @property
... def prop(self): pass
...
... @prop.setter
... @log_calls(name='A.%s.setter')
... def prop(self, val): pass
...
... @prop.deleter
... @log_calls(name='A.%s.deleter')
... def prop(self, val): pass
>>> A().f(); A().prop; A().prop = 17; del A().prop
A.prop <== called by <module>
A.prop.getter <== called by <module>
A.prop.deleter <== called by <module>
Decorating properties specified with the property
function¶
Python also lets you define properties using property
as a function.
log_calls uses the unique names of the methods that comprise the property.
>>> @log_calls(omit='prop.setter', settings=MINIMAL)
... class XX():
... def getxx(self): pass
... def setxx(self, val): pass
... def delxx(self): pass
... prop = property(getxx, setxx, delxx)
>>> xx = XX(); xx.prop; xx.prop = 5; del xx.prop
XX.getxx <== called by <module>
XX.delxx <== called by <module>
Decorating inner classes¶
By default, the explicitly given settings of a decorator of (or within) an inner class take precedence over those of the decorator of its outer class.
>>> @log_calls(settings=MINIMAL)
... class O():
... def f(self): pass
... class I():
... @log_calls(log_call_numbers=True)
... def fi(self): pass
... def gi(self): pass
O().f(); O().I().fi(); O().I().gi()
O.f <== called by <module>
O.I.fi [1] <== called by <module>
O.I.gi <== called by <module>
To give the outer settings priority, supply override=True
to the outer decorator,
as illustrated above in Precedence of inner decorators over outer decorators.
This default precedence of outer over inner is different for omit
,
in a way that attempts to meet expectations:
only
on inner and outer class decorators¶
When present and nonempty, inner only
overrides outer only
.
In I1
, only g1
is decorated, despite the outer class’s only
specifier:
>>> @log_calls(only='*_handler', settings=MINIMAL)
... class O():
... def f(self): pass
... def my_handler(self): pass
... def their_handler(self): pass
... @log_calls(only='g1')
... class I1():
... def g1(self): pass
... def some_handler(self): pass
>>> oi1 = O.I1(); oi1.g1(); oi1.some_handler()
O.I1.g1 <== called by <module>
omit
on inner and outer class decorators¶
omit
is cumulative — inner omit
is added to outer omit
:
>>> @log_calls(omit='*_handler', settings=MINIMAL)
... class O():
... def f(self): pass
... def my_handler(self): pass
... def their_handler(self): pass
... @log_calls(omit='*_function')
... class I1():
... def g1(self): pass
... def some_handler(self): pass
... def some_function(self): pass
>>> oi1 = O.I1(); oi1.g1(); oi1.some_handler(); oi1.some_function()
O.I1.g1 <== called by <module>
Further examples¶
For more examples of inner class decoration, consult the docstrings of the
functions main__lc_class_deco__inner_classes()
and main__lc_class_deco__omit_only__inner_classes()
in tests/test_log_calls__class_deco.py
.
log_calls does not decorate __repr__
¶
To avoid infinite, possibly indirect recursions, log_calls does not itself
decorate __repr__
methods, but it will decorate them with reprlib.recursive_repr()
:
>>> @log_calls()
... class A():
... def __init__(self, x): self.x = x
... def __repr__(self): return str(self.x)
The __init__
method is decorated:
>>> a = A(5)
A.__init__ <== called by <module>
arguments: self=<__main__.A object at 0x...>, x=5
A.__init__ ==> returning to <module>
but __repr__
is not:
>>> print(a) # no log_calls output
5