使用 collections 模块扩展你的数据类

简介: 使用 collections 模块扩展你的数据类

楔子




Python 标准库提供了一个 collections 模块,里面提供了很多的数据类,在工作中使用这些类能够简化我们的开发。

下面就来看看这个模块能够帮助我们做哪些事情?


搜索多个字典




假设当前有 3 个字典:dct1、dct2、dct3,现在要通过 key 查找对应的 value。如果 key 在 dct1 里面存在,那么直接返回,否则从 dct2 里面找。dct2 里面如果不存在,那么从 dct3 里面找。

这个需求该怎么实现呢?

dct1 = {"a": 1, "b": 2, "c": 3}
dct2 = {"d": 4, "e": 5, "f": 6}
dct3 = {"e": 7, "f": 8, "g": 9}
def get_value_by_key(key):
    if key in dct1:
        return dct1[key]
    elif key in dct2:
        return dct2[key]
    elif key in dct3:
        return dct3[key]
    else:
        raise KeyError
print(get_value_by_key("b"))  # 2
print(get_value_by_key("d"))  # 4
print(get_value_by_key("f"))  # 6

实现起来非常简单,但通过 ChainMap 对象可以更方便地做到这一点。

from collections import ChainMap
dct1 = {"a": 1, "b": 2, "c": 3}
dct2 = {"b": 4, "c": 5, "d": 6}
# 将多个字典传进去,
dct = ChainMap(dct1, dct2)
# 如果多个字典存在相同的 key,那么返回第一次出现的 key 对应的 value
print(dct["b"], dct["d"])
"""
2 6
"""
# 字典的 API 都可以使用
print(dct.items())
"""
ItemsView(ChainMap({'a': 1, 'b': 2, 'c': 3}, {'b': 4, 'c': 5, 'd': 6}))
"""
# 也可以使用 get,如果 key 在所有的字典中都不存在
# 则返回默认值
print(dct.get("k", 333))
"""
333
"""
# ChainMap 对象有一个 maps 属性,存储了要搜索的映射列表
# 这个列表是可变的,所以可以直接增加新映射,或者改变元素的顺序以控制查找和更新行为。
print(dct.maps)
"""
[{'a': 1, 'b': 2, 'c': 3}, {'b': 4, 'c': 5, 'd': 6}]
"""
# dct.maps 保存了原始的字典,修改 dct.maps 会影响原字典
print(dct1)
"""
{'a': 1, 'b': 2, 'c': 3}
"""
dct.maps[0]["a"] = 11111111
print(dct1)
"""
{'a': 11111111, 'b': 2, 'c': 3}
"""
# 同理修改原字典,也会影响 dct.maps

以上就是 ChainMap 对象的用法,当你需要从多个字典中进行搜索的话,它会很有用。


统计可散列的对象




我们经常会遇到数量统计相关的问题,比如有一个序列,计算里面每个元素出现了多少次。一般情况下,我们会这么做。

words = ["hello", "world",
         "hello", "beautiful", "world",
         "hello", "cruel", "world"]
counter = {}
for word in words:
    if word in counter:
        counter[word] += 1
    else:
        counter[word] = 1
print(counter)
"""
{'hello': 3, 'world': 3, 'beautiful': 1, 'cruel': 1}
"""

实现方法没有任何问题,但通过 Counter 会更方便,并且还提供了更多功能。

from collections import Counter
words = ["hello", "world",
         "hello", "beautiful", "world",
         "hello", "cruel", "world"]
# 将序列传进去即可
counter = Counter(words)
print(counter)
"""
Counter({'hello': 3, 'world': 3, 'beautiful': 1, 'cruel': 1})
"""
# Counter 继承 dict,所以字典的 API 它也是都支持的
counter = Counter(hello=3, world=3, beautiful=1, cruel=1)
print(counter)
"""
Counter({'hello': 3, 'world': 3, 'beautiful': 1, 'cruel': 1})
"""

Counter 对象还支持动态更新操作,举个例子:

