classmethod一个用处是创建可选类构造器

Definition and Introduction
通常来说, descriptor 是一种绑定着特殊行为属性的对象, 在访问它时行为被descriptor协议定义的方法所重载。这些方法是__get__, __set__ 和__delete__。 如果对象定义了任一方法,这个对象就被叫做descriptor.
访问对象的属性默认行为是get, set或delete对象字典中的属性。例如, a.x查找路径是从a.__dict__[‘x‘]开始,然后是type(a).__dict__[‘x‘],并继续查找type(a)的祖先类(不包括metaclass).如果查找的值是定义了descriptor方法的对象,python可能会重载默认行为转而调用descriptor方法。这个行为在哪里发生取决于定义了哪些descriptor方法。注意,descriptor仅在新型对象或类中有效(新型类是继承于object或type的类)
descriptor是个功能强大,通用的协议。它是实现properties, method, static methods, class methods 和 super()功能背后的机制。它被python自己用来实现在2.2版本中引入的新型类。descriptors简化底层c代码并为python程序提供一套灵活的工具。
Descriptor Protocol
descr.__get__(self, obj, type=None) --> value

descr.__set__(self, obj, value) --> None

descr.__delete__(self, obj) --> None

所有东西就这些。定义其中任一方法的对象都被认为是descriptor并重载作为属性被查找时默认行为。
如果对象同时定义了__get__和__set__, 它就被认为是一个data descriptor. 如果仅定义__get__则被称为non-data descriptor(通常用于methods,但也可做其他用途)
data和non-data descriptor不同之处在于,对于实例的字典中的实体,关心的是如何重载。如果实例字典中有一个与data decriptor同名的实体, data descriptor优先.如果是与non-data descriptor同名, 字典实体优先。
如要创建只读的data descriptor, 同时定义__get__和__set__, 然后在__set__里抛出一个AttributeError异常。定义会抛出异常的__set__方法就能保证它是data descriptor.
Invoking Descriptors
descriptor可以直接用它的方法名调用, 例如d.__get__(obj)。
另外,更常见的调用方式是通过访问属性后自动被调用。例如, obj.d看起来是在obj的字典中查找d。如果d定义了__get__方法, 则d.__get__(obj)会根据下面的优先规则被调用。
调用的细节取决于obj是对象还是类。无论怎样, descriptor只在新型类中起作用。如果一个类是object的子类,它就是新型类。
对于对象,机制在于object.__getattribute__, 它转变b.x成type(b).__dict__[‘x‘].__get__(b, type(b)).执行顺序沿着一个优先级链: data descriptor优于实例变量,实例变量优于non-datadescriptor,如有__getattr__则给予其最低的优先级。C实现可以在Object/object.c的PyObject_GeneriGetAttr()中找到。
对于类, 机制在于type.__getattribute__, 它转变B.x成B.__dict__[‘x‘].__get__(None, B)。 在python中,看起来类似:
def __getattribute__(self, key):
"Emulate type_getattro() in Objects/typeobject.c"
v = object.__getattribute__(self, key)
if hasattr(v, ‘__get__‘):
return v.__get__(None, self)
return v
需要记住的要点是:
descriptors 是由__getattribute__调用的
重载__getattribute__会阻止了descriptor的自动调用
__getattribute__是只有新型类和对象才有的
object.__getattribute__ 和 type.__getattribute__调用__get__是不一样的
data descriptor会重载实例字典
non-data descriptor 会被实例字典重载
由super()返回的对象为调用descriptors,也有个自定义的__getattribute__。 调用super(B, obj).m() 会搜索obj.__class__.__mro__, 先是基类A,马上是B然后返回A.__dict__[‘m‘].__get(obj, A)。如果m不是descriptor, 返回的m不变.如果不在字典中, m再使用object.__getattribute__重新查找。
请注意, 在python2.2中, 如果仅当m是data descriptor,super(B,obj).m()才会调用__get__()。而在python2.3中,是non-data descriptor也会被调用,除非涉及到旧型(经典)类。实现细节在objects/typeobject.c的super_getattro(),相等python版本可在guido的指南中找到。
上述详细展示了descriptor机制和细节都是内嵌到object, type和super()的__getattribute__方法中。类会继承这些机制当类继承于object或他们的meta-class能提供相似功能时。同样,类可以通过重载__getattribute__关闭descriptor调用。
Descriptor Example
下面的代码创建了一个是data descriptor的类,它在每次get或set时都打印一条消息。如想更改所有属性行为,重载__getattribute__是一个另一种方法。然而,如只想监控几个选定的属性,descriptor非常有用。
class RevealAccess(object):
"""A data descriptor that sets and returns values
normally and prints a message logging their access.
"""
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
Retrieving var "x"
10
>>> m.x = 20
Updating var "x"
>>> m.x
Retrieving var "x"
20
>>> m.y
5

