《Python从小白到大牛》第10章 函数式编程

栏目: 编程语言 · 发布时间: 6年前

内容简介:版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/tonny_guan/article/details/82769010

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/tonny_guan/article/details/82769010

《Python从小白到大牛》第10章 函数式编程

程序中反复执行的代码可以封装到一个代码块中,这个代码块模仿了数学中的函数,具有函数名、参数和返回值,这就是程序中的函数。

Python中的函数很灵活,它可以在模块中,但类之外定义,即函数,作用域是当前模块;也可以在别的函数中定义,即嵌套函数;还可以在类中定义,即方法。

定义函数

在前面的学习过程中也用到了一些函数,如果len()、min()和max(),这些函数都由 Python 官方提供的,称为内置函数(Built-in

Functions, 缩写BIF)。

注意

Python作为解释性语言函数必须先定义后调用,也就是定义函数必须在调用函数之前,否则会有错误发生。

本节介绍自定义函数,自定义函数的语法格式如下:

def 函数名(参数列表) : 
    函数体
    return 返回值

在Python中定义函数时,关键字是def,函数名需要符合标识符命名规范;多个参数列表之间可以用逗号(,)分隔,当然函数也可以没有参数。如果函数有返回数据,就需要在函数体最后使用return语句将数据返回;如果没有返回数据,则函数体中可以使用return

None或省略return语句。

函数定义示例代码如下:

# coding=utf-8
# 代码文件:chapter10/ch10.1.py


def rectangle_area(width, height): ①
    area = width * height
    return area ②


r_area = rectangle_area(320.0, 480.0) ③

print("320x480的长方形的面积:{0:.2f}".format(r_area))

上述代码第①行是定义计算长方形面积的函数rectangle_area,它有两个参数,分别是长方形的宽和高,width和height是参数名。代码第②行代码是通过return返回函数计算结果。代码第③行是调用rectangle_area函数。

函数参数

Python中的函数参数很灵活,具体体现在传递参数有多种形式上。本节介绍几种不同形式参数和调用方式。

使用关键字参数调用函数

为了提高函数调用的可读性,在函数调用时可以使用关键字参数调用。采用关键字参数调用函数,在函数定义时不需要做额外的工作。

示例代码如下:

# coding=utf-8
# 代码文件:chapter10/ch10.2.1.py


def print_area(width, height):
    area = width * height
    print("{0} x {1} 长方形的面积:{2}".format(width, height, area))


print_area(320.0, 480.0)  # 没有采用关键字参数函数调用	①
print_area(width=320.0, height=480.0)  # 采用关键字参数函数调用②
print_area(320.0, height=480.0)  # 采用关键字参数函数调用 ③
# print_area(width=320.0, height)  # 发生错误 ④
print_area(height=480.0, width=320.0)  # 采用关键字参数函数调用 ⑤

print_area函数有两个参数,在调用时没有采用关键字参数函数调用,见代码第①行,也可以使用关键字参数调用函数,见代码第②行、第③行和第⑤行,其中width和height是参数名。从上述代码比较可见采用关键字参数调用函数,调用者能够清晰地看出传递参数的含义,关键字参数对于有多参数函数调用非常有用。另外,采用关键字参数函数调用时,参数顺序可以与函数定义时参数顺序不同。

注意

在调用函数时,一旦其中一个参数采用了关键字参数形式传递,那么其后的所有参数都必须采用关键字参数形式传递。代码第④行的函数调用中第一个参数width采用了关键字参数形式,而它后面的参数没有采用关键字参数形式,因此会有错误发生。

参数默认值

在定义函数的时候可以为参数设置一个默认值,当调用函数的时候可以忽略该参数。来看下面的一个示例:

# coding=utf-8
# 代码文件:chapter9/ch10.2.2.py

def make_coffee(name="卡布奇诺"):
    return "制作一杯{0}咖啡。".format(name)

上述代码定义了makeCoffee函数,可以帮助我做一杯香浓的咖啡。由于我喜欢喝卡布奇诺,就把它设置为默认值。在参数列表中,默认值可以跟在参数类型的后面,通过等号提供给参数。

在调用的时候,如果调用者没有传递参数,则使用默认值。调用代码如下:

