Python'da bir nesnenin bayt benzeri bir nesne olup olmadığını belirlemenin doğru yolu nedir?


94

Bekleyen strancak bytesaşağıdaki şekilde iletilme durumunu ele alacak bir kodum var :

if isinstance(data, bytes):
    data = data.decode()

Maalesef bu durumda çalışmıyor bytearray. Teste daha genel bir yolu bir nesnedir ya olmadığına var mı bytesyoksa bytearray, yoksa sadece ikisi için kontrol etmelisiniz? Olacağını hasattr('decode')hissettiğim kadar kötü mü ?


6
Şahsen ben python'un ördek yazmasını en az bir sonraki adam kadar seviyorum. Ancak, girdi argümanlarınızı kontrol etmeniz ve farklı türlere zorlamanız gerekiyorsa, artık yazmaktan kaçmazsınız - Kodunuzu bir bakımı okumayı daha zor hale getiriyorsunuz. Buradaki önerim (ve diğerleri aynı fikirde olmayabilir) (tip zorlamasını ele alan ve bir temel uygulamaya delege eden) birden fazla işlev yapmak olacaktır.
mgilson

(1) Eski Python 2 koduyla uyumluluk için ihtiyacınız olmadığı sürece; hem metin hem de ikili verileri aynı anda kabul etmekten kaçının. İşleviniz metinle çalışıyorsa yalnızca kabul etmesi gerekir str. Diğer bazı kodlar, girişte mümkün olan en kısa sürede bayttan Unicode'a dönüştürmelidir. (2) "bayt benzeri" Python'da özel bir anlama sahiptir (arabellek protokolünü destekleyen nesneler (yalnızca C))
jfs

Asıl sorun, bu işlevin basit bir ASCII dizesinin <bytes> testini geçtiği Python 2'de çalışmamasıdır!
Apostolos

Yanıtlar:


76

Burada kullanabileceğiniz birkaç yaklaşım var.

Ördek yazarak

Python ördek yazılmış olduğundan , basitçe aşağıdaki işlemleri yapabilirsiniz (bu, genellikle önerilen yol gibi görünüyor):

try:
    data = data.decode()
except (UnicodeDecodeError, AttributeError):
    pass

hasattrAncak tarif ettiğiniz gibi kullanabilirsiniz ve muhtemelen iyi olur. Bu, elbette, .decode()verilen nesnenin yönteminin bir dizge döndürdüğünü ve kötü yan etkilerinin olmadığını varsaymaktır .

Kişisel olarak ya istisnayı ya da hasattryöntemi tavsiye ederim , ancak ne kullanırsanız kullanın size kalmış.

Str () kullanın

Bu yaklaşım yaygın değildir, ancak mümkündür:

data = str(data, "utf-8")

Tampon protokolünde olduğu gibi diğer kodlamalara da izin verilir .decode(). Hata işlemeyi belirtmek için üçüncü bir parametre de iletebilirsiniz.

Tek gönderimli genel işlevler (Python 3.4+)

Python 3.4 ve üzeri, functools.singledispatch aracılığıyla tek gönderimli jenerik işlevler adı verilen şık bir özellik içerir. . Bu biraz daha ayrıntılı, ama aynı zamanda daha açık:

def func(data):
    # This is the generic implementation
    data = data.decode()
    ...

@func.register(str)
def _(data):
    # data will already be a string
    ...

Ayrıca özel işleyiciler de yapabilirsiniz. bytearray ve bytesnesneler .

Dikkat : tekli gönderim işlevleri yalnızca ilk argümanda çalışır! Bu bilinçli bir özelliktir, bkz. PEP 433 .


Sağlanan standart kitaplığı tamamen unuttuğum tek gönderim jeneriklerinden bahsedilmesi için +1.
A. Wilcox

Str on str çağırmak hiçbir şey yapmadığından ve bana en açık gibi göründüğünden, onunla gittim.
A. Wilcox

Genel hasattrolarak, kod çözme işlevindeki bazı hataları yanlışlıkla yutmanızı önlemek için try / hariç'ten daha fazlasını seviyorum , ancak +1.
keredson

39

Kullanabilirsiniz:

isinstance(data, (bytes, bytearray))

Farklı temel sınıflardan dolayı burada kullanılmaktadır.

>>> bytes.__base__
<type 'basestring'>
>>> bytearray.__base__
<type 'object'>

Kontrol etmek bytes

>>> by = bytes()
>>> isinstance(by, basestring)
True

Ancak,

>>> buf = bytearray()
>>> isinstance(buf, basestring)
False

Yukarıdaki kodlar python 2.7 altında test edilmiştir.

Ne yazık ki, python 3.4 altında bunlar aynı ...

>>> bytes.__base__
<class 'object'>
>>> bytearray.__base__
<class 'object'>

1
six.string_types 2/3 uyumlu olmalıdır.
Joshua Olson

Bu tür bir kontrol, basit bir ASCII dizesinin <bytes> testini geçtiği Python 2'de çalışmaz!
Apostolos

13
>>> content = b"hello"
>>> text = "hello"
>>> type(content)
<class 'bytes'>
>>> type(text)
<class 'str'>
>>> type(text) is str
True
>>> type(content) is bytes
True

Bunun Python 2'de bir string nesnesinin bayt olarak da geçtiği güvenilir bir test olmadığını unutmayın ! Yani, yukarıdaki koda göre, type(text) is bytesDoğru olacaktır!
Apostolos

11