from collections import Counter
# Counter 需要接收一个可迭代对象,然后会遍历它
# 所以结果就是 a 出现了三次,b 出现了两次,c 出现了一次
counter = Counter("aaabbc")
print(counter)
"""
Counter({'a': 3, 'b': 2, 'c': 1})
"""
# 也可以动态更新,比如又来了一个序列
# 需要和当前的 counter 组合起来进行统计
counter.update("bcd")
print(counter)
"""
Counter({'a': 3, 'b': 3, 'c': 2, 'd': 1})
"""
# 可以看到 b 和 c 的值都增加了 1,并且出现了 d
# 需要注意的是:update 方法同样接收一个可迭代对象,然后进行遍历
# 如果我希望添加一个 key 叫 "bcd" 的话,那么要这么做
counter.update(["bcd"])
print(counter)
"""
Counter({'a': 3, 'b': 3, 'c': 2, 'd': 1, 'bcd': 1})
"""
# 访问计数,Counter 对象可以像字典一样访问
print(counter["a"])  
"""
3
"""
# 如果访问一个不存在的 key,不会引发 KeyError
# 而是会返回 0,表示对象中没有这个 key
print(counter["mmp"])  
"""
0
"""
# 还可以计算出现最多的元素,这是用的最频繁的一个功能
# 统计 string 中前三个出现次数最多的元素
string = "sasaxzsdsadfscxzcasdscxzdfscxsasadszczxczxcsds"
counter = Counter(string)
print(counter)
"""
Counter({'s': 13, 'c': 7, 'a': 6, 'x': 6, 'z': 6, 'd': 6, 'f': 2})
"""
print(counter.most_common(3))
"""
[('s', 13), ('c', 7), ('a', 6)]
"""

Counter 对象还有一个强大的功能,就是它支持算数操作以及位运算。

from collections import Counter
counter1 = Counter("aabbccc")
counter2 = Counter("bbbccdd")
print(counter1)  
print(counter2)  
"""
Counter({'a': 2, 'b': 2, 'c': 3})
Counter({'b': 3, 'c': 2, 'd': 2})
"""
# 如果 counter1 的元素出现在了 counter2 中
# 就把该元素减去,记住:减的是次数
print(counter1 - counter2)  
"""
Counter({'a': 2, 'c': 1})
"""
# a 在 counter1 中出现了 2 次,在 counter2 中没有出现,所以是 a: 2
# b 在 counter1 中出现了 2 次,在 counter2 中出现 3 次,所以一减就没有了
# c 在 counter1 中出现了 3 次,在 counter2 中出现 2 次,所以相减还剩下一次
# 至于 counter1 中没有的元素就不用管了
# 相加就很好理解了
print(counter1 + counter2) 
"""
Counter({'b': 5, 'c': 5, 'a': 2, 'd': 2}) 
"""
# 相交的话,查找公共的元素,并且取次数出现较小的那个
print(counter1 & counter2)  
"""
Counter({'b': 2, 'c': 2})
"""
# 并集的话,取较大的,记住不是相加
# 所以 b 和 c 出现的次数不会增加,只是取较大的那个
print(counter1 | counter2)  
"""
Counter({'b': 3, 'c': 3, 'a': 2, 'd': 2})
"""

以上就是 Counter 的用法,更多的还是统计次数,求 topK。


缺少的键返回默认值




很明显,这是针对于字典的。首先字典也支持这种操作,通过 setdefault 和 get 两个方法,可以用来获取 key 对应的 value,并且还能在 key 不存在的时候给一个默认值。

如果 key 存在,两者会获取 key 对应的 value;但如果 key 不存在,setdefault 会先将 key 和指定的默认值设置进去,然后再将设置的值返回,而 get 则只会返回默认值,不会进行设置。

d = {"a": 1}
# 如果 key 存在,直接返回 value
print(d.get("a", 0))  # 1
print(d.setdefault("a", 0))  # 1
# 原字典不受影响
print(d)  # {"a": 1}
# key 不存在,则返回指定的默认值
print(d.get("b", 0))  # 0
# 原字典不受影响
print(d)  # {"a": 1}
# key 不存在的话,会将 key 和默认值组成键值对,设置在字典中
print(d.setdefault("b", 0))  # 0
print(d)  # {"a": 1, "b": 0}

指的一提的是,setdefault 是一个非常实用且简洁的方法,但用的却不多。我们举一个例子:

data = [
    ("banana", 15), ("banana", 17), ("banana", 22),
    ("apple", 31), ("apple", 30), ("apple", 33),
    ("orange", 45), ("orange", 47), ("orange", 44),
]
# 如果我希望将 data 转成以下格式,该怎么办呢?
"""
{'banana': [15, 17, 22], 
 'apple': [31, 30, 33], 
 'orange': [45, 47, 44]}
"""
def change_data1():
    result = {}
    for product, count in data:
        if product not in result:
            result[product] = [count]
        else:
            result[product].append(count)
    return result
