>백엔드 개발 >파이썬 튜토리얼 >Python 표준 라이브러리 functools/itertools/operator

Python 표준 라이브러리 functools/itertools/operator

高洛峰
高洛峰원래의
2017-02-09 11:04:561786검색

소개

functools, itertools, operator는 함수형 프로그래밍을 지원하기 위해 Python 표준 라이브러리에서 제공하는 세 가지 모듈입니다. 이 세 가지 모듈을 올바르게 사용하면 더 간결하고 읽기 쉬운 Python 코드를 작성할 수 있습니다. 다음으로 세 가지 모듈의 사용법을 이해하기 위해 몇 가지 예를 사용하겠습니다.

functools 사용법

functools는 Python에서 매우 중요한 모듈로, 매우 유용한 고차 함수를 제공합니다. 고차 함수는 함수를 매개변수로 받아들이거나 함수를 반환 값으로 사용할 수 있는 함수입니다. Python의 함수도 객체이기 때문에 그러한 기능을 지원하기 쉽습니다.

partial

>>> from functools import partial

>>> basetwo = partial(int, base=2)

>>> basetwo('10010')
18

basetwo('10010')는 실제로 int('10010', base=2)을 호출하는 것과 동일합니다. 함수에 매개변수가 너무 많으면 functools.partial을 사용하여 새 함수를 생성할 수 있습니다. 코드의 가독성을 높이기 위해 부분은 실제로 간단한 클로저를 통해 내부적으로 구현됩니다.

def partial(func, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = keywords.copy()
        newkeywords.update(fkeywords)
        return func(*args, *fargs, **newkeywords)
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc

partialmethod

partialmethod는 부분적 방법과 유사하지만, 绑定一个非对象自身的方法의 경우에는 부분적 방법만 사용할 수 있으며, 다음 예를 통해 둘의 차이점을 살펴보겠습니다.

from functools import partial, partialmethod


def standalone(self, a=1, b=2):
    "Standalone function"
    print('  called standalone with:', (self, a, b))
    if self is not None:
        print('  self.attr =', self.attr)


class MyClass:
    "Demonstration class for functools"
    def __init__(self):
        self.attr = 'instance attribute'
    method1 = functools.partialmethod(standalone)  # 使用partialmethod
    method2 = functools.partial(standalone)  # 使用partial
>>> o = MyClass()

>>> o.method1()
  called standalone with: (<__main__.MyClass object at 0x7f46d40cc550>, 1, 2)
  self.attr = instance attribute

# 不能使用partial
>>> o.method2()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: standalone() missing 1 required positional argument: 'self'

singledispatch

Python은 서로 다른 매개변수 유형을 허용하는 동일한 이름의 메소드를 지원하지 않지만, 비용을 줄이기 위해 메소드 내부에 매개변수 판단을 넣는 대신 动态指定相应的方法所接收的参数类型에 싱글디스패치를 ​​빌릴 수 있습니다. . 코드 가독성.

from functools import singledispatch


class TestClass(object):
    @singledispatch
    def test_method(arg, verbose=False):
        if verbose:
            print("Let me just say,", end=" ")
        print(arg)

    @test_method.register(int)
    def _(arg):
        print("Strength in numbers, eh?", end=" ")
        print(arg)

    @test_method.register(list)
    def _(arg):
        print("Enumerate this:")

        for i, elem in enumerate(arg):
            print(i, elem)

다음은 @test_method.register(int) 및 @test_method.register(list)를 사용하여 test_method의 첫 번째 매개변수가 int 또는 list인 경우 처리를 위해 다른 메서드가 호출되도록 지정합니다.

>>> TestClass.test_method(55555)  # call @test_method.register(int)
Strength in numbers, eh? 55555

>>> TestClass.test_method([33, 22, 11])   # call @test_method.register(list)
Enumerate this:
0 33
1 22
2 11

>>> TestClass.test_method('hello world', verbose=True)  # call default
Let me just say, hello world

wraps

데코레이터는 @wraps를 사용하여 복원할 수 있는 데코레이팅된 함수의 __name__ 및 __doc__ 속성을 잃게 됩니다.

from functools import wraps


def my_decorator(f):
    @wraps(f)
    def wrapper():
        """wrapper_doc"""
        print('Calling decorated function')
        return f()
    return wrapper


@my_decorator
def example():
    """example_doc"""
    print('Called example function')
>>> example.__name__
'example'
>>> example.__doc__
'example_doc'

# 尝试去掉@wraps(f)来看一下运行结果,example自身的__name__和__doc__都已经丧失了
>>> example.__name__
'wrapper'
>>> example.__doc__
'wrapper_doc'

update_wrapper를 사용하여

from itertools import update_wrapper


def g():
    ...
g = update_wrapper(g, f)


# equal to
@wraps(f)
def g():
    ...

@wraps가 실제로 update_wrapper를 기반으로 내부적으로 구현되도록 다시 작성할 수도 있습니다.

def wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES):
    def decorator(wrapper):
        return update_wrapper(wrapper, wrapped=wrapped...)
    return decorator

lru_cache

lru_cache와 Singledispatch는 개발에 널리 사용되는 흑마술입니다. 다음으로 lru_cache를 살펴보겠습니다. 반복적인 계산 작업의 경우 缓存加速을 사용하는 것이 매우 중요합니다. lru_cache를 사용할 때와 사용하지 않을 때의 속도 차이를 확인하기 위해 fibonacci 예제를 살펴보겠습니다.

# clockdeco.py

import time
import functools


def clock(func):
    @functools.wraps(func)
    def clocked(*args, **kwargs):
        t0 = time.time()
        result = func(*args, **kwargs)
        elapsed = time.time() - t0
        name = func.__name__
        arg_lst = []
        if args:
            arg_lst.append(', '.join(repr(arg) for arg in args))
        if kwargs:
            pairs = ['%s=%r' % (k, w) for k, w in sorted(kwargs.items())]
            arg_lst.append(', '.join(pairs))
        arg_str = ', '.join(arg_lst)
        print('[%0.8fs] %s(%s) -> %r ' % (elapsed, name, arg_str, result))
        return result
    return clocked

