__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