﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-差沙的密码 -- SSHWSFC's code-随笔分类-nohtyp</title><link>http://www.blogjava.net/sshwsfc/category/29337.html</link><description>1=1</description><language>zh-cn</language><lastBuildDate>Sat, 02 Feb 2008 10:39:31 GMT</lastBuildDate><pubDate>Sat, 02 Feb 2008 10:39:31 GMT</pubDate><ttl>60</ttl><item><title>Django什么情况(1) -- Model的&amp;ldquo;造Class&amp;rdquo;概述</title><link>http://www.blogjava.net/sshwsfc/archive/2008/02/02/179019.html</link><dc:creator>差沙</dc:creator><author>差沙</author><pubDate>Sat, 02 Feb 2008 09:32:00 GMT</pubDate><guid>http://www.blogjava.net/sshwsfc/archive/2008/02/02/179019.html</guid><wfw:comment>http://www.blogjava.net/sshwsfc/comments/179019.html</wfw:comment><comments>http://www.blogjava.net/sshwsfc/archive/2008/02/02/179019.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sshwsfc/comments/commentRss/179019.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sshwsfc/services/trackbacks/179019.html</trackback:ping><description><![CDATA[<p>在朋友和同事的极力推荐下最近开始看上了python,其实主要是还是因为python是2007年度语言,怎么的也要与时俱进呀.最近一路看来有些心得,希望能与大家分享,小弟其实也只接触不到一周的python,有说错的地方还望大家指出改正.</p> <p>不打算从py的语法基础说起了,直接说说对django的心得:</p> <p>接触django首先需要了解可能就是他那个model，建立一个model就什么都有了，这对于搞java得人员来说还是挺有吸引力的（当然貌似对于动态语言这都是小儿科），那么让我们先看一个model的例子：</p> <p>偷懒了，直接拿django-admin里面的User出来了</p> <hr> <pre><span style="color: #0000ff">class</span> User(models.Model): 
    username = models.CharField(_('username'), maxlength=30, unique=True, validator_list=[validators.isAlphaNumeric])) 
    first_name = models.CharField(_('first <span style="color: #0000ff">name</span>'), maxlength=30, blank=True) 
    last_name = models.CharField(_('last <span style="color: #0000ff">name</span>'), maxlength=30, blank=True) 
    email = models.EmailField(_('e-mail address'), blank=True) 
    password = models.CharField(_('password'), maxlength=128)) 
    <span style="color: #0000ff">class</span> Meta: 
        ordering = ('username',)</pre>
<hr>

<p>每个属性就是一个库表的字段，定义起来非常简单明了，models里面提供了很多种类的Field类似上面的EmailField。不同的Field有不同的设置，可以看相应的原来来了解相关的设置．</p>
<p>在model class内部还有一个class Meta，这个Class的属性制定了这个表的一些存取策略，例如这里的ordering。MetaClass里面的属性可以用model的_meta属性取得。OK，那么这样一个model怎么就能实现对数据库表的灵活操作了呢。让我们来看看吧。</p>
<p>首先先分析一下/django/django/db/models/base.py这个文件，其中包含了models.Model这类的定义：</p>
<p>看看class定义的第一行吧，第一行就够我琢磨一阵子的了：</p>
<hr>
<pre><span style="color: #0000ff">class</span> Model(<span style="color: #0000ff">object</span>):
    __metaclass__ = ModelBase</pre>
<hr>