lru_cache를 사용하지 마세요

from clockdeco import clock


@clock
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-2) + fibonacci(n-1)


if __name__==&#39;__main__&#39;:
    print(fibonacci(6))

다음은 실행 결과에서 fibonacci(n)가 重复计算동안 있음을 알 수 있습니다. 재귀입니다. 이는 시간과 리소스가 많이 소모됩니다.

[0.00000119s] fibonacci(0) -> 0 
[0.00000143s] fibonacci(1) -> 1 
[0.00021172s] fibonacci(2) -> 1 
[0.00000072s] fibonacci(1) -> 1 
[0.00000095s] fibonacci(0) -> 0 
[0.00000095s] fibonacci(1) -> 1 
[0.00011444s] fibonacci(2) -> 1 
[0.00022793s] fibonacci(3) -> 2 
[0.00055265s] fibonacci(4) -> 3 
[0.00000072s] fibonacci(1) -> 1 
[0.00000072s] fibonacci(0) -> 0 
[0.00000095s] fibonacci(1) -> 1 
[0.00011158s] fibonacci(2) -> 1 
[0.00022268s] fibonacci(3) -> 2 
[0.00000095s] fibonacci(0) -> 0 
[0.00000095s] fibonacci(1) -> 1 
[0.00011349s] fibonacci(2) -> 1 
[0.00000072s] fibonacci(1) -> 1 
[0.00000095s] fibonacci(0) -> 0 
[0.00000095s] fibonacci(1) -> 1 
[0.00010705s] fibonacci(2) -> 1 
[0.00021267s] fibonacci(3) -> 2 
[0.00043225s] fibonacci(4) -> 3 
[0.00076509s] fibonacci(5) -> 5 
[0.00142813s] fibonacci(6) -> 8 
8

lru_cache 사용

import functools
from clockdeco import clock


@functools.lru_cache() # 1
@clock # 2
def fibonacci(n):
    if n < 2:
       return n
    return fibonacci(n-2) + fibonacci(n-1)

if __name__==&#39;__main__&#39;:
    print(fibonacci(6))

계산된 결과는 다음과 같습니다.

[0.00000095s] fibonacci(0) -> 0 
[0.00005770s] fibonacci(1) -> 1 
[0.00015855s] fibonacci(2) -> 1 
[0.00000286s] fibonacci(3) -> 2 
[0.00021124s] fibonacci(4) -> 3 
[0.00000191s] fibonacci(5) -> 5 
[0.00024652s] fibonacci(6) -> 8 
8

우리가 위에서 선택한 숫자는 충분히 크지 않습니다. 관심 있는 친구들은 둘 사이의 속도 차이를 비교하기 위해 더 큰 숫자를 선택할 수도 있습니다.

total_ordering

Python2 , __cmp__ 0/-1/1의 반환 값을 사용자 정의하여 객체의 크기를 비교할 수 있습니다. Python3에서는 __cmp__가 폐기되지만 total_ordering을 통해 수정한 다음 __lt__(), __le__(), __gt__( )을 수정할 수 있습니다. , __ge__(), __eq__(), __ne__() 및 기타 매직 메소드를 사용하여 클래스의 비교 규칙을 사용자 정의합니다. p.s: 사용하려면 클래스에 __lt__(), __le__(), __gt__(), __ge__() 중 하나를 정의하고 클래스에 __eq__() 메서드를 추가해야 합니다.

import functools


@functools.total_ordering
class MyObject:
    def __init__(self, val):
        self.val = val

    def __eq__(self, other):
        print('  testing __eq__({}, {})'.format(
            self.val, other.val))
        return self.val == other.val

    def __gt__(self, other):
        print('  testing __gt__({}, {})'.format(
            self.val, other.val))
        return self.val > other.val


a = MyObject(1)
b = MyObject(2)

