[Tutor] Good approach regarding classes attributes

Steven D'Aprano steve at pearwood.info
Sun Sep 7 20:01:01 CEST 2014


On Sun, Sep 07, 2014 at 12:00:15AM -0300, Juan Christian wrote:
> I'm writing a program that have a 'User' class. This class will have the
> following attributes:
> 
> 1. id
> 2. personaname
> 3. lastlogoff
> 4. profileurl
> 5. avatar
> 6. realname
> 7. timecreated
> 8. loccountrycode
> 
> I'm thinking about writing something like that: http://pastebin.com/7KHB2qQ8

For small code snippets, just include it in the body of your email. This 
is a small code snippet :-)


class User():
    def __init__(id):
        self.__id = id
        [URL Request to call API and get everything using the ID (JSON)]
        self.__personaname = [JSON response personaname]
        self.__lastlogoff = [JSON response personaname]
        [...]
    def get_id():
        return __id
    def get_personaname():
        return __personaname


> Is it a good approach, is this phytonic?

Nope, it's more like Java than Python. And it's buggy.

Here are some questions you should ask yourself:

- Are you likely to subclass User? If you do subclass, is it reasonable
  to treat the fields as part of the public API?

- Why are coupling the User class to the database? That makes it hard
  to separate the construction of a User (say, for testing) from
  database lookups.


This is my suggestion for a Pythonic approach, with some of the bugs 
fixed, and using more Pythonic naming conventions.


class User(object):
    # Class attribute is shared by all instances.
    _database = XXX  # reference to a database

    def __init__(self, id, persona_name, last_logoff, profile_url, 
                 avatar, real_name, time_created, loc_country_code):
        # Data validation is left as an exercise.
        self.id = id
        self.persona_name = persona_name
        self.last_logoff = last_logoff
        # [etc. ...]

    @classmethod
    def fromid(cls, id):
        args = cls._database.lookup_by_id(id)  # or some method
        return cls(*args)


And that's pretty much it for the initial version. Some features:

- There is a class attribute (and therefore shared by all instances) 
  called _database. In the Java world, I think that would be called 
  a "static variable". The leading underscore makes it a private 
  attribute by convention. By making this an attribute rather than
  hard-coding it inside methods, it makes it easy to override during
  testing:

    saved_database = User._database
    User._database = Mock()
    # continue as usual, with no further changes needed
    # when you are done:
    User._database = saved_database

- The initialiser method __init__ takes eight explicit arguments, plus
  "self". This enables you to create instances without reading them from
  the database, e.g. creating them on the fly, reading from an INI file, 
  or any other source. This is especially useful during testing.

  However, the downside of this is that you need to add argument 
  validation, since you can no longer assume the database has validated
  all the values. Or, you can just trust the caller knows what they are
  doing.

- There's an alternative constuctor offered, to support the case where 
  you do want to read the arguments from the database. So you can create
  Users two ways:

    instance = User(1234, 'fred', ...)  # provide all the arguments

    instance = User.fromid(1234)  # or via database lookup


We can extend this minimal version. Suppose you want writing to the 
attributes to update the database. We do this by making all the 
attributes computed properties, with an extra private method.

class User(object):
    # Class attribute is shared by all instances.
    _database = XXX  # reference to a database

    # The init method stays the same.
    def __init__(self, id, persona_name, last_logoff, profile_url, 
                 avatar, real_name, time_created, loc_country_code):
        # Data validation is left as an exercise.
        self.id = id
        self.persona_name = persona_name
        self.last_logoff = last_logoff
        # [etc. ...]

    # But now we add a bunch of properties.

    def _get_id(self):
        













        [URL Request to call API and get everything using the ID (JSON)]
        self.__personaname = [JSON response personaname]
        self.__lastlogoff = [JSON response personaname]
        [...]
    def get_id():
        return __id
    def get_personaname():
        return __personaname











> _______________________________________________
> Tutor maillist  -  Tutor at python.org
> To unsubscribe or change subscription options:
> https://mail.python.org/mailman/listinfo/tutor



More information about the Tutor mailing list