coffee1 = make_coffee("拿铁")	 ①
coffee2 = make_coffee() ②

print(coffee1)  # 制作一杯拿铁咖啡。
print(coffee2)  # 制作一杯卡布奇诺咖啡。

其中第①行代码是传递"拿铁"参数,没有使用默认值。第②行代码没有传递参数,因此使用默认值。

提示

在其他语言中make_coffee函数可以采用重载实现多个版本。Python不支持函数重载,而是使用参数默认值的方式提供类似函数重载的功能。因为参数默认值只需要定义一个函数就可以了,而重载则需要定义多个函数,这会增加代码量。

可变参数

Python中函数的参数个数可以变化,它可以接受不确定数量的参数,这种参数称为可变参数。Python中可变参数有两种,在参数前加*或**形式,*可变参数在函数中被组装成为一个元组,**可变参数在函数中被组装成为一个字典。

  1. *可变参数

下面看一个示例:

def sum(*numbers, multiple=1):
    total = 0.0
    for number in numbers:
        total += number
    return total * multiple

上述代码定义了一个sum()函数,用来计算传递给它的所有参数之和。*numbers是可变参数。在函数体中参数numbers被组装成为一个元组,可以使用for循环遍历numbers元组,计算他们的总和,然后返回给调用者。

下面是三次调用sum()函数的代码:

print(sum(100.0, 20.0, 30.0))  # 输出150.0
print(sum(30.0, 80.0))  # 输出110.0
print(sum(30.0, 80.0, multiple=2))  # 输出220.0 ①

double_tuple = (50.0, 60.0, 0.0)  # 元组或列表 ②
print(sum(30.0, 80.0, *double_tuple))  # 输出220.0 ③

可以看到,每次所传递参数的个数是不同的,前两次调用时都省略了multiple参数,第三次调用时传递了multiple参数,此时multiple应该使用关键字参数传递,否则有错误发生。

如果已经有一个元组变量(见代码第②行),能否传递给可变参数呢?这需要使用对元组进行拆包,见代码第③行在元组double_tuple前面加上单星号(*),单星号在这里表示将double_tuple拆包为50.0,

60.0, 0.0形式。另外,double_tuple也可以是列表对象。

注意

*可变参数不是最后一个参数时,后面的参数需要采用关键字参数形式传递。代码第①行30.0,

80.0是可变参数,后面multiple参数需要关键字参数形式传递。

  1. **可变参数

下面看一个示例:

def show_info(sep=':', **info):
    print('-----info------')
    for key, value in info.items():
        print('{0} {2} {1}'.format(key, value, sep))

上述代码定义了一个show_info()函数,用来输出一些信息,其中参数sep信息分隔符号,默认值是冒号(:)。**info是可变参数,在函数体中参数info被组装成为一个字典。

注意 **可变参数必须在正规参数之后,如果本例函数定义改为show_info(**info,sep=’:’)形式,会发生错误。

下面是三次调用show_info()函数的代码:

show_info('->', name='Tony', age=18, sex=True)  ①
show_info(sutdent_name='Tony', sutdent_no='1000', sep='-') ②

stu_dict = {'name': 'Tony', 'age': 18}  # 创建字典对象 
show_info(**stu_dict, sex=True, sep='=')  # 传递字典stu_dict ③

上述代码第①行是调用函数show_info(),第一个参数’->'是传递给sep,其后的参数name=‘Tony’,

age=18,

sex=True是传递给info,这种参数形式事实上就是关键字参数,注意键不要用引号包裹起来。

代码第②行是调用函数show_info(),sep也采用关键字参数传递,这种方式sep参数可以放置在参数列表的任何位置,其中的关键字参数被收集到info字典中。

代码第③行是调用函数show_info(),其中字典对象stu_dict,传递时stu_dict前面加上双星号(**),双星号在这里表示将stu_dict拆包为key=value对的形式。

函数返回值

Python函数的返回值也是比较灵活的,主要有三种形式:无返回值、单一返回值和多返回值。前面使用的函数基本都单一返回值,本节重点介绍无返回值和多返回值两种形式。

无返回值函数

有的函数只是为了处理某个过程,此时可以将函数设计为无返回值的。所谓无返回值,事实上是返回None,None表示没有实际意义的数据。

