Python中的装饰器是你进入Python大门的一道坎,不管你跨不跨过去它都在那里。

为什么需要装饰器

我们假设你的程序实现了say_hello()和say_goodbye()两个函数。

def say_hello():

print "hello!"

def say_goodbye():

print "hello!" # bug here

if name == 'main':

say_hello()

say_goodbye()

但是在实际调用中,我们发现程序出错了,上面的代码打印了两个hello。经过调试你发现是say_goodbye()出错了。老板要求调用每个方法前都要记录进入函数的名称,比如这样:

[DEBUG]: Enter say_hello()

Hello!

[DEBUG]: Enter say_goodbye()

Goodbye!

好,小A是个毕业生,他是这样实现的。

def say_hello():

print "[DEBUG]: enter say_hello()"

print "hello!"

def say_goodbye():

print "[DEBUG]: enter say_goodbye()"

print "hello!"

if name == 'main':

say_hello()

say_goodbye()

很low吧? 嗯是的。小B工作有一段时间了,他告诉小A可以这样写。

def debug():

import inspect

caller_name = inspect.stack()[1][3]

print "[DEBUG]: enter {}()".format(caller_name)

def say_hello():

debug()

print "hello!"

def say_goodbye():

debug()

print "goodbye!"

if name == 'main':

say_hello()

say_goodbye()

是不是好一点?那当然,但是每个业务函数里都要调用一下debug()函数,是不是很难受?万一老板说say相关的函数不用debug,do相关的才需要呢?

那么装饰器这时候应该登场了。

装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。

概括的讲,装饰器的作用就是为已经存在的函数或对象添加额外的功能。

怎么写一个装饰器

在早些时候 (Python Version < 2.4,2004年以前),为一个函数添加额外功能的写法是这样的。

def debug(func):

def wrapper():

print "[DEBUG]: enter {}()".format(func.name)

return func()

return wrapper

def say_hello():

print "hello!"

say_hello = debug(say_hello) # 添加功能并保持原函数名不变

上面的debug函数其实已经是一个装饰器了,它对原函数做了包装并返回了另外一个函数,额外添加了一些功能。因为这样写实在不太优雅,在后面版本的Python中支持了@语法糖,下面代码等同于早期的写法。

def debug(func):

def wrapper():

print "[DEBUG]: enter {}()".format(func.name)

return func()

return wrapper

@debug

def say_hello():

print "hello!"

这是最简单的装饰器,但是有一个问题,如果被装饰的函数需要传入参数,那么这个装饰器就坏了。因为返回的函数并不能接受参数,你可以指定装饰器函数wrapper接受和原函数一样的参数,比如:

def debug(func):

def wrapper(something): # 指定一毛一样的参数

print "[DEBUG]: enter {}()".format(func.name)

return func(something)

return wrapper # 返回包装过函数

@debug

def say(something):

print "hello {}!".format(something)

这样你就解决了一个问题,但又多了N个问题。因为函数有千千万,你只管你自己的函数,别人的函数参数是什么样子,鬼知道?还好Python提供了可变参数*args和关键字参数**kwargs,有了这两个参数,装饰器就可以用于任意目标函数了。

def debug(func):

def wrapper(args, **kwargs): # 指定宇宙无敌参数

print "[DEBUG]: enter {}()".format(func.name)

print 'Prepare and say...',

return func(
args, **kwargs)

return wrapper # 返回

@debug

def say(something):

print "hello {}!".format(something)

至此,你已完全掌握初级的装饰器写法。

高级一点的装饰器

