2013-08-27 46 views
6
"{}, {}, {}".format(*(1,2,3,4,5)) 

Prints:Máy phát điện có thể được sử dụng với string.format trong python không?

'1, 2, 3' 

này hoạt động, miễn là số lượng {} trong format không vượt quá chiều dài của một tuple. Tôi muốn làm cho nó hoạt động cho một tuple chiều dài tùy ý, đệm nó với - s nếu nó không đủ chiều dài. Và để tránh đưa ra các giả định về số lượng của {}, tôi muốn sử dụng một máy phát điện. Đây là những gì tôi nghĩ trong đầu:

def tup(*args): 
    for s in itertools.chain(args, itertools.repeat('-')): 
     yield s 

print "{}, {}, {}".format(*tup(1,2)) 

dự kiến:

'1, 2, -' 

Nhưng nó không bao giờ trở lại. Bạn có thể làm cho nó hoạt động với máy phát điện không? Có cách tiếp cận tốt hơn không?

Trả lời

3

Nếu bạn nghĩ về nó, bên cạnh thực tế là đối số biến giải nén tất cả các gói cùng một lúc, cũng có một thực tế là format không nhất thiết phải lấy đối số của nó theo thứ tự, như trong '{2} {1} {0}'.

Bạn có thể làm việc xung quanh việc này nếu format chỉ cần thực hiện một chuỗi thay vì yêu cầu các đối số riêng biệt, bằng cách tạo một chuỗi làm điều đúng. Dưới đây là một ví dụ nhỏ:

class DefaultList(list): 
    def __getitem__(self, idx): 
     try: 
      return super(DefaultList, self).__getitem__(idx) 
     except IndexError: 
      return '-' 

Tất nhiên phiên bản đời thực của bạn sẽ quấn một iterable tùy ý, không phân lớp list, và có lẽ sẽ phải sử dụng tee hay một bộ nhớ cache nội bộ và kéo trong các giá trị mới theo yêu cầu, chỉ mặc định khi bạn đã kết thúc.(Bạn có thể muốn tìm kiếm công thức "lazy list" hoặc "lazy sequence" tại ActiveState, bởi vì có một vài trong số chúng làm điều này.) Nhưng điều này là đủ để hiển thị ví dụ.

Bây giờ, điều này giúp chúng tôi như thế nào? Nó không; *lst trên một số DefaultList sẽ chỉ cố gắng tạo một tuple trong số đó, cho chúng ta chính xác cùng một số đối số mà chúng tôi đã có. Nhưng nếu bạn có một phiên bản của format mà chỉ có thể mất một chuỗi các args thay vào đó? Sau đó, bạn chỉ có thể vượt qua DefaultList và nó sẽ hoạt động.

Và bạn có điều đó: Formatter.vformat.

>>> string.Formatter().vformat('{0} {1} {2}', DefaultList([0, 1]), {}) 
'0 1 -' 

Tuy nhiên, có một cách dễ dàng hơn, khi bạn đang sử dụng Formatter rõ ràng thay vì ngầm bằng phương pháp str. Bạn chỉ có thể ghi đè lên get_value phương pháp của nó và/hoặc check_unused_args của nó:

class DefaultFormatter(string.Formatter): 
    def __init__(self, default): 
     self.default = default 

    # Allow excess arguments 
    def check_unused_args(self, used_args, args, kwargs): 
     pass 

    # Fill in missing arguments 
    def get_value(self, key, args, kwargs): 
     try: 
      return super(DefaultFormatter, self).get_value(key, args, kwargs) 
     except IndexError: 
      return '-' 

f = DefaultFormatter('-') 

print(f.vformat('{0} {2}', [0], {})) 
print(f.vformat('{0} {2}', [0, 1, 2, 3], {})) 

Tất nhiên bạn vẫn sẽ cần phải quấn iterator của bạn trong một cái gì đó mà cung cấp giao thức Sequence.


Khi bạn ở đó, sự cố của bạn có thể được giải quyết trực tiếp hơn nếu ngôn ngữ có giao thức "giải nén có thể lặp lại". Xem here cho một chuỗi python-ideas đề xuất một thứ như vậy và tất cả các vấn đề mà ý tưởng có. (Cũng lưu ý rằng hàm format sẽ làm cho điều này phức tạp hơn, bởi vì nó sẽ phải sử dụng giao thức giải nén trực tiếp thay vì dựa vào trình thông dịch để làm điều đó một cách kỳ diệu. Nhưng, giả sử nó làm như vậy, thì bạn chỉ cần viết một trình bao bọc đơn giản và đa năng xung quanh bất kỳ vòng lặp nào có thể xử lý __unpack__ cho nó.)

4

Bạn không thể sử dụng máy phát vô tận để điền bất kỳ*args cuộc gọi đối số tùy ý nào.

Python lặp lại trên trình tạo để tải tất cả các đối số để chuyển sang cuộc gọi và nếu trình tạo là vô tận, điều đó sẽ không bao giờ hoàn thành.

Bạn có thể sử dụng các trình tạo không vô tận mà không gặp sự cố nào. Bạn có thể sử dụng itertools.islice() để chỏm một máy phát điện:

from itertools import islice 

print "{}, {}, {}".format(*islice(tup(1,2), 3)) 

Sau khi tất cả, bạn đã biết có bao nhiêu khe mẫu của bạn có.

+0

OK. Bạn có thể đề xuất một cách tiếp cận tốt hơn? Tôi không hài lòng với việc tạo ra một máy phát điện có độ dài tối đa, đó là lãng phí (đánh bại mục đích sử dụng một máy phát điện, một danh sách sẽ làm) và sẽ không được đảm bảo luôn hoạt động. – user443854

+0

@ user443854: Bạn có thể sử dụng 'itertools.islice()' để giới hạn một trình tạo. –

+0

Tôi biết 'itertools.islice()', nhưng tôi không thấy nó áp dụng ở đây như thế nào. Tôi cần phải biết số lượng các yếu tố cần thiết trước khi tôi có thể sử dụng nó. Tôi đã hy vọng đạt được điều gì đó khác biệt. Trong tiếng Anh đơn giản, tôi muốn nói với thông dịch viên: đây là một máy phát điện, lặp lại nó nhiều lần nếu cần, nhưng không nhiều hơn. – user443854

3

Martijn Pieters có câu trả lời ngay lập tức, nhưng nếu bạn muốn tạo một số loại trình bao bọc/trợ giúp chung cho việc tự động điền format, bạn có thể xem string.Formatter.parse. Bằng cách đó, bạn có thể có được một đại diện như thế nào format thấy chuỗi định dạng, và loại bỏ các đối số đếm/tên đối số tên để tự động tìm ra bao lâu iterator của bạn cần phải được.

1

Cách tiếp cận ngây thơ sẽ là cung cấp đối số L/2 cho hàm định dạng trong đó L là độ dài của chuỗi định dạng. Kể từ khi một mã thông báo thay thế có chiều dài ít nhất 2 ký tự, bạn chắc chắn sẽ luôn có đủ giá trị để giải nén:

def tup(l, *args): 
    for s in args + (('-',) * l): 
     yield s 
s = "{}, {}, {}" 
print s.format(*list(tup(len(s)//2, 1, 2))) 

Theo đề nghị của Silas Ray một tinh tế hơn trên ràng buộc có thể được tìm thấy bằng string.Formatter.parse

import string 
def tup(l, *args): 
    for s in args + (('-',) * l): 
     yield s 
s = "{}, {}, {}" 
l = len(list(string.Formatter().parse(s))) 
print s.format(*list(tup(l, 1, 2)))