"Python" "Meta Programming" "Two" "Descriptors"

Source: Internet
Author: User

"""


#描述符实例是托管类的类属性; In addition, managed classes have properties with the same name as their own instances

#20.1.1 LineItem Third Edition: a simple descriptor
#栗子20-1 dulkfood_v3.py to manage properties of LineItem using the Quantity descriptor
Class quantity:# descriptors are based on protocol implementations without the need to create subclasses.
def __init__ (self,storage_name):
Self.storage_name = Storage_name
def __set__ (self, instance, value): # instance is a managed class instance, not self is to not conflict with descriptor instances
If value > 0:
Instance.__dict__[self.storage_name] = value #这里, the __dict__ property of the managed instance must be processed directly, and if the built-in SetAttr function is used, the __set__ method is triggered again, resulting in infinite recursion.
Else
Raise ValueError (' value must be > 0 ')
Class LineItem:
Weight = Quantity (' weight ')
Price = Quantity (' price ')
def __init__ (Self,description,weight,price):
Self.description = description
Self.weight = Weight
Self.price = Price
def subtotal (self):
Return self.weight * Self.price


Truffle = LineItem (' White truffle ', 100,0) #ValueError: value must be > 0


#20.1.2 LineItem version Fourth: Automatically get the name of the storage property
#栗子20-2 bulkfood_v4.py: Each Quantity descriptor has a unique storage_name
"In order to generate storage_name, we prefix ' _quantity# ' and then stitch an integer in the back:
The current value of the Quantity.__counter class property, each time attaching a new Quantity descriptor instance to the
Class, this value will be incremented. Using the pound sign in the prefix avoids storage_name and the user using the dot number to create
Property conflicts because nutmeg._quantity#0 is invalid for Python syntax. However, the built-in GetAttr
And the SetAttr function can use this "invalid" identifier to get and set properties, in addition to directly handle the actual
Example attribute __dict__
‘‘‘
Class Quantity:
__counter = 0

def __init__ (self):
CLS = self.__class__
prefix = cls.__name__
index = Cls.__counter
Self.storage_name = ' _{}#{} '. Format (prefix,index) #
Cls.__counter + = 1
def __get__ (self, instance, owner):
Return GetAttr (instance,self.storage_name) #这里可以使用内置的高阶函数 getattr and SetAttr access values without the use of instance.__dict__, Because the managed property and the storage property have different names, passing the store property to the GetAttr function does not trigger the descriptor, and does not appear as an infinite recursion as in example 20-1
def __set__ (self, instance, value):
If value > 0:
SetAttr (Instance,self.storage_name,value)
Else
Raise ValueError (' value must be > 0 ')

Class LineItem:
Weight = Quantity ()
Price = Quantity ()
def __init__ (Self,description,weight,price):
Self.description = description
Self.weight = Weight
Self.price = Price
def subtotal (self):
Return self.weight * Self.price

cocounts = LineItem (' Brazilian cocount ', 20,17.95)
Print (GetAttr (cocounts, ' _quantity#0 '), GetAttr (cocounts, ' _quantity#1 ')) #20 17.95
Print (GetAttr (cocounts, ' weight '), GetAttr (cocounts, ' price ') #20 17.95
Print (cocounts.weight,cocounts.price) #20 17.95
#print (cocounts._quantity#0) #SyntaxError: unexpected EOF while parsing
The __get__ method has three parameters: Self, instance, and owner. The owner parameter is a managed class, such as
LineItem), which is used when fetching properties from a managed class through a descriptor. If you use
Lineitem.weight get the managed property from the class (take weight as an example), the __get__ method of the descriptor is connected
This document was received by the Linux commune www.linuxidc.com instance parameter value is None. Therefore, the following console session will not throw Attributeerror
Often
Throwing Attributeerror exceptions is one way to implement the __get__ method, and if you choose to do so, you should fix
Change the error message to remove the confusing Nonetype and _quantity#0, which is the implementation details. Put the error message
Change to "' LineItem ' class has no such attribute" better. It is best to give the missing attributes
Name, but in this example, the descriptor does not know the name of the managed property, so this is the only
‘‘‘
#print (lineitem.weight) #AttributeError: ' Nonetype ' object has no attribute ' _quantity#0 '




#示例 20-3 bulkfood_v4b.py (partial code only): When called through a managed class, the __get__ method returns a reference to the descriptor
Class Quantity:
__counter = 0

def __init__ (self):
CLS = self.__class__
prefix = cls.__name__
index = Cls.__counter
Self.storage_name = ' _{}#{} '. Format (prefix,index) #
Cls.__counter + = 1
def __get__ (self, instance, owner):
If instance is None:
Return self #如果不是通过实例调用, returns the descriptor itself
Else
Return GetAttr (instance, self.storage_name)
#这里可以使用内置的高阶函数 getattr and SetAttr access values without using instance.__dict__ because the managed and stored properties have different names, so passing the storage attribute to the GetAttr function does not trigger the descriptor, not like example 20-1 So there's infinite recursion.
def __set__ (self, instance, value):
If value > 0:
SetAttr (Instance,self.storage_name,value)
Else
Raise ValueError (' value must be > 0 ')

Class LineItem:
Weight = Quantity ()
Price = Quantity ()
def __init__ (Self,description,weight,price):
Self.description = description
Self.weight = Weight
Self.price = Price
def subtotal (self):
Return self.weight * Self.price


Print (lineitem.weight) #<__main__. Quantity Object at 0x0000000001eca710>
br_nuts = LineItem (' Brazil nuts ', 10,34.95)
Print (Br_nuts.price) #34.95




‘‘‘
Attribute factory function vs. Descriptor class
Attribute factory functions It is not difficult to implement the enhanced descriptor class in example 20-2, just in the base of example 19-24
Add a few lines of code to the infrastructure. The implementation of the __counter variable is a difficult one, but we can define it
This document is organized by the Linux commune www.linuxidc.com as a property of the factory function object so that it persists between multiple invocations, as shown in example 20-5.
Example 20-5 bulkfood_v4prop.py: Using the attribute factory function to implement the descriptor in example 20-2
The same functionality as the class
Def quantity ():?
Try
Quantity.counter + = 1?
Except Attributeerror:
Quantity.counter = 0?
Storage_name = ' _{}:{} '. Format (' quantity ', Quantity.counter)?
def qty_getter (instance):?
Return GetAttr (instance, storage_name)
def qty_setter (instance, value):
If value > 0:
SetAttr (instance, storage_name, value)
Else
Raise ValueError (' value must be > 0 ')
Return property (Qty_getter, Qty_setter)
? There are no storage_name parameters.
? You cannot rely on class properties to share counter between multiple invocations, so define it as a quantity function
its own properties.
? If the Quantity.counter property is undefined, set the value to 0.
? We also don't have an instance variable, so we create a local variable, storage_name, with a closure to keep it
Values that are used by the following qty_getter and Qty_setter functions.
? The rest of the code is the same as example 19-24, but here you can use the built-in GetAttr and setattr letters
Instead of processing the Instance.__dict__ property.
So, which one do you like? Example 20-2 or example 20-5?
I like the way descriptor classes are, mainly for the following two reasons.
Descriptor classes can use subclass extensions; If you want to reuse the code in a factory function, it is difficult to copy and paste
There are other ways.
In example 20-5, the class properties and instance properties are persisted compared to the use of function properties and closure hold states.
The state is easier to understand.
Also, when explaining example 20-5, I didn't have the power to draw machines and little monsters. The code for the attribute factory function is not
The descriptor's method, which is known as the self and instance parameters, indicates that the
Involves a strange object relationship.
This document is organized by the Linux commune www.linuxidc.com. In a way, the feature factory function mode is simpler, but the descriptor class is more extensible.
And the application is more extensive.
‘‘‘


#20.1.3 LineItem 5th edition: A new descriptor [avoids the product information is empty, causes cannot order]
#几个描述符类的层次结构. The Autostorage base class is responsible for automatically storing attributes, Validated classes for validation, delegating responsibilities to abstract methods validate; Quantity and nonblank are Validated specific subclasses

Import ABC

Class Autostorage:
__counter = 0

def __init__ (self):
CLS = self.__class__
prefix = cls.__name__
index = Cls.__counter
Self.storage_name = ' _{}#{} '. Format (Prefix,index)
Cls.__counter + = 1

def __get__ (self, instance, owner):
If instance is None:
return self
Else
Return GetAttr (Instance,self.storage_name)

def __set__ (self, instance, value):
SetAttr (Instance,self.storage_name,value)

Class Validated (ABC. Abc,autostorage):
def __set__ (self, instance, value):
Value = Self.validate (instance,value)
Super (). __set__ (Instance,value)

@abc. Abstractmethod
def validate (Self,instance,value):
' Return validated value or raise ValueError '

Class Quantity (Validated):
"A number greater than zero"
def validate (Self,instance,value):
If value <= 0:
Raise ValueError (' value must be > 0 ')
return value

Class NonBlank (Validated):
"A string with at least one non-space character"

def validate (Self,instance,value):
Value = Value.strip ()
If Len (value) = = 0:
Raise ValueError (' value cannot be empty or blank ')
return value

Class LineItem:
Description = NonBlank ()
Weight = Quantity ()
Price = Quantity ()

def __init__ (Self,description,weight,price):
Self.description = description
Self.weight = Weight
Self.price = Price

def subtotal (self):
Return self.weight * Self.price




#20.2 Coverage vs. non-covered type descriptor
‘‘‘
Different terms are used when discussing these concepts #Python contributors and authors. The override descriptor is also called a data descriptor or a mandatory descriptor. Non-overlay descriptors are also called non-data descriptors or masking descriptors
#依附在类上的描述符无法控制为类属性赋值的操作. In fact, this means that assigning a value to a class property overrides the Descriptor attribute
‘‘‘

# # Helper function, only for displaying # # #
def cls_name (OBJ_OR_CLS):
CLS = Type (OBJ_OR_CLS)
If CLS is type:
CLS = Obj_or_cls
Return Cls.__name__.split ('. ') [-1]



def display (obj):
CLS = Type (obj)
If CLS is type:
Return ' <class {}> '. Format (obj.__name__)
Elif cls in [Type (None), int]:
return repr (obj)
Else
Return ' <{} object> '. Format (cls_name (obj))


def print_args (name, *args):
Pseudo_args = ', '. Join (display (x) for x in args)
Print ('}.__{}__ {{} ') '. Format (Cls_name (Args[0]), name, Pseudo_args)

# # # Important Class # # # for this example
Class overriding:
' Also called a data descriptor or mandatory descriptor '

def __get__ (self, instance, owner):
Print_args (' Get ', self, instance, owner)

def __set__ (self, instance, value):
Print_args (' Set ', self, instance, value)


Class Overridingnoget:
"' There is no ' __get__ ' method of overriding descriptor '

def __set__ (self, instance, value):
Print_args (' Set ', self, instance, value)



Class Nonoverriding:
' Also called a non-data descriptor or a masked descriptor '

def __get__ (self, instance, owner):
Print_args (' Get ', self, instance, owner)


Class Managed:
over = overriding ()
Over_no_get = Overridingnoget ()
Non_over = nonoverriding ()

def spam (self):
Print ('--Managed.spam ({}) '. Format (display (self))
#覆盖型描述符
obj = Managed ()
Print (Obj.over) #-> overriding.__get__ (<overriding object>, <managed object>, <class Managed>)
Print (Managed.over) #-> overriding.__get__ (<overriding object>, None, <class managed>) "Parse" because there are no instances
Obj.over = 7 #-> overriding.__set__ (<overriding object>, <managed Object>, 7)
Print (Obj.over) #-> overriding.__get__ (<overriding object>, <managed object>, <class Managed>)
obj.__dict__[' over '] = 8 #跳过描述符, set the value directly through the Obj.__dict__ property, so no content is printed
Print (VARs (obj)) #{' over ': 8} "resolve" confirmation value in the obj.__dict__ attribute, under the over key name
Print (Obj.over) #-> overriding.__get__ (<overriding object>, <managed object>, <class Managed>) " Parse "However, even if the instance property named over, the Managed.over descriptor will still overwrite the read Obj.over operation


#没有 override descriptor for the __get__ method
Print (Obj.over_no_get) #<__main__. Overridingnoget object at 0x000000000385f860> "Parse" This overlay descriptor has no __get__ method, so Obj.over_no_get gets the descriptor instance from the class
Print (Managed.over_no_get) #<__main__. Overridingnoget object at 0x0000000001ee3a58> "Parse" reads the descriptor instance directly from the managed class.
Obj.over_no_get = 7 #-> overridingnoget.__set__ (<overridingnoget object>, <managed Object>, 7)
Print (Obj.over_no_get) #<__main__. Overridingnoget object at 0x0000000002203a58> "Parse" because the __set__ method does not modify the property, so this read obj.over_no_get get is still a descriptor instance in the managed class
obj.__dict__[' over_no_get '] = 9
Print (Obj.over_no_get) #9 "Parse" Now, the Over_no_get instance property obscures the descriptor, but only the read operation is
Obj.over_no_get = 7 #-> overridingnoget.__set__ (<overridingnoget object>, <managed Object>, 7)
Print (Obj.over_no_get) #9 "parse" but when read, the descriptor is obscured as long as there is an instance property of the same name

# Non-Overlay descriptor
obj = Managed ()
Print (Obj.non_over) #-> nonoverriding.__get__ (<nonoverriding object>, <managed Object>, <class managed>)
Obj.non_over = 7
Print (Obj.non_over) #7
Print (Managed.non_over) #-> nonoverriding.__get__ (<nonoverriding object>, None, <class Managed>)
Del Obj.non_over
Print (Obj.non_over) #-> nonoverriding.__get__ (<nonoverriding object>, <managed Object>, <class managed>)

#通过类可以覆盖任何描述符
obj = Managed ()
Managed.over = 1
Managed.over_no_get = 2
Managed.non_over = 3
Print (Managed.over,managed.over_no_get,managed.non_over) #1 2 3 "parsing" reveals another non-equivalence of read-write properties: The operation of a read-class property can be defined by dependency on a managed class with __get__ method, but the operation of the Write class property is not handled by a descriptor that has the __SET__ method defined on the managed class
#... To control the operation of setting class properties, attach the descriptor to the class class, which is attached to the Meta class. By default, for a user-defined class, its meta-class is type, and we cannot add a property to the type. But in the 21st chapter, we'll create our own meta-class.







#方法是描述符
#方法是非覆盖性描述符
obj = Managed ()
Print (Obj.spam) #<bound method Managed.spam of <__main__. Managed object at 0x000000000385f860>> "Parse" Obj.spam Gets the Binding method object
Print (managed.spam) #<function managed.spam at 0x00000000038d7b70> "parse" but Managed.spam gets the function
Obj.spam = 7
Print (obj.spam) #7 The parse function does not implement the __set__ method, so the non-overriding descriptor
‘‘‘
Obj.spam and Managed.spam get a different
Object. As with descriptors, when accessed through a managed class, the __get__ method of the function returns its own reference. But
Yes, when accessed through an instance, the __get__ method of the function returns a bound method object: a callable Pair
Like, the function is wrapped and the managed instance (for example, obj) is bound to the first parameter of the function (that is,
Self), which is consistent with the behavior of the Functools.partial function
‘‘‘

#20.3 method is a descriptor

Import Collections
Class Text (collections. userstring):
def __str__ (self):
Return ' Text ({!r}) '. Format (self.data)

def reverse (self):
return Self[::-1]

Word = Text (' Forward ')
Print (word) #Text (' Forward ')
Print (Word.reverse ()) #Text (' Drawrof ')
Print (Text.reverse (word)) #Text (' drawrof ') "parsing" calls a method on a class equivalent to calling a function
Print (Type (text.reverse), type (word.reverse)) #<class ' function ' > <class ' method ' >
Print (List (map (text.reverse,[' repaid ', (10,20,30), Text (' stressed ')]))) #[' diaper ', (+, ten), ' desserts ')
Print (text.reverse.__get__ (word)) #<bound method Text.reverse of ' forward ' > ' parse ' function are non-blanket descriptors. When you call the __get__ method on a function, you pass in the instance, and you get the method that is bound to that instance.
Print (text.reverse.__get__ (none,word)) #<function text.reverse at 0x0000000001e8cd08> "resolves" the __get__ method of the calling function, if The value of the instance parameter is None, then the function itself is obtained.
Print (Word.reverse) #<bound method Text.reverse of ' forward ' >
Print (word.reverse.__self__) #Text (' Forward ')
Print (word.reverse.__func__ is text.reverse) #True
The __func__ property of the Bind method is a reference to the original function attached to the managed class.
The binding method object also has a __call__ method that handles the actual invocation process. This method will invoke the
The __func__ property refers to the original function that sets the first argument of the function to the __self__ property of the bound method.
This is how the formal parameter self is implicitly bound.
The function becomes a binding method, which is the best example of the Python language underlying usage descriptor.
‘‘‘



#20.4 Descriptor Usage Recommendations
‘‘‘
Here are some practical conclusions based on the descriptor features just discussed.
Use features to keep it simple
The built-in property class is actually a blanket descriptor, and the __set__ method and the __get__ method are all
Implementation, even if the set value method is not defined. The __set__ method of the attribute is thrown by default
Attributeerror:can ' t set attribute, so the simplest way to create a read-only property is to use the special
To avoid the problems described in the next article.
Read-only descriptors must have a __set__ method
If you use a descriptor class to implement a read-only attribute, remember that the two methods of __get__ and __set__ must be set
Otherwise, the property with the same name for the instance obscures the descriptor. The __set__ method of a read-only property simply throws
Attributeerror the exception and provides the appropriate error message.
Python provides inconsistent error messages for such exceptions. If you attempt to modify the C.real property of the complex, the resulting error message is
Attributeerror:read-only attribute; However, if you try to modify C.conjugat (the method of the E-complex object), you get
The error message was attributeerror: ' Complex ' object attribute ' conjugate ' is read-only.
The descriptor used for validation can be only __set__ method
For a validation-only descriptor, the __set__ method should check the value obtained by the value parameter, if any
, use the name of the descriptor instance as the key and set it directly in the __dict__ property of the instance. Thus, from the instance
Reading a property of the same name is fast because it is not processed by the __get__ method. See the code in example 20-1.
A descriptor with only the __get__ method can be used for efficient caching
If you write only the __get__ method, the non-overwrite descriptor is created. This descriptor can be used to
Row some resource-intensive calculations, and then set the same name property for the instance to cache the results. Instance property with the same name obscures the description
The __dict__ property of the instance, so that subsequent accesses will get the value directly from the
__get__ method.
Non-specific methods can be obscured by instance properties
Because functions and methods implement only the __get__ method, they do not handle assignment operations for instance properties of the same name.
Therefore, after a simple assignment like My_obj.the_method = 7, subsequent access through the instance
The_method gets the number 7--but does not affect the class or other instance. However, special methods are not subject to this question.
The impact of the problem. The interpreter will only look for special methods in the class, that is to say, repr (x) is actually performing
X.__CLASS__.__REPR__ (x), so the __repr__ property of X has no effect on the repr (x) method call.
For the same reason, the __getattr__ property of an instance does not break the regular property access rule.
The non-specific methods of the instance can be easily overwritten, which may sound unreliable and error-prone, but when I use Python
Has never been bothered in 15 years. However, if you are creating a large number of dynamic properties, the property name is never controlled by your
Data (as earlier in this chapter), you should know this behavior; maybe you can implement some kind of machine
Filter or escape the name of a dynamic property to maintain data integrity.
The Frozenjson class in example 19-6 does not have an issue with the instance property masking method, because the
Class has only a few special methods and a Build class method. Class methods are safe as long as they are accessed through the class.
In example 19-6, that's how I called the Frozenjson.build method--in example 19-7.
__new__ method. The Record class (see Example 19-9 and Example 19-11) and its subclasses are also safe because
Only special methods, class methods, static methods, and attributes are used. attribute is a data descriptor and therefore cannot be
Instance property overrides.
There are two features discussed in this discussion, and the descriptors discussed here are not yet covered, so before we end this chapter we will say: Document
and handling of deleted managed properties
‘‘‘


"""




































"Python" "Meta Programming" "Two" "Descriptors"

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.