python的类及对象

python中一切皆为对象,不管是类,还是实例,甚至是创建类的元类

名字空间
类和函数很相似,他们都定义了名字空间,他们都允许在声明中创建函数和闭包

类的属性
类有数据属性和非数据属性,数据属性相当于C++中的类变量,非数据属性一般就是类的方法。类有很多特殊的数据属性,例如__dict__属性,它是一个字典,包含了所有类的属性及其值,__module__属性存储类所在的module名称字符串,__class__属性存储类的名称字符串。类也有很多特殊的方法,例如__new__,__delete__,含有很多可用于重载操作符的特殊方法,所谓的重载,实际上是继承类的方法覆盖了基类的同名方法,如果把派生类的方法删除,那么下次调用的就是基类的同名方法
属性的查找顺序:instance attr -> class attr -> base class attr

实例的属性
类有属性,实例也可以有自己的属性,同时实例的__dict__属性列举的是实例自己的属性字典,与类的__dict__不同

绑定
有两种类型的绑定,一种是函数与实例的绑定,绑定之后,函数被称为方法。还有一种绑定是类与实例的绑定,如果类与实例没有绑定,那么它就是一个类对象,如果绑定则是一个实例对象,具体例子见super

super
有两种super的使用示例:
1.super中第二个参数传入实例对象
class Foo(object):
     def __init__(self):
          super(Foo,self).__init__(self)
确保isinstance(self,Foo)为true
2.super中第二个参数传入派生类对象
class RoundFloat(float):
     def __new__(cls,value):
          super(RoundFloat,cls).__new__(cls,value)
确保issubclass(cls,RoundFloat)为true

MRO
在多重继承的情况下,类的__mro__属性,可以显示类查找属性的顺序,使用多重继承,可以实现类似java中接口的概念,是解构耦合的好办法

private
使用__作为前缀的属性,在运行时被改写为_classname__attrname,在一定程度上实现了私有化
使用_作为前缀的属性,在from mymodule import *的时候,不会被导出

__getattr__
如果你在类中定义了__getattr__方法,那么在上述属性查找顺序instance attr -> class attr -> base class attr,失败之后,将会调用该类的__getattr__方法
class Example(object):
     def __init__(self,pts):
          self.pts=pts
     def __getattr__(self,attr):
          return getattr(self.pts,attr)

__slots__类属性
使用__slots__类属性,能够防止类用户创建__slots__属性规定的属性之外的属性,如果指定了slots属性,那么将不再有dict属性,另外,__slots__是类属性,如果实例指定了__slots__是不起作用的,必须在类中指定

描述符
描述符本身是一个定义了__get__,__set__,__delete__方法的类,当然也可以只定义其中几个。它作为一个类属性定义在类中。它实际上是一个属性代理,对属性的获取,设置,删除,被类的__getattribute__特殊方法,转换为对描述符的相应函数的调用,例如,假设类X定义了类属性foo,并设置它为一个描述符,那么它的实例x,访问foo属性,即x.foo被__getattribute__转换为:
type(x).__dict__['foo'].__get__(x,type(x))
注意这里传给get的参数,实例x被传入了,这就把描述符与X的实例相关联了,如果是类调用了属性,则传给__get__方法的是__get__(None,X),看如下的例子:
>>> class desc(object):
    def __init__(self,value):
        self.val=value
    def __get__(self,obj,typ=None):
        print 'getting %s' % (obj)
        return obj.__dict__[self.val]
    def __set__(self,obj,value):
        print 'setting %s=%s' % (obj,value)
        obj.__dict__[self.val]=value

>>> class Test(object):
    foo=desc('hangzhou')

>>> a=Test()
>>> a.foo  #第一次访问foo属性,因为没有设置值导致失败
getting <__main__.Test object at 0xb56aabcc>

Traceback (most recent call last):
  File "<pyshell#46>", line 1, in <module>
    a.foo
  File "<pyshell#40>", line 6, in __get__
    return obj.__dict__[self.val]