for expr in ['a < b&#39;, &#39;a <= b&#39;, &#39;a == b&#39;, &#39;a >= b', 'a > b']:
    print('\n{:<6}:&#39;.format(expr))
    result = eval(expr)
    print(&#39;  result of {}: {}&#39;.format(expr, result))

다음은 실행 결과입니다.

a < b :
  testing __gt__(1, 2)
  testing __eq__(1, 2)
  result of a < b: True

a <= b:
  testing __gt__(1, 2)
  result of a <= b: True

a == b:
  testing __eq__(1, 2)
  result of a == b: False

a >= b:
  testing __gt__(1, 2)
  testing __eq__(1, 2)
  result of a >= b: False

a > b :
  testing __gt__(1, 2)
  result of a > b: False

itertools 사용

itertools는 반복 개체를 작동하는 데 매우 유용한 기능을 제공합니다.

무한 반복자

count

count(start=0, step=1)는 매번 1씩 증가하는 무한 정수 반복자를 반환합니다. 선택적으로 시작 번호를 제공할 수 있으며 기본값은 0입니다.

>>> from itertools import count

>>> for i in zip(count(1), ['a', 'b', 'c']):
...     print(i, end=' ')
...
(1, 'a') (2, 'b') (3, 'c')

cycle

cycle(iterable)은 들어오는 시퀀스를 무한히 반복하지만 두 번째 매개변수를 제공하여 반복 횟수를 지정할 수 있습니다.

>>> from itertools import cycle

>>> for i in zip(range(6), cycle(['a', 'b', 'c'])):
...     print(i, end=' ')
...
(0, 'a') (1, 'b') (2, 'c') (3, 'a') (4, 'b') (5, 'c')

repeat

repeat(object[, times])는 요소가 무한히 반복되는 반복자를 반환합니다. 두 번째 매개변수를 제공하여 반복 횟수를 제한할 수 있습니다.

>>> from itertools import repeat

>>> for i, s in zip(count(1), repeat('over-and-over', 5)):
...     print(i, s)
...
1 over-and-over
2 over-and-over
3 over-and-over
4 over-and-over
5 over-and-over

가장 짧은 입력 시퀀스에서 종료되는 반복자

accumulate

accumulate(iterable[, func])

>>> from itertools import accumulate
>>> import operator

>>> list(accumulate([1, 2, 3, 4, 5], operator.add))
[1, 3, 6, 10, 15]

>>> list(accumulate([1, 2, 3, 4, 5], operator.mul))
[1, 2, 6, 24, 120]

chain

itertools .chain(*iterables)은 여러 반복 가능 항목을 하나의 반복자로 결합할 수 있습니다

>>> from itertools import chain

>>> list(chain([1, 2, 3], ['a', 'b', 'c']))
[1, 2, 3, 'a', 'b', 'c']

체인의 구현 원리는 다음과 같습니다

def chain(*iterables):
    # chain('ABC', 'DEF') --> A B C D E F
    for it in iterables:
        for element in it:
            yield element

chain.from_iterable

chain.from_iterable(iterable) Chain도 유사하지만 단일 iterable만 받은 다음 이 iterable의 요소를 iterator로 결합합니다.

>>> from itertools import chain

>>> list(chain.from_iterable(['ABC', 'DEF']))
['A', 'B', 'C', 'D', 'E', 'F']

구현 원리도 chain

def from_iterable(iterables):
    # chain.from_iterable(['ABC', 'DEF']) --> A B C D E F
    for it in iterables:
        for element in it:
            yield element

compress

compress(data, selectors)와 유사합니다. 두 개의 반복 가능 항목을 매개변수로 받고, 해당 요소만 반환하는 선택기에서 해당 요소를 반환합니다. 데이터가 True이면 데이터/선택기 중 하나가 소진되면 중지됩니다.

>>> list(compress([1, 2, 3, 4, 5], [True, True, False, False, True]))
[1, 2, 5]

zip_longest

zip_longest(*iterables, fillvalue=None)는 zip과 유사하지만 zip의 단점은 iterable의 특정 요소를 순회할 때 전체 순회가 중지된다는 점입니다. . 구체적인 차이점을 확인하세요. 아래 예시를 보세요

from itertools import zip_longest

r1 = range(3)
r2 = range(2)

print('zip stops early:')
print(list(zip(r1, r2)))

r1 = range(3)
r2 = range(2)

print('\nzip_longest processes all of the values:')
print(list(zip_longest(r1, r2)))

다음은 출력입니다

zip stops early:
[(0, 0), (1, 1)]

zip_longest processes all of the values:
[(0, 0), (1, 1), (2, None)]

islice

islice(iterable, stop) or islice(iterable, start, stop[, step]) 与Python的字符串和列表切片有一些类似,只是不能对start、start和step使用负值。

>>> from itertools import islice

>>> for i in islice(range(100), 0, 100, 10):
...     print(i, end=' ')
...
0 10 20 30 40 50 60 70 80 90

tee

tee(iterable, n=2) 返回n个独立的iterator,n默认为2。

from itertools import islice, tee

r = islice(count(), 5)
i1, i2 = tee(r)

print('i1:', list(i1))
print('i2:', list(i2))

for i in r:
    print(i, end=' ')
    if i > 1:
        break

下面是输出结果,注意tee(r)后,r作为iterator已经失效,所以for循环没有输出值。

i1: [0, 1, 2, 3, 4]
i2: [0, 1, 2, 3, 4]

starmap

starmap(func, iterable)假设iterable将返回一个元组流,并使用这些元组作为参数调用func:

>>> from itertools import starmap
>>> import os

>>> iterator = starmap(os.path.join,
...                    [('/bin', 'python'), ('/usr', 'bin', 'java'),
...                    ('/usr', 'bin', 'perl'), ('/usr', 'bin', 'ruby')])

>>> list(iterator)
['/bin/python', '/usr/bin/java', '/usr/bin/perl', '/usr/bin/ruby']

filterfalse

filterfalse(predicate, iterable) 与filter()相反,返回所有predicate返回False的元素。

itertools.filterfalse(is_even, itertools.count()) =>
1, 3, 5, 7, 9, 11, 13, 15, ...

takewhile

takewhile(predicate, iterable) 只要predicate返回True,不停地返回iterable中的元素。一旦predicate返回False,iteration将结束。

def less_than_10(x):
    return x < 10

itertools.takewhile(less_than_10, itertools.count())
=> 0, 1, 2, 3, 4, 5, 6, 7, 8, 9

itertools.takewhile(is_even, itertools.count())
=> 0

dropwhile

dropwhile(predicate, iterable) 在predicate返回True时舍弃元素,然后返回其余迭代结果。

itertools.dropwhile(less_than_10, itertools.count())
=> 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, ...

itertools.dropwhile(is_even, itertools.count())
=> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...

groupby

groupby(iterable, key=None) 把iterator中相邻的重复元素挑出来放在一起。p.s: The input sequence needs to be sorted on the key value in order for the groupings to work out as expected.

  • [k for k, g in groupby('AAAABBBCCDAABBB')] --> A B C D A B

  • [list(g) for k, g in groupby('AAAABBBCCD')] --> AAAA BBB CC D

>>> import itertools

>>> for key, group in itertools.groupby('AAAABBBCCDAABBB'):
...     print(key, list(group))
...
A ['A', 'A', 'A', 'A']
B ['B', 'B', 'B']
C ['C', 'C']
D ['D']
A ['A', 'A']
B ['B', 'B', 'B']
city_list = [('Decatur', 'AL'), ('Huntsville', 'AL'), ('Selma', 'AL'),
             ('Anchorage', 'AK'), ('Nome', 'AK'),
             ('Flagstaff', 'AZ'), ('Phoenix', 'AZ'), ('Tucson', 'AZ'),
             ...
            ]

def get_state(city_state):
    return city_state[1]

itertools.groupby(city_list, get_state) =>
  ('AL', iterator-1),
  ('AK', iterator-2),
  ('AZ', iterator-3), ...

iterator-1 =>  ('Decatur', 'AL'), ('Huntsville', 'AL'), ('Selma', 'AL')
iterator-2 => ('Anchorage', 'AK'), ('Nome', 'AK')
iterator-3 => ('Flagstaff', 'AZ'), ('Phoenix', 'AZ'), ('Tucson', 'AZ')

Combinatoric generators

product

product(*iterables, repeat=1)

  • product(A, B) returns the same as ((x,y) for x in A for y in B)

  • product(A, repeat=4) means the same as product(A, A, A, A)

from itertools import product


def show(iterable):
    for i, item in enumerate(iterable, 1):
        print(item, end=' ')
        if (i % 3) == 0:
            print()
    print()


print('Repeat 2:\n')
show(product(range(3), repeat=2))

print('Repeat 3:\n')
show(product(range(3), repeat=3))
Repeat 2:

(0, 0) (0, 1) (0, 2)
(1, 0) (1, 1) (1, 2)
(2, 0) (2, 1) (2, 2)

Repeat 3:

(0, 0, 0) (0, 0, 1) (0, 0, 2)
(0, 1, 0) (0, 1, 1) (0, 1, 2)
(0, 2, 0) (0, 2, 1) (0, 2, 2)
(1, 0, 0) (1, 0, 1) (1, 0, 2)
(1, 1, 0) (1, 1, 1) (1, 1, 2)
(1, 2, 0) (1, 2, 1) (1, 2, 2)
(2, 0, 0) (2, 0, 1) (2, 0, 2)
(2, 1, 0) (2, 1, 1) (2, 1, 2)
(2, 2, 0) (2, 2, 1) (2, 2, 2)

permutations

permutations(iterable, r=None)返回长度为r的所有可能的组合。

from itertools import permutations


def show(iterable):
    first = None
    for i, item in enumerate(iterable, 1):
        if first != item[0]:
            if first is not None:
                print()
            first = item[0]
        print(''.join(item), end=' ')
    print()


print('All permutations:\n')
show(permutations('abcd'))

print('\nPairs:\n')
show(permutations('abcd', r=2))

下面是输出结果

All permutations:

abcd abdc acbd acdb adbc adcb
bacd badc bcad bcda bdac bdca
cabd cadb cbad cbda cdab cdba
dabc dacb dbac dbca dcab dcba

Pairs:

ab ac ad
ba bc bd
ca cb cd
da db dc

combinations

combinations(iterable, r) 返回一个iterator,提供iterable中所有元素可能组合的r元组。每个元组中的元素保持与iterable返回的顺序相同。下面的实例中,不同于上面的permutations,a总是在bcd之前,b总是在cd之前,c总是在d之前。

from itertools import combinations


def show(iterable):
    first = None
    for i, item in enumerate(iterable, 1):
        if first != item[0]:
            if first is not None:
                print()
            first = item[0]
        print(''.join(item), end=' ')
    print()


print('Unique pairs:\n')
show(combinations('abcd', r=2))

下面是输出结果

Unique pairs:

ab ac ad
bc bd
cd

combinations_with_replacement

combinations_with_replacement(iterable, r)函数放宽了一个不同的约束:元素可以在单个元组中重复,即可以出现aa/bb/cc/dd等组合。

from itertools import combinations_with_replacement


def show(iterable):
    first = None
    for i, item in enumerate(iterable, 1):
        if first != item[0]:
            if first is not None:
                print()
            first = item[0]
        print(''.join(item), end=' ')
    print()


print('Unique pairs:\n')
show(combinations_with_replacement('abcd', r=2))

下面是输出结果

aa ab ac ad
bb bc bd
cc cd
dd

operator的使用

attrgetter

operator.attrgetter(attr)和operator.attrgetter(*attrs)

  • After f = attrgetter('name'), the call f(b) returns b.name.

  • After f = attrgetter('name', 'date'), the call f(b) returns (b.name, b.date).

  • After f = attrgetter('name.first', 'name.last'), the call f(b) returns (b.name.first, b.name.last).

我们通过下面这个例子来了解一下itergetter的用法。

>>> class Student:
...     def __init__(self, name, grade, age):
...         self.name = name
...         self.grade = grade
...         self.age = age
...     def __repr__(self):
...         return repr((self.name, self.grade, self.age))

>>> student_objects = [
...     Student('john', 'A', 15),
...     Student('jane', 'B', 12),
...     Student('dave', 'B', 10),
... ]

>>> sorted(student_objects, key=lambda student: student.age)   # 传统的lambda做法
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

>>> from operator import itemgetter, attrgetter

>>> sorted(student_objects, key=attrgetter('age'))
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

# 但是如果像下面这样接受双重比较,Python脆弱的lambda就不适用了
>>> sorted(student_objects, key=attrgetter('grade', 'age'))
[('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]

attrgetter的实现原理:

def attrgetter(*items):
    if any(not isinstance(item, str) for item in items):
        raise TypeError('attribute name must be a string')
    if len(items) == 1:
        attr = items[0]
        def g(obj):
            return resolve_attr(obj, attr)
    else:
        def g(obj):
            return tuple(resolve_attr(obj, attr) for attr in items)
    return g

def resolve_attr(obj, attr):
    for name in attr.split("."):
        obj = getattr(obj, name)
    return obj

itemgetter

operator.itemgetter(item)和operator.itemgetter(*items)

  • After f = itemgetter(2), the call f(r) returns r[2].

  • After g = itemgetter(2, 5, 3), the call g(r) returns (r[2], r[5], r[3]).

我们通过下面这个例子来了解一下itergetter的用法

>>> student_tuples = [
...     ('john', 'A', 15),
...     ('jane', 'B', 12),
...     ('dave', 'B', 10),
... ]

>>> sorted(student_tuples, key=lambda student: student[2])   # 传统的lambda做法
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

>>> from operator import attrgetter

>>> sorted(student_tuples, key=itemgetter(2))
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

# 但是如果像下面这样接受双重比较,Python脆弱的lambda就不适用了
>>> sorted(student_tuples, key=itemgetter(1,2))
[('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]

itemgetter的实现原理

def itemgetter(*items):
    if len(items) == 1:
        item = items[0]
        def g(obj):
            return obj[item]
    else:
        def g(obj):
            return tuple(obj[item] for item in items)
    return g

methodcaller

operator.methodcaller(name[, args...])

  • After f = methodcaller('name'), the call f(b) returns b.name().

  • After f = methodcaller('name', 'foo', bar=1), the call f(b) returns b.name('foo', bar=1).

methodcaller的实现原理

def methodcaller(name, *args, **kwargs):
    def caller(obj):
        return getattr(obj, name)(*args, **kwargs)
    return caller

References

DOCUMENTATION-FUNCTOOLS
DOCUMENTATION-ITERTOOLS
DOCUMENTATION-OPERATOR
HWOTO-FUNCTIONAL
HWOTO-SORTING
PYMOTW
FLENT-PYTHON


本文为作者原创,转载请先与作者联系。首发于我的博客

引言

functools, itertools, operator是Python标准库为我们提供的支持函数式编程的三大模块,合理的使用这三个模块,我们可以写出更加简洁可读的Pythonic代码,接下来我们通过一些example来了解三大模块的使用。

functools的使用

functools是Python中很重要的模块,它提供了一些非常有用的高阶函数。高阶函数就是说一个可以接受函数作为参数或者以函数作为返回值的函数,因为Python中函数也是对象,因此很容易支持这样的函数式特性。

partial

>>> from functools import partial

>>> basetwo = partial(int, base=2)

>>> basetwo('10010')
18

basetwo('10010')는 실제로 int('10010', base=2)을 호출하는 것과 동일합니다. 함수에 매개변수가 너무 많으면 functools.partial을 사용하여 논리를 단순화할 수 있습니다. 코드의 가독성을 높이고 부분은 실제로 간단한 클로저를 통해 내부적으로 구현됩니다.

def partial(func, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = keywords.copy()
        newkeywords.update(fkeywords)
        return func(*args, *fargs, **newkeywords)
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc

partialmethod

partialmethod는 부분적 방법과 유사하지만, 绑定一个非对象自身的方法의 경우 이번에는 부분적 방법만 사용할 수 있음을 다음 예를 통해 살펴보겠습니다.

from functools import partial, partialmethod


def standalone(self, a=1, b=2):
    "Standalone function"
    print('  called standalone with:', (self, a, b))
    if self is not None:
        print('  self.attr =', self.attr)


class MyClass:
    "Demonstration class for functools"
    def __init__(self):
        self.attr = 'instance attribute'
    method1 = functools.partialmethod(standalone)  # 使用partialmethod
    method2 = functools.partial(standalone)  # 使用partial
>>> o = MyClass()

>>> o.method1()
  called standalone with: (<__main__.MyClass object at 0x7f46d40cc550>, 1, 2)
  self.attr = instance attribute

# 不能使用partial
>>> o.method2()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: standalone() missing 1 required positional argument: 'self'

singledispatch

Python은 서로 다른 매개변수 유형을 허용하는 동일한 이름의 메소드를 지원하지 않지만, 비용을 줄이기 위해 메소드 내부에 매개변수 판단을 넣는 대신 动态指定相应的方法所接收的参数类型에 싱글디스패치를 ​​빌릴 수 있습니다. . 코드 가독성.

from functools import singledispatch


class TestClass(object):
    @singledispatch
    def test_method(arg, verbose=False):
        if verbose:
            print("Let me just say,", end=" ")
        print(arg)

    @test_method.register(int)
    def _(arg):
        print("Strength in numbers, eh?", end=" ")
        print(arg)

    @test_method.register(list)
    def _(arg):
        print("Enumerate this:")

        for i, elem in enumerate(arg):
            print(i, elem)

다음은 @test_method.register(int) 및 @test_method.register(list)를 사용하여 test_method의 첫 번째 매개변수가 int 또는 list인 경우 처리를 위해 각각 다른 메서드를 호출하도록 지정합니다.

>>> TestClass.test_method(55555)  # call @test_method.register(int)
Strength in numbers, eh? 55555

>>> TestClass.test_method([33, 22, 11])   # call @test_method.register(list)
Enumerate this:
0 33
1 22
2 11

>>> TestClass.test_method('hello world', verbose=True)  # call default
Let me just say, hello world

wraps

데코레이터는 @wraps를 사용하여 복원할 수 있는 데코레이팅된 함수의 __name__ 및 __doc__ 속성을 잃게 됩니다.

from functools import wraps


def my_decorator(f):
    @wraps(f)
    def wrapper():
        """wrapper_doc"""
        print('Calling decorated function')
        return f()
    return wrapper


@my_decorator
def example():
    """example_doc"""
    print('Called example function')
rrree

update_wrapper를 사용하여

>>> example.__name__
'example'
>>> example.__doc__
'example_doc'

# 尝试去掉@wraps(f)来看一下运行结果,example自身的__name__和__doc__都已经丧失了
>>> example.__name__
'wrapper'
>>> example.__doc__
'wrapper_doc'

@wraps가 실제로 update_wrapper를 기반으로 내부적으로 구현되도록 다시 작성할 수도 있습니다.

from itertools import update_wrapper


def g():
    ...
g = update_wrapper(g, f)


# equal to
@wraps(f)
def g():
    ...

lru_cache

lru_cache와 Singledispatch는 개발에 널리 사용되는 흑마술입니다. 다음으로 lru_cache를 살펴보겠습니다. 반복적인 계산 작업의 경우 缓存加速을 사용하는 것이 매우 중요합니다. lru_cache를 사용할 때와 사용하지 않을 때의 속도 차이를 확인하기 위해 fibonacci 예제를 살펴보겠습니다.

def wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES):
    def decorator(wrapper):
        return update_wrapper(wrapper, wrapped=wrapped...)
    return decorator

lru_cache를 사용하지 마세요

# clockdeco.py

import time
import functools


def clock(func):
    @functools.wraps(func)
    def clocked(*args, **kwargs):
        t0 = time.time()
        result = func(*args, **kwargs)
        elapsed = time.time() - t0
        name = func.__name__
        arg_lst = []
        if args:
            arg_lst.append(', '.join(repr(arg) for arg in args))
        if kwargs:
            pairs = ['%s=%r' % (k, w) for k, w in sorted(kwargs.items())]
            arg_lst.append(', '.join(pairs))
        arg_str = ', '.join(arg_lst)
        print('[%0.8fs] %s(%s) -> %r ' % (elapsed, name, arg_str, result))
        return result
    return clocked

다음은 실행 결과에서 fibonacci(n)가 重复计算동안 있음을 알 수 있습니다. 재귀입니다. 이는 시간과 리소스가 많이 소모됩니다.

from clockdeco import clock


@clock
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-2) + fibonacci(n-1)


if __name__==&#39;__main__&#39;:
    print(fibonacci(6))

lru_cache 사용

[0.00000119s] fibonacci(0) -> 0 
[0.00000143s] fibonacci(1) -> 1 
[0.00021172s] fibonacci(2) -> 1 
[0.00000072s] fibonacci(1) -> 1 
[0.00000095s] fibonacci(0) -> 0 
[0.00000095s] fibonacci(1) -> 1 
[0.00011444s] fibonacci(2) -> 1 
[0.00022793s] fibonacci(3) -> 2 
[0.00055265s] fibonacci(4) -> 3 
[0.00000072s] fibonacci(1) -> 1 
[0.00000072s] fibonacci(0) -> 0 
[0.00000095s] fibonacci(1) -> 1 
[0.00011158s] fibonacci(2) -> 1 
[0.00022268s] fibonacci(3) -> 2 
[0.00000095s] fibonacci(0) -> 0 
[0.00000095s] fibonacci(1) -> 1 
[0.00011349s] fibonacci(2) -> 1 
[0.00000072s] fibonacci(1) -> 1 
[0.00000095s] fibonacci(0) -> 0 
[0.00000095s] fibonacci(1) -> 1 
[0.00010705s] fibonacci(2) -> 1 
[0.00021267s] fibonacci(3) -> 2 
[0.00043225s] fibonacci(4) -> 3 
[0.00076509s] fibonacci(5) -> 5 
[0.00142813s] fibonacci(6) -> 8 
8

계산된 결과는 다음과 같습니다.

import functools
from clockdeco import clock


@functools.lru_cache() # 1
@clock # 2
def fibonacci(n):
    if n < 2:
       return n
    return fibonacci(n-2) + fibonacci(n-1)

if __name__==&#39;__main__&#39;:
    print(fibonacci(6))

우리가 위에서 선택한 숫자는 충분히 크지 않습니다. 관심 있는 친구들은 둘 사이의 속도 차이를 비교하기 위해 더 큰 숫자를 선택할 수도 있습니다.

total_ordering

Python2 , __cmp__ 0/-1/1의 반환 값을 사용자 정의하여 객체의 크기를 비교할 수 있습니다. Python3에서는 __cmp__가 폐기되지만 total_ordering을 통해 수정한 다음 __lt__(), __le__(), __gt__( )을 수정할 수 있습니다. , __ge__(), __eq__(), __ne__() 및 기타 매직 메소드를 사용하여 클래스의 비교 규칙을 사용자 정의합니다. p.s: 사용하려면 클래스에 __lt__(), __le__(), __gt__(), __ge__() 중 하나를 정의하고 클래스에 __eq__() 메서드를 추가해야 합니다.

[0.00000095s] fibonacci(0) -> 0 
[0.00005770s] fibonacci(1) -> 1 
[0.00015855s] fibonacci(2) -> 1 
[0.00000286s] fibonacci(3) -> 2 
[0.00021124s] fibonacci(4) -> 3 
[0.00000191s] fibonacci(5) -> 5 
[0.00024652s] fibonacci(6) -> 8 
8

다음은 실행 결과입니다.

import functools


@functools.total_ordering
class MyObject:
    def __init__(self, val):
        self.val = val

    def __eq__(self, other):
        print('  testing __eq__({}, {})'.format(
            self.val, other.val))
        return self.val == other.val

    def __gt__(self, other):
        print('  testing __gt__({}, {})'.format(
            self.val, other.val))
        return self.val > other.val


a = MyObject(1)
b = MyObject(2)

for expr in ['a < b&#39;, &#39;a <= b&#39;, &#39;a == b&#39;, &#39;a >= b', 'a > b']:
    print('\n{:<6}:&#39;.format(expr))
    result = eval(expr)
    print(&#39;  result of {}: {}&#39;.format(expr, result))

itertools 사용

itertools는 반복 개체를 작동하는 데 매우 유용한 기능을 제공합니다.

무한 반복자

count

count(start=0, step=1)는 매번 1씩 증가하는 무한 정수 반복자를 반환합니다. 선택적으로 시작 번호를 제공할 수 있으며 기본값은 0입니다.

a < b :
  testing __gt__(1, 2)
  testing __eq__(1, 2)
  result of a < b: True

a <= b:
  testing __gt__(1, 2)
  result of a <= b: True

a == b:
  testing __eq__(1, 2)
  result of a == b: False

a >= b:
  testing __gt__(1, 2)
  testing __eq__(1, 2)
  result of a >= b: False

a > b :
  testing __gt__(1, 2)
  result of a > b: False

cycle

cycle(iterable)은 들어오는 시퀀스를 무한히 반복하지만 두 번째 매개변수를 제공하여 반복 횟수를 지정할 수 있습니다.

>>> from itertools import count

>>> for i in zip(count(1), ['a', 'b', 'c']):
...     print(i, end=' ')
...
(1, 'a') (2, 'b') (3, 'c')

repeat

repeat(object[, times])는 요소가 무한히 반복되는 반복자를 반환합니다. 두 번째 매개변수를 제공하여 반복 횟수를 제한할 수 있습니다.

>>> from itertools import cycle

>>> for i in zip(range(6), cycle(['a', 'b', 'c'])):
...     print(i, end=' ')
...
(0, 'a') (1, 'b') (2, 'c') (3, 'a') (4, 'b') (5, 'c')

가장 짧은 입력 시퀀스에서 종료되는 반복자

accumulate

accumulate(iterable[, func])

>>> from itertools import repeat

>>> for i, s in zip(count(1), repeat('over-and-over', 5)):
...     print(i, s)
...
1 over-and-over
2 over-and-over
3 over-and-over
4 over-and-over
5 over-and-over

chain

itertools .chain(*iterables)은 여러 반복 가능 항목을 하나의 반복자로 결합할 수 있습니다

>>> from itertools import accumulate
>>> import operator

>>> list(accumulate([1, 2, 3, 4, 5], operator.add))
[1, 3, 6, 10, 15]

>>> list(accumulate([1, 2, 3, 4, 5], operator.mul))
[1, 2, 6, 24, 120]

체인의 구현 원리는 다음과 같습니다

>>> from itertools import chain

>>> list(chain([1, 2, 3], ['a', 'b', 'c']))
[1, 2, 3, 'a', 'b', 'c']

chain.from_iterable

chain.from_iterable(iterable) Chain도 유사하지만 단일 iterable만 받은 다음 이 iterable의 요소를 iterator로 결합합니다.

def chain(*iterables):
    # chain('ABC', 'DEF') --> A B C D E F
    for it in iterables:
        for element in it:
            yield element

구현 원리도 chain

>>> from itertools import chain

>>> list(chain.from_iterable(['ABC', 'DEF']))
['A', 'B', 'C', 'D', 'E', 'F']

compress

compress(data, selectors)와 유사합니다. 두 개의 반복 가능 항목을 매개변수로 받고, 해당 요소만 반환하는 선택기에서 해당 요소를 반환합니다. 데이터가 True이면 데이터/선택기 중 하나가 소진되면 중지됩니다.

def from_iterable(iterables):
    # chain.from_iterable(['ABC', 'DEF']) --> A B C D E F
    for it in iterables:
        for element in it:
            yield element

zip_longest

zip_longest(*iterables, fillvalue=None)는 zip과 유사하지만 zip의 단점은 iterable의 특정 요소를 순회할 때 전체 순회가 중지된다는 점입니다. 구체적인 차이점을 살펴보세요.

>>> list(compress([1, 2, 3, 4, 5], [True, True, False, False, True]))
[1, 2, 5]

다음은 출력

from itertools import zip_longest

r1 = range(3)
r2 = range(2)

print('zip stops early:')
print(list(zip(r1, r2)))

r1 = range(3)
r2 = range(2)

print('\nzip_longest processes all of the values:')
print(list(zip_longest(r1, r2)))

islice

islice(iterable, start)입니다. , stop[, step]) Python의 문자열은 start, start 및 step에 음수 값을 사용할 수 없다는 점을 제외하면 목록 조각과 다소 유사합니다.

zip stops early:
[(0, 0), (1, 1)]

zip_longest processes all of the values:
[(0, 0), (1, 1), (2, None)]

tee

tee(iterable, n=2)는 n개의 독립 반복자를 반환하며, n의 기본값은 2입니다.

>>> from itertools import islice

>>> for i in islice(range(100), 0, 100, 10):
...     print(i, end=' ')
...
0 10 20 30 40 50 60 70 80 90

다음은 출력 결과입니다. tee(r) 이후 r은 반복자로 유효하지 않게 되므로 for 루프에는 출력 값이 없습니다.

from itertools import islice, tee

r = islice(count(), 5)
i1, i2 = tee(r)

print('i1:', list(i1))
print('i2:', list(i2))

for i in r:
    print(i, end=' ')
    if i > 1:
        break

starmap

starmap(func, iterable)은 iterable이 튜플 스트림을 반환한다고 가정하고 다음 튜플을 인수로 사용하여 func를 호출합니다.

i1: [0, 1, 2, 3, 4]
i2: [0, 1, 2, 3, 4]

filterfalse

filterfalse(predicate, iterable)은 filter()와 반대이며 조건자가 False를 반환하는 모든 요소를 ​​반환합니다.

itertools.filterfalse(is_even, itertools.count()) =>
1, 3, 5, 7, 9, 11, 13, 15, ...

takewhile

takewhile(predicate, iterable) 只要predicate返回True,不停地返回iterable中的元素。一旦predicate返回False,iteration将结束。

def less_than_10(x):
    return x < 10

itertools.takewhile(less_than_10, itertools.count())
=> 0, 1, 2, 3, 4, 5, 6, 7, 8, 9

itertools.takewhile(is_even, itertools.count())
=> 0

dropwhile

dropwhile(predicate, iterable) 在predicate返回True时舍弃元素,然后返回其余迭代结果。

itertools.dropwhile(less_than_10, itertools.count())
=> 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, ...

itertools.dropwhile(is_even, itertools.count())
=> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...

groupby

groupby(iterable, key=None) 把iterator中相邻的重复元素挑出来放在一起。p.s: The input sequence needs to be sorted on the key value in order for the groupings to work out as expected.

  • [k for k, g in groupby('AAAABBBCCDAABBB')] --> A B C D A B

  • [list(g) for k, g in groupby('AAAABBBCCD')] --> AAAA BBB CC D

>>> import itertools

>>> for key, group in itertools.groupby('AAAABBBCCDAABBB'):
...     print(key, list(group))
...
A ['A', 'A', 'A', 'A']
B ['B', 'B', 'B']
C ['C', 'C']
D ['D']
A ['A', 'A']
B ['B', 'B', 'B']
city_list = [('Decatur', 'AL'), ('Huntsville', 'AL'), ('Selma', 'AL'),
             ('Anchorage', 'AK'), ('Nome', 'AK'),
             ('Flagstaff', 'AZ'), ('Phoenix', 'AZ'), ('Tucson', 'AZ'),
             ...
            ]

def get_state(city_state):
    return city_state[1]

itertools.groupby(city_list, get_state) =>
  ('AL', iterator-1),
  ('AK', iterator-2),
  ('AZ', iterator-3), ...

iterator-1 =>  ('Decatur', 'AL'), ('Huntsville', 'AL'), ('Selma', 'AL')
iterator-2 => ('Anchorage', 'AK'), ('Nome', 'AK')
iterator-3 => ('Flagstaff', 'AZ'), ('Phoenix', 'AZ'), ('Tucson', 'AZ')

Combinatoric generators

product

product(*iterables, repeat=1)

  • product(A, B) returns the same as ((x,y) for x in A for y in B)

  • product(A, repeat=4) means the same as product(A, A, A, A)

from itertools import product


def show(iterable):
    for i, item in enumerate(iterable, 1):
        print(item, end=' ')
        if (i % 3) == 0:
            print()
    print()


print('Repeat 2:\n')
show(product(range(3), repeat=2))

print('Repeat 3:\n')
show(product(range(3), repeat=3))
Repeat 2:

(0, 0) (0, 1) (0, 2)
(1, 0) (1, 1) (1, 2)
(2, 0) (2, 1) (2, 2)

Repeat 3:

(0, 0, 0) (0, 0, 1) (0, 0, 2)
(0, 1, 0) (0, 1, 1) (0, 1, 2)
(0, 2, 0) (0, 2, 1) (0, 2, 2)
(1, 0, 0) (1, 0, 1) (1, 0, 2)
(1, 1, 0) (1, 1, 1) (1, 1, 2)
(1, 2, 0) (1, 2, 1) (1, 2, 2)
(2, 0, 0) (2, 0, 1) (2, 0, 2)
(2, 1, 0) (2, 1, 1) (2, 1, 2)
(2, 2, 0) (2, 2, 1) (2, 2, 2)

permutations

permutations(iterable, r=None)返回长度为r的所有可能的组合。

from itertools import permutations


def show(iterable):
    first = None
    for i, item in enumerate(iterable, 1):
        if first != item[0]:
            if first is not None:
                print()
            first = item[0]
        print(''.join(item), end=' ')
    print()


print('All permutations:\n')
show(permutations('abcd'))

print('\nPairs:\n')
show(permutations('abcd', r=2))

下面是输出结果

All permutations:

abcd abdc acbd acdb adbc adcb
bacd badc bcad bcda bdac bdca
cabd cadb cbad cbda cdab cdba
dabc dacb dbac dbca dcab dcba

Pairs:

ab ac ad
ba bc bd
ca cb cd
da db dc

combinations

combinations(iterable, r) 返回一个iterator,提供iterable中所有元素可能组合的r元组。每个元组中的元素保持与iterable返回的顺序相同。下面的实例中,不同于上面的permutations,a总是在bcd之前,b总是在cd之前,c总是在d之前。

from itertools import combinations


def show(iterable):
    first = None
    for i, item in enumerate(iterable, 1):
        if first != item[0]:
            if first is not None:
                print()
            first = item[0]
        print(''.join(item), end=' ')
    print()


print('Unique pairs:\n')
show(combinations('abcd', r=2))

下面是输出结果

Unique pairs:

ab ac ad
bc bd
cd

combinations_with_replacement

combinations_with_replacement(iterable, r)函数放宽了一个不同的约束:元素可以在单个元组中重复,即可以出现aa/bb/cc/dd等组合。

from itertools import combinations_with_replacement


def show(iterable):
    first = None
    for i, item in enumerate(iterable, 1):
        if first != item[0]:
            if first is not None:
                print()
            first = item[0]
        print(''.join(item), end=' ')
    print()


print('Unique pairs:\n')
show(combinations_with_replacement('abcd', r=2))

下面是输出结果

aa ab ac ad
bb bc bd
cc cd
dd

operator的使用

attrgetter

operator.attrgetter(attr)和operator.attrgetter(*attrs)

  • After f = attrgetter('name'), the call f(b) returns b.name.

  • After f = attrgetter('name', 'date'), the call f(b) returns (b.name, b.date).

  • After f = attrgetter('name.first', 'name.last'), the call f(b) returns (b.name.first, b.name.last).

我们通过下面这个例子来了解一下itergetter的用法。

>>> class Student:
...     def __init__(self, name, grade, age):
...         self.name = name
...         self.grade = grade
...         self.age = age
...     def __repr__(self):
...         return repr((self.name, self.grade, self.age))

>>> student_objects = [
...     Student('john', 'A', 15),
...     Student('jane', 'B', 12),
...     Student('dave', 'B', 10),
... ]

>>> sorted(student_objects, key=lambda student: student.age)   # 传统的lambda做法
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

>>> from operator import itemgetter, attrgetter

>>> sorted(student_objects, key=attrgetter('age'))
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

# 但是如果像下面这样接受双重比较,Python脆弱的lambda就不适用了
>>> sorted(student_objects, key=attrgetter('grade', 'age'))
[('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]

attrgetter的实现原理:

def attrgetter(*items):
    if any(not isinstance(item, str) for item in items):
        raise TypeError('attribute name must be a string')
    if len(items) == 1:
        attr = items[0]
        def g(obj):
            return resolve_attr(obj, attr)
    else:
        def g(obj):
            return tuple(resolve_attr(obj, attr) for attr in items)
    return g

def resolve_attr(obj, attr):
    for name in attr.split("."):
        obj = getattr(obj, name)
    return obj

itemgetter

operator.itemgetter(item)和operator.itemgetter(*items)

  • After f = itemgetter(2), the call f(r) returns r[2].

  • After g = itemgetter(2, 5, 3), the call g(r) returns (r[2], r[5], r[3]).

我们通过下面这个例子来了解一下itergetter的用法

>>> student_tuples = [
...     ('john', 'A', 15),
...     ('jane', 'B', 12),
...     ('dave', 'B', 10),
... ]

>>> sorted(student_tuples, key=lambda student: student[2])   # 传统的lambda做法
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

>>> from operator import attrgetter

>>> sorted(student_tuples, key=itemgetter(2))
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

# 但是如果像下面这样接受双重比较,Python脆弱的lambda就不适用了
>>> sorted(student_tuples, key=itemgetter(1,2))
[('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]

itemgetter的实现原理

def itemgetter(*items):
    if len(items) == 1:
        item = items[0]
        def g(obj):
            return obj[item]
    else:
        def g(obj):
            return tuple(obj[item] for item in items)
    return g

methodcaller

operator.methodcaller(name[, args...])

  • After f = methodcaller('name'), the call f(b) returns b.name().

  • After f = methodcaller('name', 'foo', bar=1), the call f(b) returns b.name('foo', bar=1).

methodcaller的实现原理

def methodcaller(name, *args, **kwargs):
    def caller(obj):
        return getattr(obj, name)(*args, **kwargs)
    return caller

更多Python标准库之functools/itertools/operator相关文章请关注PHP中文网!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.