Youmai の Blog


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

Django外键赋值

发表于 2016-12-19 | 分类于 Django
class Article(models.Model):
    title = models.CharField(max_length=1024, default='')
    ...
    def __str__(self):
        return 'Article pk:%d %s' % (self.pk, self.title[:30])

class ArticleContent(models.Model):
    article = cached_fields.OneToOneField(Article)
    ...

写代码的的时候,发现了一个很奇怪的现象,当我给一个instance的外键(以_id结尾)赋值(数字)的时候 ,这个外键对应的instance的值并不会改变。

In [44]: ac = ArticleContent.objects.get(article_id=14269)
In [45]: ac.article_id
Out[45]: 14269
In [46]: ac.article_id = 14266
In [47]: ac.save()
In [48]: ac.article
Out[48]: <Article: Article pk:14266 EC: Russia, Ukraine to Meet in>
In [49]: ac.article.pk
Out[49]: 14266

如上面的代码所示,为了找到答案,我翻了一下Django的源码:

django/db/models/fields/related_descriptors.py 
145     def __get__(self, instance, cls=None):
146         """
147         Get the related instance through the forward relation.
148
149         With the example above, when getting ``child.parent``:
150
151         - ``self`` is the descriptor managing the ``parent`` attribute
152         - ``instance`` is the ``child`` instance
153         - ``cls`` is the ``Child`` class (we don't need it)
154         """
155         if instance is None:
156             return self
157
158         # The related instance is loaded from the database and then cached in
159         # the attribute defined in self.cache_name. It can also be pre-cached
160         # by the reverse accessor (ReverseOneToOneDescriptor).
161         try:
162             rel_obj = getattr(instance, self.cache_name)
163         except AttributeError:
164             val = self.field.get_local_related_value(instance)
165             if None in val:
166                 rel_obj = None
167             else:
168                 qs = self.get_queryset(instance=instance)
169                 qs = qs.filter(self.field.get_reverse_related_filter(instance))
170                 # Assuming the database enforces foreign keys, this won't fail.
171                 rel_obj = qs.get()
172                 # If this is a one-to-one relation, set the reverse accessor
173                 # cache on the related object to the current instance to avoid
174                 # an extra SQL query if it's accessed later on.
175                 if not self.field.remote_field.multiple:
176                     setattr(rel_obj, self.field.remote_field.get_cache_name(), instance)
177             setattr(instance, self.cache_name, rel_obj)
178
179         if rel_obj is None and not self.field.null:
180             raise self.RelatedObjectDoesNotExist(
181                 "%s has no %s." % (self.field.model.__name__, self.field.name)
182             )
183         else:
184             return rel_obj

注释得非常到位,当我们请求ac.article的时候,会先去检查对应的cache(在这里是_article_cache,感兴趣可以去看cache_name的生成规则,就是外键名前面加下划线,后面加cache)存不存在,如果不存在那么就进行数据库请求,请求完之后会保存到cache中。

我们再看看__set__,代码太长就不贴了(就在__get__方法下面)。除了给外键字段(article)赋值外,还会将pk字段(article_id,是lh_field.attname的值)设置为None,这样下次请求的时候就能拿到正确的值。

以上都是ForeignKey的Magic,而当我们给article_id赋值的时候,只是在给一个普通的attribute赋值而已,没有任何magic,不会清理对应外键的cache,这时候拿到的instance仍然是cache中原来的那个instance。

Django-redis如何支持存取整型和布尔值

发表于 2016-11-03 | 分类于 Django

redis支持5种数据类型,分别是string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。整型值和布尔值并不在其中。存储整型和布尔值时,redis会将其转换为字符串

127.0.0.1:6379> SET test_key 1
OK
127.0.0.1:6379> GET test_key
"1"
127.0.0.1:6379> SET test_key True
OK
127.0.0.1:6379> GET test_key
"True"
127.0.0.1:6379> SET test_key TRUE
OK
127.0.0.1:6379> GET test_key
"TRUE"
127.0.0.1:6379> SET test_key true
OK
127.0.0.1:6379> GET test_key
"true"

但是当我们使用了django-redis之后发现,我们可以正常存取整型和布尔值,这是怎么做的呢?答案自然是在源码中,直觉告诉我们,set函数里一定做了什么事情。

# django_redis.client.default.py
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None, client=None, nx=False, xx=False):
    """
    Persist a value to the cache, and set an optional expiration time.
    Also supports optional nx parameter. If set to True - will use redis setnx instead of set.
    """

    if not client:
        client = self.get_client(write=True)

    nkey = self.make_key(key, version=version)
    nvalue = self.encode(value)

    ......

set方法中调用了类的encode的方法对value进行了处理,我们再看encode:

django_redis.client.default.py
def encode(self, value): """ Encode the given value. """

if isinstance(value, bool) or not isinstance(value, integer_types):
    value = self._serializer.dumps(value)
    value = self._compressor.compress(value)
    return value

return value

看到了吧,如果是布尔值,那么要调用一个dumps方法,我们找到这个dumps方法:

django_redis.serializers.pickle.py
class PickleSerializer(BaseSerializer): ......

def dumps(self, value):
    return pickle.dumps(value, self._pickle_version)

def loads(self, value):
    return pickle.loads(force_bytes(value))

原来是用了pickle来做了序列化,这样拿出来的时候调用pickle.loads方法,就可以还原为原来的对象了。这也是django-redis支持存储任意python对象的原因,pickle帮了大忙。

至于int的存取就简单了,我们从get方法看过去:

django_redis.client.default.py
def get(self, key, default=None, version=None, client=None): """ Retrieve a value from the cache. Returns decoded value if key is found, the default if not. """ if client is None: client = self.get_client(write=False)

key = self.make_key(key, version=version)

try:
    value = client.get(key)
except _main_exceptions as e:
    raise ConnectionInterrupted(connection=client, parent=e)

if value is None:
    return default

return self.decode(value)

可以看到对从redis中取出来的值调用了decode方法:

django_redis.client.default.py
def decode(self, value): 
""" 
Decode the given value. 
""" 
try: 
    value = int(value)
except (ValueError, TypeError):
    try:
        value = self._compressor.decompress(value)
    except CompressorError:
        # Handle little values, chosen to be not compressed pass
        value = self._serializer.loads(value) return value

一目了然了吧,总结的话就是:redis存的时候还是存的字符串,但是取出来的时候会尝试去int一下。

大家可能会有这样的疑问,如果值是'123'这样的字符串怎么办呢?这样并不会有问题啦,因为字符串存到数据库之前会先做pickle.dumps,这样就可以区分开数字转换成的字符串和真实的字符串了。

另外,将整型进行pickle也是可以的,但是我觉得因为数字的存储是很平常的需求,而pickle之后会带上对象的各种附加信息,会增加空间消耗,所以作者才有了这样的设计。

如果大家需要写自己的redis client的话,可以参考一下这些设计,会给使用带来很大的方便。

Note: Python3 中pickle和cPickle已经合并为pickle

Django model去掉unique_together报错

发表于 2016-10-17 | 分类于 Django

事情是这样的,我有一个存储考试的表

class Exam(models.Model):
    category = models.ForiegnKey(Category)
    name = models.CharField(max_length=128)
    date = models.DateField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        unique_together = ('category', 'date')

category表示考试的类型,date表示考试的日期。建表的时候考虑到一个类型的考试在同一个应该只有一个考试,所以就加了一个unique_together。但是由于业务需要,这个unique_together不需要了。

用过django的人都知道,这不是个大问题,删掉unique_together的代码,然后makemigrations呗,确实,我就这么做了。但是当我migrate的时候却报错了,错误如下:

django.db.utils.OperationalError: (1553, "Cannot drop index 'insurance_exam_category_id_a430e581_uniq': needed in a foreign key constraint")

数据库不让我删除这个index,并且告诉我有一个外键约束用到了这个它。我就奇怪了,category是外键没错,但是我这个是unique_together啊,怎么可能有哪个外键用到了它呢?

没办法,我只能到数据库里寻找答案,show create table exam,输出如下:

| insurance_exam | CREATE TABLE `insurance_exam` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(128) NOT NULL,
  `date` date NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  `category_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `insurance_exam_category_id_a430e581_uniq` (`category_id`,`date`),
  CONSTRAINT `insurance_exam_category_id_a2238260_fk_insurance_category_id` FOREIGN KEY (`category_id`) REFERENCES `insurance_category` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1062 DEFAULT CHARSET=utf8mb4 |

可以看到UNIQUE KEY那一行就是unique_together,下面一行是category外键。没有其他东西了啊,到底哪个外键用到了我们的unique_together?

外键只能是category了,也没有别的外键啊。到底是怎么回事呢?

原因是这样的:在Mysql中外键会自动在表上添加一个index,也就说如果没有unique_together,我们的表应该是这样的:

| insurance_exam | CREATE TABLE `insurance_exam` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(128) NOT NULL,
  `date` date NOT NULL,
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  `category_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `category_id` (`category_id`),
  CONSTRAINT `insurance_exam_category_id_a2238260_fk_insurance_category_id` FOREIGN KEY (`category_id`) REFERENCES `insurance_category` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1062 DEFAULT CHARSET=utf8mb4 |

但是因为有了unique_together的unique_key,并且category在联合索引的左边,根据最左前缀原则,category的索引就有了,所以就不会另外建索引,这个时候category的外键约束就依赖了这个unique_key,所以删除的时候会出现那样的报错。

机智的小伙伴应该想到了,如果我们要去掉unique_together,我们可以将category的KEY加回去,这样就可以将unique_together删掉了。sql如下:

alter table exam add index(category_id);

这样,migrate就能成功了。

python string intern

发表于 2016-07-10 | 分类于 python

这篇文章会介绍CPython 2.7.12中字符串intern的知识。

首先举一个例子说明内置的intern函数的用法:

>>> s1 = 'foo!'
>>> s2 = 'foo!'
>>> s1 is s2
False
>>> s1 = intern('foo!')
>>> s1
'foo!'
>>> s2 = intern('foo!')
>>> s1 is s2
True

你应该已经体会到了用法,但是,在python内部它是怎么工作的呢?

String Intern(字符串驻留)

PyStringObject结构体

我们需要深入CPython的源码,表示Python字符串的C结构体PyStringObject在stringobject.h文件中:

typedef struct {
    PyObject_VAR_HEAD
    long ob_shash;
    int ob_sstate;
    char ob_sval[1];

    /* Invariants:
     *     ob_sval contains space for 'ob_size+1' elements.
     *     ob_sval[ob_size] == 0.
     *     ob_shash is the hash of the string or -1 if not computed yet.
     *     ob_sstate != 0 iff the string object is in stringobject.c's
     *       'interned' dictionary; in this case the two references
     *       from 'interned' to this object are *not counted* in ob_refcnt.
     */
} PyStringObject;

根据注释,如果字符串被interned,变量ob_sstate的值将不再等于0。一般来说,我们不会直接接触这个变量,而是通过下面定义的宏PyString_CHECK_INTERNED来读取它:

#define PyString_CHECK_INTERNED(op) (((PyStringObject *)(op))->ob_sstate)

interned字典

然后,让我们来看stringobject.c。24行定义了一个对象的引用,interned字符串将会存放在这个对象里。

static PyObject *interned;

实际上,这个对象就是一个正常的Python字典,在第4744行被初始化

interned = PyDict_New();

最终,所有的魔法都在第4731行``PyString_InternInPlace函数中发生。下面是函数实现:

PyString_InternInPlace(PyObject **p)
{
    register PyStringObject *s = (PyStringObject *)(*p);
    PyObject *t;
    if (s == NULL || !PyString_Check(s))
        Py_FatalError("PyString_InternInPlace: strings only please!");
    /* If it's a string subclass, we don't really know what putting
       it in the interned dict might do. */
    if (!PyString_CheckExact(s))
        return;
    if (PyString_CHECK_INTERNED(s))
        return;
    if (interned == NULL) {
        interned = PyDict_New();
        if (interned == NULL) {
            PyErr_Clear(); /* Don't leave an exception */
            return;
        }
    }
    t = PyDict_GetItem(interned, (PyObject *)s);
    if (t) {
        Py_INCREF(t);
        Py_DECREF(*p);
        *p = t;
        return;
    }

    if (PyDict_SetItem(interned, (PyObject *)s, (PyObject *)s) < 0) {
        PyErr_Clear();
        return;
    }
    /* The two references in interned are not counted by refcnt.
       The string deallocator will take care of this */
    Py_REFCNT(s) -= 2;
    PyString_CHECK_INTERNED(s) = SSTATE_INTERNED_MORTAL;
}

可以看到,interned字典中的keys都是指向字符串对象的指针,values也是同样的指针。另外,字符串的派生类不能被intern。不考虑错误检查和引用计数,我们可以用如下伪代码重写这个函数:

interned = None

def intern(string):
    if string is None or not type(string) is str:
        raise TypeError

    if string.is_interned:
        return string

    if interned is None:
        global interned
        interned = {}

    t = interned.get(string)
    if t is not None:
        return t

    interned[string] = string
    string.is_interned = True
    return string

简单吧!

字符串intern的好处

共享对象

为什么你要使用字符串intern?首先,共享字符串对象可以减少内存占用。让我们回到第一个例子,一开始,变量s1和s2指向两个不同的对象。

The two variables reference two different objects

在intern之后,他们都指向了同一个对象。第二个对象占用的内存被节省下来了。

Now, the two variables reference the same object

在处理一些低信息熵的大列表的时候,intern非常有用。例如,当我们解析一个语料库,we could benefit from the very heavy-tailed distribution of word frequencies in human languages to intern strings to our advantage。下面的例子中,我们会使用NLTK来导入Hamlet by Shakespeare,使用Heapy来观察intern前后对象的堆栈使用:

import guppy
import nltk

hp = guppy.hpy()
hp.setrelheap()

hamlet = nltk.corpus.shakespeare.words('hamlet.xml')
print hp.heap()

hamlet = [intern(wrd) for wrd in nltk.corpus.shakespeare.words('hamlet.xml')]
print hp.heap()



$ python intern.py

Partition of a set of 31187 objects. Total size = 1725752 bytes.
 Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
     0  31166 100  1394864  81   1394864  81 str
...

Partition of a set of 4555 objects. Total size = 547840 bytes.
 Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
     0      4   0   328224  60    328224  60 list
     1   4529  99   215776  39    544000  99 str
...

如你所见,我们极大地减少了字符串对象的数量(从31187减少到4555),并且内存占用减少了6.5倍

指针比较

第二,我们可以用O(1)的指针比较来代替O(n)的字节比较

为了验证这个,我测量了在比较两个字符串是否相等的时,intern与否分别的时间。下面的图应该能说服你:

Pointer comparison vs. byte-per-byte comparison

Native interning

在一些条件下,字符串会被原生interning。还是第一个例子,如果我用foo而不是foo!,字符串s1和s2就会被自动地intern。

>>> s1 = 'foo'
>>> s2 = 'foo'
>>> s1 is s2
True

Interned or not interned?

写这篇文章之前,我一直在想,在后台,字符串会根据一个参考字符串长度和构成它们的字符的规则来执行intern。我的想法离正确答案很近,但是,当我操作几组创建方法不同的字符串时,我无法推测出这条规则到底是什么!

>>> 'foo' is 'foo'
True
>>> 'foo!' is 'foo!'
False
>>> 'foo' + 'bar' is 'foobar'
True
>>> ''.join(['f']) is ''.join(['f'])
True
>>> ''.join(['f', 'o', 'o']) is ''.join(['f', 'o', 'o'])
False
>>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
True
>>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
False
>>> 'foooooooooooooooooooooooooooooo' is 'foooooooooooooooooooooooooooooo'
True

在看完这些例子后,你要承认,确实非常难以确定系统是根据什么来执行原生的intern。我们还需要阅读更多的CPython代码来找出答案。

Fact 1: all length 0 and length 1 strings are interned

还是在stringobject.c,我们这些要看PyString_FromStringAndSize和 PyString_FromString函数中都有的一些代码:

/* share short strings */
if (size == 0) {
    PyObject *t = (PyObject *)op;
    PyString_InternInPlace(&t);
    op = (PyStringObject *)t;
    nullstring = op;
    Py_INCREF(op);
} else if (size == 1 && str != NULL) {
    PyObject *t = (PyObject *)op;
    PyString_InternInPlace(&t);

非常清晰:所有长度为0和1的字符串都会被intern。

Fact 2: 在编译期间字符串会被interned

你写的Python代码并不是直接被解释器执行,代码会通过一个经典的编译链来生成一种中间语言:bytecode(字节码)。python字节码是被Python解释器这个虚拟机执行的一系类指令。你可以在这里看到所有的指令。通过使用dis模块,你可以看到一个函数或模块执行了哪些指令:

>>> import dis
>>> def foo():
...     print 'foo!'
...
>>> dis.dis(foo)
  2           0 LOAD_CONST               1 ('foo!')
              3 PRINT_ITEM
              4 PRINT_NEWLINE       
              5 LOAD_CONST               0 (None)
              8 RETURN_VALUE

正如你知道的,在Python中,一切皆对象。代码也是Python对象,它表示了一块字节码。一个代码对象带有执行时所需要的所有信息:常量,变量名等。我们发现,当在Python中创建代码对象的时候,有一些字符串被intern了:

PyCodeObject *
PyCode_New(int argcount, int nlocals, int stacksize, int flags,
           PyObject *code, PyObject *consts, PyObject *names,
           PyObject *varnames, PyObject *freevars, PyObject *cellvars,
           PyObject *filename, PyObject *name, int firstlineno,
           PyObject *lnotab)

           ...
           /* Intern selected string constants */
           for (i = PyTuple_Size(consts); --i >= 0; ) {
               PyObject *v = PyTuple_GetItem(consts, i);
               if (!PyString_Check(v))
                   continue;
               if (!all_name_chars((unsigned char *)PyString_AS_STRING(v)))
                   continue;
               PyString_InternInPlace(&PyTuple_GET_ITEM(consts, i));
           }

在codeobject.c中,consts元组包含了在编译期间定义的字面量:布尔值,浮点数,整数和在你的程序中声明的字符串。在这个元祖中存储的字符串,如果没有被all_name_chars函数过滤掉的话,将会被全部intern。

下面的例子中,s1是在编译期间被声明的。相反的是,s2是在运行时产生的:

s1 = 'foo'
s2 = ''.join(['f', 'o', 'o'])

结果是,s1将会被intern,s2不会。

all_name_chars方法将会过滤掉不是由ascii字符,数字和下划线构成的字符串(Python中变量名的命令规范):

#define NAME_CHARS \
    "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"

/* all_name_chars(s): true if all chars in s are valid NAME_CHARS */

static int
all_name_chars(unsigned char *s)
{
    static char ok_name_char[256];
    static unsigned char *name_chars = (unsigned char *)NAME_CHARS;

    if (ok_name_char[*name_chars] == 0) {
        unsigned char *p;
        for (p = name_chars; *p; p++)
            ok_name_char[*p] = 1;
    }
    while (*s) {
        if (ok_name_char[*s++] == 0)
            return 0;
    }
    return 1;
}

理解了这些,我们就知道了为什么'foo!' is 'foo!'等于False,而'foo' is 'foo'等于True,胜利了吗?还没有!

字节码优化产生了更多字符串常量

这听起来和直觉不符,但是在下面的例子中,字符串连接的输出不是在运行时,而是在编译时:

>>> 'foo' + 'bar' is 'foobar'
True

这就是为什么'foo' + 'bar'也被intern了,表达式等于True。

怎么做到的?倒数第二的源码编译器生成了第一个版本的字节码。这个原始的字节码最终会进入最后一个编译器,被称作“窥孔优化”(peephole optimization)

Compilation chain

这一步的目标是通过替换更快的指令来产生更加高效的字节码。

常量合并Constant folding

在窥孔优化期间,应用到的一项技术就是常量合并,使用更加简单的常量。想象你是一个编译器,你看到了下面这行:

SECONDS = 24 * 60 * 60

你能做什么来简化这个表达式以在运行时节约几个时钟周期。你可以用计算好的值86400来替换这个表达式。这就是foo' + 'bar'发生的事。让我们定义一个foobar函数,分解相关的字节码:

>>> import dis
>>> def foobar():
...         return 'foo' + 'bar'
>>> dis.dis(foobar)
  2           0 LOAD_CONST               3 ('foobar')
              3 RETURN_VALUE

看到了吧!根本没有看到加法和初始的两个常量'foo'和'bar'。如果CPython字节码没有被优化,那么输出应该是这样的:

>>> dis.dis(foobar)
  2           0 LOAD_CONST               1 ('foo')
              3 LOAD_CONST               2 ('bar')
              6 BINARY_ADD    
              7 RETURN_VALUE

我们解决为什么下面的表达式正确的问题:

>>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'

避免大的 .pyc 文件

那么为什么'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'不等于True呢?你还记得在你的包里面会看到的.pyc文件吗。如果一个人写了诸如['foo!'] * 10**9的代码会怎么样呢?这样会导致.pyc文件非常巨大!为了避免这个现象,窥孔优化中产生的序列长度如果大于20,将会被丢弃。

补充

窥孔优化

顾名思义,是一种很局部的优化方式,编译器仅仅在一个基本块或者多个基本块中,针对已经生成的代码,结合CPU自己指令的特点,通过一些认为可能带来性能提升的转换规则,或者通过整体的分析,通过指令转换,提升代码性能。别看这些代码转换很局部,很小,但可能会带来很大的性能提升。

这个窥孔,你可以认为是一个滑动窗口,编译器在实施窥孔优化时,就仅仅分析这个窗口内的指令。每次转换之后,可能还会暴露相邻窗口之间的某些优化机会,所以可以多次调用窥孔优化,尽可能提升性能。

窥孔优化可以在四个方面寻找优化机会:冗余指令删除,包括冗余的load和store指令以及死代码(不会执行的代码);控制流优化;强度削弱;利用特有指令。

Python 面试题集锦[转载]

发表于 2016-06-05 | 分类于 python

Python语言特性

Python的函数参数传递

看两个例子:

1
2
3
4
5
a = 1
def fun(a):
a = 2
fun(a)
print a # 1
1
2
3
4
5
a = []
def fun(a):
a.append(1)
fun(a)
print a # [1]

所有的变量都可以理解是内存中一个对象的“引用”,或者,也可以看似c中void*的感觉。

这里记住的是类型是属于对象的,而不是变量。而对象有两种,“可更改”(mutable)与“不可更改”(immutable)对象。在python中,strings, tuples, 和numbers是不可更改的对象,而list,dict等则是可以修改的对象。(这就是这个问题的重点)

当一个引用传递给函数的时候,函数自动复制一份引用,这个函数里的引用和外边的引用没有半毛关系了.所以第一个例子里函数把引用指向了一个不可变对象,当函数返回的时候,外面的引用没半毛感觉.而第二个例子就不一样了,函数内的引用指向的是可变对象,对它的操作就和定位了指针地址一样,在内存里进行修改.

如果还不明白的话,这里有更好的解释: http://stackoverflow.com/questions/986006/how-do-i-pass-a-variable-by-reference

Python中的元类(metaclass)

这个非常的不常用,但是像ORM这种复杂的结构还是会需要的,详情请看:http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python

@staticmethod和@classmethod

Python其实有3个方法,即静态方法(staticmethod),类方法(classmethod)和实例方法,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def foo(x):
print "executing foo(%s)"%(x)
class A(object):
def foo(self,x):
print "executing foo(%s,%s)"%(self,x)
@classmethod
def class_foo(cls,x):
print "executing class_foo(%s,%s)"%(cls,x)
@staticmethod
def static_foo(x):
print "executing static_foo(%s)"%x
a=A()

这里先理解下函数参数里面的self和cls. 这个self和cls是对类或者实例的绑定,对于一般的函数来说我们可以这么调用foo(x),这个函数就是最常用的,它的工作跟任何东西(类,实例)无关.对于实例方法,我们知道在类里每次定义方法的时候都需要绑定这个实例,就是foo(self, x),为什么要这么做呢?因为实例方法的调用离不开实例,我们需要把实例自己传给函数,调用的时候是这样的a.foo(x)(其实是foo(a, x)).类方法一样,只不过它传递的是类而不是实例,A.class_foo(x).注意这里的self和cls可以替换别的参数,但是python的约定是这俩,还是不要改的好.

对于静态方法其实和普通的方法一样,不需要对谁进行绑定,唯一的区别是调用的时候需要使用a.static_foo(x)或者A.static_foo(x)来调用.

\ 实例方法 类方法 静态方法
a = A() a.foo(x) a.class_foo(x) a.static_foo(x)
A 不可用 A.class_foo(x) A.static_foo(x)

更多关于这个问题:http://stackoverflow.com/questions/136097/what-is-the-difference-between-staticmethod-and-classmethod-in-python

类变量和实例变量

1
2
3
4
5
6
7
8
9
class Person:
name="aaa"
p1=Person()
p2=Person()
p1.name="bbb"
print p1.name # bbb
print p2.name # aaa
print Person.name # aaa

类变量就是供类使用的变量,实例变量就是供实例使用的.

这里p1.name="bbb"是实例调用了类变量,这其实和上面第一个问题一样,就是函数传参的问题,p1.name一开始是指向的类变量name="aaa",但是在实例的作用域里把类变量的引用改变了,就变成了一个实例变量,self.name不再引用Person的类变量name了.

可以看看下面的例子:

1
2
3
4
5
6
7
8
9
class Person:
name=[]
p1=Person()
p2=Person()
p1.name.append(1)
print p1.name # [1]
print p2.name # [1]
print Person.name # [1]

参考:http://stackoverflow.com/questions/6470428/catch-multiple-exceptions-in-one-line-except-block

Python自省

这个也是python彪悍的特性.

自省就是面向对象的语言所写的程序在运行时,所能知道对象的类型.简单一句就是运行时能够获得对象的类型.比如type(),dir(),getattr(),hasattr(),isinstance().

字典推导式

可能你见过列表推导时,却没有见过字典推导式,在2.7中才加入的:

1
d = {key: value for (key, value) in iterable}

Python中单下划线和双下划线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> class MyClass():
... def __init__(self):
... self.__superprivate = "Hello"
... self._semiprivate = ", world!"
...
>>> mc = MyClass()
>>> print mc.__superprivate
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: myClass instance has no attribute '__superprivate'
>>> print mc._semiprivate
, world!
>>> print mc.__dict__
{'_MyClass__superprivate': 'Hello', '_semiprivate': ', world!'}

__foo__: 一种约定,Python内部的名字,用来区别其他用户自定义的命名,以防冲突.

_foo: 一种约定,用来指定变量私有. 程序员用来指定私有变量的一种方式.

__foo: 这个有真正的意义: 解析器用_classname__foo来代替这个名字,以区别和其他类相同的命名.

详情见: http://stackoverflow.com/questions/1301346/the-meaning-of-a-single-and-a-double-underscore-before-an-object-name-in-python

或者: http://www.zhihu.com/question/19754941

字符串格式化:%和.format

.format在许多方面看起来更便利.对于%最烦人的是它无法同时传递一个变量和元组.你可能会想下面的代码不会有什么问题:

1
"hi there %s" % name

但是,如果name恰好是(1,2,3),它将会抛出一个TypeError异常.为了保证它总是正确的,你必须这样做:

1
"hi there %s" % (name,) # 提供一个单元素的数组而不是一个参数

但是有点丑. .format就没有这些问题. 你给的第二个问题也是这样, .format好看多了.

你为什么不用它?

  • 不知道它(在读这个之前)
  • 为了和Python2.5兼容(譬如logging库建议使用%(issue #4))

http://stackoverflow.com/questions/5082452/python-string-formatting-vs-format

迭代器和生成器

这个是stackoverflow里python排名第一的问题,值得一看: http://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do-in-python

这是中文版: http://taizilongxu.gitbooks.io/stackoverflow-about-python/content/1/README.html

*args and **kwargs

用*args和**kwargs只是为了方便并没有强制使用它们.

当你不确定你的函数里将要传递多少参数时你可以用*args.例如,它可以传递任意数量的参数:

1
2
3
4
5
6
7
8
>>> def print_everything(*args):
for count, thing in enumerate(args):
... print '{0}. {1}'.format(count, thing)
...
>>> print_everything('apple', 'banana', 'cabbage')
0. apple
1. banana
2. cabbage

相似的,**kwargs允许你使用没有事先定义的参数名:

1
2
3
4
5
6
7
>>> def table_things(**kwargs):
... for name, value in kwargs.items():
... print '{0} = {1}'.format(name, value)
...
>>> table_things(apple = 'fruit', cabbage = 'vegetable')
cabbage = vegetable
apple = fruit

你也可以混着用.命名参数首先获得参数值然后所有的其他参数都传递给*args和**kwargs.命名参数在列表的最前端.例如:

1
def table_things(titlestring, **kwargs)

*args和**kwargs可以同时在函数的定义中,但是*args必须在**kwargs前面.

当调用函数时你也可以用*和**语法.例如:

1
2
3
4
5
6
7
>>> def print_three_things(a, b, c):
... print 'a = {0}, b = {1}, c = {2}'.format(a,b,c)
...
>>> mylist = ['aardvark', 'baboon', 'cat']
>>> print_three_things(*mylist)
a = aardvark, b = baboon, c = cat

就像你看到的一样,它可以传递列表(或者元组)的每一项并把它们解包.注意必须与它们在函数里的参数相吻合.当然,你也可以在函数定义或者函数调用时用*.

http://stackoverflow.com/questions/3394835/args-and-kwargs

面向切面编程AOP和装饰器

这个AOP一听起来有点懵,同学面阿里的时候就被问懵了…

装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

这个问题比较大,推荐: http://stackoverflow.com/questions/739654/how-can-i-make-a-chain-of-function-decorators-in-python

中文: http://taizilongxu.gitbooks.io/stackoverflow-about-python/content/3/README.html

鸭子类型

“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”

我们并不关心对象是什么类型,到底是不是鸭子,只关心行为。

比如在python中,有很多file-like的东西,比如StringIO, GzipFile ,socket`。它们有很多相同的方法,我们把它们当作文件使用。

又比如list.extend()方法中,我们并不关心它的参数是不是list,只要它是可迭代的,所以它的参数可以是list/tuple/dict/字符串/生成器等.

鸭子类型在动态语言中经常使用,非常灵活,使得python不想java那样专门去弄一大堆的设计模式。

Python中重载

引自知乎: http://www.zhihu.com/question/20053359

函数重载主要是为了解决两个问题。

  1. 可变参数类型。
  2. 可变参数个数。

另外,一个基本的设计原则是,仅仅当两个函数除了参数类型和参数个数不同以外,其功能是完全相同的,此时才使用函数重载,如果两个函数的功能其实不同,那么不应当使用重载,而应当使用一个名字不同的函数。

好吧,那么对于情况 1 ,函数功能相同,但是参数类型不同,python 如何处理?答案是根本不需要处理,因为 python 可以接受任何类型的参数,如果函数的功能相同,那么不同的参数类型在 python 中很可能是相同的代码,没有必要做成两个不同函数。

那么对于情况 2 ,函数功能相同,但参数个数不同,python 如何处理?大家知道,答案就是缺省参数。对那些缺少的参数设定为缺省参数即可解决问题。因为你假设函数功能相同,那么那些缺少的参数终归是需要用的。

好了,鉴于情况 1 跟 情况 2 都有了解决方案,python 自然就不需要函数重载了。

新式类和旧式类

这个面试官问了,我说了老半天,不知道他问的真正意图是什么.

http://stackoverflow.com/questions/54867/what-is-the-difference-between-old-style-and-new-style-classes-in-python

这篇文章很好的介绍了新式类的特性

新式类很早在2.2就出现了,所以旧式类完全是兼容的问题, Python3里的类全部都是新式类. 这里有一个MRO问题可以了解下(新式类是广度优先, 旧式类是深度优先), 里讲的也很多.

__new__和__init__的区别

  1. __new__是一个静态方法,而__init__是一个实例方法.
  2. __new__方法会返回一个创建的实例,而__init__什么都不返回.
  3. 只有在__new__返回一个cls的实例时后面的__init__才能被调用.
  4. 当创建一个新实例时调用__new__,初始化一个实例时用__init__.

http://stackoverflow.com/questions/674304/pythons-use-of-new-and-init

ps: __metaclass__是创建类时起作用.所以我们可以分别使用__metaclass__,__new__和__init__来分别在类创建,实例创建和实例初始化的时候做一些小手脚.

单例模式

这个绝对常考啊.绝对要记住1~2个方法,当时面试官是让手写的.

使用__new__方法

1
2
3
4
5
6
7
8
9
class Singleton(object):
def __new__(cls, *args, **kw):
if not hasattr(cls, '_instance'):
orig = super(Singleton, cls)
cls._instance = orig.__new__(cls, *args, **kw)
return cls._instance
class MyClass(Singleton):
a = 1

共享属性

创建实例时把所有实例的__dict__指向同一个字典,这样它们具有相同的属性和方法.

1
2
3
4
5
6
7
8
9
10
class Borg(object):
_state = {}
def __new__(cls, *args, **kw):
ob = super(Borg, cls).__new__(cls, *args, **kw)
ob.__dict__ = cls._state
return ob
class MyClass2(Borg):
a = 1

装饰器版本

1
2
3
4
5
6
7
8
9
10
11
def singleton(cls, *args, **kw):
instances = {}
def getinstance():
if cls not in instances:
instances[cls] = cls(*args, **kw)
return instances[cls]
return getinstance
@singleton
class MyClass:
...

import方法

作为python的模块是天然的单例模式

1
2
3
4
5
6
7
8
9
10
11
# mysingleton.py
class My_Singleton(object):
def foo(self):
pass
my_singleton = My_Singleton()
# to use
from mysingleton import my_singleton
my_singleton.foo()

Python中的作用域

Python 中,一个变量的作用域总是由在代码中被赋值的地方所决定的。

当 Python 遇到一个变量的话他会按照这样的顺序进行搜索:

本地作用域(Local)→ 当前作用域被嵌入的本地作用域(Enclosing locals)→ 全局/模块作用域(Global)→ 内置作用域(Built-in)

GIL线程全局锁

线程全局锁(Global Interpreter Lock),即Python为了保证线程安全而采取的独立线程运行的限制,说白了就是一个核只能在同一时间运行一个线程.

见Python 最难的问题

解决办法就是多进程和下面的协程(协程也只是单CPU,但是能减小切换代价提升性能).

协程

知乎被问到了,呵呵哒,跪了

简单点说协程是进程和线程的升级版,进程和线程都面临着内核态和用户态的切换问题而耗费许多切换时间,而协程就是用户自己控制切换的时机,不再需要陷入系统的内核态.

Python里最常见的yield就是协程的思想!可以查看第九个问题.

闭包

闭包(closure)是函数式编程的重要的语法结构。闭包也是一种组织代码的结构,它同样提高了代码的可重复使用性。

当一个内嵌函数引用其外部作作用域的变量,我们就会得到一个闭包. 总结一下,创建一个闭包必须满足以下几点:

  1. 必须有一个内嵌函数
  2. 内嵌函数必须引用外部函数中的变量
  3. 外部函数的返回值必须是内嵌函数

感觉闭包还是有难度的,几句话是说不明白的,还是查查相关资料.

重点是函数运行后并不会被撤销,就像16题的instance字典一样,当函数运行完后,instance并不被销毁,而是继续留在内存空间里.这个功能类似类里的类变量,只不过迁移到了函数上.

闭包就像个空心球一样,你知道外面和里面,但你不知道中间是什么样.

lambda函数

其实就是一个匿名函数,为什么叫lambda?因为和后面的函数式编程有关.

推荐: 知乎

Python函数式编程

这个需要适当的了解一下吧,毕竟函数式编程在Python中也做了引用.

推荐: 酷壳

python中函数式编程支持:

filter 函数的功能相当于过滤器。调用一个布尔函数bool_func来迭代遍历每个seq中的元素;返回一个使bool_seq返回值为true的元素的序列。

1
2
3
4
>>>a = [1,2,3,4,5,6,7]
>>>b = filter(lambda x: x > 5, a)
>>>print b
>>>[6,7]

map函数是对一个序列的每个项依次执行函数,下面是对一个序列每个项都乘以2:

1
2
3
>>> a = map(lambda x:x*2,[1,2,3])
>>> list(a)
[2, 4, 6]

reduce函数是对一个序列的每个项迭代调用函数,下面是求3的阶乘:

1
2
>>> reduce(lambda x,y:x*y,range(1,4))
6

Python里的拷贝

引用和copy(),deepcopy()的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import copy
a = [1, 2, 3, 4, ['a', 'b']] #原始对象
b = a #赋值,传对象的引用
c = copy.copy(a) #对象拷贝,浅拷贝
d = copy.deepcopy(a) #对象拷贝,深拷贝
a.append(5) #修改对象a
a[4].append('c') #修改对象a中的['a', 'b']数组对象
print 'a = ', a
print 'b = ', b
print 'c = ', c
print 'd = ', d
输出结果:
a = [1, 2, 3, 4, ['a', 'b', 'c'], 5]
b = [1, 2, 3, 4, ['a', 'b', 'c'], 5]
c = [1, 2, 3, 4, ['a', 'b', 'c']]
d = [1, 2, 3, 4, ['a', 'b']]

Python垃圾回收机制

Python GC主要使用引用计数(reference counting)来跟踪和回收垃圾。在引用计数的基础上,通过“标记-清除”(mark and sweep)解决容器对象可能产生的循环引用问题,通过“分代回收”(generation collection)以空间换时间的方法提高垃圾回收效率。

引用计数

PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少.引用计数为0时,该对象生命就结束了。

优点:

  1. 简单
  2. 实时性

缺点:

  1. 维护引用计数消耗资源
  2. 循环引用

标记-清除机制

基本思路是先按需分配,等到没有空闲内存的时候从寄存器和程序栈上的引用出发,遍历以对象为节点、以引用为边构成的图,把所有可以访问到的对象打上标记,然后清扫一遍内存空间,把所有没标记的对象释放。

分代技术

分代回收的整体思想是:将系统中的所有内存块根据其存活时间划分为不同的集合,每个集合就成为一个“代”,垃圾收集频率随着“代”的存活时间的增大而减小,存活时间通常利用经过几次垃圾回收来度量。

Python默认定义了三代对象集合,索引数越大,对象存活时间越长。

举例:
当某些内存块M经过了3次垃圾收集的清洗之后还存活时,我们就将内存块M划到一个集合A中去,而新分配的内存都划分到集合B中去。当垃圾收集开始工作时,大多数情况都只对集合B进行垃圾回收,而对集合A进行垃圾回收要隔相当长一段时间后才进行,这就使得垃圾收集机制需要处理的内存少了,效率自然就提高了。在这个过程中,集合B中的某些内存块由于存活时间长而会被转移到集合A中,当然,集合A中实际上也存在一些垃圾,这些垃圾的回收会因为这种分代的机制而被延迟。

Python的List

推荐: http://www.jianshu.com/p/J4U6rR

Python的is

is是对比地址,==是对比值

read,readline和readlines

  • read 读取整个文件
  • readline 读取下一行,使用生成器方法
  • readlines 读取整个文件到一个迭代器以供我们遍历

Python2和3的区别

推荐:Python 2.7.x 与 Python 3.x 的主要差异

操作系统

select,poll和epoll

其实所有的I/O都是轮询的方法,只不过实现的层面不同罢了.

这个问题可能有点深入了,但相信能回答出这个问题是对I/O多路复用有很好的了解了.其中tornado使用的就是epoll的.

selec,poll和epoll区别总结

基本上select有3个缺点:

  1. 连接数受限
  2. 查找配对速度慢
  3. 数据由内核拷贝到用户态

poll改善了第一个缺点

epoll改了三个缺点.

关于epoll的: http://www.cnblogs.com/my_life/articles/3968782.html

调度算法

  1. 先来先服务(FCFS, First Come First Serve)
  2. 短作业优先(SJF, Shortest Job First)
  3. 最高优先权调度(Priority Scheduling)
  4. 时间片轮转(RR, Round Robin)
  5. 多级反馈队列调度(multilevel feedback queue scheduling)

实时调度算法:

  1. 最早截至时间优先 EDF
  2. 最低松弛度优先 LLF

死锁

原因:

  1. 竞争资源
  2. 程序推进顺序不当

必要条件:

  1. 互斥条件
  2. 请求和保持条件
  3. 不剥夺条件
  4. 环路等待条件

处理死锁基本方法:

  1. 预防死锁(摒弃除1以外的条件)
  2. 避免死锁(银行家算法)
  3. 检测死锁(资源分配图)
  4. 解除死锁
    1. 剥夺资源
    2. 撤销进程

程序编译与链接

推荐: http://www.ruanyifeng.com/blog/2014/11/compiler.html

Bulid过程可以分解为4个步骤: 预处理(Prepressing), 编译(Compilation)、汇编(Assembly)、链接(Linking)

以c语言为例:

预处理

预编译过程主要处理那些源文件中的以“#”开始的预编译指令,主要处理规则有:

  1. 将所有的“#define”删除,并展开所用的宏定义
  2. 处理所有条件预编译指令,比如“#if”、“#ifdef”、 “#elif”、“#endif”
  3. 处理“#include”预编译指令,将被包含的文件插入到该编译指令的位置,注:此过程是递归进行的
  4. 删除所有注释
  5. 添加行号和文件名标识,以便于编译时编译器产生调试用的行号信息以及用于编译时产生编译错误或警告时可显示行号
  6. 保留所有的#pragma编译器指令。

编译

编译过程就是把预处理完的文件进行一系列的词法分析、语法分析、语义分析及优化后生成相应的汇编代码文件。这个过程是整个程序构建的核心部分。

汇编

汇编器是将汇编代码转化成机器可以执行的指令,每一条汇编语句几乎都是一条机器指令。经过编译、链接、汇编输出的文件成为目标文件(Object File)

链接

链接的主要内容就是把各个模块之间相互引用的部分处理好,使各个模块可以正确的拼接。
链接的主要过程包块 地址和空间的分配(Address and Storage Allocation)、符号决议(Symbol Resolution)和重定位(Relocation)等步骤。

静态链接和动态链接

静态链接方法:静态链接的时候,载入代码就会把程序会用到的动态代码或动态代码的地址确定下来
静态库的链接可以使用静态链接,动态链接库也可以使用这种方法链接导入库

动态链接方法:使用这种方式的程序并不在一开始就完成动态链接,而是直到真正调用动态库代码时,载入程序才计算(被调用的那部分)动态代码的逻辑地址,然后等到某个时候,程序又需要调用另外某块动态代码时,载入程序又去计算这部分代码的逻辑地址,所以,这种方式使程序初始化时间较短,但运行期间的性能比不上静态链接的程序

虚拟内存技术

虚拟存储器是值具有请求调入功能和置换功能,能从逻辑上对内存容量加以扩充的一种存储系统.

分页和分段

分页: 用户程序的地址空间被划分成若干固定大小的区域,称为“页”,相应地,内存空间分成若干个物理块,页和块的大小相等。可将用户程序的任一页放在内存的任一块中,实现了离散分配。

分段: 将用户程序地址空间分成若干个大小不等的段,每段可以定义一组相对完整的逻辑信息。存储分配时,以段为单位,段与段在内存中可以不相邻接,也实现了离散分配。

分页与分段的主要区别

  1. 页是信息的物理单位,分页是为了实现非连续分配,以便解决内存碎片问题,或者说分页是由于系统管理的需要.段是信息的逻辑单位,它含有一组意义相对完整的信息,分段的目的是为了更好地实现共享,满足用户的需要.
  2. 页的大小固定,由系统确定,将逻辑地址划分为页号和页内地址是由机器硬件实现的.而段的长度却不固定,决定于用户所编写的程序,通常由编译程序在对源程序进行编译时根据信息的性质来划分.
  3. 分页的作业地址空间是一维的.分段的地址空间是二维的.

页面置换算法

  1. 最佳置换算法OPT:不可能实现
  2. 先进先出FIFO
  3. 最近最久未使用算法LRU:最近一段时间里最久没有使用过的页面予以置换.
  4. clock算法

边沿触发和水平触发

边缘触发是指每当状态变化时发生一个 io 事件,条件触发是只要满足条件就发生一个 io 事件

数据库

事务

数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。

数据库索引

推荐: http://tech.meituan.com/mysql-index.html

MySQL索引背后的数据结构及算法原理

聚集索引, 非聚集索引, B-Tree, B+Tree, 最左前缀原理

Redis原理

乐观锁和悲观锁

悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作

乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。

MVCC

MyISAM和InnoDB

MyISAM 适合于一些需要大量查询的应用,但其对于有大量写操作并不是很好。甚至你只是需要update一个字段,整个表都会被锁起来,而别的进程,就算是读进程都无法操作直到读操作完成。另外,MyISAM 对于 SELECT COUNT(*) 这类的计算是超快无比的。

InnoDB 的趋势会是一个非常复杂的存储引擎,对于一些小的应用,它会比 MyISAM 还慢。他是它支持“行锁” ,于是在写操作比较多的时候,会更优秀。并且,他还支持更多的高级应用,比如:事务。

网络

三次握手

  1. 客户端通过向服务器端发送一个SYN来创建一个主动打开,作为三路握手的一部分。客户端把这段连接的序号设定为随机数 A。
  2. 服务器端应当为一个合法的SYN回送一个SYN/ACK。ACK 的确认码应为 A+1,SYN/ACK 包本身又有一个随机序号 B。
  3. 最后,客户端再发送一个ACK。当服务端受到这个ACK的时候,就完成了三路握手,并进入了连接创建状态。此时包序号被设定为收到的确认号 A+1,而响应则为 B+1。

四次挥手

ARP协议

地址解析协议(Address Resolution Protocol): 根据IP地址获取物理地址的一个TCP/IP协议

urllib和urllib2的区别

这个面试官确实问过,当时答的urllib2可以Post而urllib不可以.

  1. urllib提供urlencode方法用来GET查询字符串的产生,而urllib2没有。这是为何urllib常和urllib2一起使用的原因。
  2. urllib2可以接受一个Request类的实例来设置URL请求的headers,urllib仅可以接受URL。这意味着,你不可以伪装你的User Agent字符串等。

Post和Get

GET和POST有什么区别?及为什么网上的多数答案都是错的

get: RFC 2616 - Hypertext Transfer Protocol – HTTP/1.1
post: RFC 2616 - Hypertext Transfer Protocol – HTTP/1.1

Cookie和Session

Cookie Session
储存位置 客户端 服务器端
目的 跟踪会话,也可以保存用户偏好设置或者保存用户名密码等 跟踪会话
安全性 不安全 安全

session技术是要使用到cookie的,之所以出现session技术,主要是为了安全。

apache和nginx的区别

nginx 相对 apache 的优点:

  • 轻量级,同样起web 服务,比apache 占用更少的内存及资源
  • 抗并发,nginx 处理请求是异步非阻塞的,支持更多的并发连接,而apache 则是阻塞型的,在高并发下nginx 能保持低资源低消耗高性能
  • 配置简洁
  • 高度模块化的设计,编写模块相对简单
  • 社区活跃

apache 相对nginx 的优点:

  • rewrite ,比nginx 的rewrite 强大
  • 模块超多,基本想到的都可以找到
  • 少bug ,nginx 的bug 相对较多
  • 超稳定

网站用户密码保存

  1. 明文保存
  2. 明文hash后保存,如md5
  3. MD5+Salt方式,这个salt可以随机
  4. 知乎使用了Bcrypy(好像)加密

HTTP和HTTPS

状态码 定义
1xx 报告 接收到请求,继续进程
2xx 成功 步骤成功接收,被理解,并被接受
3xx 重定向 为了完成请求,必须采取进一步措施
4xx 客户端出错 请求包括错的顺序或不能完成
5xx 服务器出错 服务器无法完成显然有效的请求

403: Forbidden
404: Not Found

HTTPS握手,对称加密,非对称加密,TLS/SSL,RSA

XSRF和XSS

  • CSRF(Cross-site request forgery)跨站请求伪造
  • XSS(Cross Site Scripting)跨站脚本攻击

CSRF重点在请求, XSS重点在脚本

幂等 Idempotence

HTTP方法的幂等性是指一次和多次请求某一个资源应该具有同样的副作用。(注意是副作用)

GET http://www.bank.com/account/123456,不会改变资源的状态,不论调用一次还是N次都没有副作用。请注意,这里强调的是一次和N次具有相同的副作用,而不是每次GET的结果相同。GET http://www.news.com/latest-news这个HTTP请求可能会每次得到不同的结果,但它本身并没有产生任何副作用,因而是满足幂等性的。

DELETE方法用于删除资源,有副作用,但它应该满足幂等性。比如:DELETE http://www.forum.com/article/4231,调用一次和N次对系统产生的副作用是相同的,即删掉id为4231的帖子;因此,调用者可以多次调用或刷新页面而不必担心引起错误。

POST所对应的URI并非创建的资源本身,而是资源的接收者。比如:POST http://www.forum.com/articles的语义是在http://www.forum.com/articles下创建一篇帖子,HTTP响应中应包含帖子的创建状态以及帖子的URI。两次相同的POST请求会在服务器端创建两份资源,它们具有不同的URI;所以,POST方法不具备幂等性。

PUT所对应的URI是要创建或更新的资源本身。比如:PUT http://www.forum/articles/4231的语义是创建或更新ID为4231的帖子。对同一URI进行多次PUT的副作用和一次PUT是相同的;因此,PUT方法具有幂等性。

RESTful架构(SOAP,RPC)

推荐: http://www.ruanyifeng.com/blog/2011/09/restful.html

SOAP

SOAP(原为Simple Object Access Protocol的首字母缩写,即简单对象访问协议)是交换数据的一种协议规范,使用在计算机网络Web服务(web service)中,交换带结构信息。SOAP为了简化网页服务器(Web Server)从XML数据库中提取数据时,节省去格式化页面时间,以及不同应用程序之间按照HTTP通信协议,遵从XML格式执行资料互换,使其抽象于语言实现、平台和硬件。

RPC

RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。

总结:服务提供的两大流派.传统意义以方法调用为导向通称RPC。为了企业SOA,若干厂商联合推出webservice,制定了wsdl接口定义,传输soap. 当互联网时代,臃肿SOA被简化为http+xml/json.但是简化出现各种混乱。以资源为导向,任何操作无非是对资源的增删改查,于是统一的REST出现了.

进化的顺序: RPC -> SOAP -> RESTful

CGI和WSGI

CGI是通用网关接口,是连接web服务器和应用程序的接口,用户通过CGI来获取动态数据或文件等。

CGI程序是一个独立的程序,它可以用几乎所有语言来写,包括perl,c,lua,python等等。

WSGI, Web Server Gateway Interface,是Python应用程序或框架和Web服务器之间的一种接口,WSGI的其中一个目的就是让用户可以用统一的语言(Python)编写前后端。

官方说明:PEP-3333

中间人攻击

在GFW里屡见不鲜的,呵呵.

中间人攻击(Man-in-the-middle attack,通常缩写为MITM)是指攻击者与通讯的两端分别创建独立的联系,并交换其所收到的数据,使通讯的两端认为他们正在通过一个私密的连接与对方直接对话,但事实上整个会话都被攻击者完全控制。

c10k问题

所谓c10k问题,指的是服务器同时支持成千上万个客户端的问题,也就是concurrent 10 000 connection(这也是c10k这个名字的由来)。
推荐: http://www.kegel.com/c10k.html

socket

推荐: http://www.360doc.com/content/11/0609/15/5482098_122692444.shtml

Socket = Ip address + TCP/UDP + port

浏览器缓存

推荐: http://www.cnblogs.com/skynet/archive/2012/11/28/2792503.html

304 Not Modified

HTTP1.0和HTTP1.1

推荐: http://blog.csdn.net/elifefly/article/details/3964766

  1. 请求头Host字段,一个服务器多个网站
  2. 长链接
  3. 文件断点续传
  4. 身份认证,状态管理,Cache缓存

Ajax

AJAX,Asynchronous JavaScript and XML(异步的 JavaScript 和 XML), 是与在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页的技术。

*NIX

unix进程间通信方式(IPC)

  1. 管道(Pipe):管道可用于具有亲缘关系进程间的通信,允许一个进程和另一个与它有共同祖先的进程之间进行通信。
  2. 命名管道(named pipe):命名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。命名管道在文件系统中有对应的文件名。命名管道通过命令mkfifo或系统调用mkfifo来创建。
  3. 信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数)。
  4. 消息(Message)队列:消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺
  5. 共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
  6. 内存映射(mapped memory):内存映射允许任何多个进程间通信,每一个使用该机制的进程通过把一个共享的文件映射到自己的进程地址空间来实现它。
  7. 信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
  8. 套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。

数据结构

红黑树

红黑树与AVL的比较:

AVL是严格平衡树,因此在增加或者删除节点的时候,根据不同情况,旋转的次数比红黑树要多;

红黑是用非严格的平衡来换取增删节点时候旋转次数的降低;

所以简单说,如果你的应用中,搜索的次数远远大于插入和删除,那么选择AVL,如果搜索,插入删除次数几乎差不多,应该选择RB。

编程题

台阶问题/斐波纳挈

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

1
fib = lambda n: n if n <= 2 else fib(n - 1) + fib(n - 2)

第二种记忆方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def memo(func):
cache = {}
def wrap(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return wrap
@memo
def fib(i):
if i < 2:
return 1
return fib(i-1) + fib(i-2)

第三种方法

1
2
3
4
5
def fib(n):
a, b = 0, 1
for _ in xrange(n):
a, b = b, a + b
return b

变态台阶问题

一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

1
fib = lambda n: n if n < 2 else 2 * fib(n - 1)

矩形覆盖

我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?

第2*n个矩形的覆盖方法等于第2*(n-1)加上第2*(n-2)的方法。

1
f = lambda n: 1 if n < 2 else f(n - 1) + f(n - 2)

杨氏矩阵查找

在一个m行n列二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

去除列表中的重复元素

用集合

1
list(set(l))

用字典

1
2
3
l1 = ['b','c','d','b','c','a','a']
l2 = {}.fromkeys(l1).keys()
print l2

用字典并保持顺序

1
2
3
4
l1 = ['b','c','d','b','c','a','a']
l2 = list(set(l1))
l2.sort(key=l1.index)
print l2

列表推导式

1
2
3
l1 = ['b','c','d','b','c','a','a']
l2 = []
[l2.append(i) for i in l1 if not i in l2]

面试官提到的,先排序然后删除.

链表成对调换

1->2->3->4转换成2->1->4->3.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
class Solution:
# @param a ListNode
# @return a ListNode
def swapPairs(self, head):
if head != None and head.next != None:
next = head.next
head.next = self.swapPairs(next.next)
next.next = head
return next
return head

创建字典的方法

直接创建

1
dict = {'name':'earth', 'port':'80'}

工厂方法

1
2
3
items=[('name','earth'),('port','80')]
dict2=dict(items)
dict1=dict((['name','earth'],['port','80']))

fromkeys()方法

1
2
3
4
dict1={}.fromkeys(('x','y'),-1)
dict={'x':-1,'y':-1}
dict2={}.fromkeys(('x','y'))
dict2={'x':None, 'y':None}

合并两个有序列表

知乎远程面试要求编程

尾递归

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def _recursion_merge_sort2(l1, l2, tmp):
if len(l1) == 0 or len(l2) == 0:
tmp.extend(l1)
tmp.extend(l2)
return tmp
else:
if l1[0] < l2[0]:
tmp.append(l1[0])
del l1[0]
else:
tmp.append(l2[0])
del l2[0]
return _recursion_merge_sort2(l1, l2, tmp)
def recursion_merge_sort2(l1, l2):
return _recursion_merge_sort2(l1, l2, [])

循环算法

1
2
3
4
5
6
7
8
9
10
11
12
def loop_merge_sort(l1, l2):
tmp = []
while len(l1) > 0 and len(l2) > 0:
if l1[0] < l2[0]:
tmp.append(l1[0])
del l1[0]
else:
tmp.append(l2[0])
del l2[0]
tmp.extend(l1)
tmp.extend(l2)
return tmp

交叉链表求交点

去哪儿的面试,没做出来.

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
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
def node(l1, l2):
length1, lenth2 = 0, 0
# 求两个链表长度
while l1.next:
l1 = l1.next
length1 += 1
while l2.next:
l2 = l2.next
length2 += 1
# 长的链表先走
if length1 > lenth2:
for _ in range(length1 - length2):
l1 = l1.next
else:
for _ in range(length2 - length1):
l2 = l2.next
while l1 and l2:
if l1.next == l2.next:
return l1.next
else:
l1 = l1.next
l2 = l2.next

二分查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def binarySearch(l, t):
low, high = 0, len(l) - 1
while low < high:
print low, high
mid = (low + high) / 2
if l[mid] > t:
high = mid
elif l[mid] < t:
low = mid + 1
else:
return mid
return low if l[low] == t else False
if __name__ == '__main__':
l = [1, 4, 12, 45, 66, 99, 120, 444]
print binarySearch(l, 12)
print binarySearch(l, 1)
print binarySearch(l, 13)
print binarySearch(l, 444)

快排

1
2
3
4
5
6
7
8
9
10
11
12
def qsort(seq):
if seq==[]:
return []
else:
pivot=seq[0]
lesser=qsort([x for x in seq[1:] if x<pivot])
greater=qsort([x for x in seq[1:] if x>=pivot])
return lesser+[pivot]+greater
if __name__=='__main__':
seq=[5,6,78,9,0,-1,2,3,-65,12]
print(qsort(seq))

找零问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def coinChange(values, money, coinsUsed):
#values T[1:n]数组
#valuesCounts 钱币对应的种类数
#money 找出来的总钱数
#coinsUsed 对应于目前钱币总数i所使用的硬币数目
for cents in range(1, money+1):
minCoins = cents #从第一个开始到money的所有情况初始
for value in values:
if value <= cents:
temp = coinsUsed[cents - value] + 1
if temp < minCoins:
minCoins = temp
coinsUsed[cents] = minCoins
print('面值为:{0} 的最小硬币数目为:{1} '.format(cents, coinsUsed[cents]) )
if __name__ == '__main__':
values = [ 25, 21, 10, 5, 1]
money = 63
coinsUsed = {i:0 for i in range(money+1)}
coinChange(values, money, coinsUsed)

广度遍历和深度遍历二叉树

给定一个数组,构建二叉树,并且按层次打印这个二叉树

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
## 14 二叉树节点
class Node(object):
def __init__(self, data, left=None, right=None):
self.data = data
self.left = left
self.right = right
tree = Node(1, Node(3, Node(7, Node(0)), Node(6)), Node(2, Node(5), Node(4)))
## 层次遍历
def lookup(root):
stack = [root]
while stack:
current = stack.pop(0)
print current.data
if current.left:
stack.append(current.left)
if current.right:
stack.append(current.right)
## 深度遍历
def deep(root):
if not root:
return
print root.data
deep(root.left)
deep(root.right)
if __name__ == '__main__':
lookup(tree)
deep(tree)

前中后序遍历

深度遍历改变顺序就OK了

求最大树深

1
2
3
4
def maxDepth(root):
if not root:
return 0
return max(maxDepth(root.left), maxDepth(root.right)) + 1

求两棵树是否相同

1
2
3
4
5
6
7
def isSameTree(p, q):
if p == None and q == None:
return True
elif p and q :
return p.val == q.val and isSameTree(p.left,q.left) and isSameTree(p.right,q.right)
else :
return False

前序中序求后序

推荐: http://blog.csdn.net/hinyunsin/article/details/6315502

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def rebuild(pre, center):
if not pre:
return
cur = Node(pre[0])
index = center.index(pre[0])
cur.left = rebuild(pre[1:index + 1], center[:index])
cur.right = rebuild(pre[index + 1:], center[index + 1:])
return cur
def deep(root):
if not root:
return
deep(root.left)
deep(root.right)
print root.data

单链表逆置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Node(object):
def __init__(self, data=None, next=None):
self.data = data
self.next = next
link = Node(1, Node(2, Node(3, Node(4, Node(5, Node(6, Node(7, Node(8, Node(9)))))))))
def rev(link):
pre = link
cur = link.next
pre.next = None
while cur:
tmp = cur.next
cur.next = pre
pre = cur
cur = tmp
return pre
root = rev(link)
while root:
print root.data
root = root.next

Python Metaclass

发表于 2016-05-19 | 分类于 python

这是e-satis同学在stackoverflow上的回答

类也是对象

在理解metaclass之前(metaclass也被称为元类,这里就不翻译了),你要先掌握python中的class。python中class的概念非常奇特(一切皆对象),这一点借鉴了Smalltalk这门语言。

在大部分语言中,class只是用来描述如何产生一个object(翻译成对象,这里也不翻译了)的代码段。这在python中也成立:
In most languages, classes are just pieces of code that describe how to produce an object. That’s kinda true in Python too:

1
2
3
4
5
6
7
>>> class ObjectCreator(object):
... pass
...
>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>

但是Python中的class不止如此。class也是object。

对,类也是对象

只要你使用了class关键字,Python就会创建一个object。下面的代码段在内存中创建了一个名为ObjectCreator的object:

1
2
3
>>> class ObjectCreator(object):
... pass
...

这个object(这个class)本身就具有创建object的能力,这就是为什么它是一个类。

但是,它同时也是一个object,所以你可以:

  • 将它赋值给一个变量
  • 复制它
  • 给它添加属性
  • 将它作为函数参数进行传递

e.g.:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>> print(ObjectCreator) # you can print a class because it's an object
<class '__main__.ObjectCreator'>
>>> def echo(o):
... print(o)
...
>>> echo(ObjectCreator) # you can pass a class as a parameter
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>

动态创建类

因为类即是对象,所以你可以动态地创建它们,就像创建一个普通的object。

首先,你可以在一个函数中用class关键字来创建一个类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> def choose_class(name):
... if name == 'foo':
... class Foo(object):
... pass
... return Foo # return the class, not an instance
... else:
... class Bar(object):
... pass
... return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # the function returns a class, not an instance
<class '__main__.Foo'>
>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>

但是这还不够动态,因为你还是需要自己写整个类。

因为类即是对象,它们一定是由什么东西生成的。

当你使用class关键字,Python会自动创建这个对象。但是就像Python中其他东西一样,Python总会给你提供一种方法来手动操作它。

还记得type函数吗?这个古老而强大的函数可以告诉我们一个对象的类型是什么:

1
2
3
4
5
6
7
8
>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>

well,type还有一个截然不同的能力,它也能动态创建类。type可以接受一个类的描述作为参数,然后返回一个类。(我知道,同一个函数因为传入的参数不同而表现出完全不同的用法是很傻的。但是这是Python为了保持向后兼容)

type这样工作:

type(name of the class, 
     tuple of the parent class (for inheritance, can be empty), 
     dictionary containing attributes names and values)

e.g.:

1
2
3
4
5
6
7
8
9
>>> class MyShinyClass(object):
... pass
can be created manually this way:
>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # create an instance with the class
<__main__.MyShinyClass object at 0x8997cec>

你可能注意到我们用MyShinyClass作为了类的名字,也用它来作为变量接受类的引用。我们可以给他们取不同的名字,但是没有必要将事情复杂化。

type接受一个字典来定义类的属性,所以:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> class Foo(object):
... bar = True
Can be translated to:
>>> Foo = type('Foo', (), {'bar':True})
And used as a normal class:
>>> print(Foo)
<class '__main__.Foo'>
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar)
True

并且,可以继承它:

1
2
>>> class FooChild(Foo):
pass

这个也可以写作为:

1
2
3
4
5
>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # bar is inherited from Foo
True

最终你可能会希望为你的类增加方法。只需要定义一个有恰当签名的函数并且作为属性赋值就可以了。

1
2
3
4
5
6
7
8
9
10
11
>>> def echo_bar(self):
... print(self.bar)
...
>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
>>> hasattr(Foo, 'echo_bar')
False
>>> hasattr(FooChild, 'echo_bar')
True
>>> my_foo = FooChild()
>>> my_foo.echo_bar()
True

动态创建了类之后,你能够给类添加更多的方法,就跟给一个正常创建的对象添加方法一样。

1
2
3
4
5
6
>>> def echo_bar_more(self):
... print('yet another method')
...
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
True

可以看到:在Python中,类是对象,你可以动态创建类。这些就是当你使用class关键字的时候,Python在幕后做的事。而这些,都是通过metaclass来实现。

什么是metaclass(终于开始了)

metaclass是创建class的“东西”。

你定义class是为了创建对象是吧?但是我们也知道Python中的类也是对象。

well,metaclass就是用来创建这些对象(类)的。它们是class的class,你可以理解为:

1
2
MyClass = MetaClass()
MyObject = MyClass()

你已经看到了type让你可以这样做:

1
MyClass = type('MyClass', (), {})

这是因为type方法本质上就是一个metaclass。type是Python在背后用来创建所有类的metaclass。

现在你可能想搞清楚为什么type要全部用小写字母,而不是写成Type?

我想这是为了要和str、int保持一致。type只是用来创建类对象的类。你可以通过查看__class__属性来验证这一点。在Python中,一切,所有一切都是对象。包括整数,字符串,函数和类,它们都是对象。并且它们都是从一个类创建而来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>
Now, what is the __class__ of any __class__ ?
>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>

所以,一个metaclass仅仅是创建类对象的东西。如果你愿意,你可以称呼它为类工厂。

type是Python使用的内置的metaclass,当然,你也可以创建自己的metaclass。

metaclass属性

当你定义一个类的时候,你可以添加一个__metaclass__属性。

1
2
3
class Foo(object):
__metaclass__ = something...
[...]

如果你这样做了,Python将会使用这个metaclass去创建Foo类。

注意了,这里面有一些技巧。

你首先写下了class Foo(object),但是这个时候类对象Foo还没有被在内存中创建。

Python将会在类的定义中寻找__metaclass__属性。如果找到了,Python就会用它来创建类对象Foo。如果没有,那它就用type来创建这个类。

请把上面一段多读几遍。

当你写下:

1
2
class Foo(Bar):
pass

Python做了下面的工作:

Foo中有__metaclass__属性吗?

如果有,使用__metaclass__的值在内存中创建一个类对象(我说的是类对象,请紧跟我的思路),取名为Foo。

如果Python找不到__metaclass__,他会在MODULE层面继续找__metaclass__(所谓MODULE层面,可以理解我类定义的文件内,把这个文件看成一个MODULE),并且做和前面相同的操作(但这只适用于没有继承的类,也就是旧式类)。

如果仍未找到__metaclass__,它会用Bar(第一父类)的metaclass(可能是默认的type)来创建类对象。

Be careful here that the __metaclass__ attribute will not be inherited, the metaclass of the parent (Bar.__class__) will be. If Bar used a __metaclass__ attribute that created Bar with type() (and not type.__new__()), the subclasses will not inherit that behavior.

现在最大的问题是,你能给__metaclass__赋什么值?

答案是:可以创建一个类的值。

那什么可以创建一个类呢?type或者它的子类,或者任意用到了它的东西。

自定义 metaclass

metaclass的主要目的就是在类创建的时候,动态地修改它。

通常你会为API做这些工作,因为你想创建符合当前上下文的类。想象一个很傻的例子,你想让你的模块内所有的类的属性都大写。有好几种方法能达到这个需求,其中一种就是在MODULE层面设置__metaclass__。

通过这种方法,所有这个模块内的类都会用这个metaclass来创建,我们只需要告诉这个metaclass来将所有的属性都转换成大写。

幸运的是,__metaclass__可以是任意可调用的东西,它并不需要是一个普通的类(我知道,某些名字里带有‘class’的东西并不需要是一个class,请理解这一点,这很有帮助)。所以我们用一个函数作为一个简单的例子开始。

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
# the metaclass will automatically get passed the same argument
# that you usually pass to `type`
def upper_attr(future_class_name, future_class_parents, future_class_attr):
"""
Return a class object, with the list of its attribute turned
into uppercase.
"""
# pick up any attribute that doesn't start with '__' and uppercase it
uppercase_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
# let `type` do the class creation
return type(future_class_name, future_class_parents, uppercase_attr)
__metaclass__ = upper_attr # this will affect all classes in the module
class Foo(): # global __metaclass__ won't work with "object" though
# but we can define __metaclass__ here instead to affect only this class
# and this will work with "object" children
bar = 'bip'
print(hasattr(Foo, 'bar'))
# Out: False
print(hasattr(Foo, 'BAR'))
# Out: True
f = Foo()
print(f.BAR)
# Out: 'bip'

现在,我们用一个真正的类作为metaclass来做相同的事情:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# remember that `type` is actually a class like `str` and `int`
# so you can inherit from it
class UpperAttrMetaclass(type):
# __new__ is the method called before __init__
# it's the method that creates the object and returns it
# while __init__ just initializes the object passed as parameter
# you rarely use __new__, except when you want to control how the object
# is created.
# here the created object is the class, and we want to customize it
# so we override __new__
# you can do some stuff in __init__ too if you wish
# some advanced use involves overriding __call__ as well, but we won't
# see this
def __new__(upperattr_metaclass, future_class_name,
future_class_parents, future_class_attr):
uppercase_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return type(future_class_name, future_class_parents, uppercase_attr)

但是,这种写法并不OOP(Object-oriented programming,面向对象编程),我们直接调用了type,并且我们没有覆盖或者调用父类的__new__方法,让我们这么做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class UpperAttrMetaclass(type):
def __new__(upperattr_metaclass, future_class_name,
future_class_parents, future_class_attr):
uppercase_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
# reuse the type.__new__ method
# this is basic OOP, nothing magic in there
return type.__new__(upperattr_metaclass, future_class_name,
future_class_parents, uppercase_attr)

你可能已经注意到了有个额外的参数upperattr_metaclass,这并没有什么特别的。__new__方法的第一个参数总是表示当前的实例,就像在普通的类方法中的self参数一样。

当然了,为了清晰起见,这里的名字我起的比较长。但是就像self一样,所有的参数都有它们约定俗成的名称。因此,在真实的产品代码中一个元类应该是像这样的:

1
2
3
4
5
6
7
8
9
10
11
12
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, dct):
uppercase_attr = {}
for name, val in dct.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return type.__new__(cls, clsname, bases, uppercase_attr)

我们还可以使用super让它更清晰一点,这会ease inheritance(因为你可以拥有metaclass,从metaclass继承,从type继承)

1
2
3
4
5
6
7
8
9
10
11
12
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, dct):
uppercase_attr = {}
for name, val in dct.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)

就是这样,除此之外,对于metaclass真的没什么可说的了。

使用了metaclass的代码看起来复杂的原因并不是因为metaclass本身,而是因为你通常会用metaclass做一些晦涩的事情,依赖于自省、控制继承,和像__dict__这样的变量等。

诚然,metaclass用来搞黑魔法非常合适,因而会搞出来复杂的东西。但是就metaclass本身而言,他们非常简单:

  • 拦截类的创建
  • 修改这个类
  • 返回被修改过的类

为什么要用metaclass而不是函数?

既然metaclass可以接受任意可调用的对象,那为什么我们还要用一个类来给它赋值,这样不是明显更复杂吗?

这样做有下面几个理由:

  • 意图会更加明显。当你读到UpperAttrMetaclass(type)的时候,你知道接下来会发生什么。
  • 你可以使用OOP,metaclass可以继承自别的metaclass,覆写父类的方法。metaclass甚至可以使用metaclass。
  • 你可以将代码组织得更好。当你使用元类的时候肯定不会是像我上面举的这种简单场景,通常都是针对比较复杂的问题。将多个方法归总到一个类中会很有帮助,也会使得代码更容易阅读。
  • 你可以使用__new__, __init__以及__call__这样的特殊方法。它们能帮你处理不同的任务。就算通常你可以把所有的东西都在__new__里处理掉,有些人还是觉得用__init__更舒服些。
  • 这些类被称为metaclass,靠,这肯定意味这什么,我要小心!

究竟为什么要使用metaclass

下面回答我们最大的问题,为什么我们要使用这个晦涩难懂且容易出错的特性?

好吧,一般来说,你根本用不上它:

“元就是深 度的魔法,99%的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。那些实际用到元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。” —— Python界的领袖 Tim Peters

metaclass最多的应用场景是创建API。一个典型的例子是Django的ORM系统。它允许你像下面一样定义一些东西:

1
2
3
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()

但是如果你这样做的话:

1
2
guy = Person(name='bob', age='35')
print(guy.age)

它不会返回一个IntegerField对象,而是返回了一个整数,甚至可以从数据库里取出数据。

这个可能是因为models.Model定义了__metaclass__,它用了一些魔法来将你刚刚定义的简单的Person类转换成对数据库的一个复杂的hook。Django通过暴露一个使用metaclass的API让这些复杂的东西简化,通过这个API重新创建代码,在背后完成真正的工作。

##结语
首先,你要知道类其实是能够创建出类实例的对象。事实上,类本身也是实例,他们是metaclass的实例。

1
2
3
>>> class Foo(object): pass
>>> id(Foo)
142630324

在Python中,一切皆对象,它们不是类的实例就是metaclass的实例。

除了type

type实际上是它自己的metaclass。在纯Python环境中这是不可能做到的(你不能创建一个类,metaclass是这个类自身),这是通过在实现层面通过一些手段做到的。

其次,metaclass很复杂。对于一些非常简单的类的更改,你并不需要使用metaclass。你可以用下面两个不同的技术来修改类

  • monkey patching
  • class decorators

当你需要动态修改类的时候,99%的情况下,你最好用上面两个方法。当然了,其实在99%的情况下,你根本不需要修改类:D

##The Metaclass Hook in Python 3

metaclass在python3中语法有了变化

Python3改变了metaclass的语法。你仍然可以定义__metaclass属性,但是Python会忽略它。取而代之的是,你可以在基类列表中使用一个关键字参数:

1
2
class Simple1(object, metaclass = SimpleMeta1):
...

这意味着在Python3中,直接将__metaclass__定义成一个类或者一个方法都不再可行。所有的metaclass都必须被定义成单独的类。这种方法也有好处,它让metaclass更具有一致性,当然也更易读和理解。

Django打印请求的sql

发表于 2016-05-19 | 分类于 Django

我们平时都是用django的ORM来进行数据库操作,ORM会将我们的Python代码转换成sql语句。但是,有的时候我们得到的结果并不是理想中的结果,这时候一个有效的debug方法就是查看ORM生成的sql语句。下面是获取sql的方法:

>>> from django.db import connection
>>> connection.queries
[{'sql': 'SELECT polls_polls.id, polls_polls.question, polls_polls.pub_date FROM polls_polls',
'time': '0.002'}]

注意: 只有在DEBUG=True的情况下才能获取sql,不然拿到的是一个空list

Limit 21

我们拿到sql的时候经常会看到最后有一个limit 21,但是我们的代码里并没有给查询做限制,这个limit是哪里来的呢?下面是从django的邮件列表里找到的回答:

It happens when you make queries from the shell - the LIMIT clause is added to stop your terminal filling up with thousands of records when debugging:

You were printing (or, at least, trying to print) the repr() of the queryset. To avoid people accidentally trying to retrieve and print a million results, we (well, I) changed that to only retrieve and print the first 20 results and print “remainder truncated” if there were more. This is achieved by limiting the query to 21 results (if there are 21 results there are more than 20, so we print the “truncated” message). That only happens in the repr() – i.e. it’s only for diagnostic printing. No normal user code has this limit included automatically, so you happily create a queryset that iterates over a million results.

简而言之,你的这个查询预期会返回很多行记录,但是为了在打印的时候不让屏幕充满这些数据(这些数据根本看不出来东西,垃圾数据),所以django在这些语句后面加了limit 21。

参考

http://stackoverflow.com/questions/24041448/specifying-limit-and-offset-in-django-queryset-wont-work

http://www.mail-archive.com/django-users@googlegroups.com/msg67486.html

https://docs.djangoproject.com/en/1.9/faq/models/

pyc文件和python程序的执行过程

发表于 2016-01-23 | 分类于 python

Python是一门解释型语言?

初学Python时,听到的关于Python的第一句话就是,Python是一门解释性语言,我就这样一直相信下去,直到发现了*.pyc文件的存在。如果是解释型语言,那么生成的*.pyc文件是什么呢?c应该是compiled的缩写才对啊!

为了防止其他学习Python的人也被这句话误解,那么我们就在文中来澄清下这个问题,并且把一些基础概念给理清。

解释型语言和编译型语言

计算机是不能够识别高级语言的,所以当我们运行一个高级语言程序的时候,就需要一个“翻译机”来从事把高级语言转变成计算机能读懂的机器语言的过程。这个过程分成两类,第一种是编译,第二种是解释。

编译型语言在程序执行之前,先会通过编译器对程序执行一个编译的过程,把程序转变成机器语言。运行时就不需要翻译,而直接执行就可以了。最典型的例子就是C语言。

解释型语言就没有这个编译的过程,而是在程序运行的时候,通过解释器对程序逐行作出解释,然后直接运行,最典型的例子是Ruby。

通过以上的例子,我们可以来总结一下解释型语言和编译型语言的优缺点:因为编译型语言在程序运行之前就已经对程序做出了“翻译”,所以在运行时就少掉了“翻译”的过程,所以效率比较高。但是我们也不能一概而论,一些解释型语言也可以通过解释器的优化来在对程序做出翻译时对整个程序做出优化,从而在效率上超过编译型语言。

此外,随着Java等基于虚拟机的语言的兴起,我们又不能把语言纯粹地分成解释型和编译型这两种。

用Java来举例,Java首先是通过编译器编译成字节码文件,然后在运行时通过解释器给解释成机器文件。所以我们说Java是一种先编译后解释的语言。

再换成C#,C#首先是通过编译器将C#文件编译成IL文件,然后在通过CLR将IL文件编译成机器文件。所以我们说C#是一门纯编译语言,但是C#是一门需要二次编译的语言。同理也可等效运用到基于.NET平台上的其他语言。

Python到底是什么

其实Python和Java/C#一样,也是一门基于虚拟机的语言,我们先来从表面上简单地了解一下Python程序的运行过程吧。

当我们在命令行中输入python hello.py时,其实是激活了Python的“解释器”,告诉“解释器”:你要开始工作了。可是在“解释”之前,其实执行的第一项工作和Java一样,是编译。

熟悉Java的同学可以想一下我们在命令行中如何执行一个Java的程序:

javac hello.java
java hello

只是我们在用Eclipse之类的IDE时,将这两部给融合成了一部而已。其实Python也一样,当我们执行python hello.py时,他也一样执行了这么一个过程,所以我们应该这样来描述Python,Python是一门先编译后解释的语言。

简述Python的运行过程

在说这个问题之前,我们先来说两个概念,PyCodeObject和pyc文件。

我们在硬盘上看到的pyc自然不必多说,而其实PyCodeObject则是Python编译器真正编译成的结果。我们先简单知道就可以了,继续向下看。

当python程序运行时,编译的结果则是保存在位于内存中的PyCodeObject中,当Python程序运行结束时,Python解释器则将PyCodeObject写回到pyc文件中。

当python程序第二次运行时,首先程序会在硬盘中寻找pyc文件,如果找到,则直接载入,否则就重复上面的过程。

所以我们应该这样来定位PyCodeObject和pyc文件,我们说pyc文件其实是PyCodeObject的一种持久化保存方式。

运行一段Python程序

我们来写一段程序实际运行一下:

程序本身毫无意义。我们继续看:

然而我们在程序中并没有看到pyc文件,仍然是test.py孤零零地呆在那!

那么我们换一种写法,我们把print_str方法换到另外的一个python模块中:

然后运行程序:

这个时候pyc文件出现了,其实认真思考一下不难得到原因(下面会讲),我们考虑一下实际的业务情况。

pyc的目的是重用

回想本文的第二段在解释编译型语言和解释型语言的优缺点时,我说编译型语言的优点在于,我们可以在程序运行时不用解释,而直接利用已经“翻译”过的文件。也就是说,我们之所以要把py文件编译成pyc文件,最大的优点在于我们在运行程序时,不需要重新对该模块进行重新的解释。
所以,我们需要编译成pyc文件的应该是那些可以重用的模块,这于我们在设计软件类时是一样的目的。所以Python的解释器认为:只有import进来的模块,才是需要被重用的模块。

这个时候也许有人会说,不对啊!你的这个问题没有被解释通啊,我的test.py不是也需要运行么,虽然不是一个模块,但是以后我每次运行也可以节省时间啊!

OK,我们从实际情况出发,思考下我们在什么时候才可能运行python xxx.py文件:

  1. 执行测试时。
  2. 开启一个Web进程时。
  3. 执行一个程序脚本。

我们逐个来说,第一种情况我们就不用多说了,这个时候哪怕所有的文件都没有pyc文件都是无所谓的。

第二种情况,我们试想一个webpy的程序把,我们通常这样执行:

抑或者:

然后这个程序就类似于一个守护进程一样一直监视着8181/9002端口,而一旦中断,只可能是程序被杀死,或者其他的意外情况,那么你需要恢复要做的是把整个的Web服务重启。那么既然一直监视着,把PyCodeObject一直放在内存中就足够了,完全没必要持久化到硬盘上。
最后一个情况,执行一个程序脚本,一个程序的主入口其实很类似于Web程序中的Controller,也就是说,他负责的应该是Model之间的调度,而不包含任何的主逻辑在内,如这篇文章中所提到,Controller应该就是一个Facade,无任何的细节逻辑,只是把参数转来转去而已,那么如果做算法的同学可以知道,在一段算法脚本中,最容易改变的就是算法的各个参数,那么这个时候给持久化成pyc文件就未免有些画蛇添足了。

所以我们可以这样理解Python解释器的意图,Python解释器只把我们可能重用到的模块持久化成pyc文件。

pyc的过期时间

说完了pyc文件,可能有人会想到,每次Python的解释器都把模块给持久化成了pyc文件,那么当我的模块发生了改变的时候,是不是都要手动地把以前的pyc文件remove掉呢?

当然Python的设计者是不会犯这么白痴的错误的。而这个过程其实就取决于PyCodeObject是如何写入pyc文件中的。
我们来看一下import过程的源码吧:

这段代码比较长,我们只来看我标注了的代码,其实他在写入pyc文件的时候,写了一个Long型变量,变量的内容则是文件的最近修改日期,同理,我们再看下载入pyc的代码:

不用仔细看代码,我们可以很清楚地看到原理,其实每次在载入之前都会先检查一下py文件和pyc文件保存的最后修改日期,如果不一致则重新生成一份pyc文件。

Django-优化数据库查询

发表于 2015-11-08 | 分类于 Django

##一次检索你需要的所有东西

在不同的位置多次访问数据库,一次获取一个数据集,通常来说不如在一次查询中获取它们更高效。如果你在一个循环中执行查询,这尤其重要。有可能你会做很多次数据库查询,但只需要一次就够了。

我们需要充分了解并使用select_related()和prefetch_related()

###select_related()
Returns a QuerySet that will “follow” foreign-key relationships, selecting additional related-object data when it executes its query. This is a performance booster which results in a single more complex query but means later use of foreign-key relationships won’t require database queries.

The following examples illustrate the difference between plain lookups and select_related() lookups. Here’s standard lookup:

# Hits the database.
e = Entry.objects.get(id=5)

# Hits the database again to get the related Blog object.
b = e.blog

And here’s select_related lookup:

# Hits the database.
e = Entry.objects.select_related('blog').get(id=5)

# Doesn't hit the database, because e.blog has been prepopulated
# in the previous query.
b = e.blog

You can use select_related() with any queryset of objects:

from django.utils import timezone

# Find all the blogs with entries scheduled to be published in the future.
blogs = set()

for e in Entry.objects.filter(pub_date__gt=timezone.now()).select_related('blog'):
    # Without select_related(), this would make a database query for each
    # loop iteration in order to fetch the related blog for each entry.
    blogs.add(e.blog)

The order of filter() and select_related() chaining isn’t important. These querysets are equivalent:

Entry.objects.filter(pub_date__gt=timezone.now()).select_related('blog')
Entry.objects.select_related('blog').filter(pub_date__gt=timezone.now())

You can follow foreign keys in a similar way to querying them. If you have the following models:

from django.db import models

class City(models.Model):
    # ...
    pass

class Person(models.Model):
    # ...
    hometown = models.ForeignKey(City)

class Book(models.Model):
    # ...
    author = models.ForeignKey(Person)

… then a call to Book.objects.select_related('author__hometown').get(id=4) will cache the related Person and the related City:

b = Book.objects.select_related('author__hometown').get(id=4)
p = b.author         # Doesn't hit the database.
c = p.hometown       # Doesn't hit the database.

b = Book.objects.get(id=4) # No select_related() in this example.
p = b.author         # Hits the database.
c = p.hometown       # Hits the database.

###prefetch_related()
待补充

##不要查询你不需要的东西

###使用QuerySet.values()和values_list()
当你仅仅想要一个带有值的字典或者列表,并不需要使用ORM模型对象时,可以适当使用values()。

###使用QuerySet.count()
如果你想要获取大小,不要使用 len(queryset)

###使用QuerySet.exists()
如果你想要知道是否存在至少一个结果,不要使用if queryset

###使用QuerySet.update()和delete()
通过QuerySet.update()使用批量的SQL UPDATE语句,而不是获取大量对象,设置一些值再单独保存。与此相似,在可能的地方使用批量deletes。

但是要注意,这些批量的更新方法不会在单独的实例上面调用save()或者delete()方法,意思是任何你向这些方法添加的自定义行为都不会被执行,包括由普通数据库对象的信号驱动的任何方法。

###直接使用外键的值
如果你仅仅需要外键当中的一个值,要使用对象上你已经取得的外键的值,而不是获取整个关联对象再得到它的主键。例如,执行:

entry.blog_id

而不是:

entry.blog.id

Django Interview

发表于 2015-08-23 | 分类于 Django

##Django的Model的继承有几种形式,分别是什么?

###抽象继承:

顺便复习OOP思想,继承是面向对象的大前提。抽象类之所以被创建是用来被继承的;一个类如果包含任何一种抽象方法,那么它就是抽象类;抽象方法一定要在子类中被复写;在继承关系中,抽象类永远在树枝节点上,而且对于Python来说,已经没有这么多约束了。

回到Django的Model也一样,当我们需要某些公共方法字段时,就需要一个父类为其他子类服务,这个父类没有manager,Django也不为这个类创建表,这种继承的定义方法如下:

class Animal(models.Model):
    name = models.CharField(max_length=50)
    age = models.PositiveIntegerField()

    # 下面这句决定了Animal是一个抽象类/Model
    class Meta:
        abstract = True


class Human(Animal):
    kind_hearted = models.BooleanField()
    sex = models.CharField('sex', choices=(('m','male'), ('f', 'female')), max_length=1)
    #...

上例中,Human的子model中,自然包含了name和age的字段,但是Animal不能作为正常model使用,由于没有manager,所以也不能实例化、保存。在子类中,不可以建立与这个抽象父类中的相同的字段,Django表示对报错负责。

###正常的继承,多重继承,Joined映射
和抽象继承的主要区别是父类这时也可以拥有数据库表了,并且不在身为存储公共信息的抽象类了,父类也可以进行实例化,查询等操作了。

class Country(models.Model):
    name = models.CharField(max_length=10)
    #...


class Province(Country):
    return = models.BooleanField()
    #...

###代理
即在子类中只能增加方法,而不能增加属性,在不影响父类数据存储的前提下,使子类继承父类,此时子类称为父类的“代理”。例如:

from django.contrib.auth.models import User

class Person(User):
    # this makes a class proxy
    proxy = True

    def can_dance(self):
        return True


# both Yellow and Black can_dance :)
class Yellow(Person):
    hometown = models.CharField(max_length=30)


class Black(Person)
    tribe_name = models.CharField(max_length=100)

我们为Person类增加了一个方法,可以跳舞,并且保持了User的数据字段的不变。

##Django的Queryset是什么,objects是什么,objects在哪里可以定义

query + set,已经能猜出大概,它对应着数据库中的若干条记录。

例如有一个叫做Order的模型,在project的根目录下进入shell进行操作:

$python manage.py shell
>>>from app.order.models import Order
>>>type(Order.objects)
<class 'django.db.models.models.manager.Manager'>
>>>
>>>order = Order.objects.all()
>>>type(order)
<class 'django.db.models.query.QuerySet'>

上述方法很常用,看继承关系去理解Queryset和objets。objects是每个Model默认的manager类,通过manager的方法(也可通过QuerySet的方法得到,当然QuerySet也来自于manager),得到相应的Queryset,用以对数据库模型字段进行更多的操作。

objects(manager)方法可以自定义添加,也可以直接赋值覆盖掉默认的管理方法。

试着添加一个新的管理器的步骤是这样,首先定义一个manager类继承自models.Manager,并在其中对self进行操作,如下:

# new manager
class OrderManager(models.Manager):
    def title_count(self, keyword):
        return self.filter(title__icontains=keyword).count()


class Order(models.Models):
    title = models.CharField(max_length=100)
    # ...
    #objects = models.Manager()
    objects = OrderManager()

    def __unicode__(self):
        return self.title

上述例子中我们把OrderManager赋值给了objects,替换了默认的管理器。

tips:如果增加了新的管理器,且没有替换掉默认管理器,那么默认管理器需要显式的声明出来才可以使用。

##Django中查询queryset时什么情况下用Q?

在进行相对复杂的查询时,使用django.db.models.Q对象。

例如需要进行复合条件的查询的SQL语句如下:

SELECT * FROM order WHERE id BETWEEN 20 ADN 100 AND(num <= '20' or num >= '30');

使用Q就可以写成:

from django.db.models import Q
from login.models import Order
#...
Order.objects.get(
Q(id >= 20) & (id <= 100),
Q(num <= 20) | (num >= 30)
)

##Django中想验证表单提交是否格式正确需要用到Form中的哪个函数?

is_valid()函数方法,用于检查表单提交是否正确。

##Django取消级联删除

这个文档写的比较清楚:

user = models.ForeignKey(User, blank=True, null=True, on_delete=models.SET_NULL)

并且SET_NULL只有在null为True的时候,才可以使用。

##Django中如何在Model保存前做一定的固定操作,比如写一句日志?

关键词: 信号

利用Django的Model的Signal Dispatcher, 通过django.db.models.signals.pre_save()方法,在事件发生前,发射触发信号,这一切都被调度中的receiver方法深藏功与名的保存了。
信号的处理一般都写在Model中,举个例子:

import logging
from django.db import models
from django.db.models.signals import pre_save
from django.dispatch import receiver

class Order(models.Model):
    # ...


logger = logging.getLogger(__name__)

@receiver(pre_save, sender=Order)
def pre_save_handler(sender, **kwargs):

    # 我们可以在Order这个Model保存之前尽情玩耍
    logger.debug("{},{}".format(sender, **kwargs))
    print 'fuck universe'

这样应该就实现了题中的要求,类似的方法还有比如pre_init是在Model实例之前会触发,post_init在实例之后触发,同理就是pre_save和post_save了。

##Django中如何读取和保存session,整个session的运行机制是什么。

说到session的运行机制,就一定要先说一下cookie这一段信息。一般情况下cookies都是我们的浏览器生成的(显然可以人为修改),用于服务器对户进行筛选和维护,但是这个听上去很好吃的东西,能存的东西有点少而且容易被别人利用。这时候基于cookies的session的意义就比较明显了,在客户端的cookies中我们只保存session id,而将完整信息以加密信息的形式保存到服务器端,这样服务器可以根据session id相对安全的在数据库中查询用户的更细致的信息和状态。

在Django中session和cookies的操作方法一样,如下:

# 保存session
request.session['order_id'] = order_id
# 删除session
del request.session['order_id']
# 读取session
session.get('order_id', False)
1234…18
You Wangqiu

You Wangqiu

世之奇伟、瑰怪,非常之观,常在于险远,而人之所罕至焉,故非有志者不能至也

171 日志
21 分类
24 标签
GitHub 知乎 E-Mail
© 2018 You Wangqiu
由 Hexo 强力驱动
主题 - NexT.Muse