Các trường hợp sử dụng cho phương thức chính tả 'setdefault'


192

Việc bổ sung collections.defaultdictbằng Python 2.5 sẽ giảm đáng kể nhu cầu dictcủa setdefaultphương pháp. Câu hỏi này là dành cho giáo dục tập thể của chúng tôi:

  1. Điều gì setdefaultvẫn còn hữu ích cho ngày hôm nay trong Python 2.6 / 2.7?
  2. Những trường hợp sử dụng phổ biến setdefaultđã được thay thế bằng collections.defaultdict?

Câu trả lời:


208

Bạn có thể nói defaultdictlà hữu ích cho các cài đặt mặc định trước khi điền dictsetdefaultrất hữu ích cho việc đặt mặc định trong khi hoặc sau khi điền dict .

Có lẽ là trường hợp sử dụng phổ biến nhất: Nhóm các mục (trong dữ liệu chưa được sắp xếp, sử dụng khác itertools.groupby)

# really verbose
new = {}
for (key, value) in data:
    if key in new:
        new[key].append( value )
    else:
        new[key] = [value]


# easy with setdefault
new = {}
for (key, value) in data:
    group = new.setdefault(key, []) # key might exist already
    group.append( value )


# even simpler with defaultdict 
from collections import defaultdict
new = defaultdict(list)
for (key, value) in data:
    new[key].append( value ) # all keys have a default already

Đôi khi bạn muốn đảm bảo rằng các khóa cụ thể tồn tại sau khi tạo ra một lệnh. defaultdictkhông hoạt động trong trường hợp này, vì nó chỉ tạo các khóa khi truy cập rõ ràng. Hãy nghĩ rằng bạn sử dụng một cái gì đó HTTP-ish với nhiều tiêu đề - một số là tùy chọn, nhưng bạn muốn mặc định cho chúng:

headers = parse_headers( msg ) # parse the message, get a dict
# now add all the optional headers
for headername, defaultvalue in optional_headers:
    headers.setdefault( headername, defaultvalue )

1
Thật vậy, IMHO này là trường hợp sử dụng chính để thay thế bởi defaultdict. Bạn có thể cho một ví dụ về ý nghĩa của bạn trong đoạn đầu tiên?
Eli Bendersky

2
Muhammad Alkarouri: Những gì bạn làm đầu tiên là sao chép chính tả sau đó ghi đè lên một số mục. Tôi cũng làm điều đó rất nhiều và tôi đoán đó thực sự là thành ngữ được ưa thích nhất setdefault. defaultdictMặt khác, A sẽ không hoạt động nếu không phải tất cả defaultvaluesđều bằng nhau (tức là một số 0và một số là []).
Jochen Ritzel

2
@ YHC4k, vâng. Đó là lý do tại sao tôi sử dụng headers = dict(optional_headers). Đối với trường hợp khi các giá trị mặc định không phải là tất cả bằng nhau. Và kết quả cuối cùng cũng giống như khi bạn nhận được các tiêu đề HTTP trước rồi đặt mặc định cho những người bạn không nhận được. Và nó khá hữu dụng nếu bạn đã có optional_headers. Hãy thử mã 2 bước đã cho của tôi và so sánh nó với mã của bạn và bạn sẽ thấy ý tôi là gì.
Muhammad Alkarouri

19
hoặc chỉ cần làmnew.setdefault(key, []).append(value)
fmalina

2
Tôi thấy thật kỳ lạ khi câu trả lời hay nhất sôi nổi defaultdictthậm chí còn tốt hơn setdefault(vậy trường hợp sử dụng bây giờ ở đâu?). Ngoài ra, ChainMapsẽ xử lý tốt hơn httpví dụ, IMO.
YvesgereY

29

Tôi thường sử dụng setdefaultcho các đối số từ khóa, như trong hàm này:

def notify(self, level, *pargs, **kwargs):
    kwargs.setdefault("persist", level >= DANGER)
    self.__defcon.set(level, **kwargs)
    try:
        kwargs.setdefault("name", self.client.player_entity().name)
    except pytibia.PlayerEntityNotFound:
        pass
    return _notify(level, *pargs, **kwargs)