无返回值函数示例代码如下:

# coding=utf-8
# 代码文件:chapter9/ch10.3.1.py

def show_info(sep=':', **info): ①
    """定义**可变参数函数"""
    print('-----info------')
    for key, value in info.items():
        print('{0} {2} {1}'.format(key, value, sep))
    return  # return None 或省略 	②


result = show_info('->', name='Tony', age=18, sex=True)
print(result)  # 输出 None 


def sum(*numbers, multiple=1): ③
    """定义*可变参数函数"""
    if len(numbers) == 0:
        return  # return None 或省略 ④
    total = 0.0
    for number in numbers:
        total += number
    return total * multiple


print(sum(30.0, 80.0))  # 输出110.0
print(sum(multiple=2)) # 输出 None ⑥

上述代码定义了两个函数,这个两个函数事实上是在10.3.2节示例基础上的重构。其中代码第①行的show_info()只是输出一些信息,不需要返回数据,因此可以省略return语句。如果一定要使用return语句,见代码第②行在函数结束前使用return或return

None。

对于本例中的show_info()函数强加return语句显然是多此一举,但是有时使用return或return

None是必要的。代码第③行定义了sum()函数,如果numbers中数据是空的,后面的求和计算也就没有意义了,可以在函数的开始判断numbers中算法有数据,如果没有数据则使用return或return

None跳出函数,见代码第④行。

多返回值函数

有时需要函数返回多个值,实现返回多个值的实现方式有很多,简单实现是使用元组返回多个值,因为元组作为数据结构可以容纳多个数据,另外元组是不可变的,使用起来比较安全。

下面来看一个示例:

# coding=utf-8
# 代码文件:chapter9/ch10.3.2.py


def position(dt, speed):  ①
    posx = speed[0] * dt  ②
    posy = speed[1] * dt  ③
    return (posx, posy)  ④


move = position(60.0, (10, -5)) ⑤

print("物体位移:({0}, {1})".format(move[0], move[1])) ⑥

这个示例是计算物体在指定时间和速度时的位移。第①行代码是定义position函数,其中dt参数是时间,speed参数是元组类型,speed第一个元素X轴上的速度,speed第二个元素Y轴上的速度。position函数的返回值也是元组类型。

函数体中的第②行代码是计算X方向的位移,第③行代码是计算Y方向的位移。最后,第④行代码将计算后的数据返回,(posx,

posy)是元组类型实例。

第⑤行代码调用函数,传递的时间是60.0秒,速度是(10,

-5)。第⑥行代码打印输出结果,结果如下:

物体位移:(600.0, -300.0)

函数变量作用域

变量可以在模块中创建,作用域是整个模块,称为全局变量。变量也可以在函数中创建,默认情况下作用域是整个函数,称为局部变量。

示例代码如下:

# coding=utf-8
# 代码文件:chapter9/ch10.4.py

# 创建全局变量x
x = 20 ①

def print_value():
    print("函数中x = {0}".format(x)) ②

print_value()
print("全局变量x = {0}".format(x))

输出结果:

函数中x = 20
全局变量x = 20

上述代码第①行是创建全局变量x,全局变量作用域是整个模块,所以在print_value()函数中也可以访问变量x,见代码第②行。

修改上述示例代码如下:

# 创建全局变量x
x = 20 

def print_value():
    # 创建局部变量x
    x = 10  ①
    print("函数中x = {0}".format(x)) 

print_value()
print("全局变量x = {0}".format(x))

输出结果:

函数中x = 10
全局变量x = 20

上述代码的是print_value()函数中添加x =

10语句,见代码第①行,这会函数中创建x变量,函数中的x变量与全局变量x命名相同,在函数作用域内会屏蔽全局x变量。

函数中创建的变量默认作用域是当前函数,这可以防止 程序员 少犯错误,因为函数中创建的变量,如果作用域是整个模块,那么在其他函数中也可以访问,Python无法从语言层面定义常量,所以在其他函数中由于误操作修改了变量,这样一来很容易导致程序出现错误。即便笔者不赞同,但Python提供这种可能,通过在函数中将变量声明为global的,可以把变量的作用域变成全局的。

