howto-descriptor.pdf
(
118 KB
)
Pobierz
Descriptor HowTo Guide
Release3.4.0
Guido van Rossum
Fred L. Drake, Jr., editor
April 17, 2014
Python Software Foundation
Email:docs@python.org
Contents
1
Abstract
2
2
Definition and Introduction
2
3
Descriptor Protocol
2
4
Invoking Descriptors
2
5
Descriptor Example
3
6
Properties
4
7
Functions and Methods
5
8
Static Methods and Class Methods
6
Author Raymond Hettinger
Contact <python at rcn dot com>
Contents
•
Descriptor HowTo Guide
–
Abstract
–
Definition and Introduction
–
Descriptor Protocol
–
Invoking Descriptors
–
Descriptor Example
–
Properties
–
Functions and Methods
–
Static Methods and Class Methods
1 Abstract
Defines descriptors, summarizes the protocol, and shows how descriptors are called. Examines a custom descriptor
and several built-in python descriptors including functions, properties, static methods, and class methods. Shows
how each works by giving a pure Python equivalent and a sample application.
Learning about descriptors not only provides access to a larger toolset, it creates a deeper understanding of how
Python works and an appreciation for the elegance of its design.
2 Definition and Introduction
In general, a descriptor is an object attribute with “binding behavior”, one whose attribute access has been overrid-
den by methods in the descriptor protocol. Those methods are
__get__()
,
__set__()
, and
__delete__()
.
If any of those methods are defined for an object, it is said to be a descriptor.
The default behavior for attribute access is to get, set, or delete the attribute from an object’s dictionary. For
instance,
a.x
has a lookup chain starting with
a.__dict__[’x’]
, then
type(a).__dict__[’x’]
, and
continuing through the base classes of
type(a)
excluding metaclasses. If the looked-up value is an object
defining one of the descriptor methods, then Python may override the default behavior and invoke the descriptor
method instead. Where this occurs in the precedence chain depends on which descriptor methods were defined.
Descriptors are a powerful, general purpose protocol. They are the mechanism behind properties, methods, static
methods, class methods, and
super()
. They are used throughout Python itself to implement the new style
classes introduced in version 2.2. Descriptors simplify the underlying C-code and offer a flexible set of new tools
for everyday Python programs.
3 Descriptor Protocol
descr.__get__(self,obj,type=None)-->value
descr.__set__(self,obj,value)-->None
descr.__delete__(self,obj)-->None
That is all there is to it. Define any of these methods and an object is considered a descriptor and can override
default behavior upon being looked up as an attribute.
If an object defines both
__get__()
and
__set__()
, it is considered a data descriptor. Descriptors that
only define
__get__()
are called non-data descriptors (they are typically used for methods but other uses are
possible).
Data and non-data descriptors differ in how overrides are calculated with respect to entries in an instance’s dic-
tionary. If an instance’s dictionary has an entry with the same name as a data descriptor, the data descriptor takes
precedence. If an instance’s dictionary has an entry with the same name as a non-data descriptor, the dictionary
entry takes precedence.
To make a read-only data descriptor, define both
__get__()
and
__set__()
with the
__set__()
raising
an
AttributeError
when called. Defining the
__set__()
method with an exception raising placeholder is
enough to make it a data descriptor.
4 Invoking Descriptors
A descriptor can be called directly by its method name. For example,
d.__get__(obj)
.
Alternatively, it is more common for a descriptor to be invoked automatically upon attribute access. For example,
obj.d
looks up
d
in the dictionary of
obj
. If
d
defines the method
__get__()
, then
d.__get__(obj)
is
invoked according to the precedence rules listed below.
The details of invocation depend on whether
obj
is an object or a class.
For objects, the machinery is in
object.__getattribute__()
which transforms
b.x
into
type(b).__dict__[’x’].__get__(b,type(b))
. The implementation works through a precedence
chain that gives data descriptors priority over instance variables, instance variables priority over non-data descrip-
tors, and assigns lowest priority to
__getattr__()
if provided. The full C implementation can be found in
PyObject_GenericGetAttr()
in
Objects/object.c
.
For
classes,
the
machinery
is
in
type.__getattribute__()
which
transforms
B.x
into
B.__dict__[’x’].__get__(None,B)
. In pure Python, it looks like:
def
__getattribute__
(
self
,key):
"Emulatetype_getattro()inObjects/typeobject.c"
v
=
object
.
__getattribute__(
self
,key)
if
hasattr
(v,
’__get__’
):
return
v
.
__get__(
None
,
self
)
return
v
The important points to remember are:
• descriptors are invoked by the
__getattribute__()
method
• overriding
__getattribute__()
prevents automatic descriptor calls
•
object.__getattribute__()
and
type.__getattribute__()
make
different
calls
to
__get__()
.
• data descriptors always override instance dictionaries.
• non-data descriptors may be overridden by instance dictionaries.
The object returned by
super()
also has a custom
__getattribute__()
method for invoking descrip-
tors. The call
super(B,obj).m()
searches
obj.__class__.__mro__
for the base class
A
immediately
following
B
and then returns
A.__dict__[’m’].__get__(obj,B)
. If not a descriptor,
m
is returned un-
changed. If not in the dictionary,
m
reverts to a search using
object.__getattribute__()
.
The implementation details are in
super_getattro()
in
Objects/typeobject.c
and a pure Python equivalent
can be found in
Guido’s Tutorial
.
The details above show that the mechanism for descriptors is embedded in the
__getattribute__()
methods
for
object
,
type
, and
super()
. Classes inherit this machinery when they derive from
object
or if they have
a meta-class providing similar functionality. Likewise, classes can turn-off descriptor invocation by overriding
__getattribute__()
.
5 Descriptor Example
The following code creates a class whose objects are data descriptors which print a message for each get or set.
Overriding
__getattribute__()
is alternate approach that could do this for every attribute. However, this
descriptor is useful for monitoring just a few chosen attributes:
class
RevealAccess
(
object
):
"""Adatadescriptorthatsetsandreturnsvalues
normallyandprintsamessageloggingtheiraccess.
"""
def
__init__
(
self
,initval
=
None
,name
=
’var’
):
self
.
val
=
initval
self
.
name
=
name
def
__get__
(
self
,obj,objtype):
print
(
’Retrieving’
,
self
.
name)
return
self
.
val
def
__set__
(
self
,obj,val):
print
(
’Updating’
,
self
.
name)
self
.
val
=
val
>>>
class
MyClass
(
object
):
x
=
RevealAccess(
10
,
’var"x"’
)
y
=
5
>>>
m
=
MyClass()
>>>
m
.
x
Retrievingvar
"x"
10
>>>
m
.
x
=
20
Updatingvar
"x"
>>>
m
.
x
Retrievingvar
"x"
20
>>>
m
.
y
5
The protocol is simple and offers exciting possibilities. Several use cases are so common that they have been pack-
aged into individual function calls. Properties, bound and unbound methods, static methods, and class methods
are all based on the descriptor protocol.
6 Properties
Calling
property()
is a succinct way of building a data descriptor that triggers function calls upon access to
an attribute. Its signature is:
property
(fget
=
None
,fset
=
None
,fdel
=
None
,doc
=
None
)
->
property
attribute
The documentation shows a typical use to define a managed attribute
x
:
class
C
(
object
):
def
getx
(
self
):
return
self
.
__x
def
setx
(
self
,value):
self
.
__x
=
value
def
delx
(
self
):
del
self
.
__x
x
=
property
(getx,setx,delx,
"I’mthe’x’property."
)
To see how
property()
is implemented in terms of the descriptor protocol, here is a pure Python equivalent:
class
Property
(
object
):
"EmulatePyProperty_Type()inObjects/descrobject.c"
def
__init__
(
self
,fget
=
None
,fset
=
None
,fdel
=
None
,doc
=
None
):
self
.
fget
=
fget
self
.
fset
=
fset
self
.
fdel
=
fdel
if
doc
isNoneand
fget
isnotNone
:
doc
=
fget
.
__doc__
self
.
__doc__
=
doc
def
__get__
(
self
,obj,objtype
=
None
):
if
obj
isNone
:
return
self
if
self
.
fget
isNone
:
raise
AttributeError
(
"unreadableattribute"
)
return
self
.
fget(obj)
def
__set__
(
self
,obj,value):
if
self
.
fset
isNone
:
raise
AttributeError
(
"can’tsetattribute"
)
self
.
fset(obj,value)
def
__delete__
(
self
,obj):
if
self
.
fdel
isNone
:
raise
AttributeError
(
"can’tdeleteattribute"
)
self
.
fdel(obj)
def
getter
(
self
,fget):
return
type
(
self
)(fget,
self
.
fset,
self
.
fdel,
self
.
__doc__)
def
setter
(
self
,fset):
return
type
(
self
)(
self
.
fget,fset,
self
.
fdel,
self
.
__doc__)
def
deleter
(
self
,fdel):
return
type
(
self
)(
self
.
fget,
self
.
fset,fdel,
self
.
__doc__)
The
property()
builtin helps whenever a user interface has granted attribute access and then subsequent
changes require the intervention of a method.
For instance, a spreadsheet class may grant access to a cell value through
Cell(’b10’).value
. Subsequent
improvements to the program require the cell to be recalculated on every access; however, the programmer does
not want to affect existing client code accessing the attribute directly. The solution is to wrap access to the value
attribute in a property data descriptor:
class
Cell
(
object
):
...
def
getvalue
(
self
,obj):
"Recalculatecellbeforereturningvalue"
self
.
recalc()
return
obj
.
_value
value
=
property
(getvalue)
7 Functions and Methods
Python’s object oriented features are built upon a function based environment. Using non-data descriptors, the
two are merged seamlessly.
Class dictionaries store methods as functions. In a class definition, methods are written using
def
and
lambda
,
the usual tools for creating functions. The only difference from regular functions is that the first argument is
reserved for the object instance. By Python convention, the instance reference is calledselfbut may be calledthis
or any other variable name.
To support method calls, functions include the
__get__()
method for binding methods during attribute access.
This means that all functions are non-data descriptors which return bound or unbound methods depending whether
they are invoked from an object or a class. In pure python, it works like this:
class
Function
(
object
):
...
def
__get__
(
self
,obj,objtype
=
None
):
"Simulatefunc_descr_get()inObjects/funcobject.c"
return
types
.
MethodType(
self
,obj,objtype)
Running the interpreter shows how the function descriptor works in practice:
>>>
class
D
(
object
):
deff(self,x):
returnx
Plik z chomika:
agentpl
Inne pliki z tego folderu:
c-api.pdf
(860 KB)
distributing.pdf
(218 KB)
extending.pdf
(405 KB)
faq.pdf
(450 KB)
howto-argparse.pdf
(118 KB)
Inne foldery tego chomika:
Zgłoś jeśli
naruszono regulamin