bir değişkenin yinelenebilir olduğunu ancak bir dizge olmadığını nasıl anlarsınız


92

Tek bir öğe veya çift öğe olabilen bir argüman alan bir fonksiyonum var:

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

Böylece:

>>> tekrarlanabilir (("f", "f"))
Evet

>>> tekrarlanabilir (["f", "f"])
Evet

>>> tekrarlanabilir ("ff")
Hayır

Sorun şu ki, dizge teknik olarak yinelenebilir, bu yüzden denerken sadece ValueError'ı yakalayamıyorum arg[1]. İsinstance () kullanmak istemiyorum çünkü bu iyi bir uygulama değil (ya da bana öyle söylendi).


1
Python'un hangi sürümü? Cevabın 2. * ve 3 arasında farklı olduğuna inanıyoruz
Kathy Van Stone

4
Yanlış söylendi, durum kötü bir uygulama değil.
Lennart Regebro

3
Oh, bekle, belki bir nesne türünü kontrol etmenin kötü olduğu ve bunun programın bozulduğunun bir göstergesi olduğu ilkesinden bahsediyor? Bu prensipte doğrudur (ama her zaman pratikte değil). Bu böyle bir durum olabilir veya olmayabilir. Ancak sorun olan şey işlev değil, türleri kontrol etme alışkanlığıdır.
Lennart Regebro

@Lennart: canonical.org/~kragen/isinstance olsa da modası geçmiş olabilir
priestc

@up Bu, tür tabanlı işlev aşırı yüklemesinden bahsetmez ve isinstancebunu dinamik olarak yazılmış dillerde yapmanın yoludur. Her gün kullanılmayacak bir şey, ancak haklı durumlarda sorun yok.
Kos

Yanıtlar:


50

Anlık kullanım (neden kötü bir uygulama olduğunu anlamıyorum)

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

StringTypes kullanımına dikkat edin. Bazı belirsiz türdeki dizeleri unutmamamızı sağlar.

Başta bu, türetilmiş dizge sınıfları için de çalışır.

class MyString(str):
    pass

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

Ayrıca, bu önceki soruya bir göz atmak isteyebilirsiniz .

Şerefe.


Not: Python 3'te davranış değişti StringTypesve basestringartık tanımlanmıyor. İhtiyaçlarınız bağlı olarak, sonrasında tekrar takabilmek isinstancetarafından strya da bir alt kümesi bir tanımlama grubu (str, bytes, unicode)Cython kullanıcıları için, örneğin. @Theron Luhn'un bahsettiği gibi , kullanabilirsiniz six.


Güzel, scvalex. Şimdi -1'imi kaldırıyorum ve +1 yapıyorum :-).
Tom

2
Bence kötü uygulama fikri ördek yazma prensibinden kaynaklanıyor. Belirli bir sınıfın üyesi olmak, kullanılabilecek tek nesne olduğu veya beklenen yöntemlerin mevcut olduğu anlamına gelmez . Ama bence bazen yöntemin mevcut olsa bile ne yaptığını anlayamıyorsunuz, bu yüzden isinstancetek yol bu olabilir.
estani

2
Not: types.StringTypes, Python 3'te mevcut değildir. Py3k'de sadece bir dize türü olduğundan, bunun güvenli olduğunu düşünüyorum do isinstance(arg, str). Geriye dönük uyumlu bir sürüm için pythonhosted.org/six/#six.string_types
Theron Luhn

Kesinlikle Python3 kullanıyorum ve Python3'te bulunmadığını fark ettim types.StringTypes. Python2'nin değeri nedir?
kevinarpe

2
2017 : Bu cevap artık geçerli değil, Python'un tüm sürümleriyle çalışan biri için stackoverflow.com/a/44328500/99834 sayfasına bakın .
sorin

26

2017 itibarıyla, işte Python'un tüm sürümleriyle çalışan taşınabilir bir çözüm:

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


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