print(change_data1())
"""
{'banana': [15, 17, 22], 
 'apple': [31, 30, 33], 
 'orange': [45, 47, 44]}
"""
# 结果没问题,但如果用 setdefault 的话会更方便
def change_data2():
    result = {}
    for product, count in data:
        result.setdefault(product, []).append(count)
    return result
print(change_data2())
"""
{'banana': [15, 17, 22], 
 'apple': [31, 30, 33], 
 'orange': [45, 47, 44]}
"""

但这个功能也可以通过 defaultdict 完成,该类要求调用者传递一个类型,当 key 不存在时会返回对应类型的零值。

from collections import defaultdict
d = defaultdict(int)
print(d)  # defaultdict(<class 'int'>, {})
print(d["a"])  # 0
print(d)  # defaultdict(<class 'int'>, {'a': 0})
d = defaultdict(tuple)
print(d)  # defaultdict(<class 'tuple'>, {})
print(d["a"])  # ()
print(d)  # defaultdict(<class 'tuple'>, {'a': ()})
# 之前的例子就可以这么做
data = [
    ("banana", 15), ("banana", 17), ("banana", 22),
    ("apple", 31), ("apple", 30), ("apple", 33),
    ("orange", 45), ("orange", 47), ("orange", 44),
]
result = defaultdict(list)
for product, count in data:
    result[product].append(count)
# defaultdict 继承 dict,支持字典的 API
print(dict(result))
"""
{'banana': [15, 17, 22], 
 'apple': [31, 30, 33], 
 'orange': [45, 47, 44]}
"""
# 整个过程就是,key 如果存在,那么获取 value
# key 不存在,那么将指定类型的零值作为 value(这里是空列表)
# 并且返回之前,会先将 key、value 添加到键值对中
# 再比如之前的词频统计
string = "aabbccdddddee"
counter = defaultdict(int)
for c in string:
    counter[c] += 1
print(dict(counter))
"""
{'a': 2, 'b': 2, 'c': 2, 'd': 5, 'e': 2}
"""

么样,是不是很方便呢?在实例化 defaultdict 的时候,指定一个类型即可,获取一个不存在的 key 的时候,会返回指定的类型的零值,并且还会将 key 和零值添加到字典中。

此外 defaultdict 还可以自定义返回值,只需要指定一个不需要参数的函数即可。

from collections import defaultdict
# 此时的默认值就是 default
d = defaultdict(lambda: "default")
print(d["aa"])  # default
# 此外还可以添加参数,因为单独指定了 aa,所以打印的时候以指定的为准
# 如果没有指定,那么才会得到默认值
d4 = defaultdict(lambda: "default", aa="bar")
print(d4["aa"])  # bar
print(d4["bb"])  # default

那么估计会有人好奇,这是如何实现的呢?其实主要是实现了一个叫做 __missing__ 的魔法方法。字典在查找元素的时候,会调用 __getitem__,然后在找不到的时候会去调用 __missing__。但是注意:dict 这个类本身并没有实现 __missing__,所以我们需要继承自 dict,然后在子类中实现。

class MyDict(dict):
    def __getitem__(self, item):
        # 执行父类的 __getitem__ 方法
        # 如果 key 不存在,会去执行 __missing__ 方法
        value = super().__getitem__(item)
        # 所以这里的 value 就是 __missing__ 方法的返回值
        return value
    def __missing__(self, key):
        self[key] = "搞事情ヘ(´ー`ヘ)搞事情"
        return self[key]
d = MyDict([("a", 3), ("b", 4)])
print(d)  # {'a': 3, 'b': 4}
print(d["mmm"])  # 搞事情ヘ(´ー`ヘ)搞事情
print(d)  # {'a': 3, 'b': 4, 'mmm': '搞事情ヘ(´ー`ヘ)搞事情'}

都是一些基础的内容了,突然想到了,就提一遍。


双端队列




如果你需要维护一个序列,并根据需求动态地往序列的尾部添加元素和弹出元素,那么你会选择什么序列呢?很明显,如果只在尾部操作,那么列表一定是最合适的选择。

但如果我们操作的不止是尾部,还有头部呢?比如往序列的头部添加和弹出元素,此时双端队列就是一个不错的选择。