修改上述示例代码如下:

# 创建全局变量x
x = 20 

def print_value():
    global x  ①
    x = 10   ②
    print("函数中x = {0}".format(x)) 

print_value()
print("全局变量x = {0}".format(x))

输出结果:

函数中x = 10
全局变量x = 10

代码第①行是在函数中声明x变量作用域为全局变量,所以代码第②行修改x值,就是修改全局变量x的数值。

在一个函数中使用return关键字返回数据,但是有时候会使用yield 关键字返回数据。yield 关键字的函数返回的是一个生成器(generator)对象,生成器对象是一种可迭代对象。

如果想计算平方数列,通常的实现代码如下:

def square(num):  ①
    n_list = []

    for i in range(1, num + 1):
        n_list.append(i * i) ②

    return n_list ③

for i in square(5):  ④
    print(i, end=' ')

返回结果是:

首先定义一个函数,见代码第①行。代码第②行通过循环计算一个数的平方,并将结果保存到一个列表对象n_list中。最后返回列表对象,见代码第③行。代码第④行是遍历返回的列表对象。

在Python中还可以有更加好解决方案,实现代码如下:

def square(num):

    for i in range(1, num + 1):
        yield i * i    ①


for i in square(5):  ②
    print(i, end=' ')

返回结果是:

代码第①行使用了yield关键字返回平方数,不再需要return关键字了。代码第②行调用函数square()返回的是生成器对象。生成器对象是一种可迭代对象,可迭代对象通过__next__()方法获得元素,代码第②行的for循环能够遍历可迭代对象,就是隐式地调用生成器的__next__()方法获得元素的。

显式地调用生成器的__next__()方法,在Python Shell中运行示例代码如下:

>>> def square(num):

    for i in range(1, num + 1):
            yield i * i

        
>>> n_seq = square(5)
<generator object square at 0x000001C8F123CE60>
>>> n_seq.__next__()  ①
1
>>> n_seq.__next__()
4
>>> n_seq.__next__()
9
>>> n_seq.__next__()
16
>>> n_seq.__next__()
25
>>> n_seq.__next__() ②
Traceback (most recent call last):
  File "<pyshell#33>", line 1, in <module>
    n_seq.__next__()
StopIteration
>>>

上述代码第①行~第②行调用了6次__next__()方法,但第6次调用则会抛出StopIteration异常,这是因为已经没有元素可迭代了。

生成器函数是通过yield返回数据,与return不同的是:return语句一次返回所有数据,函数调用结束;而yield语句只返回一个元素数据,函数调用不会结束,只是暂停,直到__next__()方法被调用,程序继续执行yield语句之后的语句代码。这个过程如图10-1所示。

《Python从小白到大牛》第10章 函数式编程

注意

生成器特别适合用于遍历一些大序列对象,它无须将对象的所有元素都载入内存后才开始进行操作。而是仅在迭代至某个元素时才会将该元素载入内存。

嵌套函数

在本节之前定义的函数都是全局函数,并将他们定义在全局作用域中。函数还可定义在另外的函数体中,称作“嵌套函数”。

示例代码:

# coding=utf-8
# 代码文件:chapter10/ch10.6.py

def calculate(n1, n2, opr):
    multiple = 2

    # 定义相加函数
    def add(a, b): ①
        return (a + b) * multiple

    # 定义相减函数
    def sub(a, b): ②
        return (a - b) * multiple

    if opr == '+':
        return add(n1, n2)
    else:
        return sub(n1, n2)

print(calculate(10, 5, '+'))  # 输出结果是30
# add(10, 5) 发生错误 ③
# sub(10, 5)  发生错误 ④

上述代码中定义了两个嵌套函数:add()和sub(),见代码第①行和第②行。嵌套函数可以访问所在外部函数calculate()中的变量multiple,而外部函数不能访问嵌套函数局部变量。另外,嵌套函数的作用域在外部函数体内,因此在外部函数体之外直接访问嵌套函数会发生错误,见代码第③行和第④行。

函数式编程基础

函数式编程(functional

programming)与面向对象编程一样都一种编程范式,函数式编程,也称为面向函数的编程。

