Python işlevleri, ilettiğiniz parametre türlerini nasıl işler?


305

Yanılmıyorsam, Python'da bir işlev oluşturmak şu şekilde çalışır:

def my_func(param1, param2):
    # stuff

Ancak, aslında bu parametrelerin türlerini vermezsiniz. Ayrıca, hatırlarsam, Python güçlü bir şekilde yazılan bir dildir, Python'un, işlev yaratıcısının beklediğinden farklı bir türde bir parametre geçirmenize izin vermemesi gerektiği gibi görünüyor. Ancak, Python fonksiyonun kullanıcısının uygun tiplerde geçtiğini nasıl bilebilir? Fonksiyonun parametreyi gerçekten kullandığı varsayılarak program yanlış tipteyse ölecek mi? Türü belirtmeniz gerekiyor mu?


15
Bu sorudaki kabul edilen cevabın Python'un sunduğu mevcut yeteneklerle daha uyumlu olacak şekilde güncellenmesi gerektiğini düşünüyorum . Bence bu cevap işi yapıyor.
code_dredd

Yanıtlar:


173

Python kuvvetle her nesne çünkü yazıldığında sahip bir tür, her nesne bilen türünü, bunun bir amacı olduğu "sanki" kazara veya kasten bir türde bir nesne kullanmak imkansız farklı tip ve nesne üzerindeki tüm temel işlemler şunlardır türüne devredildi.

Bunun isimlerle bir ilgisi yok . Python'daki bir ad "bir türe sahip değildir": bir ad tanımlanırsa ve ad bir nesneye başvuruyorsa ve nesnenin bir türü varsa (ancak aslında ad üzerinde bir türü zorlamaz : a name bir isimdir).

Python'daki bir isim, farklı zamanlarda farklı nesnelere (hepsi olmasa da çoğu programlama dilinde olduğu gibi) mükemmel bir şekilde atıfta bulunabilir ve isim üzerinde bir zamanlar X tipi bir nesneye atıfta bulunmuşsa, o zaman sonsuza üzerinde tip X. Kısıtların diğer nesnelere yalnızca başvurmak için kısıtlı oluyor isimlerin olsa bazı meraklıları, "güçlü yazarak" kavramının bir parçası olmayan statik isimler yazarak ( do kısıtlı ve statik içinde olsun, AKA derleme zaman, moda da) terimini bu şekilde kötüye kullan.


71
Bu yüzden güçlü yazım o kadar güçlü değil gibi görünüyor, bu özel durumda, statik yazımdan daha zayıf. bu açıdan. Yanılıyorsam lütfen beni düzeltin.
liang

19
@liang Bu bir görüş, bu yüzden doğru ya da yanlış olamazsınız. Kesinlikle benim görüşüm ve birçok dil denedim. Parametrelerin türünü (ve dolayısıyla üyeleri) bulmak için IDE'mi kullanamam python'un büyük bir dezavantajı. Bu dezavantaj, ördek yazmanın avantajlarından daha önemli ise, sorduğunuz kişiye bağlıdır.
Maarten Bodewes

6
Ama bu soruların hiçbirine cevap vermiyor: "Ancak, Python fonksiyonun kullanıcısının uygun tiplerde geçtiğini nasıl bilebilir? Fonksiyon sadece parametreyi kullandığını varsayarak program sadece yanlış tipse ölür mü? Türü belirtmeniz gerekiyor mu? " veya ..
qPCR4vir

4
@ qPCR4vir, herhangi bir nesne bir argüman olarak geçirilebilir. Hatası (bir istisna, program yakalamak için kodlanmış eğer olmaz "die", bkz try/ except) bir operasyon nesnesi desteklemediği teşebbüs edildiğinde ve eğer ortaya çıkar. Python 3.5'te artık isteğe bağlı olarak argümanların "türlerini belirtebilirsiniz", ancak spesifikasyon ihlal edilirse kendi başına herhangi bir hata oluşmaz; yazım şekli yalnızca analiz vb. gerçekleştiren ayrı araçlara yardımcı olmak içindir, Python'un davranışını değiştirmez.
Alex Martelli