双端队列支持从任意一端添加和删除元素,更为常用的两种数据结构(即栈和队列)就是双端队列的退化形式,它们的输入和输出被限制在某一端。

from collections import deque
# 接收一个可迭代对象,然后进行遍历
d = deque("abcdefg")
print(d) 
"""
deque(['a', 'b', 'c', 'd', 'e', 'f', 'g'])
"""
print(len(d)) 
"""
7
"""
print(d[0]) 
"""
a
"""
print(d[-1])  
"""
g
"""
# 由于 deque 是一种序列容器,因此同样支持 list 的操作
# 如:通过索引获取元素,查看长度,删除元素,反转元素等等
# list 支持的操作,deque 基本上都支持
d.reverse()
print(d)  # deque(['g', 'f', 'e', 'd', 'c', 'b', 'a'])
d.remove("c")
print(d)  # deque(['g', 'f', 'e', 'd', 'b', 'a'])

deque 还有很多的 API,比如添加元素。

from collections import deque
d = deque("abc")
# 添加元素,可以从两端添加
d.append("Hello")  # 从尾部添加
d.appendleft("World")  # 也可以从头部添加
print(d)
"""
deque(['World', 'a', 'b', 'c', 'Hello'])
"""
# 还可以使用 insert, 如果范围越界,自动添加在两端
d.insert(100, "古明地觉")
print(d)
"""
deque(['World', 'a', 'b', 'c', 'Hello', '古明地觉'])
"""
# 也可以通过extend,extendleft 一次添加多个元素
d = deque([1, 2, 3])
d.extend([4, 5, 6])
print(d)
"""
deque([1, 2, 3, 4, 5, 6])
"""
d.extendleft([7, 8, 9])
print(d)
"""
deque([9, 8, 7, 1, 2, 3, 4, 5, 6])
"""
# 注意添加的顺序,我们是从左边开始添加的
# 先添加 7,然后 8 会跑到 7 的左边,所以是结果是倒过来的

再来看看删除元素:

from collections import deque
d = deque(range(1, 7))
print(d)
"""
deque([1, 2, 3, 4, 5, 6])
"""
# 调用 pop 方法可以从尾部弹出一个元素
print(d.pop())  # 6
print(d.pop())  # 5
print(d.pop())  # 4
# pop 是从右端删除一个元素,popleft 是从左端开始删除一个元素
# 但如果想 pop 掉指定索引的元素,则只能用 pop 函数,传入索引值即可
print(d.popleft())  # 1
print(d)
"""
deque([2, 3])
"""
# 注意:deque 和 queue一样,是线程安全的
# 它们均受 GIL 这把超级大锁保护,可以不同的线程中进行消费
# 如果想清空里面的元素,可以像 list、dict 一样,使用 clear 方法
d.clear()
print(d)  
"""
deque([])
"""

最后 deque 还有一个非常有意思的方法,叫 rotate,它是做什么的呢?来看一下。

from collections import deque
# 按任意一个方向进行旋转,从而跳过某些元素。
# d.rotate(n):n 大于0,从右边开始取 n 个元素放到左边
# n 小于 0,从左边取 n 个元素放到右边
d = deque(range(10))
print(d)  
"""
deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
"""
# 从右边取 2 个元素放到左边,所以 8 和 9 被放到了左边
d.rotate(2)
print(d)  
"""
deque([8, 9, 0, 1, 2, 3, 4, 5, 6, 7])
"""
d.rotate(-3)
# 从左边取 3 个元素放到右边,所以 8、9、0 被放到了右边
print(d)  
"""
deque([1, 2, 3, 4, 5, 6, 7, 8, 9, 0])
"""

当然双端队列默认是容量无限的,但很多时候我们需要给队列加上容量限制,如何加呢?

from collections import deque
# 限制队列的大小
# 我们在初始化一个双端队列的时候,还可以限制它的大小
d = deque("abcdefg", maxlen=5)
# 我们初始化 7 个元素,但是指定最大长度只有 5
# 所以前面两个元素("a" 和 "b")就被挤出去了
print(d)
"""
deque(['c', 'd', 'e', 'f', 'g'], maxlen=5)
"""
# 当我往前面添加元素的时候,后面的就被挤出去了
# 因为队列最多只能容纳 5 个元素
d.appendleft("Hello")
print(d)  
"""
deque(['Hello', 'c', 'd', 'e', 'f'], maxlen=5)
"""

当你要维护一个从首尾两端添加、删除元素的序列时,使用 deque 是一个非常正确的选择。