descriptor 协议非常简单并且提供令人兴奋的各种可能性。几个用例是如此通用以至于他们都被打包成单独的涵数调用。properties, bound 和 unbound 方法,static方法, class方法都是基于descriptor 协议。

Properties
调用property()是构建data descriptor(存取属性会触发函数)的一个简洁方式。它的标志是:
property(fget=None, fset=None, fdel=None, doc=None) -> property attribute
下面展示了一个典型应用:
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‘m the ‘x‘ property.")
property() python版实现:
class Property(object):
"Emulate PyProperty_Type() in 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, "unreadable attribute"
return self.fget(obj)
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError, "can‘t set attribute"
self.fset(obj, value)
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError, "can‘t delete attribute"
self.fdel(obj)
内建property()非常有用,当用户接口用来保证属性可用或在随后改变时需要干预。
比如, 一个spreadsheet类打算通过Cell(‘b10‘).value访问一个cell的值, 程序要求在每次访问之后随之重新计算cell的值;然而,程序员不希望客户端代码直接存取属性。解决方法是包装属性成一个property data descriptor.
class Cell(object):
. . .
def getvalue(self, obj):
"Recalculate cell before returning value"
self.recalc()
return obj._value
value = property(getvalue)
Functions and Methods
python的面向对象特性是建立于基于函数的环境。因为有了non-data descriptor,函数和方法完美统一。
类字典中存储函数成方法。在类定义中, 定义方法使用def和lambda, 如同创建函数一样。唯一不一样的地方就是第一个参数是预留给对象实例的。按python惯例,实例引用被叫做self,但是也可以叫做this或其它变量名。
为了支持方法调用, 含有__get__()的函数绑定成方法。这意味着,所有函数都是non-data descriptor,它们会返回绑定方法还是非绑定方法取决于调用者是对象还是类。python版本如下:
class Function(object):
. . .
def __get__(self, obj, objtype=None):
"Simulate func_descr_get() in Objects/funcobject.c"
return types.MethodType(self, obj, objtype)
在解释中展示function descriptor是如何实际工作的:
>>> class D(object):
def f(self, x):
return x
>>> d = D()
>>> D.__dict__[‘f‘] # Stored internally as a function
<function f at 0x00C450702881064151>
>>> D.f # Get from a class becomes an unbound method
<unbound method D.f>
>>> d.f # Get from an instance becomes a bound method
<bound method D.f of <__main__.D object at 0x00B18C90>>
输出显示bound和unbound方法是两个不同类型。而它们可能就是如此被实现, 在实际的实现C代码(Objects/classobjec.c的PyMethod_Type)中,是一个对象,两种不同表述,取决于im_self字段被设置与否.
同样, 调用方法的效果也依赖于im_self. 如果im_self有值(绑定),原始函数(存在im_func)被调用且第一个参数设置为实例。如果未绑定, 所有参数不作更改传给原始函数。实际C实现代码instancemethod_call看起来仅稍微复杂,包括了一些类型检查。
Static Methods and Class Methods
non-data descriptor提供一种简单机制把函数绑定成方法
为了复用, 函数含有一个__get__(), 所以当被当作属性访问时,他们转化成了方法。non-data descriptor将obj.f(*args)转换成f(obj, *args), 而调用klass.f(*args)变成调用f(*args).
这个图表总结这两个变种:
Transformation Called from an Object Called from a Class
function f(obj, *args) f(*args)
staticmethod f(*args) f(*args)
classmethod f(type(obj), *args) f(klass, *args)
static method不做任何改变返回下面的函数。调用c.f或C.f都是相当于查找到object.__getattribute__(c, "f")或object.__getattribute__(C, "f"). 结果是, 函数无论从对象或类访问都变得一样。
static method的方法好处是不需要引用self变量
例如, 一个统计包可能包含一个处理实验数据的容器类。类提供正常方法来计算均值,中值和其他基于数据的统计公式。然而, 可能有些函数是概念相关去独立于数据的,如erf(x)在统计工作中是一个便利转换程式却不直接依赖于数据。它既能从对象调用s.erf(1.5)又能从类调用Sample.erf(1.5).
>>> class E(object):
def f(x):
print x
f = staticmethod(f)
>>> print E.f(3)
3
>>> print E().f(3)
3
python版本staticmethod如下:
class StaticMethod(object):
"Emulate PyStaticMethod_Type() in Objects/funcobject.c"
def __init__(self, f):
self.f = f
def __get__(self, obj, objtype=None):
return self.f
不同于static method, class method在调用函数前预先在参数列表中有类引用。格式是一样,无论调用者是对象还是类。
>>> class E(object):
def f(klass, x):
return klass.__name__, x
f = classmethod(f)
>>> print E.f(3)
(‘E‘, 3)
>>> print E().f(3)
(‘E‘, 3)
当函数仅需要类引用而不关心任何实例数据时这种行为非常有用。classmethod一个用处是创建可选类构造器。 在python2.3, 使用classmethod dict.fromkeys() 创建从keys列表中一个新字典, 如下:
class Dict:
. . .
def fromkeys(klass, iterable, value=None):
"Emulate dict_fromkeys() in Objects/dictobject.c"
d = klass()
for key in iterable:
d[key] = value
return d
fromkeys = classmethod(fromkeys)
现在可以如下创建一个新dict:
>>> Dict.fromkeys(‘abracadabra‘)
{‘a‘: None, ‘r‘: None, ‘b‘: None, ‘c‘: None, ‘d‘: None}
python版本classmethod如下:
class ClassMethod(object):
"Emulate PyClassMethod_Type() in 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