Thật tuyệt vời khi điều chỉnh các đối số trong trình bao bọc xung quanh các hàm lấy đối số từ khóa.


16

defaultdict là tuyệt vời khi giá trị mặc định là tĩnh, giống như một danh sách mới, nhưng không quá nhiều nếu nó động.

Ví dụ, tôi cần một từ điển để ánh xạ các chuỗi thành các số nguyên duy nhất. defaultdict(int)sẽ luôn sử dụng 0 cho giá trị mặc định. Tương tự như vậy, defaultdict(intGen())luôn luôn tạo ra 1.

Thay vào đó, tôi đã sử dụng một lệnh chính quy:

nextID = intGen()
myDict = {}
for lots of complicated stuff:
    #stuff that generates unpredictable, possibly already seen str
    strID = myDict.setdefault(myStr, nextID())

Lưu ý rằng điều đó dict.get(key, nextID())là không đủ vì tôi cũng cần có thể tham khảo các giá trị này sau này.

intGen là một lớp nhỏ tôi xây dựng tự động tăng int và trả về giá trị của nó:

class intGen:
    def __init__(self):
        self.i = 0

    def __call__(self):
        self.i += 1
    return self.i

Nếu ai đó có cách để làm điều này với defaultdicttôi muốn thấy nó.


để biết cách thực hiện với (một lớp con) defaultdict, hãy xem câu hỏi này: stackoverflow.com/questions/2912231/ chủ
weronika

8
Bạn có thể thay thế intGenbằng itertools.count().next.
Antimon

7
nextID()Giá trị của nó sẽ được tăng lên mỗi khi myDict.setdefault()được gọi, ngay cả khi giá trị mà nó trả về không được sử dụng như một strID. Điều này có vẻ lãng phí bằng cách nào đó và minh họa một trong những điều tôi không thích setdefault()nói chung - cụ thể là nó luôn đánh giá defaultđối số của nó cho dù nó có thực sự được sử dụng hay không.
martineau

Bạn có thể làm điều đó với defaultdict: myDict = defaultdict(lambda: nextID()). Sau đó, strID = myDict[myStr]trong vòng lặp.
musiphil

3
Để có được hành vi mà bạn mô tả với defaultdict, tại sao không chỉ myDict = defaultdict(nextID)?
bốn mươi phút

10

Tôi sử dụng setdefault()khi tôi muốn một giá trị mặc định trong một OrderedDict. Không có một bộ sưu tập Python tiêu chuẩn nào thực hiện cả hai, nhưng có nhiều cách để thực hiện một bộ sưu tập như vậy.


9

Như hầu hết các câu trả lời trạng thái setdefaulthoặc defaultdictsẽ cho phép bạn đặt giá trị mặc định khi khóa không tồn tại. Tuy nhiên, tôi muốn chỉ ra một cảnh báo nhỏ liên quan đến các trường hợp sử dụng setdefault. Khi trình thông dịch Python thực thi, setdefaultnó sẽ luôn đánh giá đối số thứ hai cho hàm ngay cả khi khóa tồn tại trong từ điển. Ví dụ:

In: d = {1:5, 2:6}

In: d
Out: {1: 5, 2: 6}

In: d.setdefault(2, 0)
Out: 6

In: d.setdefault(2, print('test'))
test
Out: 6

Như bạn có thể thấy, printcũng đã được thực hiện mặc dù 2 đã tồn tại trong từ điển. Điều này trở nên đặc biệt quan trọng nếu bạn dự định sử dụng setdefaultví dụ để tối ưu hóa như thế nào memoization. Nếu bạn thêm một hàm gọi đệ quy làm đối số thứ hai vàosetdefault , bạn sẽ không nhận được bất kỳ hiệu suất nào từ nó vì Python sẽ luôn gọi hàm đệ quy.

Kể từ khi ghi nhớ đã được đề cập, một cách khác tốt hơn là sử dụng trang trí funcools.lru_cache nếu bạn xem xét việc tăng cường chức năng với ghi nhớ. lru_cache xử lý các yêu cầu bộ đệm cho chức năng đệ quy tốt hơn.


8

Như Muhammad đã nói, có những tình huống đôi khi bạn chỉ muốn đặt giá trị mặc định. Một ví dụ tuyệt vời về điều này là một cấu trúc dữ liệu được đưa vào đầu tiên, sau đó được truy vấn.

Hãy xem xét một trie. Khi thêm một từ, nếu cần một mã con nhưng không có mặt, nó phải được tạo để mở rộng trie. Khi truy vấn sự hiện diện của một từ, một mã con bị thiếu chỉ ra rằng từ đó không có mặt và nó không nên được tạo ra.

Một defaultdict không thể làm điều này. Thay vào đó, một lệnh chính quy thông thường với các phương thức get và setdefault phải được sử dụng.


5

Về mặt lý thuyết, setdefaultvẫn sẽ hữu ích nếu đôi khi bạn muốn đặt mặc định và đôi khi không. Trong cuộc sống thực, tôi đã không gặp phải trường hợp sử dụng như vậy.

Tuy nhiên, trường hợp sử dụng thú vị xuất phát từ thư viện chuẩn (Python 2.6, _threadinglocal.py):

>>> mydata = local()
>>> mydata.__dict__
{'number': 42}
>>> mydata.__dict__.setdefault('widgets', [])
[]
>>> mydata.widgets
[]

Tôi sẽ nói rằng sử dụng __dict__.setdefaultlà một trường hợp khá hữu ích.

Chỉnh sửa : Như nó xảy ra, đây là ví dụ duy nhất trong thư viện tiêu chuẩn và nó là trong một bình luận. Vì vậy, có thể không đủ trường hợp để biện minh cho sự tồn tại củasetdefault . Tuy nhiên, đây là một lời giải thích:

Các đối tượng lưu trữ các thuộc tính của chúng trong __dict__thuộc tính. Khi nó xảy ra, __dict__thuộc tính có thể ghi bất cứ lúc nào sau khi tạo đối tượng. Nó cũng là một cuốn từ điển không a defaultdict. Không thể cảm nhận được các đối tượng trong trường hợp chung __dict__defaultdictvì điều đó sẽ làm cho mỗi đối tượng có tất cả các định danh pháp lý làm thuộc tính. Vì vậy, tôi không thể thấy trước bất kỳ thay đổi nào đối với các đối tượng Python bị loại bỏ __dict__.setdefault, ngoài việc xóa nó hoàn toàn nếu nó được coi là không hữu ích.


1
Bạn có thể giải thích - điều gì làm cho _dict .setdefault đặc biệt hữu ích?
Eli Bendersky

1
@Eli: Tôi nghĩ vấn đề __dict__là bằng cách thực hiện a dict, không phải a defaultdict.
Katriel

1
Ổn thỏa. Tôi không bận tâm về việc setdefaultở lại Python, nhưng thật tò mò khi thấy rằng giờ nó gần như vô dụng.
Eli Bendersky

@Eli: Tôi đồng ý. Tôi không nghĩ có đủ lý do để nó được giới thiệu ngày hôm nay nếu nó không có ở đó. Nhưng ở đó đã có, sẽ rất khó để tranh luận về việc loại bỏ nó, với tất cả các mã sử dụng nó.
Muhammad Alkarouri