# non-string iterables    
assert iterable(("f", "f"))    # tuple
assert iterable(["f", "f"])    # list
assert iterable(iter("ff"))    # iterator
assert iterable(range(44))     # generator
assert iterable(b"ff")         # bytes (Python 2 calls this a string)

# strings or non-iterables
assert not iterable(u"ff")     # string
assert not iterable(44)        # integer
assert not iterable(iterable)  # function

Bytestrings ile 2/3 arasında bazı küçük tutarsızlıklar var, ancak yerel "dizeyi" kullanırsanız ikisi de yanlıştır
Nick T

16

Python 2.6'dan beri, soyut temel sınıfların tanıtılmasıyla ( isinstancesomut sınıflarda değil ABC'lerde kullanılır) artık tamamen kabul edilebilir olarak kabul edilmektedir. Özellikle:

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

Bu, (sadece sınıf adını değiştirme) tam kopyası olan Iterabletanımlanan _abcoll.py(bir uygulama ayrıntı collections.py) ... sebebi bu eserleri istediğiniz gibi iken collections.Iterabledeğil, ikincisi dizeleri olduğundan emin olmak için ekstra mil gidiyor olmasıdır Iterable.register(str)Bu classifadeden hemen sonra açıkça çağrılarak yinelenebilir olarak kabul edilir .

Elbette , tanımınızdan özellikle hariç tutmak istediğiniz diğer sınıflar için çağrıdan önce __subclasshook__geri dönerek bunu artırmak kolaydır .Falseany

Her durumda, siz bu yeni modül aktardıktan sonra myiter, isinstance('ciao', myiter.NonStringIterable)olacak Falseve isinstance([1,2,3], myiter.NonStringIterable)olacak Truebir soyut temel sınıf tanımlamak ... ve Python 2.6 ve daha sonra bu somutlaştırmak tür kontroller için uygun şekilde kabul edilir - Eğer talep gibi, ve kontrol edin isinstance.


Python 3'te isinstance('spam', NonStringIterable)geri döner True.
Nick T

1
(...) ve Python 2.6 ve sonrasında bu, bu tür kontrolleri somutlaştırmanın uygun yolu olarak kabul edilir (...) İyi bilinen soyut sınıf kavramını bu şekilde kötüye kullanmanın doğru bir yol olarak düşünülebileceğini anlayamıyorum. Bunun doğru yolu, bunun yerine görünüşe benzer bir operatör tanıtmaktır .
Piotr Dobrogost

Alex, Nick'in bunun Python 3'te çalışmadığına dair iddiasını ele alabilir misin? Cevabı beğendim, ancak geleceğe yönelik kod yazdığımdan emin olmak istiyorum.
Merlyn Morgan-Graham

Çünkü doğru, MerlynMorgan-Graham, @ __iter__ olduğunu şimdi "kolay arttırdıklarında için" paragraf uygulanabilir ve örneğin olur Yani Python 3'te dizeleri uygulanan if issublass(cls, str): return Falsebaşlangıcında eklenecek ihtiyaçlarını __subclasshook__sıra tanımlayan başka sınıflar olarak ( __iter__ama senin zihniyet "dizge olmayan yinelemeler" olarak kabul edilmemelidir).
Alex Martelli

@AlexMartelli Python 3 if issublass(C, str): return Falseiçin eklenmesi gerektiğini söylemiyor musunuz ?
Rob Smallshire

4

Bunun eski bir gönderi olduğunun farkındayım, ancak İnternet gelecek nesillerine yaklaşımımı eklemeye değer olduğunu düşündüm. Aşağıdaki işlev hem Python 2 hem de 3 ile çoğu durumda benim için çalışıyor gibi görünüyor:

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

Bu , ikinci bağımsız değişkeni bir dizge veya unicode dizge olmadığında bir hasattryükseltecek yerleşik olanı kullanarak (mis) ile yinelenebilir TypeErrorolmayan bir dizge olup olmadığını kontrol eder.


3