KeyError: 'hangzhou'
>>> a.foo='sd'
setting <__main__.Test object at 0xb56aabcc>=sd
>>> a.foo
getting <__main__.Test object at 0xb56aabcc>
'sd'
>>> b.foo='jh'
>>> b.foo
'jh'
>>> a.foo  #从这里可以看到在第二个实例b设置了foo属性之后,a实例的foo属性没有改变,这些都是由于通过描述符,把属性的值存储在各个实例的内部了
getting <__main__.Test object at 0xb56aabcc>
'sd'
>>>

属性
典型用例
使用property函数来控制对类中的属性的访问,正常的使用property的示例如下:
>>> class MyProperty(object):
    def __init__(self,val):
        self.__x=val
    def get_x(self):
        return self.__x
    def set_x(self,val):
        self.__x=val
    x=property(get_x,set_x)
   
>>> m=MyProperty(10)
>>> m.x
10
>>> m.x=100
>>> m.x
100
描述符模拟property
实际上property实现对属性的访问,是一种特殊的描述符,我们知道描述符是属性的代理,property实际上是把属性代理给类的实例,看下面使用描述符模拟实现的proerty
>>> class MyProper(object):
    def __init__(self,getf=None,setf=None,delf=None):
        self.getf=getf
        self.setf=setf
        self.delf=delf
    def __get__(self,obj,typ=None):
        return self.getf(obj)
    def __set__(self,obj,val):
        self.setf(obj,val)

        
>>> class MyTest(object):
    def __init__(self,val):
        self.__x=val
    def get_x(self):
        return self.__x
    def set_x(self,val):
        self.__x=val
    x=MyProper(get_x,set_x)


>>> mt=MyTest(111)
>>> mt.x=222
>>> mt.x
222
>>> my=MyTest(444)
>>> my.x
444
>>> my.x=555
>>> my.x
555
>>> mt.x
222
这个版本中MyTest.__dict__中可以看到get_x和set_x方法,类用户甚至可以直接调用这两个函数进行赋值,这显然不符合我们的预期

隐藏属性访问方法
>>> class MyClass(object):
    def __init__(self,val):
        self._x=val
    
    def x():
        def fget(self):
            return self._x
        return locals()
    x=property(**x())

    
>>> mc=MyClass(77)
>>> mc.x
77
>>> mc.x=100

Traceback (most recent call last):
  File "<pyshell#150>", line 1, in <module>
    mc.x=100
AttributeError: can't set attribute
在这个例子中使用了未绑定函数x,在其中定义了property函数所需要的函数,并把它作为关键字参数传递给property,从而定义了描述符,由于get函数是在函数x的命名空间中,所以在类Myclass中是看不到的,这就达到了隐藏属性访问方法的目的

新版本中隐藏属性访问方法
在新版本中给出了更好的实现方式
>>> class NewProper(object):
    def __init__(self,value):
        self.__x=value
    @property
    def x(self):
        return self.__x
    @x.setter
    def x(self,val):
        self.__x=val


>>> n=NewProper(2)
>>> n.x
2
>>> n.x=10
>>> n.x
10

元类
类本身就是一个对象,元类就是用来创建类的。
类的创建过程
class Foo(object):
     def __init__(self,x):
          self.x = x
     def blah(self):
          print "hello world"
这个类的创建过程可以用python描述如下:
class_name = 'Foo'
class_parents=(object,)
class_body='''
def __init__(self,x):
          self.x = x
def blah(self):
          print "hello world"
'''
class_dict={}
exec(class_body,globals(),class_dict)
Foo=type(class_name,class_parents,class_dict)
对exec的解释如下:
execfile ( filename [, globals [, locals ] ] )

This function is similar to the exec statement, but parses a file instead of a string. It is different from the import statement in that it does not use the module administration — it reads the file unconditionally and does not create a new module. [1]

The arguments are a file name and two optional dictionaries. The file is parsed and evaluated as a sequence of Python statements (similarly to a module) using the globals and locals dictionaries as global and local namespace. If provided, locals can be any mapping object. Remember that at module level, globals and locals are the same dictionary. If two separate objects are passed as globals and locals, the code will be executed as if it were embedded in a class definition.

Changed in version 2.4: formerly locals was required to be a dictionary.