比如 asyncio 的锁 Lock,当获取锁时,就会创建一个 Future 对象,并保存在双端队列中。


带有名字的元组



元组的话,我们都是通过索引来获取元素,但通过索引的话,如果你不手动数一数,你是不知道该索引会对应哪一个元素的。所以问题来了,可不可以给里面的元素一个字段名呢?我们通过字段名来获取对应的值不就行啦,没错,这就是 namedtuple。

from collections import namedtuple
# 传入名字,和字段
Person = namedtuple("Person", ["name", "age", "gender"])
person1 = Person(name="satori", age=16, gender="f")
print(person1)
"""
Person(name='satori', age=16, gender='f')
"""
print(person1.name, person1.age, person1.gender)
"""
satori 16 f
"""
print(person1[0])
"""
satori
"""
# 不仅可以像普通的 tuple 一样使用索引访问
# 还可以像类一样通过 . 字段名访问
person2 = Person("satori", 16, "f")
# 注意:这个和普通的元组一样,是不可以修改的
try:
    person2.name = "xxx"
except AttributeError as e:
    print(e)  # can't set attribute
# 非法字段名,不能使用 Python 的关键字
try:
    namedtuple("keywords", ["for", "in"])
except ValueError as e:
    print(e)
    """
    Type names and field names cannot be a keyword: 'for'
    """
# 如果字段名重复了,会报错
try:
    namedtuple("Person", ["name", "age", "age"])
except ValueError as e:
    print(e)
    """
    Encountered duplicate field name: 'age'
    """
# 如果非要加上重名字段,可以设置一个参数
Person = namedtuple("Person", ["name", "age", "age"],
                    rename=True)
print(Person)  
"""
<class '__main__.Person'>
"""
person3 = Person("koishi", 15, 15)
# 可以看到重复的字段名会按照索引的值,在前面加上一个下划线
# 比如第二个 age 重复,它的索引是多少呢?是 2,所以默认帮我们把字段名修改为 _2
print(person3)  
"""
Person(name='koishi', age=15, _2=15)
"""
# 此外我们所有的字段名都保存在 _fields 属性中
print(person3._fields)  
"""
('name', 'age', '_2')
"""

但 namedtuple 还有一个不完美的地方,就是它无法指定字段的类型,所以我们更推荐使用 typing 模块里的 NamedTuple。

from typing import NamedTuple
class Person(NamedTuple):
    name: str
    age: int
    gender: str
p = Person("satori", 16, "f")
print(p)
"""
Person(name='satori', age=16, gender='f')
"""
# 同样能够基于索引和字段名来获取值
print(p[0], p.name)
"""
satori satori
"""
# 创建类的话,还可以这么创建
Person = NamedTuple('Person', name=str, age=int, gender=str)
Person = NamedTuple(
    'Person', [("name", str), ("age", int), ("gender", str)]
)

更建议使用 NamedTuple。


记住键值对顺序的字典



从 Python3.6 开始,字典遍历默认是有序的,但我们不应该依赖这个特性。如果希望字典有序,应该使用 OrderDict 字典子类。

from collections import OrderedDict
d = OrderedDict()
d["a"] = "A"
d["b"] = "B"
d["c"] = "C"
for k, v in d.items():
    print(k, v)
"""
a A
b B
c C
"""
# 此外也可以在初始化的时候,添加元素
print(OrderedDict({"a": 1}))  
"""
OrderedDict([('a', 1)])
"""
# 相等性,对于常规字典来说,只要里面元素一样便是相等的,不考虑顺序
# 但是对于OrderDict来说,除了元素,顺序也要一样,否则就不相等
d1 = {"a": 1, "b": 2}
d2 = {"b": 2, "a": 1}
print(d1 == d2)  
"""
True
"""
d1 = OrderedDict({"a": 1, "b": 2})
d2 = OrderedDict({"b": 2, "a": 1})
print(d1 == d2)  
"""
False
"""
# 重排,在 OrderDict 中可以使用 move_to_end 方法
# 将某个键移动到序列的起始位置或末尾位置来改变顺序
d3 = OrderedDict({"a": 1, "b": 2, "c": 3, "d": 4})
# 表示将 key="c" 的键值对移动到末尾
d3.move_to_end("c")  
print(d3)  
"""
OrderedDict([('a', 1), ('b', 2), ('d', 4), ('c', 3)])
"""
# 表示将 key="c" 的这个键值对移动到行首
d3.move_to_end("c", last=False)  
print(d3)  
"""
OrderedDict([('c', 3), ('a', 1), ('b', 2), ('d', 4)])
"""
# 从尾部弹出一个元素
print(d3.popitem())
"""
('d', 4)
"""
# 从头部弹出一个元素
print(d3.popitem(last=False))
"""
('c', 3)
"""

