> 백엔드 개발 > 파이썬 튜토리얼 > Python에서 관점 지향 프로그래밍 AOP 및 데코레이터를 사용하는 방법

Python에서 관점 지향 프로그래밍 AOP 및 데코레이터를 사용하는 방법

王林
풀어 주다: 2023-05-10 08:13:07
앞으로
1384명이 탐색했습니다.

AOP란 무엇입니까

AOP는 관점 지향 프로그래밍입니다. 간단히 말해서 코드를 클래스의 지정된 메서드와 위치로 동적으로 자르는 프로그래밍 아이디어가 관점 지향 프로그래밍입니다.

특정 클래스의 특정 메서드를 잘라내는 코드 조각을 관점이라고 부르며, 어떤 클래스와 메서드가 잘려지는지를 진입점이라고 합니다. 이러한 방식으로 여러 클래스에 공통된 코드를 슬라이스로 추출한 다음 필요할 때 객체를 잘라서 원래 동작을 변경할 수 있습니다.

이러한 생각은 원본 코드 로직을 더 명확하게 만들고 원본 코드에 영향을 주지 않게 만들 수 있으며 권한 관리, 로깅, 트랜잭션 관리 등에 자주 사용됩니다.

Python의 데코레이터는 매우 유명한 디자인이며 측면 요구 사항이 있는 시나리오에서 자주 사용됩니다.

예를 들어 Django는 권한 제어, 콘텐츠 필터링, 요청 관리 등과 같은 요구 사항의 다양한 측면을 완료하기 위해 수많은 데코레이터를 사용합니다.

Decorator

Python 데코레이터(기능적 데코레이터)는 원래 함수 이름이나 클래스 이름을 변경하지 않고 함수에 새 함수를 추가하는 데 사용되는 함수입니다.

데코레이터의 작동 방식에 대해 자세히 알아보려면 저와 함께 가세요.

먼저 개념을 명확히 해야 합니다. Python의 모든 것은 객체이고, 함수도 객체입니다!

그래서 객체로서의 함수는 다른 함수에서 정의될 수 있습니다. 다음 예를 살펴보십시오.

def a():
    def b():
        print("I'm b")
    b()
    c = b
    return c
d = a()
d()
b()
c()
로그인 후 복사

출력 결과는 다음과 같습니다.

I'm b
I'm b
throws NameError: name 'b' is not Defined Error
throws NameError: name 'c' is not 정의된 오류

위에서 볼 수 있듯이 함수는 객체이므로 다음과 같습니다.

  • 을 변수에 할당할 수 있습니다.

  • 을 다른 함수에서 정의할 수 있습니다.

그런 다음 return</ code>은 함수 객체를 반환할 수 있습니다. 이 함수 개체는 다른 함수 내에 정의되어 있습니다. 범위가 다르기 때문에 <code>return에 의해 반환된 함수만 호출할 수 있으며 bc 함수는 <code>a</code 함수에 정의되고 할당됩니다. > 호출 가능 외부 범위에서는 호출할 수 없습니다. return 可以返回一个函数对象。这个函数对象是在另一个函数中定义的。由于作用域不同,所以只有 return 返回的函数可以调用,在函数 a 中定义和赋值的函数 bc 在外部作用域是无法调用的。

这意味着一个功能可以 return 另一个功能。

除了可以作为对象返回外,函数对象还可以作为参数传递给另一个函数:

def a():
    print("I&#39;m a")
def b(func):
    print("I&#39;m b")
    func()
b(a)
로그인 후 복사

输出结果:

I'm b
I&#39;m a

OK,现在,基于函数的这些特性,我们就可以创建一个装饰器,用来在不改变原函数的情况下,实现功能。

函数装饰器

比如,我们要在函数执行前和执行后分别执行一些别的操作,那么根据上面函数可以作为参数传递,我们可以这样实现,看下面示例:

def a():
    print("I&#39;m a")
