2009-06-28 30 views
52

Tôi có một chức năng mà phải mất một cuộc tranh cãi đó có thể là một mục duy nhất hoặc một mục kép:thế nào để biết một biến là iterable nhưng không phải là một chuỗi

def iterable(arg) 
    if #arg is an iterable: 
     print "yes" 
    else: 
     print "no" 

để:

 
>>> iterable(("f","f")) 
yes 

>>> iterable(["f","f"]) 
yes 

>>> iterable("ff") 
no 

Vấn đề là chuỗi có thể lặp lại về mặt kỹ thuật, vì vậy tôi không thể bắt được ValueError khi thử arg[1]. Tôi không muốn sử dụng isinstance(), bởi vì đó không phải là thực hành tốt (hoặc vì vậy tôi nói).

+1

Phiên bản nào của Python? Tôi tin rằng câu trả lời là khác nhau giữa 2. * và 3 –

+0

Tôi đang sử dụng phiên bản 2.5 – priestc

+4

Bạn được cho biết không chính xác, việc khôi phục không phải là hành vi không tốt. –

Trả lời

34

Sử dụng isinstance (Tôi không hiểu tại sao nó xấu thực hành)

import types 
if not isinstance(arg, types.StringTypes): 

Lưu ý việc sử dụng StringTypes. Nó đảm bảo rằng chúng ta không quên về một số loại tối nghĩa của chuỗi.

Ở phía trên, điều này cũng hoạt động đối với các lớp chuỗi có nguồn gốc.

class MyString(str): 
    pass 

isinstance(MyString(" "), types.StringTypes) # true 

Ngoài ra, bạn có thể muốn xem điều này previous question.

Chúc mừng.


NB: hành vi thay đổi trong Python 3 như StringTypesbasestring không còn xác định. Tùy thuộc vào nhu cầu của bạn, bạn có thể thay thế chúng theo số isinstance theo str hoặc một bộ phụ gồm (str, bytes, unicode), ví dụ: cho người dùng Cython. Là @Theron Luhn được đề cập, bạn cũng có thể sử dụng six.

+0

Nice, scvalex. Tôi đang loại bỏ -1 của tôi bây giờ và làm cho nó một +1 :-). – Tom

+1

