De belangrijkste verschillen tussen Python 2.7.x en Python 3.x met voorbeelden

Vele beginnende Python-gebruikers vragen zich af met welke versie van Python ze moeten beginnen. Mijn antwoord op deze vraag is meestal iets in de trant van “ga gewoon voor de versie waarin je favoriete tutorial is geschreven, en bekijk later de verschillen.”

Maar wat als je een nieuw project begint en de keuze hebt om te kiezen? Ik zou zeggen dat er op dit moment geen “goed” of “fout” is, zolang zowel Python 2.7.x als Python 3.x de bibliotheken ondersteunen die je van plan bent te gebruiken. Het is echter de moeite waard om eens te kijken naar de belangrijkste verschillen tussen deze twee meest populaire versies van Python om veel voorkomende valkuilen te vermijden bij het schrijven van de code voor een van beide, of als u van plan bent om uw project te porten.

Secties

  • Secties
  • De __future__ module
  • De printfunctie
    • Python 2
    • Python 3
  • Elkaar delen

    • Python 2
    • Python 3

    Unicode

    • Python 2
    • Python 3
  • xrange
    • Python 2
    • Python 3
    • De __contains__ methode voor range objecten in Python 3
      • Note over de snelheidsverschillen in Python 2 en 3
  • Roepen uitzonderingen op
    • Python 2
    • Python 3
  • Behandeling van uitzonderingen
    • Python 2
    • Python 3
  • Python 2
  • Python 3
  • Voor-loop variabelen en het lek in de globale naamruimte
    • Python 2
    • Python 3
  • Vergelijking van niet-ordenbare typen
    • Python 2
    • Python 3
  • Parsing user inputs via input()
    • Python 2
    • Python 3
  • Retureren van iterabele objecten in plaats van lijsten

    • Python 2
    • Python 3
  • Banker’s Rounding
    • Python 2
    • Python 3
  • Meer artikelen over Python 2 en Python 3
  • De __future__ module

    Python 3.x introduceerde enkele Python 2-incompatibele sleutelwoorden en functies die kunnen worden geïmporteerd via de ingebouwde __future__ module in Python 2. Het is aanbevolen om __future__ te importeren als u Python 3.x ondersteuning voor uw code plant. Bijvoorbeeld, als we Python 3.x’s integer division gedrag in Python 2 willen, kunnen we het importeren via

    from __future__ import division

    Meer functies die kunnen worden geïmporteerd uit de __future__ module staan in de onderstaande tabel:

    feature optional in mandatory in effect
    nested_scopes 2.1.0b1 2.2 PEP 227:Statically Nested Scopes
    generators 2.2.0a1 2.3 PEP 255:Simple Generators
    division 2.2.0a2 3.0 PEP 238:De delingsoperator veranderen
    absolute_import 2.5.0a1 3.0 PEP 328:Imports: Multi-line and Absolute/Relative
    with_statement 2.5.0a1 2.6 PEP 343:The “with” Statement
    print_function 2.6.0a2 3.0 PEP 3105:Maak van print een functie
    unicode_literals 2.6.0a2 3.0 PEP 3112:Bytes literals in Python 3000

    (Bron: (https://docs.python.org/2/library/__future__.html#module-__future

    from platform import python_version

    De printfunctie

    Zeer triviaal, en de verandering in de print-syntax is waarschijnlijk de meest bekende verandering, maar toch is het het vermelden waard: Python 2’s print statement is vervangen door de print() functie, wat betekent dat we het object dat we willen printen moeten wikkelen in parantheses.

    Python 2 heeft geen probleem met extra parantheses, maar Python 3 daarentegen zou een SyntaxError geven als we de print-functie de Python 2-weg aanroepen zonder de haakjes.

    Python 2

    print 'Python', python_version()print 'Hello, World!'print('Hello, World!')print "text", ; print 'print more text on the same line'
    Python 2.7.6Hello, World!Hello, World!text print more text on the same line

    Python 3

    print('Python', python_version())print('Hello, World!')print("some text,", end="")print(' print more text on the same line')
    Python 3.4.1Hello, World!some text, print more text on the same line
    print 'Hello, World!'
     File "<ipython-input-3-139a7c5835bd>", line 1 print 'Hello, World!' ^SyntaxError: invalid syntax

    Note:

    Het afdrukken van “Hello, World” hierboven via Python 2 zag er heel “normaal” uit. Als we echter meerdere objecten binnen de parantheses hebben, maken we een tuple, aangezien print een “statement” is in Python 2, en geen functie-aanroep.

    print 'Python', python_version()print('a', 'b')print 'a', 'b'
    Python 2.7.7('a', 'b')a b

    Geïntegreerde deling

    Deze wijziging is vooral gevaarlijk als u code port, of als je Python 3 code uitvoert in Python 2, omdat de verandering in het delingsgedrag van gehele getallen vaak onopgemerkt blijft (het geeft geen SyntaxError).
    Dus heb ik nog steeds de neiging om een float(3)/2 of 3/2.0 te gebruiken in plaats van een 3/2 in mijn Python 3 scripts om de Python 2 jongens wat moeite te besparen (en vice versa, adviseer ik een from __future__ import division in uw Python 2 scripts).

    Python 2

    print 'Python', python_version()print '3 / 2 =', 3 / 2print '3 // 2 =', 3 // 2print '3 / 2.0 =', 3 / 2.0print '3 // 2.0 =', 3 // 2.0
    Python 2.7.63 / 2 = 13 // 2 = 13 / 2.0 = 1.53 // 2.0 = 1.0

    Python 3

    print('Python', python_version())print('3 / 2 =', 3 / 2)print('3 // 2 =', 3 // 2)print('3 / 2.0 =', 3 / 2.0)print('3 // 2.0 =', 3 // 2.0)
    Python 3.4.13 / 2 = 1.53 // 2 = 13 / 2.0 = 1.53 // 2.0 = 1.0

    Unicode

    Python 2 heeft ASCII str() types, aparte unicode(), maar geen byte type.

    Nu, in Python 3, hebben we eindelijk Unicode (utf-8) strings, en 2 byte klassen: byte en bytearrays.

    Python 2

    print 'Python', python_version()
    Python 2.7.6
    print type(unicode('this is like a python3 str type'))
    <type 'unicode'>
    print type(b'byte type does not exist')
    <type 'str'>
    print 'they are really' + b' the same'
    they are really the same
    print type(bytearray(b'bytearray oddly does exist though'))
    <type 'bytearray'>

    Python 3

    print('Python', python_version())print('strings are now utf-8 \u03BCnico\u0394é!')
    Python 3.4.1strings are now utf-8 μnicoΔé!
    print('Python', python_version(), end="")print(' has', type(b' bytes for storing data'))
    Python 3.4.1 has <class 'bytes'>
    print('and Python', python_version(), end="")print(' also has', type(bytearray(b'bytearrays')))
    and Python 3.4.1 also has <class 'bytearray'>
    'note that we cannot add a string' + b'bytes for data'
    ---------------------------------------------------------------------------TypeError Traceback (most recent call last)<ipython-input-13-d3e8942ccf81> in <module>()----> 1 'note that we cannot add a string' + b'bytes for data'TypeError: Can't convert 'bytes' object to str implicitly

    xrange

    Het gebruik van xrange() is erg populair in Python 2.x voor het maken van een iterabel object, bijv, in een for-lus of list/set-dictionary-comprehension.
    Het gedrag leek erg op dat van een generator (d.w.z., “lazy evaluation”), maar hier is de xrange-iterable niet uitputbaar – wat betekent dat je er oneindig over zou kunnen itereren.

    Dankzij de “lazy-evaluation”, is het voordeel van de gewone range() dat xrange() over het algemeen sneller is als je er maar 1 keer over hoeft te itereren (b.v. in een for-loop). In tegenstelling tot eenmalige iteraties is het echter niet aan te raden als je de iteratie meerdere keren herhaalt, omdat de generatie elke keer van voren af aan gebeurt!

    In Python 3, is de range() geïmplementeerd als de xrange() functie, zodat een speciale xrange() functie niet meer bestaat (xrange() roept een NameError op in Python 3).

    import timeitn = 10000def test_range(n): return for i in range(n): passdef test_xrange(n): for i in xrange(n): pass 

    Python 2

    print 'Python', python_version()print '\ntiming range()'%timeit test_range(n)print '\n\ntiming xrange()'%timeit test_xrange(n)
    Python 2.7.6timing range()1000 loops, best of 3: 433 µs per looptiming xrange()1000 loops, best of 3: 350 µs per loop

    Python 3

    print('Python', python_version())print('\ntiming range()')%timeit test_range(n)
    Python 3.4.1timing range()1000 loops, best of 3: 520 µs per loop
    print(xrange(10))
    ---------------------------------------------------------------------------NameError Traceback (most recent call last)<ipython-input-5-5d8f9b79ea70> in <module>()----> 1 print(xrange(10))NameError: name 'xrange' is not defined

    De __contains__ methode voor reeksobjecten in Python 3

    Een ander vermeldenswaardig ding is dat range een “nieuwe” __contains__ methode heeft gekregen in Python 3.x (met dank aan Yuchen Ying, die ons hierop wees). De __contains__ methode kan “look-ups” in Python 3.x range aanzienlijk versnellen voor integer en Boolean types.

    x = 10000000
    def val_in_range(x, val): return val in range(x)
    def val_in_xrange(x, val): return val in xrange(x)
    print('Python', python_version())assert(val_in_range(x, x/2) == True)assert(val_in_range(x, x//2) == True)%timeit val_in_range(x, x/2)%timeit val_in_range(x, x//2)
    Python 3.4.11 loops, best of 3: 742 ms per loop1000000 loops, best of 3: 1.19 µs per loop

    Gebaseerd op de timeit resultaten hierboven, zie je dat de uitvoering voor de “look up” ongeveer 60.000 sneller was wanneer het een integer type was in plaats van een float. Echter, sinds Python 2.x’s range of xrange geen __contains__ methode heeft, zou de “opzoeksnelheid” niet veel verschillen voor gehele getallen of vlottende getallen:

    print 'Python', python_version()assert(val_in_xrange(x, x/2.0) == True)assert(val_in_xrange(x, x/2) == True)assert(val_in_range(x, x/2) == True)assert(val_in_range(x, x//2) == True)%timeit val_in_xrange(x, x/2.0)%timeit val_in_xrange(x, x/2)%timeit val_in_range(x, x/2.0)%timeit val_in_range(x, x/2)
    Python 2.7.71 loops, best of 3: 285 ms per loop1 loops, best of 3: 179 ms per loop1 loops, best of 3: 658 ms per loop1 loops, best of 3: 556 ms per loop

    Hieronder de “bewijzen” dat de methode __contain__ nog niet aan Python 2.x is toegevoegd:

    __contain__.x nog niet is toegevoegd:

    print('Python', python_version())range.__contains__
    Python 3.4.1<slot wrapper '__contains__' of 'range' objects>
    print 'Python', python_version()range.__contains__
    Python 2.7.7---------------------------------------------------------------------------AttributeError Traceback (most recent call last)<ipython-input-7-05327350dafb> in <module>() 1 print 'Python', python_version()----> 2 range.__contains__AttributeError: 'builtin_function_or_method' object has no attribute '__contains__'
    print 'Python', python_version()xrange.__contains__
    Python 2.7.7---------------------------------------------------------------------------AttributeError Traceback (most recent call last)<ipython-input-8-7d1a71bfee8e> in <module>() 1 print 'Python', python_version()----> 2 xrange.__contains__AttributeError: type object 'xrange' has no attribute '__contains__'

    Note over de snelheidsverschillen in Python 2 en 3

    Sommigen hebben gewezen op het snelheidsverschil tussen Python 3’s range() en Python2’s xrange(). Aangezien ze op dezelfde manier zijn geïmplementeerd zou men dezelfde snelheid verwachten. Maar het verschil hier komt gewoon uit het feit dat Python 3 over het algemeen langzamer draait dan Python 2.

    def test_while(): i = 0 while i < 20000: i += 1 return
    print('Python', python_version())%timeit test_while()
    Python 3.4.1100 loops, best of 3: 2.68 ms per loop
    print 'Python', python_version()%timeit test_while()
    Python 2.7.61000 loops, best of 3: 1.72 ms per loop

    Uitzonderingen oproepen

    Waar Python 2 beide notaties accepteert, de ‘oude’ en de ‘nieuwe’ syntaxis, verslikt Python 3 zich (en roept op zijn beurt een SyntaxError op) als we het argument van de uitzondering niet tussen haakjes plaatsen:

    Python 2

    print 'Python', python_version()
    Python 2.7.6
    raise IOError, "file error"
    ---------------------------------------------------------------------------IOError Traceback (most recent call last)<ipython-input-8-25f049caebb0> in <module>()----> 1 raise IOError, "file error"IOError: file error
    raise IOError("file error")
    ---------------------------------------------------------------------------IOError Traceback (most recent call last)<ipython-input-9-6f1c43f525b2> in <module>()----> 1 raise IOError("file error")IOError: file error

    Python 3

    print('Python', python_version())
    Python 3.4.1
    raise IOError, "file error"
     File "<ipython-input-10-25f049caebb0>", line 1 raise IOError, "file error" ^SyntaxError: invalid syntax

    De juiste manier om een uitzondering op te roepen in Python 3:

    print('Python', python_version())raise IOError("file error")
    Python 3.4.1---------------------------------------------------------------------------OSError Traceback (most recent call last)<ipython-input-11-c350544d15da> in <module>() 1 print('Python', python_version())----> 2 raise IOError("file error")OSError: file error

    Handling exceptions

    Ook de afhandeling van uitzonderingen is in Python 3 enigszins veranderd. In Python 3 moeten we nu het “as” sleutelwoord gebruiken

    Python 2

    print 'Python', python_version()try: let_us_cause_a_NameErrorexcept NameError, err: print err, '--> our error message'
    Python 2.7.6name 'let_us_cause_a_NameError' is not defined --> our error message

    Python 3

    print('Python', python_version())try: let_us_cause_a_NameErrorexcept NameError as err: print(err, '--> our error message')
    Python 3.4.1name 'let_us_cause_a_NameError' is not defined --> our error message

    Omdat next().next()) zo’n veelgebruikte functie (methode) is, is dit een andere syntaxiswijziging (of liever wijziging in de implementatie) die het vermelden waard is: waar je zowel de functie als de methode syntaxis kunt gebruiken in Python 2.7.5, is de next() functie het enige dat overblijft in Python 3 (het aanroepen van de .next() methode roept een AttributeError op).

    Python 2

    print 'Python', python_version()my_generator = (letter for letter in 'abcdefg')next(my_generator)my_generator.next()
    Python 2.7.6'b'

    Python 3

    print('Python', python_version())my_generator = (letter for letter in 'abcdefg')next(my_generator)
    Python 3.4.1'a'
    my_generator.next()
    ---------------------------------------------------------------------------AttributeError Traceback (most recent call last)<ipython-input-14-125f388bb61b> in <module>()----> 1 my_generator.next()AttributeError: 'generator' object has no attribute 'next'

    For-loop-variabelen en het lek in de wereldwijde naamruimte

    Goed nieuws is: In Python 3.x lekken for-loop variabelen niet meer naar de globale namespace!

    Dit gaat terug op een wijziging die in Python 3.x is doorgevoerd en in What’s New In Python 3.0 als volgt wordt beschreven:

    “List comprehensions ondersteunen niet langer de syntactische vorm . Gebruik in plaats daarvan . Merk ook op dat list comprehensions een andere semantiek hebben: ze zijn dichter bij syntactische suiker voor een generator expressie binnen een list() constructor, en in het bijzonder worden de loop control variabelen niet langer gelekt in de omringende scope.”

    Python 2

    print 'Python', python_version()i = 1print 'before: i =', iprint 'comprehension: ', print 'after: i =', i
    Python 2.7.6before: i = 1comprehension: after: i = 4

    Python 3

    print('Python', python_version())i = 1print('before: i =', i)print('comprehension:', )print('after: i =', i)
    Python 3.4.1before: i = 1comprehension: after: i = 1

    Vergelijken van niet-ordenbare typen

    Een andere leuke verandering in Python 3 is dat er een TypeError als waarschuwing wordt gegeven als we niet-ordenbare typen proberen te vergelijken.

    Python 2

    print 'Python', python_version()print " > 'foo' = ", > 'foo'print "(1, 2) > 'foo' = ", (1, 2) > 'foo'print " > (1, 2) = ", > (1, 2)
    Python 2.7.6 > 'foo' = False(1, 2) > 'foo' = True > (1, 2) = False

    Python 3

    print('Python', python_version())print(" > 'foo' = ", > 'foo')print("(1, 2) > 'foo' = ", (1, 2) > 'foo')print(" > (1, 2) = ", > (1, 2))
    Python 3.4.1---------------------------------------------------------------------------TypeError Traceback (most recent call last)<ipython-input-16-a9031729f4a0> in <module>() 1 print('Python', python_version())----> 2 print(" > 'foo' = ", > 'foo') 3 print("(1, 2) > 'foo' = ", (1, 2) > 'foo') 4 print(" > (1, 2) = ", > (1, 2))TypeError: unorderable types: list() > str()

    Parsing user inputs via input()

    Gelukkig maar waar, is de input()-functie in Python 3 zodanig aangepast dat deze de gebruikersinvoer altijd opslaat als str-objecten. Om het gevaarlijke gedrag in Python 2 te vermijden om andere types dan strings in te lezen, moeten we in plaats daarvan raw_input() gebruiken.

    Python 2

    Python 2.7.6 op darwinType "help", "copyright", "credits" of "license" voor meer informatie.>>> my_input = input('voer een getal in: ')voer een getal in: 123>>> type(mijn_input)<type 'int'>>>> my_input = raw_input('voer een getal in: ')voer een getal in: 123>>> type(mijn_input)<type 'str'>

    Python 3

    Python 3.4.1 op darwinType "help", "copyright", "credits" of "license" voor meer informatie.>>> my_input = input('voer een getal in: ')voer een getal in: 123>>> type(mijn_input)<klasse 'str'>

    Retourneer iterable objecten in plaats van lijsten

    Zoals we al gezien hebben in het xrange gedeelte, geven sommige functies en methoden in Python 3 nu iterabele objecten terug – in plaats van lijsten in Python 2.

    Omdat we daar meestal toch maar één keer overheen itereren, denk ik dat deze verandering heel zinvol is om geheugen te besparen. Het is echter ook mogelijk – in tegenstelling tot generatoren – om er meerdere keren overheen te itereren als dat nodig is, het is alleen niet zo efficiënt.

    En voor die gevallen waarin we de list-objecten echt nodig hebben, kunnen we het iterable object gewoon omzetten in een list via de list()-functie.

    Python 2

    print 'Python', python_version()print range(3)print type(range(3))
    Python 2.7.6<type 'list'>

    Python 3

    print('Python', python_version())print(range(3))print(type(range(3)))print(list(range(3)))
    Python 3.4.1range(0, 3)<class 'range'>

    Enkele veelgebruikte functies en methoden die in Python 3 geen lijsten meer teruggeven:

    • zip()

    • map()

    • filter()

    • dictionary’s .keys() methode

    • dictionary’s .values() methode

    • dictionary’s .items() methode

    Banker’s Rounding

    Python 3 heeft de nu standaard manier van afronden van decimalen aangenomen wanneer het resulteert in een gelijkspel (.5) bij de laatste significante cijfers. Nu, in Python 3, worden decimalen afgerond naar het dichtstbijzijnde even getal. Hoewel het een ongemak is voor de portabiliteit van de code, is het een betere manier van afronden dan afronden naar boven, omdat het de bias naar grote getallen vermijdt. Voor meer informatie, zie de uitstekende Wikipedia artikelen en paragrafen:

    • https://en.wikipedia.org/wiki/Rounding#Round_half_to_even
    • https://en.wikipedia.org/wiki/IEEE_floating_point#Roundings_to_nearest

    Python 2

    print 'Python', python_version()
    Python 2.7.12
    round(15.5)
    16.0
    round(16.5)
    17.0

    Python 3

    print('Python', python_version())
    Python 3.5.1
    round(15.5)
    16
    round(16.5)
    16

    Meer artikelen over Python 2 en Python 3

    Hier volgt een lijst met een aantal goede artikelen over Python 2 en 3 die ik zou aanraden als vervolg-vervolg.

    / Porten naar Python 3

    • Moet ik Python 2 of Python 3 gebruiken voor mijn ontwikkel-activiteit?

    • Wat is er nieuw in Python 3.0

    • Porteren naar Python 3

    • Porteren van Python 2 code naar Python 3

    • Hoe houd je Python 3 vooruit kan

    / Pro en anti Python 3

    • 10 geweldige functies van Python die je niet kunt gebruiken omdat je weigert te upgraden naar Python 3
    • Alles wat je niet wilde weten over Unicode in Python 3

    • Python 3 is killing Python

    • Python 3 kan Python doen herleven

    • Python 3 is prima

    Geef een reactie

    Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *