[Tutor] Good approach regarding classes attributes
Peter Otten
__peter__ at web.de
Sun Sep 7 16:06:01 CEST 2014
Juan Christian wrote:
> On Sun, Sep 7, 2014 at 5:04 AM, Peter Otten <__peter__ at web.de> wrote:
>>
>> It's not a good approach and it's not pythonic.
>>
>> In Python you should avoid accessor functions and (pseudo-)private
>> __attributes ("Python is not Java"). So
>>
>> class User:
>> def __init__(self, id):
>> self.id = id
>> # load attributes
>> self.personname = [personname from JSON]
>> ...
>>
>> user = User(42)
>>
>> is certainly better. You might also consider making the class less
>> dependent
>> of the way you acquire the corresponding data:
>>
>> class User: # in Python 2: class User(object): ...
>> def __init__(self, id, personname, ...)
>> self.id = id
>> self.personname = personname
>> ...
>> @classmethod
>> def from_id(class_, id):
>> # load attributes
>> return User(id, personname, ...)
>>
>> jeff = User(42, "Jeff", ...)
>> jack = User.from_id(43)
>
>
>
> Ok, no pseudo-private attributes. I read it in tutorials from 2013 and in
> the course that I'm taking that this would be a good pythonic way to deal
> with class attributes. They wouldn't be truly private, but someone using
> the program would see the " __ " in the beginning of the attribute and
> wouldn't call it directly, "because we are all adults and such", you
> saying that this approach doesn't exist in real life?
The double underscore plus name mangling is mainly to avoid name collisions
in subclasses; to signal "this is private" a single underscore would
suffice. But you then go on to make the attribute public via a a getter. In
that case my first choice are normal attributes so that you can write
print(user.personname) # pythonic
instead of
print(user.get_personname()) # Javaism
If you want to prohibit the user from doing
user.personname = "Frankenstein"
because the new name is not propagated to the database and the assignment
puts your application into an inconsistent state which you want to avoid by
some "bondage and discipline" you can change personname into a property:
class User:
def __init__(self, id):
...
self._personname = [as extracted from the JSON]
@property
def personname(self):
return self._personname
user = User(42)
print(user.personname) # implicitly calls the personname(self) method
> I can't give all the 8 attributes to '__init__' because I don't even have
> them.
At least not now ;) My suggestion would decouple creation of the User
instance and fetching of user-related data from a server.
> I would call it with ID only and them the API server would return me
> all the info, and then I would set them. I didn't learn '@classmethod'
> decoration yet, but I presume it would work as a 'get()', right? The thing
> is, where 'user with id 43' is stored? You get it using 'from_id' but we
> don't have any list in there the store users, I got confused in that part.
Maybe it becomes clearer with a small change. Instead of the classmethod you
could use a normal function:
class User:
def __init__(self, id, personname, ...):
self.id = id
self.personname = personname
...
def fetch_user_from_server(id):
json_user = fetch data_from_server(id)
return User(id, json_user["personname"], ...)
jim = fetch_user_from_server(42)
If you should later decide that you want to provide a way to allow entering
new users you could use the User class for that, too:
def create_new_user():
personname = input("Name: ") # in real code this would rather be a
# gui dialog or web page
...
return User(None, personname, ...)
new_guy = create_new_user()
save_user_to_server(new_guy)
You don't have to worry that the __init__() method tries to load data for an
inexistent user.
But even if you are sure you'll never do that it is still a good idea to
keep concerns separate, if only to write independent unit tests for the User
class and the server access.
More information about the Tutor
mailing list