Python并不是彻底的函数式编程语言,但是还是提供了一些函数式编程必备的技术,主要有:函数类型和Lambda表达式,他们是实现函数式编程的基础。

函数类型

Python提供了一种函数类型function,任何一个函数都有函数类型,但是函数调用时,就创建了函数类型实例,即函数对象。函数类型实例与其他类型实例一样,在使用场景上没有区别:它可以赋值给一个变量;也可以作为参数传递给一个函数;还可以作为函数返回值使用。

为了理解函数类型,先重构10.6节中嵌套函数的示例,示例代码如下:

# coding=utf-8
# 代码文件:chapter10/ch10.7.1.py

def calculate_fun(opr):	①
    # 定义相加函数
    def add(a, b):
        return a + b

    # 定义相减函数
    def sub(a, b):
        return a - b

    if opr == '+':
        return add ②
    else:
        return sub ③

f1 = calculate_fun('+')  ④
f2 = calculate_fun('-')  ⑤

print(type(f1))

print("10 + 5 = {0}".format(f1(10, 5)))	 ⑥
print("10 - 5 = {0}".format(f2(10, 5)))  ⑦

输出结果:

<class 'function'>
10 + 5 = 30
10 - 5 = 10

上述代码第①行重构了calculate_fun()函数的定义,现在只接收一个参数opr。代码第②行是在opr

'+'为True时返回add函数名,否则返回sub函数名,见代码第③行。这里的函数名本质上函数对象。calculate_fun()函数与10.5节示例calculate()函数不同之处在于,calculate_fun()函数返回的是函数对象,calculate()函数返回的是整数(相加或相减计算之后的结果)。所以代码第④行的f1是add()函数对象,代码第⑤行的f2是sub()函数对象。

函数对象是可以与函数一样进行调用的。事实上在第⑥行之前没有真正调用add()函数进行相加计算,f1(10,

5)表达式才真的调用了add()函数。第⑦行的f2(10, 5)表达式是调用sub()函数。

Lambda表达式

理解了函数类型和函数对象,学习Lambda表达式就简单了。Lambda表达式本质上一种匿名函数,匿名函数也是函数有函数类型,也可以创建函数对象。

定义Lambda表达式语法如下:

lambda 参数列表 : Lambda体

lambda是关键字声明这是一个Lambda表达式,“参数列表”与函数的参数列表是一样的,但不需要小括号包裹起来,冒号后面是“Lambda体”,Lambda表达式主要的代码在此处编写,类似于函数体。

注意

Lambda体部分不能是一个代码块,不能包含多条语句,只有一条语句,语句会计算一个结果返回给Lambda表达式,但是与函数不同是,不需要使用return语句返回。与其他语言中的Lambda表达式相比,Python中提供Lambda表达式只能处理一些简单的计算。

重构10.7.1节示例,代码如下:

# coding=utf-8
# 代码文件:chapter10/ch10.7.2.py

def calculate_fun(opr):
    if opr == '+':
        return lambda a, b: (a + b) ①
    else:
        return lambda a, b: (a - b) ②

f1 = calculate_fun('+')
f2 = calculate_fun('-')

print(type(f1))

print("10 + 5 = {0}".format(f1(10, 5)))
print("10 - 5 = {0}".format(f2(10, 5)))

输出结果:

<class 'function'>
10 + 5 = 30
10 - 5 = 10

上述代码第①行替代了add()函数,第②行替代了sub()函数,代码变的非常的简单。

三大基础函数

函数式编程本质是通过函数处理数据,过滤、映射和聚合是处理数据的三大基本操作。针对但其中三大基本操作Python提供了三个基础的函数:filter()、map()和reduce()。

  1. filter()

过滤操作使用filter()函数,它可以对可迭代对象的元素进行过滤,filter()函数语法如下:

filter(function, iterable)

其中参数function是一个函数,参数iterable是可迭代对象。filter()函数调用时iterable会被遍历,它的元素逐一传入function函数,function函数返回布尔值,在function函数中编写过滤条件,如果为True的元素被保留,如果为False的元素被过滤掉。

下面通过一个示例介绍一下filter()函数使用,示例代码如下:

users = ['Tony', 'Tom', 'Ben', 'Alex']

users_filter = filter(lambda u: u.startswith('T'), users) ①
print(list(users_filter))