Bilmediğimiz bir şeyi bilmediğiniz sürece bu kod doğru değildir:

if isinstance(data, bytes):
    data = data.decode()

Kodlamasını bilmiyorsunuz (görünüyorsunuz) data. UTF-8 olduğunu varsayıyorsunuz , ancak bu çok yanlış olabilir. Kodlamayı bilmediğiniz için metniniz de yoktur . Güneşin altında herhangi bir anlamı olabilecek baytlarınız var.

İyi haber şu ki, rastgele bayt dizilerinin çoğu geçerli UTF-8 değildir, bu nedenle bu kesildiğinde errors='strict', sessizce yanlış bir şey yapmak yerine yüksek sesle kırılacaktır ( varsayılandır). Daha da iyi haber şu ki, geçerli UTF-8 olan bu rastgele dizilerin çoğunun aynı zamanda geçerli ASCII olması ( neredeyse ) herkesin nasıl ayrıştırılacağını kabul .

Kötü haber şu ki, bunu düzeltmenin makul bir yolu yok. Kodlama bilgisi sağlamanın standart bir yolu vardır: stryerine kullanın bytes. Bazı üçüncü taraf kodu size daha fazla bağlam veya bilgi olmadan bir nesne bytesveya bytearraynesne verdiyse, tek doğru eylem başarısız olmaktır.


Şimdi, kodlamayı bildiğinizi varsayarsak, functools.singledispatchburada kullanabilirsiniz :

@functools.singledispatch
def foo(data, other_arguments, ...):
    raise TypeError('Unknown type: '+repr(type(data)))

@foo.register(str)
def _(data, other_arguments, ...):
    # data is a str

@foo.register(bytes)
@foo.register(bytearray)
def _(data, other_arguments, ...):
    data = data.decode('encoding')
    # explicit is better than implicit; don't leave the encoding out for UTF-8
    return foo(data, other_arguments, ...)

Bu yöntemler üzerinde çalışmaz datave ilk argüman olmalıdır. Bu kısıtlamalar sizin için işe yaramazsa, bunun yerine diğer yanıtlardan birini kullanın.


Yazdığım kütüphanede, bu özel yöntem için, aldığım baytların ve / veya baytların UTF-8 kodlu olduğunu kesinlikle biliyorum.
A. Wilcox

1
@AndrewWilcox: Yeterince adil, ancak bu bilgiyi gelecekteki Google trafiği için bırakıyorum.
Kevin

4

Ne çözmek istediğinize bağlı. Her iki durumu da bir dizeye dönüştüren aynı koda sahip olmak istiyorsanız, türü önce bytesilke dönüştürebilir ve ardından kodunu çözebilirsiniz. Bu şekilde, tek satırlık:

#!python3

b1 = b'123456'
b2 = bytearray(b'123456')

print(type(b1))
print(type(b2))

s1 = bytes(b1).decode('utf-8')
s2 = bytes(b2).decode('utf-8')

print(s1)
print(s2)

Bu şekilde sizin için cevap şu olabilir:

data = bytes(data).decode()

Her neyse, 'utf-8'birkaç bayt ayırmak istemiyorsanız, kod çözücüye açıkça yazmanızı öneririm . Bunun nedeni, siz veya bir başkası kaynak kodunu bir dahaki sefere okuduğunda, durumun daha belirgin hale gelmesidir.


3

Burada iki soru var ve cevapları farklı.

İlk soru, bu yazının başlığı, Python'da bir nesnenin bayt benzeri bir nesne olup olmadığını belirlemenin doğru yolu nedir? Bu tipler, yerleşik bir dizi içerir ( bytes, bytearray, array.array, memoryview, diğerleri?) Ve muhtemelen aynı zamanda kullanıcı tanımlı türleri. Bunları kontrol etmenin bildiğim en iyi yolu memoryview, onlardan bir tane oluşturmaya çalışmaktır :

>>> memoryview(b"foo")
<memory at 0x7f7c43a70888>
>>> memoryview(u"foo")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: memoryview: a bytes-like object is required, not 'str'

Orijinal gönderinin gövdesinde bunun yerine soru soruluyor gibi geliyor Bir nesnenin decode () 'u destekleyip desteklemediğini nasıl test edebilirim? @ elizabeth-myers'ın bu soruya yukarıdaki cevabı harika. Bayt benzeri tüm nesnelerin decode () işlevini desteklemediğini unutmayın.


1
Bunu yaparsanız .release(), bağlam yöneticisi sürümünü aramanız veya kullanmanız gerektiğini unutmayın .
o11c

CPython'da geçici olanın memoryviewhemen serbest bırakılacağını ve .release()örtük olarak çağrılacağını düşünüyorum. Ancak, tüm Python uygulamaları referans olarak sayılmadığından, buna güvenmemenin en iyisi olduğuna katılıyorum.
Jack O'Connor

0

Test if isinstance(data, bytes)veya if type(data) == bytesvb., Basit bir ASCII dizesinin testten geçtiği Python 2'de çalışmaz! Hem Python 2 hem de Python 3 kullandığım için, bunun üstesinden gelmek için aşağıdaki kontrolü yapıyorum:

if str(type(data)).find("bytes") != -1: print("It's <bytes>")

Biraz çirkin, ama sorunun sorduğu işi yapıyor ve her zaman en basit şekilde çalışıyor.


Python2 str nesneleri şunlardır bytes : str is bytes-> TruePython2'de
snakecharmerb

Açıkçası, bu nedenle algılama sorunu! :)
Apostolos
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.