1
Tập tin theo chương trình phòng thủ. setdefaultlàm rõ rằng bạn đang gán cho một dict thông qua một khóa có thể tồn tại hoặc không tồn tại và nếu nó không tồn tại, bạn muốn nó được tạo với một giá trị mặc định: ví dụ d.setdefault(key,[]).append(value). Ở những nơi khác trong chương trình bạn thực hiện alist=d[k]khi k được tính và bạn muốn ném ngoại lệ nếu k không ở d (mà với một defaultdict có thể yêu cầu assert k in dhoặc thậm chíif not ( k in d): raise KeyError
nigel222

3

Một nhược điểm của defaultdictover dict( dict.setdefault) là một defaultdictđối tượng tạo ra một mục mới MỌI khóa không tồn tại được cung cấp (ví dụ với ==, print). Ngoài ra, defaultdictlớp học thường ít phổ biến hơndict lớp, khó khăn hơn để tuần tự hóa nó IME.

Các hàm PS IMO | các phương thức không có nghĩa là làm biến đổi một đối tượng, không nên làm biến đổi một đối tượng.


Nó không phải tạo một đối tượng mới mỗi lần. Bạn có thể dễ dàng làm defaultdict(lambda l=[]: l)thay thế.
Artyer

6
Không bao giờ làm những gì @Artyer gợi ý - mặc định có thể thay đổi sẽ cắn bạn.
Brandon Humpert

2

Dưới đây là một số ví dụ về setdefault để thể hiện tính hữu dụng của nó:

"""
d = {}
# To add a key->value pair, do the following:
d.setdefault(key, []).append(value)

# To retrieve a list of the values for a key
list_of_values = d[key]

# To remove a key->value pair is still easy, if
# you don't mind leaving empty lists behind when
# the last value for a given key is removed:
d[key].remove(value)

# Despite the empty lists, it's still possible to 
# test for the existance of values easily:
if d.has_key(key) and d[key]:
    pass # d has some values for key

# Note: Each value can exist multiple times!
"""
e = {}
print e
e.setdefault('Cars', []).append('Toyota')
print e
e.setdefault('Motorcycles', []).append('Yamaha')
print e
e.setdefault('Airplanes', []).append('Boeing')
print e
e.setdefault('Cars', []).append('Honda')
print e
e.setdefault('Cars', []).append('BMW')
print e
e.setdefault('Cars', []).append('Toyota')
print e

# NOTE: now e['Cars'] == ['Toyota', 'Honda', 'BMW', 'Toyota']
e['Cars'].remove('Toyota')
print e
# NOTE: it's still true that ('Toyota' in e['Cars'])

2

Tôi viết lại câu trả lời được chấp nhận và tạo điều kiện cho người mới.

#break it down and understand it intuitively.
new = {}
for (key, value) in data:
    if key not in new:
        new[key] = [] # this is core of setdefault equals to new.setdefault(key, [])
        new[key].append(value)
    else:
        new[key].append(value)


# easy with setdefault
new = {}
for (key, value) in data:
    group = new.setdefault(key, []) # it is new[key] = []
    group.append(value)



# even simpler with defaultdict
new = defaultdict(list)
for (key, value) in data:
    new[key].append(value) # all keys have a default value of empty list []

Ngoài ra, tôi phân loại các phương pháp như tham khảo:

dict_methods_11 = {
            'views':['keys', 'values', 'items'],
            'add':['update','setdefault'],
            'remove':['pop', 'popitem','clear'],
            'retrieve':['get',],
            'copy':['copy','fromkeys'],}

1

Tôi sử dụng setdefault thường xuyên khi, lấy cái này, đặt mặc định (!!!) trong từ điển; phần nào thông thường là từ điển os.envir:

# Set the venv dir if it isn't already overridden:
os.environ.setdefault('VENV_DIR', '/my/default/path')

Ít gọn gàng hơn, nó trông như thế này:

# Set the venv dir if it isn't already overridden:
if 'VENV_DIR' not in os.environ:
    os.environ['VENV_DIR'] = '/my/default/path')

Điều đáng chú ý là bạn cũng có thể sử dụng biến kết quả:

venv_dir = os.environ.setdefault('VENV_DIR', '/my/default/path')

Nhưng điều đó ít cần thiết hơn so với trước khi defaultdicts tồn tại.


1

