__get__
, __set__
and __delete__
. If at least one of these methods is defined for an object, then it becomes a handle.ax
has the following attribute search chain: a.__dict__['x']
, then in type(a).__dict__['x']
, and further along the base classes type(a)
not including metaclasses. If the desired value is an object in which there is at least one of the methods that define the descriptor, then the python can change the standard search chain and call one of the descriptor methods. How and when this happens depends on which descriptor methods are defined for the object. Descriptors are called only for objects or classes of a new style (a class is such if it inherits from object
or type
).super()
. Inside the python itself, with their help, classes of a new style are implemented, which were introduced in version 2.2. Descriptors simplify understanding of the underlying C code, and also provide a flexible set of new tools for any programs on python. descr.__get__(self, obj, type=None) --> descr.__set__(self, obj, value) --> None descr.__delete__(self, obj) --> None
__get__
and __set__
, then it is considered a data descriptor (data descriptor). Descriptors that define only __get__
are called no-data descriptors. They are called so because they are used for methods, but other ways of using them are also possible.__get__
and __set__
, and have __set__
throw an AttributeError
exception. Defining the __set__
method and throwing an exception is enough for this descriptor to be considered a data descriptor.d.__get__(obj)
.obj.d
searches for d
in the dictionary obj
. If d
specifies the __get__
method, then d.__get__(obj)
will be called. The call will be made according to the rules described below.obj
object or class is. In any case, descriptors work only for objects and classes of the new style. A class is a new style class if it is a descendant of object
.object.__getattribute__
, which converts the bx
record to type(b).__dict__['x'].__get__(b, type(b))
. The implementation works through the predecessor chain, in which data descriptors take precedence over object variables, object variables take precedence over non-data descriptors, and the __getattr__
method has the lowest priority, if defined. The full C implementation can be found in PyObject_GenericGetAttr()
in the Objects/object.c
.type.__getattribute__
, which converts the Bx
record to B.__dict__['x'].__get__(None, B)
. On pure python, it looks like this: def __getattribute__(self, key): " type_getattro() Objects/typeobject.c" v = object.__getattribute__(self, key) if hasattr(v, '__get__'): return v.__get__(None, self) return v
__getattribute__
method__getattribute__
stop automatically __getattribute__
handles__getattribute__
is available only inside classes and objects of a new style.object.__getattribute__
and type.__getattribute__
make different calls to __get__
super()
also has its own implementation of the __getattribute__
method, with which it calls the descriptors. The call super(B, obj).m()
searches in obj.__class__.__mro__
base class A
, followed immediately by B
, and returns A.__dict__['m'].__get__(obj, A)
. If it is not a descriptor, then m
returned unchanged. If m
not in the dictionary, then we return to the search through object.__getattribute__
.super(B, obj).m()
called __get__
only if m
was a data descriptor. In Python 2.3, no data descriptors are also called, except when using old-style classes. Implementation details can be found in super_getattro()
in the Objects/typeobject.c
, and the equivalent on pure python can be found in the Guido manual .__getattribute__()
method for object
, type
and super
. Classes inherit this algorithm when they inherit from object
or if they have a metaclass that implements this functionality. Thus, classes can disable the invocation of descriptors if they override __getattribute__()
.get
or set
call. Overriding __getattribute__
is an alternative approach with which we could do this for each attribute. But if we want to monitor only individual attributes, then this is easier to do with a descriptor. class RevealAccess(object): """ , , , . """ def __init__(self, initval=None, name='var'): self.val = initval self.name = name def __get__(self, obj, objtype): print '', self.name return self.val def __set__(self, obj, val): print '' , self.name self.val = val >>> class MyClass(object): x = RevealAccess(10, 'var "x"') y = 5 >>> m = MyClass() >>> mx var "x" 10 >>> mx = 20 var "x" >>> mx var "x" 20 >>> my 5
property()
enough to create a data descriptor that calls the functions you need during attribute access. Here is its signature: property(fget=None, fset=None, fdel=None, doc=None) --> ,
property()
to create 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, " 'x'.")
property
on pure python, so that it is clear how property()
implemented using the descriptor protocol: class Property(object): " PyProperty_Type() Objects/descrobject.c" def __init__(self, fget=None, fset=None, fdel=None, doc=None): self.fget = fget self.fset = fset self.fdel = fdel self.__doc__ = doc def __get__(self, obj, objtype=None): if obj is None: return self if self.fget is None: raise AttributeError, " " return self.fget(obj) def __set__(self, obj, value): if self.fset is None: raise AttributeError, " " self.fset(obj, value) def __delete__(self, obj): if self.fdel is None: raise AttributeError, " " self.fdel(obj)
property()
implementation can help when an attribute access interface existed and some changes occurred that resulted in the intervention of the method.Cell('b10').value
. As a result of subsequent changes in the program, it was necessary to ensure that this value is recalculated with each access to the cell, but the programmer does not want to change the client code that accesses the attribute directly. This problem can be solved by wrapping the value
attribute with a data descriptor that will be created with property()
: class Cell(object): . . . def getvalue(self, obj): " " self.recalc() return obj._value value = property(getvalue)
def
and lambda
- standard tools for creating functions. The only difference between these functions and ordinary ones is that the first argument is reserved for an object instance. This argument is usually called self
, but it can be called this
or any other word that can be called variables.__get__
methods, the functions include the __get__
method, which automatically makes them descriptors of no data when searching for attributes. Functions return related or unrelated methods, depending on what this descriptor was called through. class Function(object): . . . def __get__(self, obj, objtype=None): " func_descr_get() Objects/funcobject.c" return types.MethodType(self, obj, objtype)
>>> class D(object): def f(self, x): return x >>> d = D() >>> D.__dict__['f'] # <function f at 0x00C45070> >>> Df # <unbound method Df> >>> df # <bound method Df of <__main__.D object at 0x00B18C90>>
PyMethod_Type
implementation in the Objects/classobject.c
contains a single object with two different mappings that depend only on whether there is a value in the im_self
field or whether it contains NULL
(C equivalent None
values).im_self
field. If it is set (i.e., the method is bound), then the original function (stored in the im_func
field) is called, as expected, with the first argument set to the value of the object instance. If it is not connected, then all arguments are passed without changing the original function. The real C implementation of the instancemethod_call()
bit more complicated because it involves some type checks and the like.__get__
method, with which they become methods, during the search for attributes and automatic calling of handles. No data obj.f(*args)
convert the obj.f(*args)
call obj.f(*args)
to the f(obj, *args)
call, and the klass.f(*args)
call becomes the f(*args)
call.Transformation | Called through object | Called through class | |
---|---|---|---|
Descriptor | function | f (obj, * args) | f (* args) |
staticmethod | f (* args) | f (* args) | |
classmethod | f (type (obj), * args) | f (klass, * args) |
cf
or Cf
equivalent to calls to object.__getattribute__(c, "f")
or object.__getattribute__(C, "f")
. As a result, the function is equally accessible from both the object and the class.self
variable.erf(x)
is a simple conversion function that is needed in statistics, but does not depend on the specific data set in this class. It can be called both from an object and from a class: s.erf(1.5) --> 0.9332
or Sample.erf(1.5) --> 0.9332
.staticmethod()
returns a function unchanged, this example is not surprising: >>> class E(object): def f(x): print x f = staticmethod(f) >>> print Ef(3) 3 >>> print E().f(3) 3
staticmethod()
would look like this: class StaticMethod(object): " PyStaticMethod_Type() Objects/funcobject.c" def __init__(self, f): self.f = f def __get__(self, obj, objtype=None): return self.f
>>> class E(object): def f(klass, x): return klass.__name__, x f = classmethod(f) >>> print Ef(3) ('E', 3) >>> print E().f(3) ('E', 3)
classmethod()
is to create alternative class constructors. In Python 2.3, the class method dict.fromkeys()
creates a new dictionary from the list of keys. The equivalent on pure python will be: class Dict: . . . def fromkeys(klass, iterable, value=None): " dict_fromkeys() Objects/dictobject.c" d = klass() for key in iterable: d[key] = value return d fromkeys = classmethod(fromkeys)
>>> Dict.fromkeys('abracadabra') {'a': None, 'r': None, 'b': None, 'c': None, 'd': None}
classmethod()
would look like this: class ClassMethod(object): " PyClassMethod_Type() Objects/funcobject.c" def __init__(self, f): self.f = f def __get__(self, obj, klass=None): if klass is None: klass = type(obj) def newfunc(*args): return self.f(klass, *args) return newfunc
Source: https://habr.com/ru/post/122082/
All Articles