时间: 2024-10-21 15:50:49

classmethod一个用处是创建可选类构造器的相关文章

java如何在文件中读取一个字符串并创建以这个字符为名字的类的对象

<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">我们一般用properties或者XML文件作为资源存储的文件,现在主要介绍一下对properties的操作</span> 1.我们在src文件下新建一个名称为config的包 2.在config下新建一个file,把它命名为demo.properties 3. 把下面的几段

构建一个学生Student,根据类Student的定义,创建五个该类的对象,输出每个学生的信息,计算并输出这五个学生Java语言成绩的平均值,以及计算并输出他们Java语言成绩的最大值和最小值。

定义一个表示学生信息的类Student,要求如下: (1)类Student的成员变量: sNO 表示学号: sName表示姓名: sSex表示性别: sAge表示年龄: sJava:表示Java课程成绩. (2)类Student带参数的构造方法: 在构造方法中通过形参完成对成员变量的赋值操作. (3)类Student的方法成员: getNo():获得学号: getName():获得姓名: getSex():获得性别: getAge()获得年龄: getJava():获得Java 课程成绩 根据类

Swift游戏实战-跑酷熊猫 02 创建熊猫类

原文:Swift游戏实战-跑酷熊猫 02 创建熊猫类 要点: 如何继承SKSpriteNode :子类必须调用SKSpriteNode的一个指定构造器 init(){ super.init(texture:texture,color:UIColor.whiteColor(),size:size) } 设置场景的背景颜色: self.backgroundColor = SKColor(red:113/255,green:197/255,blue:207,alpha:1) 熊猫类实例化以及定位 @l