输出结果:

['Tony', 'Tom']

代码第①行调用filter()函数过滤users列表,过滤条件是T开通的元素,lambda u:

u.startswith(‘T’)是一个Lambda表达式,它提供了过滤条件。filter()函数还不是一个列表,需要使用list()函数转换过滤之后的数据为列表。

再看一个示例:

number_list = range(1, 11)
nmber_filter = filter(lambda it: it % 2 == 0, number_list)
print(list(nmber_filter))

该示例实现了获取1~10数字中的偶数,输出结果如下:

[2, 4, 6, 8, 10]
  1. map()

映射操作使用map()函数,它可以对可迭代对象的元素进行变换,map()函数语法如下:

map(function, iterable)

其中参数function是一个函数,参数iterable是可迭代对象。map()函数调用时iterable会被遍历,它的元素逐一传入function函数,在function函数中对元素进行变换。

下面通过一个示例介绍一下map()函数使用,示例代码如下:

users = ['Tony', 'Tom', 'Ben', 'Alex']

users_map = map(lambda u: u.lower(), users) ①  
print(list(users_map))

输出结果:

['tony', 'tom', 'ben', 'alex']

上述代码第①行调用map()函数将users列表元素转换为小写字母,变换使用Lambda表达式lambda

u:

u.lower()。map()函数返回的还不是一个列表,需要使用list()函数将过滤之后的数据转换为列表。

函数式编程时数据可以从一个函数“流”入另外一个函数,但是遗憾的是Python并不支持“链式”API。例如想获取users列表中“T”开通的名字,再将其转换为小写字母,这样的需求需要使用filter()函数进行过滤,再使用map()函数进行映射变换。实现代码如下:

users = ['Tony', 'Tom', 'Ben', 'Alex']

users_filter = filter(lambda u: u.startswith('T'), users)

# users_map = map(lambda u: u.lower(), users_filter) ①
users_map = map(lambda u: u.lower(), filter(lambda u: u.startswith('T'), users)) ②

print(list(users_map))

上述代码第①行和第②行实现相同功能。

  1. reduce()

聚合操作会将多个数据聚合起来输出单个数据,聚合操作中最基础的是归纳函数reduce(),reduce()函数会将多个数据按照指定的算法积累叠加起来,最后输出一个数据。

reduce()函数语法如下:

reduce(function, iterable[, initializer])

参数function是聚合操作函数,该函数有两个参数,参数iterable是可迭代对象;参数initializer初始值。

下面通过一个示例介绍一下reduce()函数使用。下面示例实现了对一个数列求和运算,代码如下:

from functools import reduce ①

a = (1, 2, 3, 4)
a_reduce = reduce(lambda acc, i: acc + i, a)  # 10 ②
# a_reduce = reduce(lambda acc, i: acc + i, a, 2)  # 12 ③
print(a_reduce)

reduce()函数是在functools模块中定义的,所以要使用reduce()函数需要导入functools模块,见代码第①行。代码第②行是调用reduce()函数,其中lambda

acc, i: acc +

i是进行聚合操作的Lambda表达式,该Lambda表达式有两个参数,其中acc参数是上次累积计算结果,i当前元素,acc

+

i表达式是进行累加。reduce()函数最后的计算结果是一个数值,直接可以使用通过reduce()函数返回。代码第行是传入了初始值2,则计算的结果是12。

本章小结

通过对本章内容的学习,读者可以熟悉在Python中如何定义函数、函数参数和函数返回值,了解函数变量作用域和嵌套函数。最后还介绍了Python中函数式编程基础。

配套视频

https://edu.csdn.net/combo/detail/822

配套源代码

http://www.zhijieketang.com/group/8

作者微博:@tony_关东升

邮箱:eorient@sina.com

智捷课堂微信公共号:zhijieketang

Python读者服务QQ群:628808216


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

New Dark Age

New Dark Age

James Bridle / Verso Books / 2018-7-17 / GBP 16.99

As the world around us increases in technological complexity, our understanding of it diminishes. Underlying this trend is a single idea: the belief that our existence is understandable through comput......一起来看看 《New Dark Age》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

MD5 加密
MD5 加密

MD5 加密工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具