2
@AlexMartelli. Teşekkür! Benim için bu doğru cevap: "Hata (bir istisna, program onu ​​yakalamak için kodlanmışsa" ölmeyecek ", deneyin / hariç bakın) .."
qPCR4vir

753

Diğer cevaplar ördek tipini ve tzot'un basit cevabını açıklamakta iyi bir iş çıkardı :

Python'un değişkenleri yoktur, değişkenlerin türü ve değeri olduğu diğer diller gibi; türlerini bilen nesnelere işaret eden isimleri vardır.

Bununla birlikte , 2010'dan bu yana ilginç bir şey değişti (soru ilk sorulduğunda), yani PEP 3107'nin uygulanması (Python 3'te uygulandı). Şimdi aslında bir parametrenin tipini ve bunun gibi bir fonksiyonun dönüş tipinin tipini belirtebilirsiniz:

def pick(l: list, index: int) -> int:
    return l[index]

Burada pick2 parametre, bir liste lve bir tamsayı alır index. Ayrıca bir tamsayı döndürmelidir.

Yani burada lçok fazla çaba harcamadan görebileceğimiz bir tamsayı listesi olduğu ima ediliyor , ancak daha karmaşık fonksiyonlar için listenin ne içermesi gerektiği konusunda biraz kafa karıştırıcı olabilir. Ayrıca varsayılan değerinin index0 olmasını da istiyoruz. Bunu çözmek için pickbunun gibi yazmayı seçebilirsiniz :

def pick(l: "list of ints", index: int = 0) -> int:
    return l[index]

Şimdi lsözdizimsel olarak izin verilen türü olarak bir dize koyduğumuzu unutmayın , ancak programlı olarak ayrıştırmak için iyi değildir (ki daha sonra geri döneceğiz).

Python'un TypeErrorbir şamandıra geçirirseniz yükselmeyeceğini belirtmek önemlidir index, bunun nedeni Python'un tasarım felsefesinin ana noktalarından biridir: "Hepimiz yetişkinleri burada kabul ediyoruz" , yani bir işleve neleri aktarabileceğinizin ve neleri yapamayacağınızın farkında olun. Gerçekten TypeErrors atar kod yazmak istiyorsanız isinstance, iletilen argümanın uygun türden veya bunun bir alt sınıfından olup olmadığını kontrol etmek için işlevi kullanabilirsiniz :

def pick(l: list, index: int = 0) -> int:
    if not isinstance(l, list):
        raise TypeError
    return l[index]

Bunu neden nadiren yapmanız gerektiği ve bunun yerine ne yapmanız gerektiği hakkında daha fazla bilgi bir sonraki bölümde ve yorumlarda ele alınmaktadır.

PEP 3107 sadece kod okunabilirliğini arttırmakla kalmaz, aynı zamanda burada okuyabileceğiniz birkaç uygun kullanım durumuna da sahiptir .


Tip açıklamaları, tip ipuçları için standart bir modül sunan PEP 484'ün tanıtımı ile Python 3.5'te çok daha fazla dikkat çekti .

Bu tip ipuçları , şimdi PEP 484 uyumlu olan tip denetleyicisi mypy'den ( GitHub ) geldi .

Yazma modülü ile birlikte, aşağıdakileri içeren oldukça kapsamlı bir tür ipucu koleksiyonu ile birlikte gelir:

  • List, Tuple, Set, Map- için list, tuple, setve mapsırasıyla.
  • Iterable - Jeneratörler için kullanışlıdır.
  • Any - ne zaman bir şey olabilir.
  • Union- aksine, belirli bir tür kümedeki herhangi bir şey olabileceği zaman Any.
  • Optional- zaman olabilir Yok olmak. İçin kestirme Union[T, None].
  • TypeVar - jenerik ilaçlarla kullanılır.
  • Callable - öncelikle işlevler için kullanılır, ancak diğer callables için kullanılabilir.

Bunlar en yaygın tip ipuçlarıdır. Yazma modülünün belgelerinde tam bir liste bulunabilir .

Yazma modülünde sunulan ek açıklama yöntemlerini kullanan eski örnek:

from typing import List

def pick(l: List[int], index: int) -> int:
    return l[index]

Güçlü bir özellik, Callablebir işlevi bağımsız değişken olarak alan açıklama yöntemleri yazmanıza izin veren özelliktir . Örneğin:

from typing import Callable, Any, Iterable

def imap(f: Callable[[Any], Any], l: Iterable[Any]) -> List[Any]:
    """An immediate version of map, don't pass it any infinite iterables!"""
    return list(map(f, l))

Yukarıdaki örnek, TypeVarbunun yerine kullanımı ile daha kesin hale gelebilir Any, ancak cevabımı yazım ipucunun etkinleştirdiği harika yeni özellikler hakkında çok fazla bilgi ile doldurduğuma inanıyorum çünkü bu okuyucuya bir alıştırma olarak kaldı.


Önceden, örneğin Sfenks ile bir Python kodu belgelendiğinde , yukarıdaki işlevselliklerden bazıları, şu şekilde biçimlendirilmiş docstrings yazılarak elde edilebilirdi:

def pick(l, index):
    """
    :param l: list of integers
    :type l: list
    :param index: index at which to pick an integer from *l*
    :type index: int
    :returns: integer at *index* in *l*
    :rtype: int
    """
    return l[index]

Gördüğünüz gibi, bu bir dizi ekstra satır alır (tam sayı ne kadar açık olmak istediğinize ve doktorunuzu nasıl biçimlendirdiğinize bağlıdır). Ancak şimdi, PEP 3107'nin birçok (hepsi?) Yoldan daha üstün bir alternatif sunduğunu açıklığa kavuşturmak gerekir . Bu özellikle , gördüğümüz gibi, bu tip ipuçları / ek açıklamalar için açık ve kesin ancak esnek olacak şekilde kullanılabilecek bir sözdizimi tanımlayan standart bir modül sağlayan PEP 484 ile kombinasyon halinde doğrudur. güçlü kombinasyon.

Kişisel görüşüme göre, bu Python'un şimdiye kadarki en büyük özelliklerinden biri. İnsanların gücünü kullanmaya başlamalarını bekleyemem. Uzun cevap için özür dilerim, ama heyecanlandığımda olan şey bu.


Tip ipuçlarını yoğun şekilde kullanan Python koduna bir örnek burada bulunabilir .


2
@rickfoosusa: Bu özelliğin eklendiği Python 3'ü çalıştırmıyorsunuz.
erb

26
Bir dakika bekle! Eğer parametre ve dönüş tipini tanımlamak a'yı yükseltmezse TypeError, pick(l: list, index: int) -> into zaman tek satırlı tanımlamanın kullanılmasının anlamı nedir? Ya da yanlış anladım, bilmiyorum.
Erdin Eray

24
@Eray Erdin: Bu yaygın bir yanlış anlama ve hiç de kötü bir soru değil. Dokümantasyon amacıyla kullanılabilir, IDE'lerin statik analiz kullanarak (yanıtta bahsettiğim mypy gibi) çalışma zamanından önce daha iyi otomatik tamamlama yapmasına ve hataları bulmasına yardımcı olur. Çalışma zamanının bilgilerden faydalanabileceği ve programları hızlandırabileceği umutları var, ancak bunun uygulanması çok uzun sürecek. Sen belki de (bilgi saklanır sizin için TypeErrors atar bir dekoratör oluşturmak mümkün __annotations__fonksiyon nesnenin öznitelik).
erb

2
@ErdinEray TypeErrors atmanın kötü bir fikir olduğunu eklemeliyim (hata ayıklama hiçbir zaman eğlenceli değildir, ne kadar iyi tasarlanmış istisnalar olursa olsun). Ama korkmayın, cevabımda açıklanan yeni özelliklerin avantajı daha iyi bir yol sağlar: çalışma zamanında herhangi bir kontrole güvenmeyin, çalışma süresinden önce her şeyi mypy ile yapmayın veya PyCharm gibi statik analizi yapan bir düzenleyici kullanın .
erb

2
@Tony: İki veya daha fazla nesneyi döndürdüğünüzde aslında bir demet döndürürsünüz, bu yüzden Tuple tipi bilgi notunu kullanmalısınız, yanidef f(a) -> Tuple[int, int]:
erb

14

Bir tür belirtmezsiniz. Yöntem, yalnızca iletilen parametrelerde tanımlanmayan özniteliklere erişmeye çalışırsa (çalışma zamanında) başarısız olur.

Yani bu basit fonksiyon:

def no_op(param1, param2):
    pass

... hangi iki argüman içeri aktarılırsa aktarılmaz.

Ancak, bu işlev:

def call_quack(param1, param2):
    param1.quack()
    param2.quack()

... her ikisi de adlandırılabilir çağrılabilir özniteliklere sahip değilse param1ve param2çalışmazsa çalışma zamanında başarısız olur quack.


+1: Nitelikler ve yöntemler statik olarak belirlenmez. Bu "uygun tür" veya "yanlış tür" kavramının, türün işlevde düzgün çalışıp çalışmadığı ile belirlenir.
S.Lott

11

Birçok dilde, belirli bir türde ve bir değeri olan değişkenler bulunur. Python'un değişkenleri yoktur; nesneler vardır ve bu nesnelere başvurmak için adları kullanırsınız.

Diğer dillerde, şunları söylediğinizde:

a = 1

sonra (tipik olarak tamsayı) bir değişken içeriğini 1 değerine değiştirir.

Python'da,

a = 1

aracı “adı kullanımı a nesneye atıfta 1 ”. Etkileşimli bir Python oturumunda aşağıdakileri yapabilirsiniz:

>>> type(1)
<type 'int'>

Fonksiyon typenesne ile çağrılır 1; her nesne türünü bildiğinden, typesöz konusu türü bulmak ve geri vermek kolaydır .

Benzer şekilde, bir işlev tanımladığınızda

def funcname(param1, param2):

işlev iki nesne alır param1ve param2türlerine bakılmaksızın bunları adlandırır . Alınan nesnelerin belirli bir türde olduğundan emin olmak istiyorsanız, işlevinizi gerekli tür (ler) deki gibi kodlayın ve eğer yapılmazsa atılan istisnaları yakalayın. Atılan istisnalar genellikle TypeError(geçersiz bir işlem kullandınız) ve AttributeError(var olmayan bir üyeye erişmeye çalıştınız (yöntemler de üyelerdir)).


8

Python, statik veya derleme zamanı türü denetimi anlamında güçlü bir şekilde yazılmamıştır.

Çoğu Python kodu "Ördek Yazma" adı altındadır - örneğin, readbir nesnede bir yöntem ararsınız - nesnenin diskte veya sokette bir dosya olması önemli değildir, sadece N okumak istiyorsunuz bayt.


21
Python edilir kesinlikle yazılı. Ayrıca dinamik olarak yazılmıştır.
Daniel Newby

1
Ama bu soruların hiçbirine cevap vermiyor: "Ancak, Python fonksiyonun kullanıcısının uygun tiplerde geçtiğini nasıl bilebilir? Fonksiyon sadece parametreyi kullandığını varsayarak program sadece yanlış tipse ölür mü? Türü belirtmeniz gerekiyor mu? " veya ..
qPCR4vir

6

As Alex Martelli açıklıyor ,

Normal, Pythonic, tercih edilen çözüm neredeyse her zaman "ördek yazma" dır: bağımsız değişkeni istenen bir türdeymiş gibi kullanmayı deneyin, argüman gerçekte değilse ortaya çıkabilecek tüm istisnaları yakalayan bir try / hariç ifadesinde yapın Bu türden (veya güzel bir şekilde taklit eden başka bir tür ;-) ve hariç yan tümcesinde, başka bir şey deneyin ("başka bir türdeyse" argümanını kullanarak).

Yararlı bilgiler için gönderisinin geri kalanını okuyun.


5

Python, işlevlerine ne ilettiğinizi umursamıyor. Aradığınızda my_func(a,b), param1 ve param2 değişkenleri a ve b değerlerini tutacaktır. Python, fonksiyonu uygun türlerle çağırdığınızı bilmez ve programcıdan bununla ilgilenmesini bekler. İşleviniz farklı türde parametrelerle çağrılırsa, bunlara erişen kodu try / hariç blokları ile sarabilir ve parametreleri istediğiniz şekilde değerlendirebilirsiniz.


11
Python'un değişkenleri yoktur, değişkenlerin türü ve değeri olduğu diğer diller gibi; türlerini bilen nesnelere işaret eden isimleri vardır .
tzot

2

Hiçbir zaman türü belirtmezsiniz; Python ördek yazma kavramına sahiptir ; temel olarak parametreleri işleyen kod, belki de bir parametrenin uygulanması beklenen belirli yöntemleri çağırarak, onlar hakkında bazı varsayımlar yapar. Parametre yanlış tipteyse, bir istisna atılır.

Genel olarak uygun türdeki nesnelerin etrafından geçtiğinizden emin olmak kodunuza bağlıdır - bunu önceden uygulayacak bir derleyici yoktur.


2

Bu sayfada bahsetmeye değer ördek yazmanın tek bir istisnası var.

Ne zaman strfonksiyonu çağıran __str__sınıf yöntemini de ustaca türünü сhecks:

>>> class A(object):
...     def __str__(self):
...         return 'a','b'
...
>>> a = A()
>>> print a.__str__()
('a', 'b')
>>> print str(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __str__ returned non-string (type tuple)

Sanki Guido bir programın beklenmedik bir türle karşılaşması durumunda hangi istisnayı yükseltmesi gerektiğini ima ediyor.


1

Python'da her şeyin bir türü vardır. Bir Python işlevi, argümanların türü destekliyorsa yapması istenen her şeyi yapar.

Örnek: düzenlenebilir fooher şeyi ekleyecektir __add__;) türü hakkında endişelenmeden. Yani bu, başarısızlığı önlemek için, yalnızca eklemeyi destekleyen şeyleri sağlamalısınız.

def foo(a,b):
    return a + b

class Bar(object):
    pass

class Zoo(object):
    def __add__(self, other):
        return 'zoom'

if __name__=='__main__':
    print foo(1, 2)
    print foo('james', 'bond')
    print foo(Zoo(), Zoo())
    print foo(Bar(), Bar()) # Should fail

1

Bunu diğer cevaplarda görmedim, bu yüzden bunu pota ekleyeceğim.

Diğerlerinin söylediği gibi, Python işlev veya yöntem parametrelerinde türü zorlamaz. Ne yaptığınızı bildiğiniz ve gerçekten geçirilen bir şeyin türünü gerçekten bilmeniz gerekiyorsa, kontrol edip kendiniz için ne yapacağınıza karar vereceğiniz varsayılır.

Bunu yapmak için ana araçlardan biri isinstance () işlevidir.

Örneğin, normal utf-8 kodlu dizelerden ziyade ham ikili metin verisi almayı bekleyen bir yöntem yazarsam, parametrelerin türünü giriş yolunda kontrol edebilir ve bulduklarıma uyarlayabilir veya bir reddetmek için istisna.

def process(data):
    if not isinstance(data, bytes) and not isinstance(data, bytearray):
        raise TypeError('Invalid type: data must be a byte string or bytearray, not %r' % type(data))
    # Do more stuff

Python ayrıca nesnelere kazmak için her türlü aracı sağlar. Eğer cesursanız, anında keyfi sınıflardan kendi nesnelerinizi oluşturmak için importlib'i bile kullanabilirsiniz. JSON verilerinden nesneleri yeniden oluşturmak için bunu yaptım. Böyle bir şey C ++ gibi statik bir dilde bir kabus olurdu.


1

Yazma modülünü etkili bir şekilde kullanmak için (Python 3.5'te yeni olan) all ( *) öğesini içerir.

from typing import *

Ve kullanıma hazır olacaksınız:

List, Tuple, Set, Map - for list, tuple, set and map respectively.
Iterable - useful for generators.
Any - when it could be anything.
Union - when it could be anything within a specified set of types, as opposed to Any.
Optional - when it might be None. Shorthand for Union[T, None].
TypeVar - used with generics.
Callable - used primarily for functions, but could be used for other callables.

Ancak, yine de sizin gibi tip adları kullanabilirsiniz int, list, dict, ...


1

Herkes değişken türleri belirtmek istiyorsa bir sarıcı uyguladım.

import functools

def type_check(func):

    @functools.wraps(func)
    def check(*args, **kwargs):
        for i in range(len(args)):
            v = args[i]
            v_name = list(func.__annotations__.keys())[i]
            v_type = list(func.__annotations__.values())[i]
            error_msg = 'Variable `' + str(v_name) + '` should be type ('
            error_msg += str(v_type) + ') but instead is type (' + str(type(v)) + ')'
            if not isinstance(v, v_type):
                raise TypeError(error_msg)

        result = func(*args, **kwargs)
        v = result
        v_name = 'return'
        v_type = func.__annotations__['return']
        error_msg = 'Variable `' + str(v_name) + '` should be type ('
        error_msg += str(v_type) + ') but instead is type (' + str(type(v)) + ')'
        if not isinstance(v, v_type):
                raise TypeError(error_msg)
        return result

    return check

Şu şekilde kullanın:

@type_check
def test(name : str) -> float:
    return 3.0

@type_check
def test2(name : str) -> str:
    return 3.0

>> test('asd')
>> 3.0

>> test(42)
>> TypeError: Variable `name` should be type (<class 'str'>) but instead is type (<class 'int'>)

>> test2('asd')
>> TypeError: Variable `return` should be type (<class 'str'>) but instead is type (<class 'float'>)

DÜZENLE

Bağımsız değişkenlerin (veya dönüşlerin) türlerinden herhangi biri bildirilmezse yukarıdaki kod çalışmaz. Aşağıdaki düzenleme yardımcı olabilir, ancak sadece kwargs için çalışır ve argümanları kontrol etmez.

def type_check(func):

    @functools.wraps(func)
    def check(*args, **kwargs):
        for name, value in kwargs.items():
            v = value
            v_name = name
            if name not in func.__annotations__:
                continue

            v_type = func.__annotations__[name]

            error_msg = 'Variable `' + str(v_name) + '` should be type ('
            error_msg += str(v_type) + ') but instead is type (' + str(type(v)) + ') '
            if not isinstance(v, v_type):
                raise TypeError(error_msg)

        result = func(*args, **kwargs)
        if 'return' in func.__annotations__:
            v = result
            v_name = 'return'
            v_type = func.__annotations__['return']
            error_msg = 'Variable `' + str(v_name) + '` should be type ('
            error_msg += str(v_type) + ') but instead is type (' + str(type(v)) + ')'
            if not isinstance(v, v_type):
                    raise TypeError(error_msg)
        return result

    return check
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.