Önceki yanıtları birleştirerek şunları kullanıyorum:

import types
import collections

#[...]

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

#[Do stuff...]

% 100 aptalca bir kanıt değil, ancak bir nesne yinelenebilir değilse, yine de geçmesine izin verebilir ve ördek yazmaya geri dönebilirsiniz.


Düzenleme: Python3

types.StringTypes == (str, unicode). Phython3 eşdeğeri:

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

İthalat
ifadeniz

3

2 kere

Önerirdim:

hasattr(x, '__iter__')

ya da David Charles'ın bunu Python3 için ince ayar yaptığı yorumu ışığında, peki ya:

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

3.x

yerleşik basestringsoyut tür kaldırıldı . strBunun yerine kullanın . strVe bytestürleri ortak temel sınıfa gerektirecek ortak noktası işlevi yeterince yok.


3
Belki de __iter__Python 3'te dizelerde olduğu için?
davidrmcharles

@DavidCharles Oh, gerçekten mi? Benim hatam. Ben bir Jython kullanıcısıyım ve Jython'un şu anda sürüm 3'ü yok.
mike rodent

Bu gerçekten bir cevap, daha çok bir yorum / soru değil ve 3.x için yanlış. Temizler misin? "Str" ve "bayt" türlerinin, paylaşılan bir temel sınıfı garanti edecek kadar ortak işlevselliğe sahip olmadığını iddia etmek için gerekçe ekleyebilir misiniz? 3.x'in kilit noktalarından biri, Unicode baytlarını birinci sınıf bir vatandaş yapmaktı.
smci

Yukarıdakilerden herhangi birini neden yazdığıma dair bir fikrim yok. Cevabımı zaten düzenlemenize rağmen "3.x" altındaki tüm metni silmeyi öneriyorum. İsterseniz daha fazla düzenleyin.
mike rodent

0

Doğru şekilde işaret ettiğiniz gibi, tek bir dizge bir karakter dizisidir.

Yani gerçekten yapmak istediğiniz şey arg, isinstance veya type (a) == str kullanarak ne tür bir dizi olduğunu bulmaktır .

Değişken miktarda parametre alan bir işlevi gerçekleştirmek istiyorsanız, bunu şu şekilde yapmalısınız:

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

işlev ("ff") ve işlev ("ff", "ff") çalışacaktır.

Sizinki gibi isiterable () bir fonksiyonun gerekli olduğu bir senaryo göremiyorum. Bu kötü stil olan isinstance () değil, isinstance () kullanmanız gereken durumlar.


4
Kullanmaktan type(a) == strkaçınılmalıdır. Bu kötü bir uygulamadır çünkü benzer türleri veya bunlardan türetilen türleri hesaba katmaz str. typetür hiyerarşisine tırmanmaz, oysa tırmanır isinstance, bu nedenle kullanmak daha iyidir isinstance.
AkiRoss

0

Alex Martelli'nin mükemmel hack'ini açıkça genişletmek collections.pyve etrafındaki bazı soruları ele almak için: python 3.6+ sürümündeki mevcut çalışma çözümü

import collections
import _collections_abc as cabc
import abc


class NonStringIterable(metaclass=abc.ABCMeta):

    __slots__ = ()

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

    @classmethod
    def __subclasshook__(cls, c):
        if cls is NonStringIterable:
            if issubclass(c, str):
                return False
            return cabc._check_methods(c, "__iter__")
        return NotImplemented

ve gösterdi

>>> typs = ['string', iter(''), list(), dict(), tuple(), set()]
>>> [isinstance(o, NonStringIterable) for o in typs]
[False, True, True, True, True, True]

iter('')Örneğin, istisnalara eklemek istiyorsanız , satırı değiştirin

            if issubclass(c, str):
                return False

olmak

            # `str_iterator` is just a shortcut for `type(iter(''))`*
            if issubclass(c, (str, cabc.str_iterator)):
                return False

almak

[False, False, True, True, True, True]
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.