Tôi nghĩ rằng ý tưởng thực hành xấu là vì nguyên tắc [vịt gõ] (http://en.wikipedia.org/wiki/Duck_typing). Là một thành viên của một lớp cụ thể không có nghĩa là nó là đối tượng _only_ có thể được sử dụng cũng như các phương thức dự kiến ​​có sẵn. Nhưng tôi nghĩ đôi khi bạn không thể suy ra phương pháp này làm gì ngay cả khi nó có mặt, vì vậy 'isinstance' có thể là cách duy nhất. – estani

+3

-1, có điều kiện sẽ là 'True' ngay cả khi đầu vào là một số, hàm, lớp ... –

1

Khi bạn chỉ ra chính xác, một chuỗi đơn là chuỗi ký tự.

Vì vậy, điều bạn thực sự muốn làm là tìm ra loại chuỗi arg là sử dụng isinstance hoặc gõ (a) == str.

Nếu bạn muốn thực hiện một chức năng mà phải mất một số lượng biến các thông số, bạn nên làm điều đó như thế này:

def function(*args): 
    # args is a tuple 
    for arg in args: 
     do_something(arg) 

chức năng ("ff") và chức năng ("ff", "ff") sẽ làm việc.

Tôi không thể thấy kịch bản có chức năng() giống như của bạn là cần thiết. Nó không phải là isinstance() đó là phong cách xấu nhưng tình huống mà bạn cần sử dụng isinstance().

+1

Bạn nên tránh sử dụng 'loại (a) == str'. Đó là một thực hành tồi vì nó không tính đến các loại tương tự hoặc các kiểu bắt nguồn từ 'str'. 'type' không leo lên hệ thống phân cấp kiểu, trong khi' isinstance' làm, do đó tốt hơn là sử dụng 'isinstance'. – AkiRoss

16

Kể từ Python 2.6, với việc giới thiệu các lớp cơ sở trừu tượng, isinstance (được sử dụng trên ABC, không phải lớp bê tông) hiện được coi là hoàn toàn chấp nhận được. Cụ thể là:

from abc import ABCMeta, abstractmethod 

class NonStringIterable: 
    __metaclass__ = ABCMeta 

    @abstractmethod 
    def __iter__(self): 
     while False: 
      yield None 

    @classmethod 
    def __subclasshook__(cls, C): 
     if cls is NonStringIterable: 
      if any("__iter__" in B.__dict__ for B in C.__mro__): 
       return True 
     return NotImplemented 

Đây là một bản sao chính xác (chỉ thay đổi tên lớp) của Iterable theo quy định tại _abcoll.py (một chi tiết thi hành collections.py) ... lý do này làm việc như bạn muốn, trong khi collections.Iterable không , là sau này đi thêm dặm để đảm bảo chuỗi được coi là có thể lặp lại, bằng cách gọi Iterable.register(str) một cách rõ ràng ngay sau tuyên bố class này.

Tất nhiên thật dễ dàng để tăng thêm __subclasshook__ bằng cách trả lại False trước cuộc gọi any cho các lớp khác mà bạn muốn loại trừ cụ thể khỏi định nghĩa của mình.

Trong mọi trường hợp, sau khi bạn đã nhập khẩu mô-đun mới này như myiter, isinstance('ciao', myiter.NonStringIterable) sẽ Falseisinstance([1,2,3], myiter.NonStringIterable) sẽ True, cũng giống như bạn yêu cầu - và bằng Python 2.6 và sau này được coi là cách thích hợp để thể hiện như thế kiểm tra ... xác định một lớp cơ sở trừu tượng và kiểm tra isinstance trên đó.

+0

Trong Python 3 'isinstance ('spam', NonStringIterable)' trả về 'True'. –

+1

* (...) và trong Python 2.6 và sau này được coi là cách thích hợp để thể hiện kiểm tra như vậy (...) * Làm thế nào lạm dụng khái niệm nổi tiếng của lớp trừu tượng theo cách như vậy có thể được xem xét * cách thích hợp * vượt quá sự hiểu biết của tôi. Cách thích hợp sẽ là giới thiệu một số nhà điều hành * trông giống như * thay thế. –

+0

Alex, bạn có thể giải quyết xác nhận của Nick rằng điều này không hoạt động trong Python 3? Tôi thích câu trả lời, nhưng tôi muốn đảm bảo rằng tôi đang viết mã chứng minh trong tương lai. –

4

Tôi nhận thấy đây là một bài đăng cũ nhưng nghĩ rằng nó đáng để thêm phương pháp tiếp cận của tôi cho áp phích trên Internet. Hàm dưới đây dường như làm việc cho tôi trong hầu hết các tình huống với cả hai Python 2 và 3:

def is_collection(obj): 
    """ Returns true for any iterable which is not a string or byte sequence. 
    """ 
    try: 
     if isinstance(obj, unicode): 
      return False 
    except NameError: 
     pass 
    if isinstance(obj, bytes): 
     return False 
    try: 
     iter(obj) 
    except TypeError: 
     return False 
    try: 
     hasattr(None, obj) 
    except TypeError: 
     return True 
    return False 

kiểm tra này cho một tổ chức phi chuỗi iterable bởi (mis) sử dụng được xây dựng trong hasattr mà sẽ nâng cao một TypeError khi nó đối số thứ hai không phải là chuỗi hoặc chuỗi unicode.

3

Bằng cách kết hợp trả lời trước đó, tôi đang sử dụng:

import types 
import collections 

#[...] 

if isinstance(var, types.StringTypes) \ 
    or not isinstance(var, collections.Iterable): 

#[Do stuff...] 

Không phải 100% fools bằng chứng, nhưng nếu một đối tượng không phải là một iterable bạn vẫn có thể để cho nó trôi qua và rơi trở lại để gõ vịt.

+0

Câu lệnh nhập của bạn phải là 'loại' không 'loại' – PaulR

1

huh, không nhận được nó ... có gì sai với đi

hasattr(x, '__iter__') 

?

... NB elgehelge đặt này trong một chú thích ở đây, nói rằng "nhìn vào câu trả lời chi tiết hơn của tôi" nhưng tôi không thể tìm thấy/câu trả lời chi tiết của mình

sau

Trong xem bình luận của David Charles về Python3, về:

hasattr(x, '__iter__') and not isinstance(x, (str, bytes)) 

? Rõ ràng "basestring" không còn là một loại trong Python3:

https://docs.python.org/3.0/whatsnew/3.0.html

The builtin basestring abstract type was removed. Use str instead. The str and bytes types don’t have functionality enough in common to warrant a shared base class. 
+1

Có lẽ vì '__iter__' có trên các chuỗi trong Python 3? –

+0

@DavidCharles Oh, thật sao? Lỗi của tôi. Tôi là người dùng Jython và Jython hiện không có phiên bản 3. –

5

Tính đến năm 2017, đây là một giải pháp di động làm việc với tất cả các phiên bản của Python:

#!/usr/bin/env python 
import collections 
import six 


def iterable(arg): 
    return isinstance(arg, collections.Iterable) and not isinstance(arg, six.string_types) 


x = iterable(("f", "f")) and iterable(["f", "f"]) and not iterable("ff") 

print(x) 
Các vấn đề liên quan