Một trường hợp sử dụng khác mà tôi không nghĩ đã được đề cập ở trên. Đôi khi bạn giữ một bộ đệm bộ nhớ cache của các đối tượng theo id của chúng trong đó phiên bản chính nằm trong bộ đệm và bạn muốn đặt bộ đệm khi bị thiếu.

return self.objects_by_id.setdefault(obj.id, obj)

Điều đó hữu ích khi bạn luôn muốn giữ một cá thể cho mỗi id riêng biệt cho dù bạn có nhận được obj mỗi lần như thế nào. Ví dụ: khi các thuộc tính đối tượng được cập nhật trong bộ nhớ và lưu vào bộ nhớ được hoãn lại.


1

Một trường hợp sử dụng rất quan trọng tôi vừa tình cờ thấy: dict.setdefault() rất tốt cho mã đa luồng khi bạn chỉ muốn một đối tượng chính tắc duy nhất (trái ngược với nhiều đối tượng xảy ra bằng nhau).

Ví dụ, (Int)FlagEnum trong Python 3.6.0 có một lỗi : nếu nhiều luồng đang cạnh tranh cho một thành (Int)Flagviên hỗn hợp , cuối cùng có thể có nhiều hơn một:

from enum import IntFlag, auto
import threading

class TestFlag(IntFlag):
    one = auto()
    two = auto()
    three = auto()
    four = auto()
    five = auto()
    six = auto()
    seven = auto()
    eight = auto()

    def __eq__(self, other):
        return self is other

    def __hash__(self):
        return hash(self.value)

seen = set()

class cycle_enum(threading.Thread):
    def run(self):
        for i in range(256):
            seen.add(TestFlag(i))

threads = []
for i in range(8):
    threads.append(cycle_enum())

for t in threads:
    t.start()

for t in threads:
    t.join()

len(seen)
# 272  (should be 256)

Giải pháp là sử dụng setdefault()như bước cuối cùng để lưu thành viên tổng hợp được tính toán - nếu một thành viên khác đã được lưu thì nó được sử dụng thay cho thành phần mới, đảm bảo các thành viên Enum duy nhất.


0

[Chỉnh sửa] Rất sai!Setdefault sẽ luôn kích hoạt long_computing, Python háo hức.

Mở rộng về câu trả lời của Típ. Đối với tôi trường hợp sử dụng tốt nhất là cơ chế bộ nhớ cache. Thay vì:

if x not in memo:
   memo[x]=long_computation(x)
return memo[x]

Tiêu thụ 3 dòng và 2 hoặc 3 tra cứu, tôi sẽ vui vẻ viết :

return memo.setdefault(x, long_computation(x))

Ví dụ tốt. Tôi vẫn nghĩ 3 dòng này dễ hiểu hơn, nhưng có lẽ bộ não của tôi sẽ phát triển để đánh giá cao setdefault.
Bob Stein

5
Những cái đó không tương đương. Trong đầu tiên, long_computation(x)chỉ được gọi nếu x not in memo. Trong khi đó trong lần thứ hai, long_computation(x)luôn luôn được gọi. Chỉ có nhiệm vụ là có điều kiện, mã tương đương setdefaultsẽ trông như sau: v = long_computation(x)/ if x not in memo:/ memo[x] = v.
Dan D.


0

Trường hợp sử dụng khác nhau setdefault()khi bạn không muốn ghi đè giá trị của khóa đã được đặt. defaultdictghi đè, trong khi setdefault()không. Đối với các từ điển lồng nhau, thông thường bạn chỉ muốn đặt mặc định nếu khóa chưa được đặt, vì bạn không muốn xóa từ điển phụ hiện tại. Đây là khi bạn sử dụngsetdefault() .

Ví dụ với defaultdict:

>>> from collection import defaultdict()
>>> foo = defaultdict()
>>> foo['a'] = 4
>>> foo['a'] = 2
>>> print(foo)
defaultdict(None, {'a': 2})

setdefault không ghi đè:

>>> bar = dict()
>>> bar.setdefault('a', 4)
>>> bar.setdefault('a', 2)
>>> print(bar)
{'a': 4}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.