<p>Model采用了new style class定义，关于这个内容大家可以放狗看一下，第一行是一个__metaclass__属性的定义，该属性的值是ModelBase，这是一个类。__metaclass__的意思是，指定一个class，这个class的实例就是本class，相信您已经晕了。那么就拿这个Model的例子来说明一下，如果没有__metaclass__这个属性，产生一个实例就是正常的流程，有了这个属性流程会有改变：</p>
<p>首先调用BaseModel.__new__(cls, name, bases, attrs)这个方法，回返回的值是一个class类型，然后用这个class来创建实例。其实BaseModel就是Model的元类，来制定Model这个类的最终样子。关于元类的更多信息请看<a href="http://wiki.woodpecker.org.cn/moin/MetaClassInPython" target="_blank">这里</a></p>
<p>那么我们的目光一下转移到BaseModel这个类上，我有种直觉，Meta这个class最后可以用_meta来取就是在这里做的手脚，看一下BaseModel的定义吧，有点长：</p>
<hr>
<pre><span style="color: #0000ff">class</span> ModelBase(type):
    "<span style="color: #8b0000">Metaclass for all models</span>"
    <span style="color: #0000ff">def</span> __new__(cls, <span style="color: #0000ff">name</span>, bases, attrs):
        # If this isn't a subclass of Model, don't do anything special.</pre><pre>        <span style="color: #0000ff">if</span> <span style="color: #0000ff">name</span> == 'Model' or not filter(lambda b: issubclass(b, Model), bases):    <strong>#1</strong>
            <span style="color: #0000ff">return</span> super(ModelBase, cls).__new__(cls, <span style="color: #0000ff">name</span>, bases, attrs)

        # Create the <span style="color: #0000ff">class</span>.</pre><pre>        new_class = type.__new__(cls, <span style="color: #0000ff">name</span>, bases, {'__module__': attrs.pop('__module__')})    <strong>#2</strong></pre><pre>        new_class.add_to_class('_meta', Options(attrs.pop('Meta', None)))     <strong>#3</strong></pre><pre>        new_class.add_to_class('DoesNotExist', <span style="color: #0000ff">types</span>.<span style="color: #0000ff">ClassType</span>('DoesNotExist', (ObjectDoesNotExist,), {}))

        # Build complete list of parents                                      #4
        for base in bases:
            # TODO: Checking for the presence of '_meta' is hackish.
            <span style="color: #0000ff">if</span> '_meta' in dir(base):
                new_class._meta.parents.append(base)
                new_class._meta.parents.extend(base._meta.parents)

        model_module = <span style="color: #0000ff">sys</span>.<span style="color: #0000ff">modules</span>[new_class.__module__]

        <span style="color: #0000ff">if</span> getattr(new_class._meta, 'app_label', None) is None:
            # Figure out the app_label by looking one level up.
            # For 'django.contrib.sites.models', this would be 'sites'.
            new_class._meta.app_label = model_module.__name__.<span style="color: #0000ff">split</span>('.')[-2]  #5

        # Bail out early <span style="color: #0000ff">if</span> we have already created this <span style="color: #0000ff">class</span>.
        m = get_model(new_class._meta.app_label, <span style="color: #0000ff">name</span>, False)                 #6
        <span style="color: #0000ff">if</span> m is not None:
            <span style="color: #0000ff">return</span> m

        # Add all attributes to the <span style="color: #0000ff">class</span>.
        for obj_name, obj in attrs.items():
            new_class.add_to_class(obj_name, obj)                             #7

        # Add Fields inherited from parents
        for parent in new_class._meta.parents:
            for field in parent._meta.fields:
                # Only <span style="color: #0000ff">add</span> parent fields <span style="color: #0000ff">if</span> they aren't defined for this <span style="color: #0000ff">class</span>.
                try:
                    new_class._meta.get_field(field.<span style="color: #0000ff">name</span>)
                except FieldDoesNotExist:
                    field.contribute_to_class(new_class, field.<span style="color: #0000ff">name</span>)          #8

        new_class._prepare()

        register_models(new_class._meta.app_label, new_class)                 #9
        # Because of the way imports happen (recursively), we may or may not be
        # the first <span style="color: #0000ff">class</span> for this model to <span style="color: #0000ff">register</span> with the framework. There
        # should only be one <span style="color: #0000ff">class</span> for each model, so we must always <span style="color: #0000ff">return</span> the
        # registered <span style="color: #0000ff">version</span>.
        <span style="color: #0000ff">return</span> get_model(new_class._meta.app_label, <span style="color: #0000ff">name</span>, False)              #10</pre>
<p>
<hr>

<p></p>
<p>简单分析一下这个代码：</p>
<p>1. 检查class是否为Model的子类，不是的话，不做任何处理，直接传给父类处理，也就相当于正常的处理了class，注意super在多重继承的时候应该严格使用 </p>
<p>2. 用type来创建类，创建的就是正常的ModelClass</p>
<p>3. 这句很重要，add_to_class是Model里面的class方法，这个方法其实就是传入name和value，给Model添加class属性.看到了，原来神奇的_meta就是这么来的. 提到add_to_class方法，简单看一下它的代码：</p>
<hr>
<pre>    <span style="color: #0000ff">def</span> add_to_class(cls, <span style="color: #0000ff">name</span>, value):
        <span style="color: #0000ff">if</span> <span style="color: #0000ff">name</span> == 'Admin':
            assert type(value) == <span style="color: #0000ff">types</span>.<span style="color: #0000ff">ClassType</span>, "<span style="color: #8b0000">%r attribute of %s model must be a class, not a %s object</span>" % (<span style="color: #0000ff">name</span>, cls.__name__, type(value))
            value = AdminOptions(**dict([(k, v) for k, v in value.__dict__.items() <span style="color: #0000ff">if</span> not k.startswith('_')]))
        <span style="color: #0000ff">if</span> hasattr(value, 'contribute_to_class'):
            value.contribute_to_class(cls, <span style="color: #0000ff">name</span>)
        <span style="color: #0000ff">else</span>:
            setattr(cls, <span style="color: #0000ff">name</span>, value)
    add_to_class = classmethod(add_to_class)</pre>
