Bind the instance argument of a descriptor method to the calling object instance
在第二个参数描述符,和一
1 2 3 4 5 6 7 8 9 10 | class Desc(): def __get__(self,instance,owner): print("I was called by",str(instance),"and am owned by",str(owner)) return self class Test(): desc = Desc() t = Test() t.desc |
我会去了解如何创建一个可以绑定到另一个,第二个参数的广义方法(比其他)
实例(例如,不只是一个东西,我真的想)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | class Length(object): '''Descriptor used to manage a basic unit system for length''' conversion = {'inches':1,'centimeters':2.54,'feet':1/12,'meters':2.54/100} def __set__(self,instance,length): '''length argument is a tuple of (magnitude,unit)''' instance.__value = length[0] instance.__units = length[1] def __get__(self,instance,owner): return self @MagicalDecoratorOfTruth def get_in(self, instance, unit): #second argument is bound to instance object '''Returns the value converted to the requested units''' return instance.__value * (self.conversion[units] / self.conversion[instance.__units]) class Circle(object): diameter = Length() def __init__(self,diameter,units): Circle.diameter.__set__((diameter,units)) c = Circle(12,'inches') assert c.diameter.get_in('feet') == 1 c.diameter = (1,'meters') assert c.diameter.get_in('centimeters') == 100 |
我有一个尝试是一个单向的
1 2 3 4 5 6 7 | class Test(): @classmethod def myclassmethod(klass): pass t = Test() t.myclassmethod() |
然而,我不确定这如何应用到上述的情况。
一种方式,以避免整个问题的实例,对象是通过明确的描述方法。
1 2 3 4 | c = Circle(12,'inches') assert c.diameter.get_in(c,'feet') == 1 c.diameter = (1,'meters') assert c.diameter.get_in(c,'centimeters') == 100 |
然而,这似乎真的violate d.r.y.启动,和丑陋的。
型
对于这类事情,描述符协议中还有一个钩子——也就是说,当从类级别访问描述符对象时,EDOCX1的值(0)将是
在相反的方向思考这个是很有用的。我们从
1 2 3 4 | class Circle(object): diameter = Length() def __init__(self, diameter, units): self.diameter = (diameter, units) |
注意,我并没有试图手动调用
现在,对于描述符来说,实际上一切都是一样的。我清理了用于转换
但对于
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | class Length(object): conversion = {'inches':1.0, 'centimeters':2.54, 'feet':1.0/12, 'meters':2.54/100} def __set__(self, instance, length): instance.__value = length[0] instance.__units = length[1] def __get__(self, instance, owner): if instance is None: return self return (instance.__value, instance.__units) def get_in(self, instance, units): c_factor = self.conversion[units] / self.conversion[instance.__units] return (c_factor * instance.__value, units) |
号
现在,我们可以掌握实际的
1 2 3 4 5 6 | # This works and prints the conversion for `c`. c = Circle(12, 'inches') Circle.diameter.get_in(c, 'feet') # This won't work because you short-circuit as soon as you type `c.diameter` c.diameter.get_in('feet') |
避免需要离开实例的一个选项是monkey patch一个使用
1 2 3 4 5 6 7 | class Circle(object): diameter = Length() def __init__(self, diameter, units): self.diameter = (diameter, units) self.convert = lambda attr, units: ( getattr(self.__class__, attr).get_in(self, units) ) |
。
现在实例
1 2 | >>> c.convert('diameter', 'feet') (1.0, 'feet') |
您可以将
但是在一天结束的时候,你仍然需要非常小心。从表面上看,这看起来很吸引人,但实际上你在你的对象之间增加了很多耦合。从表面上看,它可能看起来像是你正在把对单位转换的关注从对对象"成为一个圆"的关注中分离出来——但实际上你在增加其他程序员必须解决的复杂层次。你要把你的类和这个特殊的描述符结合起来。如果有人在重构中确定直径转换作为一个完全在
在一天结束的时候,你还必须问这会给你带来什么。据我所知,在您的示例中,除了能够作为所谓的"fluent interface"设计风格的一部分诱导转换计算的非常小的便利性外,它什么都不买…例如,副作用和函数调用似乎只是属性访问。
就我个人而言,我不喜欢这种语法。我更愿意使用像
1 | convert(c.diameter, 'feet') |
。
比
1 | Circle.diameter.convert('feet') |
。
像第一个版本这样的函数通常是在模块级别上运行的,它们可以在它们将要操作的类型上进行一般化。可以对它们进行扩展,以便更容易地处理新类型(如果希望继承函数,还可以将它们封装到它们自己的单独类中)。通常,它们也更容易测试,因为调用它们所需的机器要少得多,而且测试模拟对象可以更简单。事实上,在像python这样的动态类型语言中,允许像
这并不是说一种方法肯定比另一种更好。一个好的设计师可以在任何一种方法中找到优点。一个糟糕的设计师可能会把这两种方法搞得一团糟。但总的来说,我发现当这些异常的python角用于解决非异常的常规问题时,常常会导致混乱。
型
多亏了prpl.mnky.dshwshr的帮助,我能够极大地改进整个方法(并在这个过程中了解了很多描述符)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | class Measurement(): '''A basic measurement''' def __new__(klass,measurement=None,cls_attr=None,inst_attr=None,conversion_dict=None): '''Optionally provide a unit conversion dictionary.''' if conversion_dict is not None: klass.conversion_dict = conversion_dict return super().__new__(klass) def __init__(self,measurement=None,cls_attr=None,inst_attr=None,conversion_dict=None): '''If object is acting as a descriptor, the name of class and instance attributes associated with descriptor data are stored in the object instance. If object is not acting as a descriptor, measurement data is stored in the object instance.''' if cls_attr is None and inst_attr is None and measurement is not None: #not acting as a descriptor self.__measurement = measurement elif cls_attr is not None and inst_attr is not None and measurement is None: #acting as a descriptor self.__cls_attr = cls_attr self.__inst_attr = inst_attr #make sure class and instance attributes don't share a name if cls_attr == inst_attr: raise ValueError('Class and Instance attribute names cannot be the same.') else: raise ValueError('BOTH or NEITHER the class and instance attribute name must be or not be provided. If they are not provided, a measurement argument is required.') ##Descriptor only methods def __get__(self,instance,owner): '''The measurement is returned; the descriptor itself is returned when no instance supplied''' if instance is not None: return getattr(instance,self.__inst_attr) else: return self def __set__(self,instance,measurement): '''The measurement argument is stored in inst_attr field of instance''' setattr(instance,self.__inst_attr,measurement) ##Other methods def get_in(self,units,instance=None): '''The magnitude of the measurement in the target units''' #If Measurement is not acting as a descriptor, convert stored measurement data try: return convert( self.__measurement, units, self.conversion_dict ) except AttributeError: pass #If Measurement is acting as a descriptor, convert associated instance data try: return convert( getattr(instance,self.__inst_attr), units, getattr(type(instance),self.__cls_attr).conversion_dict ) except Exception: raise def to_tuple(self,instance=None): try: return self.__measurement except AttributeError: pass return getattr(instance,self.inst_attr) class Length(Measurement): conversion_dict = { 'inches':1, 'centimeters':2.54, 'feet':1/12, 'meters':2.54/100 } class Mass(Measurement): conversion_dict = { 'grams':1, 'pounds':453.592, 'ounces':453.592/16, 'slugs':453.592*32.1740486, 'kilograms':1000 } def convert(measurement, units, dimension_conversion = None): '''Returns the magnitude converted to the requested units using the conversion dictionary in the provide dimension_conversion object, or using the provided dimension_conversion dictionary. The dimension_conversion argument can be either one.''' #If a Measurement object is provided get measurement tuple if isinstance(measurement,Measurement): #And if no conversion dictionary, use the one in measurement object if dimension_conversion is None: dimension_conversion = measurement.conversion_dict measurement = measurement.to_tuple() #Use the dimension member [2] of measurement tuple for conversion if it's there if dimension_conversion is None: try: dimension_conversion = measurement[2] except IndexError: pass #Get designated conversion dictionary try: conversion_dict = dimension_conversion.conversion_dict except AttributeError: conversion_dict = dimension_conversion #Get magnitude and units from measurement tuple try: meas_mag = measurement[0] meas_units = measurement[1] except (IndexError,TypeError): raise TypeError('measurement argument should be indexed type with magnitude in measurement[0], units in measurement[1]') from None #Finally perform and return the conversion try: return meas_mag * (conversion_dict[units] / conversion_dict[meas_units]) except IndexError: raise IndexError('Starting and ending units must appear in dimension conversion dictionary.') from None class Circle(): diameter = Length(cls_attr='diameter',inst_attr='_diameter') def __init__(self,diameter): self.diameter = diameter class Car(): mass = Mass(cls_attr='mass',inst_attr='_mass') def __init__(self,mass): self.mass = mass c = Circle((12,'inches')) assert convert(c.diameter,'feet',Length) == 1 assert Circle.diameter.get_in('feet',c) == 1 assert c.diameter == (12,'inches') d = Circle((100,'centimeters',Length)) assert convert(d.diameter,'meters') == 1 assert Circle.diameter.get_in('meters',d) == 1 assert d.diameter == (100,'centimeters',Length) x = Length((12,'inches')) assert x.get_in('feet') == 1 assert convert(x,'feet') == 1 |