def b(func):
    print(&#39;在函数执行前,做一些操作&#39;)
    func()
    print("在函数执行后,做一些操作")
b(a)
로그인 후 복사

输出结果:

在函数执行前,做一些操作
I&#39;m a
在函数执行后,做一些操作

但是这样的话,原函数就变成了另一个函数,每加一个功能,就要在外面包一层新的函数,这样原来调用的地方,就会需要全部修改,这明显不方便,就会想,有没有办法可以让函数的操作改变,但是名称不改变,还是作为原函数呢。

看下面示例:

def a():
    print("I&#39;m a")

def c(func):
    def b():
        print(&#39;在函数执行前,做一些操作&#39;)
        func()
        print("在函数执行后,做一些操作")
    return b

a = c(a)
a()
로그인 후 복사

输出结果:

在函数执行前,做一些操作
I&#39;m a
在函数执行后,做一些操作

如上,我们可以将函数再包一层,将新的函数 b,作为对象返回。这样通过函数 c,将 a 改变并重新赋值给 a,这样就实现了改变函数 a,并同样使用函数 a 来调用。

但是这样写起来非常不方便,因为需要重新赋值,所以在 Python 中,可以通过 @ 来实现,将函数作为参数传递。

看下示例:

def c(func):
    def b():
        print(&#39;在函数执行前,做一些操作&#39;)
        func()
        print("在函数执行后,做一些操作")
    return b
@c
def a():
    print("I&#39;m a")
a()
로그인 후 복사

输出结果:

在函数执行前,做一些操作
I&#39;m a
在函数执行后,做一些操作

如上,通过 @c,就实现了将函数 a 作为参数,传入 c,并将返回的函数重新作为函数 a。这 c

이는 하나의 함수가 다른 함수를 반환할 수 있다는 의미입니다.

객체로 반환되는 것 외에도 함수 객체는 다른 함수에 매개변수로 전달될 수도 있습니다.

def c(func):
    def b():
        print(&#39;在函数执行前,做一些操作&#39;)
        result = func()
        print("在函数执行后,做一些操作")
        return result
    return b
@c
def a():
    print("函数执行中。。。")
    return "I&#39;m a"
print(a())
로그인 후 복사
출력 결과:


I'm b
I'm a

🎜 자, 이제 함수의 이러한 특성을 기반으로 원래 함수를 변경하지 않고 함수를 구현하는 데코레이터를 만들 수 있습니다. 🎜🎜Function Decorator🎜🎜예를 들어 함수 실행 전후에 다른 작업을 수행하려는 경우 위 함수에 따라 다음과 같이 구현할 수 있습니다. 예: 🎜
def c(func):
    def b(name, age):
        print(&#39;在函数执行前,做一些操作&#39;)
        result = func(name, age)
        print("在函数执行后,做一些操作")
        return result
    return b
@c
def a(name, age):
    print("函数执行中。。。")
    return "我是 {}, 今年{}岁 ".format(name, age)
print(a(&#39;Amos&#39;, 24))
로그인 후 복사
로그인 후 복사
🎜🎜출력 결과: 🎜🎜🎜🎜함수가 실행되기 전에 몇 가지 작업을 수행합니다🎜나는🎜함수가 실행된 후 몇 가지 작업을 수행합니다🎜🎜🎜그러나 이 경우 원래 함수는 다른 함수가 됩니다. 함수가 추가될 때마다 외부에서 새 함수를 래핑하려면 원래 호출 위치를 모두 수정해야 합니다. 이는 분명히 불편한데, 함수의 동작을 변경하지 않고 변경할 수 있는 방법이 있는지 궁금합니다. 이름을 그대로 유지하고 원래 기능으로 사용합니다. 🎜🎜🎜아래 예를 보세요: 🎜🎜
def c(func):
    def b(*args, **kwargs):
        print(&#39;在函数执行前,做一些操作&#39;)
        result = func(*args, **kwargs)
        print("在函数执行后,做一些操作")
        return result
    return b
@c
def a(name, age):
    print(&#39;函数执行中。。。&#39;)
    return "我是 {}, 今年{}岁 ".format(name, age)
@c
def d(sex, height):
    print(&#39;函数执行中。。。&#39;)
    return &#39;性别:{},身高:{}&#39;.format(sex, height)
print(a(&#39;Amos&#39;, 24))
print(d(&#39;男&#39;, 175))
로그인 후 복사
로그인 후 복사
🎜🎜출력 결과: 🎜🎜🎜🎜함수가 실행되기 전에 몇 가지 작업을 수행합니다🎜나는🎜함수가 실행된 후 몇 가지 작업을 수행합니다🎜🎜🎜위와 같이 , 다른 레이어 래핑 기능을 사용하고 새 함수 b를 객체로 반환할 수 있습니다. 이런 식으로 c 함수를 통해 a가 변경되고 a에 다시 할당되어 변경 함수 a</code가 실현됩니다. >, 그리고 <code>a 함수를 사용하여 호출할 수도 있습니다. 🎜🎜근데 이건 재할당이 필요해서 작성하기가 많이 불편한데, Python에서는 @를 통해 구현할 수 있고 함수를 매개변수로 전달합니다. 🎜🎜🎜예제를 보세요: 🎜🎜
def c(func):
    def b(*args, **kwargs):
        print(&#39;在函数执行前,做一些操作&#39;)
        result = func(*args, **kwargs)
        print("在函数执行后,做一些操作")
        return result
    return b
@c
def a(name, age):
    print(&#39;函数执行中。。。&#39;)
    return "我是 {}, 今年{}岁 ".format(name, age)
print(a.__name__)
로그인 후 복사
로그인 후 복사
🎜🎜 출력 결과: 🎜🎜🎜🎜함수가 실행되기 전에 몇 가지 작업을 수행합니다🎜나는🎜함수가 실행된 후 몇 가지 작업을 수행합니다🎜🎜🎜위와 같이, @c를 전달하면 a 함수를 매개변수로 전달하고, c를 전달하고, 반환된 함수를 함수로 재사용할 수 있습니다. 에 . 이 c는 단순한 함수 데코레이터입니다. 🎜🎜🎜그리고 함수에 반환 값이 있으면 변경된 함수에도 다음과 같이 반환 값이 있어야 합니다. 🎜🎜
from functools import wraps
def c(func):
    @wraps(func)
    def b(*args, **kwargs):
        print(&#39;在函数执行前,做一些操作&#39;)
        result = func(*args, **kwargs)
        print("在函数执行后,做一些操作")
        return result
    return b
@c
def a(name, age):
    print(&#39;函数执行中。。。&#39;)
    return "我是 {}, 今年{}岁 ".format(name, age)
print(a.__name__)
로그인 후 복사
로그인 후 복사
🎜🎜출력 결과: 🎜🎜🎜🎜함수가 실행되기 전에 몇 가지 작업을 수행하세요🎜The 기능이 실행 중입니다. . . 🎜함수 실행 후 작업을 하세요🎜I'm a🎜

如上所示:通过将返回值进行传递,就可以实现函数执行前后的操作。但是你会发现一个问题,就是为什么输出 I&#39;m a 会在最后才打印出来?

因为 I&#39;m a 是返回的结果,而实际上函数是 print("在函数执行后,做一些操作") 这一操作前运行的,只是先将返回的结果给到了 result,然后 result 传递出来,最后由最下方的 print(a()) 打印了出来。

那如何函数 a 带参数怎么办呢?很简单,函数 a 带参数,那么我们返回的函数也同样要带参数就好啦。

看下面示例:

def c(func):
    def b(name, age):
        print(&#39;在函数执行前,做一些操作&#39;)
        result = func(name, age)
        print("在函数执行后,做一些操作")
        return result
    return b
@c
def a(name, age):
    print("函数执行中。。。")
    return "我是 {}, 今年{}岁 ".format(name, age)
print(a(&#39;Amos&#39;, 24))
로그인 후 복사
로그인 후 복사

输出结果:

在函数执行前,做一些操作
函数执行中。。。
在函数执行后,做一些操作
我是 Amos, 今年24岁

但是又有问题了,我写一个装饰器 c,需要装饰多个不同的函数,这些函数的参数各不相同,那么怎么办呢?简单,用 *args**kwargs 来表示所有参数即可。

如下示例:

def c(func):
    def b(*args, **kwargs):
        print(&#39;在函数执行前,做一些操作&#39;)
        result = func(*args, **kwargs)
        print("在函数执行后,做一些操作")
        return result
    return b
@c
def a(name, age):
    print(&#39;函数执行中。。。&#39;)
    return "我是 {}, 今年{}岁 ".format(name, age)
@c
def d(sex, height):
    print(&#39;函数执行中。。。&#39;)
    return &#39;性别:{},身高:{}&#39;.format(sex, height)
print(a(&#39;Amos&#39;, 24))
print(d(&#39;男&#39;, 175))
로그인 후 복사
로그인 후 복사

输出结果:

在函数执行前,做一些操作
函数执行中。。。
在函数执行后,做一些操作
我是 Amos, 今年24岁

在函数执行前,做一些操作
函数执行中。。。
在函数执行后,做一些操作
性别:男,身高:175

如上就解决了参数的问题,哇,这么好用。那是不是这样就没有问题了?并不是!经过装饰器装饰后的函数,实际上已经变成了装饰器函数 c 中定义的函数 b,所以函数的元数据则全部改变了!

如下示例:

def c(func):
    def b(*args, **kwargs):
        print(&#39;在函数执行前,做一些操作&#39;)
        result = func(*args, **kwargs)
        print("在函数执行后,做一些操作")
        return result
    return b
@c
def a(name, age):
    print(&#39;函数执行中。。。&#39;)
    return "我是 {}, 今年{}岁 ".format(name, age)
print(a.__name__)
로그인 후 복사
로그인 후 복사

输出结果:

b

会发现函数实际上是函数 b 了,这就有问题了,那么该怎么解决呢,有人就会想到,可以在装饰器函数中先把原函数的元数据保存下来,在最后再讲 b 函数的元数据改为原函数的,再返回 b。这样的确是可以的!但我们不这样用,为什么?

因为 Python 早就想到这个问题啦,所以给我们提供了一个内置的方法,来自动实现原数据的保存和替换工作。哈哈,这样就不同我们自己动手啦!

看下面示例:

from functools import wraps
def c(func):
    @wraps(func)
    def b(*args, **kwargs):
        print(&#39;在函数执行前,做一些操作&#39;)
        result = func(*args, **kwargs)
        print("在函数执行后,做一些操作")
        return result
    return b
@c
def a(name, age):
    print(&#39;函数执行中。。。&#39;)
    return "我是 {}, 今年{}岁 ".format(name, age)
print(a.__name__)
로그인 후 복사
로그인 후 복사

输出结果:

a

使用内置的 wraps 装饰器,将原函数作为装饰器参数,实现函数原数据的保留替换功能。

耶!装饰器还可以带参数啊,你看上面 wraps 装饰器就传入了参数。哈哈,是的,装饰器还可以带参数,那怎么实现呢?

看下面示例:

from functools import wraps
def d(name):
    def c(func):
        @wraps(func)
        def b(*args, **kwargs):
            print(&#39;装饰器传入参数为:{}&#39;.format(name))
            print(&#39;在函数执行前,做一些操作&#39;)
            result = func(*args, **kwargs)
            print("在函数执行后,做一些操作")
            return result
        return b
    return c
@d(name=&#39;我是装饰器参数&#39;)
def a(name, age):
    print(&#39;函数执行中。。。&#39;)
    return "我是 {}, 今年{}岁 ".format(name, age)

print(a(&#39;Amos&#39;, 24))
로그인 후 복사

输出结果:

装饰器传入参数为:我是装饰器参数
在函数执行前,做一些操作
函数执行中。。。
在函数执行后,做一些操作
我是 Amos, 今年24岁

如上所示,很简单,只需要在原本的装饰器之上,再包一层,相当于先接收装饰器参数,然后返回一个不带参数的装饰器,然后再将函数传入,最后返回变化后的函数。

这样就可以实现很多功能了,这样可以根据传给装饰器的参数不同,来分别实现不同的功能。

另外,可能会有人问, 可以在同一个函数上,使用多个装饰器吗?答案是:可以!

看下面示例:

from functools import wraps
def d(name):
    def c(func):
        @wraps(func)
        def b(*args, **kwargs):
            print(&#39;装饰器传入参数为:{}&#39;.format(name))
            print(&#39;我是装饰器d: 在函数执行前,做一些操作&#39;)
            result = func(*args, **kwargs)
            print("我是装饰器d: 在函数执行后,做一些操作")
            return result
        return b
    return c
def e(name):
    def c(func):
        @wraps(func)
        def b(*args, **kwargs):
            print(&#39;装饰器传入参数为:{}&#39;.format(name))
            print(&#39;我是装饰器e: 在函数执行前,做一些操作&#39;)
            result = func(*args, **kwargs)
            print("我是装饰器e: 在函数执行后,做一些操作")
            return result
        return b
    return c
@e(name=&#39;我是装饰器e&#39;)
@d(name=&#39;我是装饰器d&#39;)
def func_a(name, age):
    print(&#39;函数执行中。。。&#39;)
    return "我是 {}, 今年{}岁 ".format(name, age)

print(func_a(&#39;Amos&#39;, 24))
行后,做一些操作
我是 Amos, 今年24岁
로그인 후 복사

输出结果:

装饰器传入参数为:我是装饰器e
我是装饰器e: 在函数执行前,做一些操作

装饰器传入参数为:我是装饰器d
我是装饰器d: 在函数执行前,做一些操作
函数执行中。。。
我是装饰器d: 在函数执行后,做一些操作

我是装饰器e: 在函数执

如上所示,当两个装饰器同时使用时,可以想象成洋葱,最下层的装饰器先包装一层,然后一直到最上层装饰器,完成多层的包装。然后执行时,就像切洋葱,从最外层开始,只执行到被装饰函数运行时,就到了下一层,下一层又执行到函数运行时到下一层,一直到执行了被装饰函数后,就像切到了洋葱的中间,然后再往下,依次从最内层开始,依次执行到最外层。

示例:

当一个函数 abcd 三个装饰器装饰时,执行顺序如下图所示,多个同理。

@d
@c
@b
def a():
    pass
로그인 후 복사

Python에서 관점 지향 프로그래밍 AOP 및 데코레이터를 사용하는 방법

类装饰器

在函数装饰器方面,很多人搞不清楚,是因为装饰器可以用函数实现(像上面),也可以用类实现。因为函数和类都是对象,同样可以作为被装饰的对象,所以根据被装饰的对象不同,一同有下面四种情况:

  • 函数装饰函数

  • 类装饰函数

  • 函数装饰类

  • 类装饰类

下面我们依次来说明一下这四种情况的使用。

1、函数装饰函数

from functools import wraps
def d(name):
    def c(func):
        @wraps(func)
        def b(*args, **kwargs):
            print(&#39;装饰器传入参数为:{}&#39;.format(name))
            print(&#39;在函数执行前,做一些操作&#39;)
            result = func(*args, **kwargs)
            print("在函数执行后,做一些操作")
            return result
        return b
    return c
@d(name=&#39;我是装饰器参数&#39;)
def a(name, age):
    print(&#39;函数执行中。。。&#39;)
    return "我是 {}, 今年{}岁 ".format(name, age)

print(a.__class__)
# 输出结果:
<class &#39;function&#39;>
로그인 후 복사

此为最常见的装饰器,用于装饰函数,返回的是一个函数。

2、类装饰函数

也就是通过类来实现装饰器的功能而已。通过类的 __call__ 方法实现:

from functools import wraps
class D(object):
    def __init__(self, name):
        self._name = name
    def __call__(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print(&#39;装饰器传入参数为:{}&#39;.format(self._name))
            print(&#39;在函数执行前,做一些操作&#39;)
            result = func(*args, **kwargs)
            print("在函数执行后,做一些操作")
            return result
        return wrapper
@D(name=&#39;我是装饰器参数&#39;)
def a(name, age):
    print(&#39;函数执行中。。。&#39;)
    return "我是 {}, 今年{}岁 ".format(name, age)
print(a.__class__)
# 输出结果:
<class &#39;function&#39;>
로그인 후 복사

以上所示,只是将用函数定义的装饰器改为使用类来实现而已。还是用于装饰函数,因为在类的 __call__ 中,最后返回的还是一个函数。

此为带装饰器参数的装饰器实现方法,是通过 __call__ 方法。

若装饰器不带参数,则可以将 __init__ 方法去掉,但是在使用装饰器时,需要 @D() 这样使用,如下:

from functools import wraps
class D(object):
    def __call__(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print(&#39;在函数执行前,做一些操作&#39;)
            result = func(*args, **kwargs)
            print("在函数执行后,做一些操作")
            return result
        return wrapper
@D()
def a(name, age):
    print(&#39;函数执行中。。。&#39;)
    return "我是 {}, 今年{}岁 ".format(name, age)

print(a.__class__)
# 输出结果:
<class &#39;function&#39;>
로그인 후 복사

如上是比较方便简答的,使用类定义函数装饰器,且返回对象为函数,元数据保留。

3、函数装饰类

下面重点来啦,我们常见的装饰器都是用于装饰函数的,返回的对象也是一个函数,而要装饰类,那么返回的对象就要是类,且类的元数据等也要保留。

不怕丢脸的说,目前我还不知道怎么实现完美的类装饰器,在装饰类的时候,一般有两种方法:

返回一个函数,实现类在创建实例的前后执行操作,并正常返回此类的实例。但是这样经过装饰器的类就属于函数了,其无法继承,但可以正常调用创建实例。

如下:

from functools import wraps
def d(name):
    def c(cls):
        @wraps(cls)
        def b(*args, **kwargs):
            print(&#39;装饰器传入参数为:{}&#39;.format(name))
            print(&#39;在类初始化前,做一些操作&#39;)
            instance = cls(*args, **kwargs)
            print("在类初始化后,做一些操作")
            return instance
        return b
    return c
@d(name=&#39;我是装饰器参数&#39;)
class A(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age
        print(&#39;类初始化实例,{} {}&#39;.format(self.name, self.age))

a = A(&#39;Amos&#39;, 24)
print(a.__class__)
print(A.__class__)

# 输出结果:
装饰器传入参数为:我是装饰器参数
在类初始化前,做一些操作
类初始化实例,Amos 24
在类初始化后,做一些操作
<class &#39;__main__.A&#39;>
<class &#39;function&#39;>
로그인 후 복사

如上所示,就是第一种方法。

4、类装饰类

接上文,返回一个类,实现类在创建实例的前后执行操作,但类已经改变了,创建的实例也已经不是原本类的实例了。

看下面示例:

def desc(name):
    def decorator(aClass):
        class Wrapper(object):
            def __init__(self, *args, **kwargs):
                print(&#39;装饰器传入参数为:{}&#39;.format(name))
                print(&#39;在类初始化前,做一些操作&#39;)
                self.wrapped = aClass(*args, **kwargs)
                print("在类初始化后,做一些操作")

            def __getattr__(self, name):
                print(&#39;Getting the {} of {}&#39;.format(name, self.wrapped))
                return getattr(self.wrapped, name)

            def __setattr__(self, key, value):
                if key == &#39;wrapped&#39;:  # 这里捕捉对wrapped的赋值
                    self.__dict__[key] = value
                else:
                    setattr(self.wrapped, key, value)

        return Wrapper
    return decorator
@desc(name=&#39;我是装饰器参数&#39;)
class A(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age
        print(&#39;类初始化实例,{} {}&#39;.format(self.name, self.age))

a = A(&#39;Amos&#39;, 24)
print(a.__class__)
print(A.__class__)
print(A.__name__)
로그인 후 복사

输出结果:

装饰器传入参数为:我是装饰器参数
在类初始化前,做一些操作
类初始化实例,Amos 24
在类初始化后,做一些操作
.decorator..Wrapper'>

Wrapper

如上,看到了吗,通过在函数中新定义类,并返回类,这样函数还是类,但是经过装饰器后,类 A 已经变成了类 Wrapper,且生成的实例 a 也是类 Wrapper 的实例,即使通过 __getattr__ 和 __setattr__ 两个方法,使得实例a的属性都是在由类 A 创建的实例 wrapped 的属性,但是类的元数据无法改变。很多内置的方法也就会有问题。我个人是不推荐这种做法的!

所以,我推荐在代码中,尽量避免类装饰器的使用,如果要在类中做一些操作,完全可以通过修改类的魔法方法,继承,元类等等方式来实现。如果避免不了,那也请谨慎处理。

위 내용은 Python에서 관점 지향 프로그래밍 AOP 및 데코레이터를 사용하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:yisu.com
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