<p>
<hr>
最后一句是制定这个方法是class方法，特点就是方法的第一个参数是本class，其实classmethod就是一个装饰器，在2。4之后可以使用@来简写。这里不得不提的是他对Admin的特殊处理，虽然AdminOption不是在admin模块里面的，但是这么做还是跟一个Admin的东东绑定起来了，在java的世界解耦是一件大事，看到下面还有对'contribute_to_class'这个方法的特殊处理，django为啥不弄的解耦点呢。而且同样是包装成Option，一个是在BaseModel里面弄（那个Meta的包装），一个在add_to_class方法里面弄，实在有点不优雅，可能还没了解太多，不知道他的深度用意吧。</p>
<p>4. Meta的集成，Option的这个类提供继承方法</p>
<p>5. 取得applabel，就是把model的名字分割取到数第二个，我很喜欢-2这样的设定</p>
<p>6. get_model方法取得缓存里面的东西。</p>
<p>7. 把所有的class attr拿出来搞一遍，一般的属性就setattr弄回去了，要是这个属性有contribute_to_class这个callable属性，那就执行之（Admin的处理完全也可以这样，其实我们常用的objects就是用这个方法弄的）</p>
<p>8. 每个Field调用自己的contribute_to_class方法来进行特殊的处理</p>
<p>9. 进入缓存，，暂且叫缓存吧，里面的东西大家看看很简单 文件在 /django/django/db/models/loading.py 里面还是有很多内容的</p>
<p>10.看注释说的很清楚了，我们一定要在缓存里面拿model。</p>
<p>&nbsp;</p>
<p>其中需要指出的是，new_class._prepare() 这个方法，简单列出片段：</p>
<hr>
<pre>    <span style="color: #0000ff">def</span> _prepare(cls):
        # Creates some methods once self._meta has been populated.
        opts = cls._meta
        opts._prepare(cls)

        ．．．．

        dispatcher.send(signal=signals.class_prepared, sender=cls)</pre>
<hr>

<p>中间省略了一些代码，不是我没看懂的就是没意思的，关键要看这个dispatcher呀。这里是监听者模式，dispatcher.send(signal=signals.class_prepared, sender=cls)放松了一个包含特定信号的事件，让监听的人可以做相应的处理。这样的信号还有很多种，我们可以很简单的截获信号，处理相应的内容。也许您还记得，在我们创建db的时候，会提示创建一个超级用户，其实就是用这个方法来弄的。</p>
<p>那我为什么要把这个单独拿出来说呢，因为监听这个事件的人不是别人，是"objects"！！！在文件/django/django/db/models/manager.py的开头就有这样的代码：</p>
<hr>
<pre><span style="color: #0000ff">def</span> ensure_default_manager(sender):
    cls = sender
    <span style="color: #0000ff">if</span> not hasattr(cls, '_default_manager'):
        # Create the default manager, <span style="color: #0000ff">if</span> needed.
        try:
            cls._meta.get_field('objects')
            raise ValueError, "<span style="color: #8b0000">Model %s must specify a custom Manager, because it has a field named 'objects'</span>" % cls.__name__
        except FieldDoesNotExist:
            pass
        cls.add_to_class('objects', Manager())

dispatcher.connect(ensure_default_manager, signal=signals.class_prepared)</pre>
<p>
<hr>
</p>
<p>定义了一个callable，然后监听signals.class_prepared信号，呵呵，连上了吧，只要Class准备好了，就调用这个方法。简单判断有没有_default_manager'属性和meta里面的objects后，开始插入objects。cls.add_to_class('objects', Manager()) 很熟悉吧，前面讲过不多说了。</p>
<p>PS：写到这里不由得感叹，为啥同样是往Class里面加入东东，要搞这么多的花样呢，我以前也写过一个Rails配置加载过程的<a href="http://www.blogjava.net/sshwsfc/archive/2006/11/20/82401.html" target="_blank">分析文档</a>，虽然一个简单的加载配置就把所有动态语言玩个遍，但是这也太不规范了吧，可能不这么玩就不算&#8220;动态&#8221;语言了吧，哈哈。</p>
<p>&nbsp;</p>
<p>终于Model的Class造好了，相信大家以后造自己的Class也能玩出更多的花样。那么可以开始下一步了。我到老家后再写吧，打算讲讲神奇的objects，也就是Manager，其实就是玩QuerySet。。那里有很多的__xxx__方法，很好很强大</p>   <img src ="http://www.blogjava.net/sshwsfc/aggbug/179019.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sshwsfc/" target="_blank">差沙</a> 2008-02-02 17:32 <a href="http://www.blogjava.net/sshwsfc/archive/2008/02/02/179019.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>