• Python入门篇-装饰器


                  Python入门篇-装饰器

                                          作者:尹正杰

    版权声明:原创作品,谢绝转载!否则将追究法律责任。

    一.装饰器概述

    装饰器(无参)
      它是一个函数
      函数作为它的形参
      返回值也是一个函数
      可以使用@functionname方式,简化调用
    
    装饰器和高阶函数
      装饰器是高阶函数,但装饰器是对传入函数的功能的装饰(功能增强)
    
    带参装饰器
      它是一个函数
      函数作为它的形参
      返回值是一个不带参的装饰器函数
      使用@functionname(参数列表)方式调用
      可以看做在装饰器外层又加了一层函数

    二.为什么要用装饰器

    1>.在不是用装饰器的情况下,给某个函数添加功能

    在解释为什么使用装饰器之前,完美来看一个需求:
      一个加法函数,想增强它的功能,能够输出被调用过以及调用的参数信息     
    def add(x, y):       return x + y
      增加信息输出功能:     
    def add(x, y):       print("call add, x + y") # 日志输出到控制台       return x + y
      上面的加法函数是完成了需求,但是有以下的缺点     打印语句的耦合太高,换句话说,我们不推荐去修改初始的add函数原始代码。     加法函数属于业务功能,而输出信息的功能,属于非业务功能代码,不该放在业务函数加法中

    2>.使用高阶函数给某个函数添加功能

     1 #!/usr/bin/env python
     2 #_*_coding:utf-8_*_
     3 #@author :yinzhengjie
     4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
     5 #EMAIL:y1053419035@qq.com
     6 
     7 
     8 def add(x,y):
     9     return x + y
    10 
    11 def logger(func):
    12     print('begin') # 增强的输出
    13     f = func(4,5)
    14     print('end') # 增强的功能
    15     return f
    16 
    17 print(logger(add))
    18 
    19 
    20 
    21 #以上代码输出结果如下:
    22 begin
    23 end
    24 9

    3>.解决了传参的问题,进一步改变

     1 #!/usr/bin/env python
     2 #_*_coding:utf-8_*_
     3 #@author :yinzhengjie
     4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
     5 #EMAIL:y1053419035@qq.com
     6 
     7 
     8 def add(x,y):
     9     return x + y
    10 
    11 def logger(func,*args,**kwargs):
    12     print('begin') # 增强的输出
    13     f = func(*args,**kwargs)
    14     print('end') # 增强的功能
    15     return f
    16 
    17 print(logger(add,5,y=60))
    18 
    19 
    20 
    21 #以上代码输出结果如下:
    22 begin
    23 end
    24 65

    4>.柯里化实现add函数功能增强

     1 #!/usr/bin/env python
     2 #_*_coding:utf-8_*_
     3 #@author :yinzhengjie
     4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
     5 #EMAIL:y1053419035@qq.com
     6 
     7 def add(x,y):
     8     return x + y
     9 
    10 def logger(fn):
    11     def wrapper(*args,**kwargs):
    12         print('begin')
    13         x = fn(*args,**kwargs)
    14         print('end')
    15         return x
    16     return wrapper
    17 
    18 # print(logger(add)(5,y=50))        #海航代码等价于下面两行代码,只是换了一种写法而已
    19 add = logger(add)
    20 print(add(x=5, y=10))
    21 
    22 
    23 #以上代码输出结果如下:
    24 begin
    25 end
    26 15

    5>.装饰器语法糖

    #!/usr/bin/env python
    #_*_coding:utf-8_*_
    #@author :yinzhengjie
    #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
    #EMAIL:y1053419035@qq.com
    
    
    """
        定义一个装饰器
    """
    def logger(fn):
        def wrapper(*args,**kwargs):
            print('begin')
            x = fn(*args,**kwargs)
            print('end')
            return x
        return wrapper
    
    @logger # 等价于add = logger(add),这就是装饰器语法
    def add(x,y):
        return x + y
    
    print(add(45,40))
    
    
    
    #以上代码输出结果如下:
    begin
    end
    85

    三.帮助文档之文档字符串

    1>.定义python的文档字符串

     1 #!/usr/bin/env python
     2 #_*_coding:utf-8_*_
     3 #@author :yinzhengjie
     4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
     5 #EMAIL:y1053419035@qq.com
     6 
     7 
     8 """
     9 Python的文档
    10     Python是文档字符串Documentation Strings
    11     在函数语句块的第一行,且习惯是多行的文本,所以多使用三引号
    12     惯例是首字母大写,第一行写概述,空一行,第三行写详细描述
    13     可以使用特殊属性__doc__访问这个文档
    14 """
    15 
    16 def add(x,y):
    17     """This is a function of addition"""
    18     a = x+y
    19     return x + y
    20 
    21 print("name = {}
    doc = {}".format(add.__name__, add.__doc__))
    22 
    23 print(help(add))
    24 
    25 
    26 
    27 #以上代码执行结果如下:
    28 name = add
    29 doc = This is a function of addition
    30 Help on function add in module __main__:
    31 
    32 add(x, y)
    33     This is a function of addition
    34 
    35 None

    2>.装饰器的副作用

     1 #!/usr/bin/env python
     2 #_*_coding:utf-8_*_
     3 #@author :yinzhengjie
     4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
     5 #EMAIL:y1053419035@qq.com
     6 
     7 
     8 def logger(fn):
     9     def wrapper(*args,**kwargs):
    10         'I am wrapper'
    11         print('begin')
    12         x = fn(*args,**kwargs)
    13         print('end')
    14         return x
    15     return wrapper
    16 
    17 @logger #add = logger(add)
    18 def add(x,y):
    19     '''This is a function for add'''
    20     return x + y
    21 
    22 
    23 print("name = {}
    doc= {}".format(add.__name__, add.__doc__))      #原函数对象的属性都被替换了,而使用装饰器,我们的需求是查看被封装函数的属性,如何解决?
    24 
    25 
    26 
    27 
    28 #以上代码执行结果如下:
    29 name = wrapper
    30 doc= I am wrapper

    3>.提供一个函数,被封装函数属性==copy==> 包装函数属性

     1 #!/usr/bin/env python
     2 #_*_coding:utf-8_*_
     3 #@author :yinzhengjie
     4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
     5 #EMAIL:y1053419035@qq.com
     6 
     7 
     8 """
     9     通过copy_properties函数将被包装函数的属性覆盖掉包装函数
    10     凡是被装饰的函数都需要复制这些属性,这个函数很通用
    11     可以将复制属性的函数构建成装饰器函数,带参装饰器
    12 """
    13 def copy_properties(src, dst): # 可以改造成装饰器
    14     dst.__name__ = src.__name__
    15     dst.__doc__ = src.__doc__
    16 
    17 def logger(fn):
    18     def wrapper(*args,**kwargs):
    19         'I am wrapper'
    20         print('begin')
    21         x = fn(*args,**kwargs)
    22         print('end')
    23         return x
    24     copy_properties(fn, wrapper)
    25     return wrapper
    26 
    27 @logger #add = logger(add)
    28 def add(x,y):
    29     '''This is a function for add'''
    30     return x + y
    31 
    32 print("name = {}
    doc = {}".format(add.__name__, add.__doc__))
    33 
    34 
    35 
    36 
    37 #以上代码执行结果如下:
    38 name = add
    39 doc = This is a function for add

    4>.提供一个函数,被封装函数属性==copy==> 包装函数属性,改造成带参装饰器

     1 #!/usr/bin/env python
     2 #_*_coding:utf-8_*_
     3 #@author :yinzhengjie
     4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
     5 #EMAIL:y1053419035@qq.com
     6 
     7 
     8 def copy_properties(src): # 柯里化
     9     def _copy(dst):
    10         dst.__name__ = src.__name__
    11         dst.__doc__ = src.__doc__
    12         return dst
    13     return _copy
    14 
    15 def logger(fn):
    16     @copy_properties(fn) # wrapper = copy_properties(fn)(wrapper)
    17     def wrapper(*args,**kwargs):
    18         'I am wrapper'
    19         print('begin')
    20         x = fn(*args,**kwargs)
    21         print('end')
    22         return x
    23     return wrapper
    24 
    25 @logger #add = logger(add)
    26 def add(x,y):
    27     '''This is a function for add'''
    28     return x + y
    29 
    30 print("name = {}
    doc = {}".format(add.__name__, add.__doc__))
    31 
    32 
    33 
    34 #以上代码执行结果如下:
    35 name = add
    36 doc = This is a function for add

    5>.使用Python提供的wrap装饰器修改被装饰的doc信息 

     1 #!/usr/bin/env python
     2 #_*_conding:utf-8_*_
     3 #@author :yinzhengjie
     4 #blog:http://www.cnblogs.com/yinzhengjie
     5 
     6 from functools import wraps
     7 
     8 
     9 def logger(fn):
    10 
    11     @wraps(fn)      #其实查看wraps源码是利用update_wrapper实现的(需要有偏函数知识),但是实际开发中我们推荐使用wraps装饰去。
    12     def wrapper(*args,**kwargs):
    13         '''This is a function for wrapper'''
    14         ret = fn(*args,**kwargs)
    15         return ret
    16     return wrapper
    17 
    18 
    19 @logger
    20 def add(x,y):
    21     '''This is a function for add'''
    22     return x + y
    23 
    24 print("name = {}
    doc = {}".format(add.__name__, add.__doc__))
    25 
    26 
    27 
    28 
    29 
    30 #以上代码执行结果如下:
    31 name = add
    32 doc = This is a function for add

    四.装饰器案例

    1>.无参装饰器

     1 #!/usr/bin/env python
     2 #_*_coding:utf-8_*_
     3 #@author :yinzhengjie
     4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
     5 #EMAIL:y1053419035@qq.com
     6 
     7 import datetime
     8 import time
     9 
    10 """
    11     定义一个装饰器
    12 """
    13 def logger(fn):
    14     def wrap(*args, **kwargs):
    15         # before 功能增强
    16         print("args={}, kwargs={}".format(args,kwargs))
    17         start = datetime.datetime.now()
    18         ret = fn(*args, **kwargs)
    19         # after 功能增强
    20         duration = datetime.datetime.now() - start
    21         print("function {} took {}s.".format(fn.__name__, duration.total_seconds()))
    22         return ret
    23     return wrap
    24 
    25 @logger # 相当于add = logger(add),调用装饰器
    26 def add(x, y):
    27     print("===call add===========")
    28     time.sleep(2)
    29     return x + y
    30 
    31 print(add(4, y=7))
    32 
    33 
    34 
    35 #以上代码输出结果如下:
    36 args=(4,), kwargs={'y': 7}
    37 ===call add===========
    38 function add took 2.000114s.
    39 11

    2>.有参装饰器

     1 #!/usr/bin/env python
     2 #_*_coding:utf-8_*_
     3 #@author :yinzhengjie
     4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
     5 #EMAIL:y1053419035@qq.com
     6 
     7 import datetime,time
     8 
     9 def copy_properties(src): # 柯里化
    10     def _copy(dst):
    11         dst.__name__ = src.__name__
    12         dst.__doc__ = src.__doc__
    13         return dst
    14     return _copy
    15 
    16 """
    17 定义装饰器:
    18     获取函数的执行时长,对时长超过阈值的函数记录一下
    19 """
    20 def logger(duration):
    21     def _logger(fn):
    22         @copy_properties(fn) # wrapper = wrapper(fn)(wrapper)
    23         def wrapper(*args,**kwargs):
    24             start = datetime.datetime.now()
    25             ret = fn(*args,**kwargs)
    26             delta = (datetime.datetime.now() - start).total_seconds()
    27             print('so slow') if delta > duration else print('so fast')
    28             return ret
    29         return wrapper
    30     return _logger
    31 
    32 @logger(5) # add = logger(5)(add)
    33 def add(x,y):
    34     time.sleep(3)
    35     return x + y
    36 
    37 print(add(5, 6))
    38 
    39 
    40 
    41 #以上代码执行结果如下:
    42 so fast
    43 11
     1 #!/usr/bin/env python
     2 #_*_coding:utf-8_*_
     3 #@author :yinzhengjie
     4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
     5 #EMAIL:y1053419035@qq.com
     6 
     7 import datetime,time
     8 
     9 def copy_properties(src): # 柯里化
    10     def _copy(dst):
    11         dst.__name__ = src.__name__
    12         dst.__doc__ = src.__doc__
    13         return dst
    14     return _copy
    15 
    16 """
    17 定义装饰器:
    18     获取函数的执行时长,对时长超过阈值的函数记录一下
    19 """
    20 def logger(duration, func=lambda name, duration: print('{} took {}s'.format(name, duration))):
    21     def _logger(fn):
    22         @copy_properties(fn) # wrapper = wrapper(fn)(wrapper)
    23         def wrapper(*args,**kwargs):
    24             start = datetime.datetime.now()
    25             ret = fn(*args,**kwargs)
    26             delta = (datetime.datetime.now() - start).total_seconds()
    27             if delta > duration:
    28                 func(fn.__name__, duration)
    29             return ret
    30         return wrapper
    31     return _logger
    32 
    33 @logger(5) # add = logger(5)(add)
    34 def add(x,y):
    35     time.sleep(3)
    36     return x + y
    37 
    38 print(add(5, 6))
    39 
    40 
    41 
    42 #以上代码输出结果如下:
    43 11
    将记录的功能提取出来,这样就可以通过外部提供的函数来灵活的控制输出

    五.functools模块

    1>.functools概述

    functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS,updated=WRAPPER_UPDATES)  
      类似copy_properties功能
      wrapper 包装函数、被更新者,wrapped 被包装函数、数据源
      元组WRAPPER_ASSIGNMENTS中是要被覆盖的属性'__module__', '__name__', '__qualname__', '__doc__', '__annotations__'模块名、名称、限定名、文档、参数注解
      元组WRAPPER_UPDATES中是要被更新的属性,__dict__属性字典
      增加一个__wrapped__属性,保留着wrapped函数

    2>.functools模块案例

     1 #!/usr/bin/env python
     2 #_*_coding:utf-8_*_
     3 #@author :yinzhengjie
     4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
     5 #EMAIL:y1053419035@qq.com
     6 
     7 import datetime, time, functools
     8 
     9 def logger(duration, func=lambda name, duration: print('{} took {}s'.format(name, duration))):
    10     def _logger(fn):
    11         @functools.wraps(fn)
    12         def wrapper(*args,**kwargs):
    13             start = datetime.datetime.now()
    14             ret = fn(*args,**kwargs)
    15             delta = (datetime.datetime.now() - start).total_seconds()
    16             if delta > duration:
    17                 func(fn.__name__, duration)
    18             return ret
    19         return wrapper
    20     return _logger
    21 
    22 @logger(5) # add = logger(5)(add)
    23 def add(x,y):
    24     time.sleep(1)
    25     return x + y
    26 
    27 print(add(5, 6), add.__name__, add.__wrapped__, add.__dict__, sep='
    ')
    28 
    29 
    30 
    31 
    32 #以上代码执行结果如下:
    33 11
    34 add
    35 <function add at 0x0000000002A0F378>
    36 {'__wrapped__': <function add at 0x0000000002A0F378>}

    六.小试牛刀

    1>.实现Base64编码(要求自己实现算法,不用库)

     1 #!/usr/bin/env python#_*_conding:utf-8_*_
     2 #@author :yinzhengjie
     3 #blog:http://www.cnblogs.com/yinzhengjie
     4 
     5 from string import ascii_lowercase, ascii_uppercase, digits
     6 import base64
     7 
     8 
     9 bytesBase64 = (ascii_uppercase + ascii_lowercase + digits + "+/").encode("utf-8")       #注意,我们这里拿到的是一个字节序列哟~
    10 
    11 
    12 def base64encode(src:str,code_type="utf-8") -> bytes:
    13     res = bytearray()
    14 
    15     if isinstance(src,str):
    16         _src = src.encode(code_type)
    17     elif isinstance(src,bytes):
    18         _src = src
    19     else:
    20         raise TypeError
    21 
    22     length = len(_src)
    23 
    24     for offset in range(0,length,3):
    25         triple = _src[offset:offset+3]      #切片可以越界
    26 
    27         r = 3 - len(triple)
    28 
    29         if r:
    30             triple += b'x00'  * r            #便于计算先补零,即传入的字符串转换成字节后不足3个字节就补零
    31 
    32         """
    33             bytes和bytearray都是按照字节操作的,需要转换为整数才能进行位运算,将3个字节看成一个整体转成字节bytes,
    34         使用大端模式,如"abc => 0x616263"
    35         """
    36         b = int.from_bytes(triple,'big')
    37 
    38         for i in range(18,-1,-6):
    39             index = b >> i if i == 18 else b >> i & 0x3F        #注意,十六进制0x3F使用而二进制表示为: "11 1111"
    40             res.append(bytesBase64[index])
    41 
    42 
    43         if r:
    44             res[-r:] = b'=' * r     #替换等号,从索引-r到末尾使用右边的多个元素依次替换
    45 
    46     return bytes(res)
    47 
    48 
    49 if __name__ == '__main__':
    50     testList = ["a", "`", "ab", "abc", "jason", "yinzhengjie", "尹正杰2019"]
    51     for item in testList:
    52         print(item)
    53         print("自定义的base64编码:{}".format(base64encode(item)))
    54         print("使用base64标准库编码:{}".format(base64.b64encode(item.encode())))
    55         print("*" * 50)
    参考案例

    2>.实现一个cache装饰器,实现可过期被清楚的功能

    缓存的应用场景:
      有数据需要频繁使用。
      获取数据代价高,即每次获取都需要大量或者较长等待时间。
    使用缓存来提高查询速度,用内存空间换取查询,加载时间。cache的应用极广,比如硬件CPU的一级,二级缓存,硬盘自带的缓存空间,软件的redies,varnish集群缓存软件等等。
      1 #!/usr/bin/env python
      2 #_*_conding:utf-8_*_
      3 #@author :yinzhengjie
      4 #blog:http://www.cnblogs.com/yinzhengjie
      5 
      6 
      7 from  functools import wraps
      8 import  time,inspect,datetime
      9 
     10 
     11 """
     12     缓存装饰器实现
     13 """
     14 def jason_cache(duration):
     15     def _cache(fn):
     16         local_cache = {}  # 对不同函数名是不同的cache
     17         @wraps(fn)
     18         def wrapper(*args, **kwargs):
     19             # 使用缓存时才清除过期的key
     20             expire_keys = []
     21             for k, (_, stamp) in local_cache.items():
     22                 now = datetime.datetime.now().timestamp()
     23                 if now - stamp > duration:
     24                     expire_keys.append(k)
     25 
     26             for k in  expire_keys:
     27                 local_cache.pop(k)
     28 
     29             sig = inspect.signature(fn)
     30             params = sig.parameters  # 参数处理,构建key,获取一个只读的有序字典
     31             target = {}  # 目标参数字典
     32 
     33             """
     34             param_name = [key for key in params.keys()]
     35             #位置参数
     36             for i,v in enumerate(args):
     37                 k = param_name[i]
     38                 target[k] = v
     39             target.update(zip(params.keys(),args))
     40 
     41             #关键词参数
     42             for k,v in kwargs.items():
     43                 target[k] = v
     44             target.update(kwargs)
     45             """
     46             # 位置参数,关键字参数二合一处理
     47             target.update(zip(params.keys(), args), **kwargs)
     48 
     49             # 缺省值处理
     50             for k in (params.keys() - target.keys()):
     51                 target[k] = params[k].default
     52 
     53             """
     54             target.update(((k,params[k].default) for k in (params.keys() - target.keys())))
     55 
     56             for k,v in params.items():
     57                 if k not in target.keys():
     58                     target[k] = v.default
     59             """
     60             key = tuple(sorted(target.items()))
     61 
     62             #待补充,判断是否需要缓存
     63             if key not in local_cache.keys():
     64                 local_cache[key] = fn(*args, **kwargs),datetime.datetime.now().timestamp()
     65             return key, local_cache[key]
     66         return wrapper
     67     return _cache
     68 
     69 
     70 
     71 """
     72     装饰查看函数执行时间
     73 """
     74 def logger(fn):
     75 
     76     def wrapper(*args,**kwargs):
     77         start = datetime.datetime.now()
     78         ret = fn(*args,**kwargs)
     79         delta = (datetime.datetime.now() - start).total_seconds()
     80         print(fn.__name__,delta)
     81         return ret
     82     return wrapper
     83 
     84 """
     85     使用多个装饰器,需要注意调用过程和执行过程
     86         调用过程:
     87             生长洋葱一样,遵循就近原则,即离得近的装饰器先装饰,从内向外。
     88         执行过程:
     89             剥洋葱一样,从外向里执行
     90 """
     91 @logger
     92 @jason_cache(10)
     93 def add(x,y,z=30):
     94     time.sleep(3)
     95     return x + y + z
     96 
     97 
     98 if __name__ == '__main__':
     99     result = []
    100     result.append(add(10,20))
    101     result.append(add(10,y=20))
    102     result.append(add(10, 20, 30))
    103     result.append(add(10,z=30,y=20))
    104     result.append(add(x=10,y=20,z=30))
    105 
    106     for item in result:
    107         print(item)
    参考案例

    七.装饰器的用途

      装饰器是AOP面向切面编程 Aspect Oriented Programming的思想的体现。

      面向对象往往需要通过继承或者组合依赖等方式调用一些功能,这些功能的代码往往可能再多个类中出现,例如logger功能代码。这样造成代码的重复,增加了耦合。loggger的改变影响所有其它的类或方法。

      而AOP再许哟啊的类或者方法上切下,前后的切入点可以加入增强的功能。让调用者和被调用者解耦,这是一种不修改原来的业务代码,给程序员动态添加功能的技术。例如logger函数就是对业务函数增加日志的功能,而业务函数中应该把业务无关的日志功能剥离干净。

    八.装饰器应用场景

    日志,监控,权限,审计,参数检查,路由等处理。
    
    这些功能与业务功能无关,是很多都需要的公有的功能,所有适合独立出来,需要的时候,对目标对象进行增强。
    
    简单讲:缺什么,补什么。
  • 相关阅读:
    DataGridView中绑定List泛型的问题 [轉]
    .NET 導入EXCEL後數值型toString會變成空問題
    Linq連接List時多值時使用方法
    SQL2008报表三种实现Reporting Service2008匿名访问的方法(转)
    IIS7 WCF HTTP 错误 404.3 Not Found
    sql2005取得TABLE主鍵及欄位名稱,說明
    MSSQL禁用/啟用TRIGGER
    IE6下a:hover span失效问题(转载)
    asp.ner上传文件限制(转载)
    css优先级(转载)
  • 原文地址:https://www.cnblogs.com/yinzhengjie/p/10964821.html
Copyright © 2020-2023  润新知