[python] Encapsulatie mbv properties

Pagina: 1
Acties:

Acties:
  • 0 Henk 'm!

  • HarmoniousVibe
  • Registratie: September 2001
  • Laatst online: 10:33
Sindskort ben ik met wat nieuwe talen (Ruby, Python, C#) aan het experimenteren geslaan, omdat PHP me een beetje begon te vervelen :). Alhoewel ik wel wat algemene programmeervaardigheden en kennis bezit, ben ik qua python nog een beginneling. Hopelijk kunnen jullie me helpen.

Ik heb een vraag mbt het encapsuleren van data in een klasse. Vanuit mijn java-achtergrond ben ik gewend om simpelweg voor alle velden een private var te maken met public accessor methods (of deze door een IDE te laten genereren bij bijv. Data-driven EJB's). Het bekende verhaal dus.

Nu worden getters en setters in python beschouwd als zaken die de productiviteit en de leesbaarheid niet ten goede komen. Toen ik dit voor het eerst las dacht ik: "WTF? Wat moet je dan? public velden gebruiken? Dat is toch een stap terug?". Enfin, na wat rondneuzen kwam ik erachter dat dat inderdaad is wat men in python propageert. Het cruciale verschil met Java is echter dat je naderhand aan deze variabelen behavior kunt toevoegen mbv properties. Dit wordt mooi samengevat in het volgende stukje.
Getters and setters are evil. Evil, evil, I say! Python objects are not Java beans. Do not write getters and setters. This is what the 'property' built-in is for. And do not take that to mean that you should write getters and setters, and then wrap them in 'property'. That means that until you prove that you need anything more than a simple attribute access, don't write getters and setters. They are a waste of CPU time, but more important, they are a waste of programmer time. Not just for the people writing the code and tests, but for the people who have to read and understand them as well.

In Java, you have to use getters and setters because using public fields gives you no opportunity to go back and change your mind later to using getters and setters. So in Java, you might as well get the chore out of the way up front. In Python, this is silly, because you can start with a normal attribute and change your mind at any time, without affecting any clients of the class. So, don't write getters and setters.
Dus je kunt behavior toevoegen of veranderen zonder dat je de api daarmee kapot maakt. Ideaal dus. Er is echter nog een dingetje waar ik over struikel in python. Stel je hebt de volgende class:

code:
1
2
3
4
class Person:
    def __init__(self, name, email):
        self.name = "jan janssen"
        self.email = "a@b.com"


Als je nu een stukje implementatie wilt toevoegen aan bijvoorbeeld de naam, dan pas schrijf je een getter en evt een setter en koppel je deze mbv property() aan het veld "name".

code:
1
2
3
4
5
6
7
8
9
10
11
class Person:
    def __init__(self, name, email):
        self.name = "jan janssen"
        self.email = "a@b.com"

    def get_name(self):
        return self.name.capitalize()
    def set_name(self, name)
        self.name = name.capitalize()
    
    name = property(get_name, set_name)


Dit was mijn eerste poging, maar dit werkt niet. Als ik nu obj.name aanroep dan krijg ik nog steeds "jan janssen" terug en niet "Jan Janssen". M.a.w. het echte veld "name" prevaleert over de property "name". Het probleem is nu dat ik mijn class helemaal moet gaan refactoren. Wat mij de beste manier lijkt is alle self.name herschrijven naar self_name, en dat consistent doorvoeren in de hele klasse. Dus dan krijg je zoiets:

code:
1
2
3
4
5
6
7
8
9
10
11
class Person:
    def __init__(self, name, email):
        self._name = "jan janssen"
        self.email = "a@b.com"

    def get_name(self):
        return self._name.capitalize()
    def set_name(self, name):
        self._name = name.capitalize()
    
    name = property(get_name, set_name)


Dit werkt wel, maar echt elegant vind ik het niet. In python schrijft men tbv de productiviteit geen accessor methods voor simpele veld-toegang: akkoord. Maar wordt die productiviteit niet voor een gedeelte onderuit gehaald door het feit dat je elke occurrence van een bepaalde instantievariabele moet gaan herschrijven? En dat is niet eens het grootste probleem; met een goede editor en/of een slimme regex kom je een heel eind. Maar de leesbaarheid en daarmee de onderhoudbaarheid wordt er zo niet echt beter op, imho.

Mijn vraag is of er een betere/slimmere manier is om toch implementatie toe te voegen aan je velden zonder de api van je class te breken? Mijn excuses als dit een n00b-vraag is; ik ben nieuw met python.

12 × LG 330Wp (Enphase) | Daikin FTXM-N 3,5+2,0+2,0kW | Panasonic KIT-WC03J3E5 3kW


Acties:
  • 0 Henk 'm!

  • Robtimus
  • Registratie: November 2002
  • Laatst online: 25-09 17:46

Robtimus

me Robtimus no like you

Het probleem is volgens mij dat tijdens het creeren van een object, __init__ je property "name" overschrijft door een string waarde.

Het is hoe dan ook aan te bevelen om je fields private te maken. Voor zover ik me kan herinneren vereist dat zelfs __ (2x dus) als prefix: self.__name

More than meets the eye
There is no I in TEAM... but there is ME
system specs


Acties:
  • 0 Henk 'm!

  • phobosdeimos
  • Registratie: Augustus 2007
  • Laatst online: 09:10
Een enkele underscore voor private variabelen, dubbele underscore voor echt "interne" zaken, want dan gaat python name mangling toepassen (dan kan je er dus echt niet meer aan van buitenaf).
Je kan het bekijken als "_" een waarschuwing is "dit is privaat", en "__" is nog sterker, een verbod als het ware.
Gooi alsjeblieft je Java balast overboord, anders ga je gewoon Java of PHP code schrijven in Python.
Eenmaal je met Python werkt, zal je pas echt beseffen wat voor een hilarische klucht Java is (ref: http://www.joelonsoftware...ePerilsofJavaSchools.html).

[ Voor 24% gewijzigd door phobosdeimos op 13-12-2008 14:11 ]


Acties:
  • 0 Henk 'm!

  • user109731
  • Registratie: Maart 2004
  • Niet online
Je ontkomt er volgens mij niet aan dat je in de class name in _name moet veranderen en een property moet toevoegen. Maar kun je in de andere class methods niet gewoon name laten staan zodat je de property aanroept?

Je kunt property() btw ook als decorator gebruiken, dan krijg je zoiets:
Python:
1
2
3
4
5
6
7
8
9
10
11
12
class Person:
    def __init__(self, name, email):
        self._name = "jan janssen"
        self.email = "a@b.com"

    @property
    def name(self):
        return self._name.capitalize()

    @name.setter
    def name(self, name):
        self._name = name.capitalize()

Of lambda's voor simpele getters:
Python:
1
name = property(lambda self: self._name.capitalize())


Het leuke van een dynamische taal is dat je ook nog met __getattr__ en __setattr__ aan de slag kunt, maar of dat nu zo netjes is ;)

[ Voor 39% gewijzigd door user109731 op 13-12-2008 14:50 ]


Acties:
  • 0 Henk 'm!

Verwijderd

phobosdeimos schreef op zaterdag 13 december 2008 @ 14:07:
Een enkele underscore voor private variabelen, dubbele underscore voor echt "interne" zaken, want dan gaat python name mangling toepassen (dan kan je er dus echt niet meer aan van buitenaf).
Je kan het bekijken als "_" een waarschuwing is "dit is privaat", en "__" is nog sterker, een verbod als het ware.
Gooi alsjeblieft je Java balast overboord, anders ga je gewoon Java of PHP code schrijven in Python.
Eenmaal je met Python werkt, zal je pas echt beseffen wat voor een hilarische klucht Java is (ref: http://www.joelonsoftware...ePerilsofJavaSchools.html).
Ohja, name mangling staat gewoon vast zie [url=http://ww.python.org/doc/1.5/tut/node67.html]hier[/url en werkt ook nog steeds in huidige versies. Draai onderstaand voorbeeld maar eens:
code:
1
2
3
4
5
6
7
8
9
10
class Attr:
    __priv = "foo"

    def __init__(self):
        pass

a = Attr

print a._Attr__priv # Deze werkt
print a.__priv # Deze geeft Attribute Error


Python kent per filosofie geen private/protected/public verschil. Het is over aan de netheid van de gebruikers van je code.

Acties:
  • 0 Henk 'm!

  • HarmoniousVibe
  • Registratie: September 2001
  • Laatst online: 10:33
JanDM schreef op zaterdag 13 december 2008 @ 14:18:
Je ontkomt er volgens mij niet aan dat je in de class name in _name moet veranderen en een property moet toevoegen. Maar kun je in de andere class methods niet gewoon name laten staan zodat je de property aanroept?
Helaas, dat lijkt niet te werken. Hij overschrijft dan gewoon de property, waardoor ik de uncapitalized name terugkrijg als ik obj.name of self.name aanroep... :(. M.a.w. properties lijken alleen te werken indien je ze aanroept van buitenaf. Beetje vreemd als je het mij vraagt, maar goed.

Jammer dat het niet kan zonder de variabelenaam/scope te veranderen. Dat vind ik juist zo mooi aan de attr_* functionaliteit in Ruby. Dan hoef je alleen attr_accessor :name te vervangen door een getter en een setter als je implementatie wilt toevoegen. Een beetje wat ik in mijn tweede code-snippet probeerde zeg maar. Dat is een veel minder werk en oh zo elegant.

Verder ben ik hard bezig om mijn lava legacy overboord te gooien (tsja, vanuit academisch oogpunt blijft java toch een mooie taal om gestructureerd te leren denken/programmeren), omdat ik inderdaad merk dat het me nogal eens in de weg zit. Hence, mijn neiging om meteen overal maar accessors voor te gaan schrijven toen ik met python/ruby begon.

[ Voor 31% gewijzigd door HarmoniousVibe op 14-12-2008 00:58 ]

12 × LG 330Wp (Enphase) | Daikin FTXM-N 3,5+2,0+2,0kW | Panasonic KIT-WC03J3E5 3kW


Acties:
  • 0 Henk 'm!

  • Brian
  • Registratie: Oktober 2006
  • Laatst online: 05-08 14:09
Eh, misschien een leuke suggestie om een andere topic over te openen over de "perils of java"? Kan nog wel een interessante discussie worden :)

Acties:
  • 0 Henk 'm!

  • HarmoniousVibe
  • Registratie: September 2001
  • Laatst online: 10:33
JanDM schreef op zaterdag 13 december 2008 @ 14:18:
Je ontkomt er volgens mij niet aan dat je in de class name in _name moet veranderen en een property moet toevoegen.

...
OT: Niemand die een geniale manier heeft gevonden om dit probleem te omzeilen?

12 × LG 330Wp (Enphase) | Daikin FTXM-N 3,5+2,0+2,0kW | Panasonic KIT-WC03J3E5 3kW


Acties:
  • 0 Henk 'm!

  • Creepy
  • Registratie: Juni 2001
  • Laatst online: 23-09 21:37

Creepy

Tactical Espionage Splatterer

Opschoon actie..... ff geduld...
En we kunnen weer verder ontopic :)

[ Voor 36% gewijzigd door Creepy op 14-12-2008 21:00 ]

"I had a problem, I solved it with regular expressions. Now I have two problems". That's shows a lack of appreciation for regular expressions: "I know have _star_ problems" --Kevlin Henney


Acties:
  • 0 Henk 'm!

  • Wolfboy
  • Registratie: Januari 2001
  • Niet online

Wolfboy

ubi dubium ibi libertas

LB06 schreef op zaterdag 13 december 2008 @ 21:10:
[...]

Helaas, dat lijkt niet te werken. Hij overschrijft dan gewoon de property, waardoor ik de uncapitalized name terugkrijg als ik obj.name of self.name aanroep... :(. M.a.w. properties lijken alleen te werken indien je ze aanroept van buitenaf. Beetje vreemd als je het mij vraagt, maar goed.
Dat is niet correct hoor, jouw probleem wordt veroorzaakt door het niet gebruiken van new-style classes. Je moet gewoon eventjes object overerven en het werkt wel.

Hier is een voorbeeldje van een werkend stukje code
Python:
1
2
3
4
5
6
7
8
9
10
class Spam(object):
    def __init__(self, eggs):
        self.eggs = eggs

    def get_eggs(self):
        print 'Getting eggs: %s' % self._eggs
        return self._eggs

    def set_eggs(self, eggs):
        print 'Settings eggs to: %s' % eggs


En hier het testje
>>> spam = Spam('spam and eggs')
Settings eggs to: spam and eggs
>>> spam.eggs
Getting eggs: spam and eggs
'spam and eggs'


Over het algemeen kan je er vanuit gaan dat allles wat in Ruby kan, ook in Python kan en vice versa. Er zijn wel wat verschillen uiteraard, maar nagenoeg alle mogelijkheden hebben een vergelijkbare tegenhanger.

Blog [Stackoverflow] [LinkedIn]

Pagina: 1