使用MyEclipse反向工程快速创建持久化类、映射文件和Hibernate组件

在MyEclipse中创建连接模板 一.选中此项 二.右键新建一个连接模板 三.进入此页面依次选择数据库.Driver name 为这个连接模板的名字.点击Add JARs选择驱动类(在oracle下的安装目录下的ojdbc6.jar).在Save password可选 (是否保存密码)然后点Next 四.选择数据库模式,点Add,选择SCOTT模式,Finsh. 创建Hibernate配置文件一.选择项目 二.如果没有对应的版本.去掉所有的√,点Next 三.如果存在xml文件选择Existi

关于Java中基类构造器的调用问题

在<Java编程思想>第7章复用类中有这样一段话,值得深思.当子类继承了父类时,就涉及到了基类和导出类(子类)这两个类.从外部来看,导出类就像是一个与基类具有相同接口的新类,或许还会有一些额外的方法和域.但继承并不只是复制基类的接口.当创建一个导出类对象时,该对象包含了一个基类的子对象,这个子对象与你用基类直接创建的对象是一样的,二者区别在于,后者来自于外部,而基类的子对象是被包裹在导出类对象内部. 这就引发出了一个很重要的问题,对基类子对象的正确初始化也是至关重要的(我们可能在子类的使用基类

API是什么?有何用处,其中常用类

API(application programming interface)        如何在新项目中使用原来的类            如何生产:            1.打开原项目            2.项目名->右键->export->java-JAR file            3.选择要打包的类            4.JAR file:选择生成的jar包在什么位置(自己写文件名)            5.完成,会在指定位置有一个.jar包 如何使用:    

C#在某个线程上创建的控件不能成为在另一个线程上创建的控件的父级

首先在form1的窗体载入中新建了一个Class1对象并将本身的引用传递进入其构造函数,然后在Class1的构造函数中创建一个线程.该线程所代理的方法事件是本类中的一个add方法.而add方法的内容则是在form1上放一个textbox.然而这个流程你需要注意的有几个问题:1.哪个是主线程?所谓主线程是第一个启动的线程,是从main开始的.form1的这个窗体是由主线程创建的.2.Thread t的线程是什么?t是由主线程创建的,t的操作内容是在由主线程创建的窗体上放一个textbox.也就是说

如何:从代码创建 UML 类图(ZZ)

您拖动的一个或多个类将显示在关系图上. 它们依赖的类将显示在"UML 模型资源管理器"中. 参见 模型表示类型的方式. 将程序代码中的类添加到 UML 模型 打开一个 C# 项目. 将一个 UML 类图.解决方案: 在"体系结构"菜单上,选择"新建关系图". 在"添加新关系图"对话框中选择"UML 类图". 如果您还没有,将建模项目创建. 打开"体系结构资源管理器": 在"体系

一天一个Java基础——对象和类

1.在Java中你所做的全部工作就是定义类,产生那些类的对象,以及发送消息给这些对象 2.可以在类中设置两种类型的元素:字段(也被称作数据成员)和方法(也被称作成员函数) 3.字段可以是任何类型的对象,可以通过其引用与其进行通信:也可以是基本类型中的一种.如果字段是对某个对象的引用,那么必须初始化该引用,以便使其与一个实际的对象向关联(使用new来实现) 4.可以把两个类放在同一个文件中,但是文件中只能有一个类是公共的.此外,公共类必须与文件同名 1.1 构造方法构造对象 构造方法是一种特殊的方