使用 OrderDict 要比 dict 更加耗费内存,因此在存储大量键值对的时候,思考一下,是否需要保证键值对有序。

但在实现 LRU 缓存的时候,OrderDict 非常常用,比如某个键被访问了,通过 move_to_end 移到头部。当缓存满了的时候,通过 popitem 弹出尾部元素。


小结




以上就是 collections 模块的用法,这个模块还是非常好用的,我们拿出来说一说。

相关文章
|
21天前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
17天前
|
机器学习/深度学习 算法 大数据
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
2024“华为杯”数学建模竞赛,对ABCDEF每个题进行详细的分析,涵盖风电场功率优化、WLAN网络吞吐量、磁性元件损耗建模、地理环境问题、高速公路应急车道启用和X射线脉冲星建模等多领域问题,解析了问题类型、专业和技能的需要。
2563 22
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
|
15天前
|
人工智能 IDE 程序员
期盼已久!通义灵码 AI 程序员开启邀测,全流程开发仅用几分钟
在云栖大会上,阿里云云原生应用平台负责人丁宇宣布,「通义灵码」完成全面升级,并正式发布 AI 程序员。
|
13天前
|
存储 关系型数据库 分布式数据库
GraphRAG:基于PolarDB+通义千问+LangChain的知识图谱+大模型最佳实践
本文介绍了如何使用PolarDB、通义千问和LangChain搭建GraphRAG系统,结合知识图谱和向量检索提升问答质量。通过实例展示了单独使用向量检索和图检索的局限性,并通过图+向量联合搜索增强了问答准确性。PolarDB支持AGE图引擎和pgvector插件,实现图数据和向量数据的统一存储与检索,提升了RAG系统的性能和效果。
|
17天前
|
机器学习/深度学习 算法 数据可视化
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
2024年中国研究生数学建模竞赛C题聚焦磁性元件磁芯损耗建模。题目背景介绍了电能变换技术的发展与应用,强调磁性元件在功率变换器中的重要性。磁芯损耗受多种因素影响,现有模型难以精确预测。题目要求通过数据分析建立高精度磁芯损耗模型。具体任务包括励磁波形分类、修正斯坦麦茨方程、分析影响因素、构建预测模型及优化设计条件。涉及数据预处理、特征提取、机器学习及优化算法等技术。适合电气、材料、计算机等多个专业学生参与。
1556 16
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
|
19天前
|
编解码 JSON 自然语言处理
通义千问重磅开源Qwen2.5,性能超越Llama
击败Meta,阿里Qwen2.5再登全球开源大模型王座
826 14
|
14天前
|
人工智能 开发框架 Java
重磅发布!AI 驱动的 Java 开发框架:Spring AI Alibaba
随着生成式 AI 的快速发展,基于 AI 开发框架构建 AI 应用的诉求迅速增长,涌现出了包括 LangChain、LlamaIndex 等开发框架,但大部分框架只提供了 Python 语言的实现。但这些开发框架对于国内习惯了 Spring 开发范式的 Java 开发者而言,并非十分友好和丝滑。因此,我们基于 Spring AI 发布并快速演进 Spring AI Alibaba,通过提供一种方便的 API 抽象,帮助 Java 开发者简化 AI 应用的开发。同时,提供了完整的开源配套,包括可观测、网关、消息队列、配置中心等。
621 7
|
7天前
|
Docker 容器
Docker操作 (五)
Docker操作 (五)
170 69
|
7天前
|
Docker 容器
Docker操作 (三)
Docker操作 (三)
167 69
|
19天前
|
人工智能 自动驾驶 机器人
吴泳铭:AI最大的想象力不在手机屏幕,而是改变物理世界
过去22个月,AI发展速度超过任何历史时期,但我们依然还处于AGI变革的早期。生成式AI最大的想象力,绝不是在手机屏幕上做一两个新的超级app,而是接管数字世界,改变物理世界。
628 52
吴泳铭:AI最大的想象力不在手机屏幕,而是改变物理世界