If the locals dictionary is omitted it defaults to the globals dictionary. If both dictionaries are omitted, the expression is executed in the environment where execfile() is called. The return value is None.

可见exec函数,在提供了globals和locals两个参数之后,代码就像嵌入在类中一样,也就是说,在exec执行后,在class_dict中包含了__init__和blah这两个key

对type的解释如下:

在python文档的built-in functions中对type函数有如下描述:
type ( object ) type ( name, bases, dict )

With one argument, return the type of an object. The return value is a type object. The isinstance() built-in function is recommended for testing the type of an object.

With three arguments, return a new type object. This is essentially a dynamic form of the class statement. The name string is the class name and becomes the __name__ attribute; the bases tuple itemizes the base classes and becomes the __bases__ attribute; and the dict dictionary is the namespace containing definitions for class body and becomes the __dict__ attribute. For example, the following two statements create identical type objects:

>>>
>>> class X(object):
...     a = 1
...
>>> X = type('X', (object,), dict(a=1))
在使用三个参数的时候,分别指定了类名,基类列表,属性字典。使用type函数,我们可以动态创建一个类,这也是metaclass的实现基础
通过type函数动态生成了类Foo,上面这段代码很好的描述了类的创建过程,也是我们对类属性,namespace,类绑定和类的本身有了更好的理解

最后一步调用type创建类是可以自定义的,其优先级从高到低如下:
1.类显示定义了元类
class Foo:
     __metaclass__=type
2.如果没有指定元类,检查使用基类的metaclass
class Foo(object):pass
Foo使用object的metaclass,及type,所以二者的type是相同的
3.使用全局的__metaclass__
__metaclass__=type
class Foo:
     pass
4.使用默认的元类,在python2中是types.ClassType,在python3中是type
需要特别说明的是,如果类的metaclass指定了type,那么就相当与使用new style class,即使没有指定object作为基类

元类的使用
元类通常用来规范类的创建,看如下的例子:
class DocMeta(type):
     def __init__(self,name,bases,dict):
          for key,value in dict.items():
               if key.starswith('__'): continue #隐藏属性
               if not hasattr(value,'__call__'): continue #不可调用的属性
               if not getattr(value,"__doc__"):
                    raise TypeError('%s must have a docstring' % key)
          type.__init__(self,name,bases,dict)
这是一个自定义的元类,它继承自type,结合上面创建类的过程,我们可以轻易的明白,使用这个派生类之后,我们就自定义了类的创建过程。通常我们会使用一个基类来使用新创建的metaclass,然后所有其他的类从基类派生

下面这个例子是重载__new__的
class TypedProperty(object):
     def __init__(self,type,default=None):
          self.name=None
          self.type=type
          if default:
               self.default=default
          else: 
               self.default=type()
     def __get__(self,instance,cls):
          return getattr(instance,self.name,self.default)
     def __set__(self,instance,value):
          if not isinstance(value,self.type):
               raise TypeError("must be a %s" % self.type)
          setattr(instance,self.name,value)
     def __delete(self,instance):
          raise AttributeError('can't delete attribute')
class TypeMeta(type):
     def __new__(cls,name,bases,dict):
          slots=[]
          for key,value in dict.items():
               if isinstance(value,TypedProperty):
                    value.name="_" + key
                    slots.append(value.name)
          dict['__slots__']=slots
          return type.__new__(cls,name,bases,dict)
class Typed:
     __metaclass__=TypedMeta

class Foo(Typed):
     name = TypedProperty(str)
     num=TypedProperty(int,42)
Typed是基类,他是用的metaclass是TypedMeta,它使用了描述符TypedProperty来代理属性。在元类TypeMeta中重载了__new__函数,它根据创建类的dict中的key值来填写__slots__属性和描述符的name属性,这样在类创建完成后,描述符的name属性就已经有值了,同时也限定了类的客户不能创建新的类属性
需要特别说明的是,如果查看type.__dict__就可以知道,大部分的特殊函数都是在type中定义的,也就是说,我们可以从type继承之后,自定义这些特殊函数的功能,不仅仅是__init__和__new__




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值