带参数的装饰器和类装饰器属于进阶的内容。在理解这些装饰器之前,最好对函数的闭包和装饰器的接口约定有一定了解。(参见http://betacat.online/posts/python-closure/)

带参数的装饰器

假设我们前文的装饰器需要完成的功能不仅仅是能在进入某个函数后打出log信息,而且还需指定log的级别,那么装饰器就会是这样的。

def logging(level):

def wrapper(func):

def inner_wrapper(args, **kwargs):

print "[{level}]: enter function {func}()".format(

level=level,

func=func.name)

return func(
args, **kwargs)

return inner_wrapper

return wrapper

@logging(level='INFO')

def say(something):

print "say {}!".format(something)

如果没有使用@语法,等同于

say = logging(level='INFO')(say)

@logging(level='DEBUG')

def do(something):

print "do {}...".format(something)

if name == 'main':

say('hello')

do("my work")

是不是有一些晕?你可以这么理解,当带参数的装饰器被打在某个函数上时,比如@logging(level='DEBUG'),它其实是一个函数,会马上被执行,只要这个它返回的结果是一个装饰器时,那就没问题。细细再体会一下。

'''

基于类实现的装饰器

装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。在Python中一般callable对象都是函数,但也有例外。只要某个对象重载了__call__()方法,那么这个对象就是callable的。

class Test():

def call(self):

print 'call me!'

t = Test()

t() # call me

像__call__这样前后都带下划线的方法在Python中被称为内置方法,有时候也被称为魔法方法。重载这些魔法方法一般会改变对象的内部行为。上面这个例子就让一个类对象拥有了被调用的行为。

回到装饰器上的概念上来,装饰器要求接受一个callable对象,并返回一个callable对象(不太严谨,详见后文)。那么用类来实现也是也可以的。我们可以让类的构造函数__init__()接受一个函数,然后重载__call__()并返回一个函数,也可以达到装饰器函数的效果。

【不带参数的装饰器】

'''

class logging(object):

def init(self,func):

self.func = func

def __call__(self, *args, **kwargs):
print ('[DEBUG]:enter function {func}'.format(func = self.func.__name__))
return self.func(*args,**kwargs)

@logging

def say(something):

print ('say %s' % something)

say('abc')

'''

【结果】

[DEBUG]:enter function say

say abc

'''

'''

【带参数的装饰器】

如果需要通过类形式实现带参数的装饰器,那么会比前面的例子稍微复杂一点。那么在构造函数里接受的就不是一个函数,而是传入的参数。通过类把这些参数保存起来。然后在重载__call__方法是就需要接受一个函数并返回一个函数。

'''

class logging(object):

def init(self,level='INFO'):

self.level = level

def __call__(self, func): #接收函数
def wrapper(*args,**kwargs):
print ('[%s]: enter function %s' % (self.level,func.__name__))
func(*args,**kwargs)
return wrapper #返回函数

@logging(level='DEBUG')

def say(something):

print ('say %s' % something)

say('hello python')

'''

【结果】

[DEBUG]: enter function say

say hello python

'''

【内置的装饰器】

内置的装饰器和普通装饰器原理是一样的,只不过返回的不是函数,而是类对象

@property

在了解这个装饰器前,你需要知道在不使用装饰器怎么写一个属性

class X(object):

def init(self,x):

self._x = x

def getx(self):
return self._x def setx(self, x):
self._x = x def delx(self):
del self._x p = property(getx,setx,delx,'I am doc for property')

xx = X('abc')

print (xx.p)

'''

【结果】

abc

'''

def getx(self):

return self._x

def setx(self, x):

self._x = x

def delx(self):

del self._x

p = property(getx,setx,delx,'I am doc for property')

print (p)

print (property())

'''

【结果】

<property object at 0x102253098>

<property object at 0x1022530e8>

'''

'''

以上就是一个Python属性的标准写法,其实和Java挺像的,但是太罗嗦。有了@语法糖,能达到一样的效果但看起来更简单。

'''

@property

def getx(self):

return self._x

print (property())

'''

【结果】

<property object at 0x102253138>

'''

'''

【备注】属性有三个装饰器:setter, getter, deleter ,都是在property()的基础上做了一些封装,因为setter和deleter是property()的第二和第三个参数,不能直接套用@语法。getter装饰器和不带getter的属性装饰器效果是一样的,估计只是为了凑数,本身没有任何存在的意义。经过@property装饰过的函数返回的不再是一个函数,而是一个property对象。

'''

'''

【classmethod & staticmethod】

'''

'''

尽管classmethod和staticmethod非常的相似,但是两者在具体的使用上还是有着细微的差别:classmethod必须使用类对象作为第一个参数,而staticmethod则可以不传递任何参数。

让我们通过实际的例子来看看。

样板

让我们假设有处理日期信息的类:

class Date(object):

day = 0

month = 0

year = 0

def __init__(self, day=0, month=0, year=0):
self.day = day
self.month = month
self.year = year

这个类很显然可以被用来存储某些日期信息(不考虑时区信息;让我们假设所有的日期都用UTC表示)

这里定义了__init__,典型的类实例初始化方法,它作为典型的instancemethod接受参数,其中第一个传递的必要参数是新建的实例本身。

类方法

有一些可以通过使用classmethod很好解决的任务。

假设我们有很多('dd-mm-yyyy')格式字符串的日期信息,想要把它们创建成Date类实例。我们不得不在项目的不同地方做这些事情。

所以我们必须要做到:

分析得到的年月日字符串,把它们转化成三个整形变量或者拥有三个元素的元组的变量。

通过传递这些值实例化Date。

得到:

day, month, year = map(int, string_date.split('-'))

date1 = Date(day, month, year)

C++拥有重载的特性可以达到这种目的,但是Python缺乏此类特性。所以,python使用classmethod的方式。让我们尝试一种另类的构造函数。

'''

class Date(object):

day = 0

month = 0

year = 0

def __init__(self,day=0,month=0,year=0):
self.day = day
self.month = month
self.year = year @classmethod
def from_str(cls,date_as_str):
day,month,year = date_as_str.split('-')
date1 = cls(year,month,day)
return date1

date2 = Date.from_str('2018-12-15')

print (date2)

print (date2.year)

date4 = Date(1,1,2020)

print (date4.year)

'''

【结果】

<main.Date object at 0x102a50f28>

2018

2020

'''

class subDate(Date):

pass

date3 = subDate.from_str('2019-01-01')

print (date3)

print (date3.year)

date5 = subDate(1,1,2021)

print (date5.year)

'''

【结果】

<main.subDate object at 0x102a560b8>

2019

2021

'''

'''

进一步分析一下以上代码的执行,以及它的优势:

1.在一个地方解析日期字符串并且重复使用它。

2.做到很好的封装(相对于把执行字符串解析作为一个单独的函数在任何地方执行,这里使用的方法更符合OOP的范式)

3.cls表示类对象,而不是类实例。这样很酷,因为如果我们继承Date类,那么所有的子类也都将拥有from_string这个方法。

'''

'''

静态方法

那么staticmethod又是什么呢?它和classmethod非常的相似,但是不强制要求传递参数(但是做的事与类方法或实例方法一样)。

让我们来看一个使用的例子。

我们有一个日期字符串需要以某种方式验证。这个任务与之前一样要定义在Date类内部,但是不要求实例化它。

静态方法在这种情况下就非常有用。看一下下面这个代码片段:

'''

class Date2(object):

@staticmethod

def is_date_valid(date_as_str):

year,month,day = map(int,date_as_str.split('-'))

return year <= 3999 and month <=12 and day <= 31

is_date = Date2.is_date_valid('2019-1-1')

print (is_date)

'''

【结果】

True

'''

'''

现在正如我们了解到的staticmethod的使用,我们不需要访问它所属的类,它本质上就是一个函数,调用方式和调用函数一样,不同的是它不关注对象和对象内部属性。

'''

'''

@staticmethod,@classmethod

有了@property装饰器的了解,这两个装饰器的原理是差不多的。@staticmethod返回的是一个staticmethod类对象,而@classmethod返回的是一个classmethod类对象。他们都是调用的是各自的__init__()构造函数。

class classmethod(object):

"""

classmethod(function) -> method

"""

def init(self, function): # for @classmethod decorator

pass

# ...

class staticmethod(object):

"""

staticmethod(function) -> method

"""

def init(self, function): # for @staticmethod decorator

pass

# ...

装饰器的@语法就等同调用了这两个类的构造函数。

class Foo(object):

@staticmethod
def bar():
pass # 等同于 bar = staticmethod(bar)

至此,我们上文提到的装饰器接口定义可以更加明确一些,装饰器必须接受一个callable对象,其实它并不关心你返回什么,可以是另外一个callable对象(大部分情况),也可以是其他类对象,比如property。

'''

'''

【装饰器里那些坑】

'''

def html_tags(tag_name):

print ('begin outer function.')

def wrapper_(func):

print ("begin of inner wrapper function.")

def wrapper(args, **kwargs):

content = func(
args, **kwargs)

print ("<{tag}>{content}</{tag}>".format(tag=tag_name, content=content))

print ('end of inner wrapper function.')

return wrapper

print ('end of outer function')

return wrapper_

@html_tags('b')

def b_hello(name='b_hello'):

return 'Hello {}!'.format(name)

@html_tags('div')

def div_hello(name='div_hello'):

return 'Hello %s' % name

b_hello()

b_hello('wxue')

b_hello('wqi')

div_hello()

div_hello('wxue')

div_hello('wqi')

'''

在装饰器中我在各个可能的位置都加上了print语句,用于记录被调用的情况。你知道他们最后打印出来的顺序吗?如果你心里没底,那么最好不要在装饰器函数之外添加逻辑功能,否则这个装饰器就不受你控制了。以下是输出结果:

begin outer function.

end of outer function

begin of inner wrapper function.

end of inner wrapper function.

begin outer function.

end of outer function

begin of inner wrapper function.

end of inner wrapper function.

Hello b_hello!

Hello wxue!

Hello wqi!

Hello div_hello
Hello wxue
Hello wqi

'''

'''

错误的函数签名和文档

装饰器装饰过的函数,看上去名字没变,其实已经变了

'''

def logging(func):

def wrapper(args,**kwargs):

print ('[DEBUG]: enter %s' % func.name)

return func(
args,**kwargs)

return wrapper

@logging

def say(something):

print ('say %s' % something)

print (say.name) #wrapper

'''

为什么会这样呢?只要你想想装饰器的语法糖@代替的东西就明白了。@等同于这样的写法。

say = logging(say)

logging其实返回的函数名字刚好是wrapper,那么上面的这个语句刚好就是把这个结果赋值给say,say的__name__自然也就是wrapper了,不仅仅是name,其他属性也都是来自wrapper,比如doc,source等等。

使用标准库里的functools.wraps,可以基本解决这个问题。

'''

from functools import wraps

def logging(func):

@wraps(func)

def wrapper(args,**kwargs):

print ('[DEBUG]: enter %s' % func.name)

return func(
args,**kwargs)

return wrapper

@logging

def say(something):

print ('say %s' % something)

print (say.name) #say

'''

看上去不错!主要问题解决了,但其实还不太完美。因为函数的签名和源码还是拿不到的。

import inspect

print inspect.getargspec(say) # failed

print inspect.getsource(say) # failed

如果要彻底解决这个问题可以借用第三方包,比如wrapt。后文有介绍。

'''

'''

不能装饰@classmethod 和@staticmethod

'''

from functools import wraps

def logging(func):

@wraps(func)

def wrapper(args,**kwargs):

print ('[DEBUG] : enter %s' % func.name)

return func(
args,**kwargs)

return wrapper

class Car(object):

def init(self, model):

self.model = model

@logging  # 装饰实例方法,OK
def run(self):
print ("{} is running!".format(self.model)) @logging # 装饰静态方法,Failed
@staticmethod
def check_model_for(obj):
if isinstance(obj, Car):
print ("The model of your car is {}".format(obj.model))
else:
print ("{} is not a car!".format(obj))

Car.check_model_for('abc') #AttributeError: 'staticmethod' object has no attribute 'name'

'''

前面已经解释了@staticmethod这个装饰器,其实它返回的并不是一个callable对象,而是一个staticmethod对象,

那么它是不符合装饰器要求的(比如传入一个callable对象),你自然不能在它之上再加别的装饰器。

要解决这个问题很简单,只要把你的装饰器放在@staticmethod之前就好了,

因为你的装饰器返回的还是一个正常的函数,然后再加上一个@staticmethod是不会出问题的。

'''

from functools import wraps

def logging(func):

@wraps(func)

def wrapper(args,**kwargs):

print ('[DEBUG]: enter %s' % func.name)

return func(
args,**kwargs)

return wrapper

class Car(object):

def init(self,model):

self.model = model

@logging

def run(self):

print ('%s is running..' % self.model)

@staticmethod
@logging
def check_model_for(obj):
if isinstance(obj,Car):
print ('The model of your car is %s' % obj.model)
else:
print ('%s is not a car!' % obj)

Car.check_model_for('abc')

'''

【结果】

[DEBUG]: enter check_model_for

abc is not a car!

'''

'''

[如何优化你的装饰器]

嵌套的装饰函数不太直观,我们可以使用第三方包类改进这样的情况,让装饰器函数可读性更好。

decorator.py

是一个非常简单的装饰器加强包,你可以很直观的先定义包装函数wrapper(),再使用decorator(func,wrapper)方法就可以完成一个装饰器

记得要先pip install decorator哦

'''

from decorator import decorate

def wrapper(func,args,**kwargs):

print ('[DEBUG] : enter %s' % func.name)

return func(
args,**kwargs)

def logging(func):

return decorate(func,wrapper)

@logging

def hello(something):

print ('hello %s' % something)

hello('decorator')

'''

[DEBUG] : enter hello

hello decorator

'''

'''

你也可以使用她自带的@decorator装饰器来完成你的装饰器

'''

from decorator import decorator

@decorator

def logging(func,args,**kwargs):

print ('[DEBUG]: enter %s' % func.name)

return func(
args,**kwargs)

@logging

def hello(something):

print ('hello %s' % something)

hello('wqi')

print (hello.name)

print (hello.doc)

import inspect

print (inspect.getsource(hello))

print (inspect.getsource(hello.wrapped))

'''

hello wqi

hello

None

@logging

def hello(something):

print ('hello %s' % something)

@logging

def hello(something):

print ('hello %s' % something)

【解析】

原文的解析是:

decorator.py实现的装饰器能完整保留原函数的name,doc和args,唯一有问题的就是inspect.getsource(func)返回的还是装饰器的源代码,你需要改成inspect.getsource(func.wrapped)。

但是我的运行结果却如上,原因待查(猜测是最新版的模块代码有完善)

'''

'''

wrapt

wrapt是一个功能非常完善的包,用于实现各种你想到或者你没想到的装饰器。使用wrapt实现的装饰器你不需要担心之前inspect中遇到的所有问题,因为它都帮你处理了,甚至inspect.getsource(func)也准确无误。

import wrapt

without argument in decorator

@wrapt.decorator

def logging(wrapped, instance, args, kwargs): # instance is must

print "[DEBUG]: enter {}()".format(wrapped.name)

return wrapped(*args, **kwargs)

@logging

def say(something): pass

使用wrapt你只需要定义一个装饰器函数,但是函数签名是固定的,必须是(wrapped, instance, args, kwargs),注意第二个参数instance是必须的,就算你不用它。当装饰器装饰在不同位置时它将得到不同的值,比如装饰在类实例方法时你可以拿到这个类实例。根据instance的值你能够更加灵活的调整你的装饰器。另外,args和kwargs也是固定的,注意前面没有星号。在装饰器内部调用原函数时才带星号。

如果你需要使用wrapt写一个带参数的装饰器,可以这样写。

def logging(level):

@wrapt.decorator

def wrapper(wrapped, instance, args, kwargs):

print "[{}]: enter {}()".format(level, wrapped.name)

return wrapped(*args, **kwargs)

return wrapper

@logging(level="INFO")

def do(work): pass

关于wrapt的使用,建议查阅官方文档,在此不在赘述。

http://wrapt.readthedocs.io/en/latest/quick-start.html

'''

'''

【小结】

Python的装饰器和Java的注解(Annotation)并不是同一回事,和C#中的特性(Attribute)也不一样,完全是两个概念。

装饰器的理念是对原函数、对象的加强,相当于重新封装,所以一般装饰器函数都被命名为wrapper(),意义在于包装。函数只有在被调用时才会发挥其作用。比如@logging装饰器可以在函数执行时额外输出日志,@cache装饰过的函数可以缓存计算结果等等。

而注解和特性则是对目标函数或对象添加一些属性,相当于将其分类。这些属性可以通过反射拿到,在程序运行时对不同的特性函数或对象加以干预。比如带有Setup的函数就当成准备步骤执行,或者找到所有带有TestMethod的函数依次执行等等。

至此我所了解的装饰器已经讲完,但是还有一些内容没有提到,比如装饰类的装饰器。有机会再补充。谢谢观看。

'''

参考原文地址:http://www.cnblogs.com/cicaday/p/python-decorator.html 谢谢作者 很详细

【Python】【装饰器】的更多相关文章

  1. 关于python装饰器

    关于python装饰器,不是系统的介绍,只是说一下某些问题 1 首先了解变量作用于非常重要 2 其次要了解闭包 def logger(func): def inner(*args, **kwargs) ...

  2. python装饰器通俗易懂的解释!

    1.python装饰器 刚刚接触python的装饰器,简直懵逼了,直接不懂什么意思啊有木有,自己都忘了走了多少遍Debug,查了多少遍资料,猜有点点开始明白了.总结了一下解释得比较好的,通俗易懂的来说 ...

  3. Python 装饰器学习

    Python装饰器学习(九步入门)   这是在Python学习小组上介绍的内容,现学现卖.多练习是好的学习方式. 第一步:最简单的函数,准备附加额外功能 1 2 3 4 5 6 7 8 # -*- c ...

  4. python 装饰器修改调整函数参数

    简单记录一下利用python装饰器来调整函数的方法.现在有个需求:参数line范围为1-16,要求把9-16的范围转化为1-8,即9对应1,10对应2,...,16对应8. 下面是例子: def fo ...

  5. python 装饰器学习(decorator)

    最近看到有个装饰器的例子,没看懂, #!/usr/bin/python class decorator(object): def __init__(self,f): print "initi ...

  6. Python装饰器详解

    python中的装饰器是一个用得非常多的东西,我们可以把一些特定的方法.通用的方法写成一个个装饰器,这就为调用这些方法提供一个非常大的便利,如此提高我们代码的可读性以及简洁性,以及可扩展性. 在学习p ...

  7. 关于python装饰器(Decorators)最底层理解的一句话

    一个decorator只是一个带有一个函数作为参数并返回一个替换函数的闭包. http://www.xxx.com/html/2016/pythonhexinbiancheng_0718/1044.h ...

  8. Python装饰器由浅入深

    装饰器的功能在很多语言中都有,名字也不尽相同,其实它体现的是一种设计模式,强调的是开放封闭原则,更多的用于后期功能升级而不是编写新的代码.装饰器不光能装饰函数,也能装饰其他的对象,比如类,但通常,我们 ...

  9. Python装饰器与面向切面编程

    今天来讨论一下装饰器.装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志.性能测试.事务处理等.装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数 ...

  10. python装饰器方法

    前几天向几位新同事介绍项目,被问起了@login_required的实现,我说这是django框架提供的装饰器方法,验证用户是否登录,只要这样用就行了,因为自己不熟,并没有做过多解释. 今天查看dja ...

随机推荐

  1. MySQL中kill掉所有表的进程

    同事打电话告诉我用户数据库挂掉了. 我起床看一下进程列表. mysql>show processlist; 出来哗啦啦好几屏幕的, 没有一千也有几百条, 查询语句把表锁住了, 赶紧找出第一个Lo ...

  2. MUI学习01-顶部导航栏

    建议:先看一下MUI注意事项 连接:http://ask.dcloud.net.cn/article/122 固定栏靠前 所谓的固定栏,也就是带有.mui-bar属性的节点,都是基于fixed定位的元 ...

  3. ExecuteExcel4Macro (宏函数)使用说明

    用ExecuteExcel4Macro从未打开的Excel工作簿中读取数据(转载) 从另外一个未打开的Excel文件中读取数据的函数 下面这个函数调用XLM宏从未打开的工作簿中读取数据. *注意:   ...

  4. 2016CCPC长春 - B/D/F/H/I/J/K - (待补)

    目录: B - Fraction D - Triangle F - Harmonic Value Description H - Sequence I I - Sequence II B题:HDU 5 ...

  5. LeetCode 12 - 整数转罗马数字 - [简单模拟]

    题目链接:https://leetcode-cn.com/problems/integer-to-roman/ 题解: 把 $1,4,5,9,10,40,50, \cdots, 900, 1000$ ...

  6. 25个常用PowerShell命令总结

    尽管Windows PowerShell已经出现一段时间了,习惯命令行的管理员可能对了解PowerShell功能的基础很感兴趣. 下面我们看看能由Windows PowerShell完成的最常见的25 ...

  7. xcode工程编译错误之iOS解决CUICatalog: Invalid asset name supplied问题

    [问题分析]: 这个问题其实是老问题,产生原因就是因为在使用的时候 [UIImage imageNamed:]时,图片不存在或者传入的图片名为nil. [解决方法]: 添加一个系统断点,来判断如果图片 ...

  8. ffmpeg的编译和安装

    1. 先到ffmpeg官网上下载ffmpeg源码,然后配置.编译 http://ffmpeg.org/download.html 可以如下进行配置: ./configure --prefix=/usr ...

  9. 【Python】-NO.97.Note.2.Python -【Python 基本数据类型】

    1.0.0 Summary Tittle:[Python]-NO.97.Note.2.Python -[Python 基本数据类型] Style:Python Series:Python Since: ...

  10. Unity之流光效果

    效果如图: shader如下: Shader "Unlit/Walk light" { Properties { _MainTex ("Base (RGB), Alpha ...