How can I define a class in Python?

Quite simple, I'm learning Python, and I can't find a reference that tells me how to write the following:

public class Team {
     private String name;
     private String logo;
     private int members;

     public Team(){}

     // Getters/setters
}

Later:

Team team = new Team();
team.setName("Oscar");
team.setLogo("http://....");
team.setMembers(10);

That is a class Team with the properties: name/logo/members

Edit

After a few attempts I got this:

class Team:
    pass

Later

team = Team()
team.name = "Oscar"
team.logo = "http://..."
team.members = 10

Is this the Python way? It feels odd (coming from a strongly typed language of course).


Solution 1:

Here is what I would recommend:

class Team(object):
    def __init__(self, name=None, logo=None, members=0):
        self.name = name
        self.logo = logo
        self.members = members

team = Team("Oscar", "http://...", 10)

team2 = Team()
team2.name = "Fred"

team3 = Team(name="Joe", members=10)

Some notes on this:

  1. I declared that Team inherits from object. This makes Team a "new-style class"; this has been recommended practice in Python since it was introduced in Python 2.2. (In Python 3.0 and above, classes are always "new-style" even if you leave out the (object) notation; but having that notation does no harm and makes the inheritance explicit.) Here's a Stack Overflow discussion of new-style classes.

  2. It's not required, but I made the initializer take optional arguments so that you can initialize the instance on one line, as I did with team and team3. These arguments are named, so you can either provide values as positional parameters (as with team) or you can use the argument= form as I did with team3. When you explicitly specify the name of the arguments, you can specify arguments in any order.

  3. If you needed to have getter and setter functions, perhaps to check something, in Python you can declare special method functions. This is what Martin v. Löwis meant when he said "property descriptors". In Python, it is generally considered good practice to simply assign to member variables, and simply reference them to fetch them, because you can always add in the property descriptors later should you need them. (And if you never need them, then your code is less cluttered and took you less time to write. Bonus!)

Here's a good link about property descriptors: http://adam.gomaa.us/blog/2008/aug/11/the-python-property-builtin/

Note: Adam Gomaa's blog seems to have disappeared from the web. Here's a link to a saved copy at archive.org:

https://web.archive.org/web/20160407103752/http://adam.gomaa.us/blog/2008/aug/11/the-python-property-builtin/

  1. It doesn't really matter if you specify values as part of the call to Team() or if you poke them into your class instance later. The final class instance you end up with will be identical.
team = Team("Joe", "http://example.com", 1)
team2 = Team()
team2.name = "Joe"
team2.logo = "http://example.com"
team2.members = 1

print(team.__dict__ == team2.__dict__)

The above will print True. (You can easily overload the == operator for Team instances, and make Python do the right thing when you say team == team2, but this doesn't happen by default.)


I left out one thing in the above answer. If you do the optional argument thing on the __init__() function, you need to be careful if you want to provide a "mutable" as an optional argument.

Integers and strings are "immutable". You can never change them in place; what happens instead is Python creates a new object and replaces the one you had before.

Lists and dictionaries are "mutable". You can keep the same object around forever, adding to it and deleting from it.

x = 3   # The name "x" is bound to an integer object with value 3
x += 1  # The name "x" is rebound to a different integer object with value 4

x = []  # The name "x" is bound to an empty list object
x.append(1)  # The 1 is appended to the same list x already had

The key thing you need to know: optional arguments are evaluated only once, when the function is compiled. So if you pass a mutable as an optional argument in the __init__() for your class, then each instance of your class shares one mutable object. This is almost never what you want.

class K(object):
    def __init__(self, lst=[]):
        self.lst = lst

k0 = K()
k1 = K()

k0.lst.append(1)

print(k0.lst)  # prints "[1]"
print(k1.lst)  # also prints "[1]"

k1.lst.append(2)

print(k0.lst)  # prints "[1, 2]"

The solution is very simple:

class K(object):
    def __init__(self, lst=None):
        if lst is None:
            self.lst = []  # Bind lst with a new, empty list
        else:
            self.lst = lst # Bind lst with the provided list

k0 = K()
k1 = K()

k0.lst.append(1)

print(k0.lst)  # prints "[1]"
print(k1.lst)  # prints "[]"

This business of using a default argument value of None, then testing that the argument passed is None, qualifies as a Python design pattern, or at least an idiom you should master.

Solution 2:

class Team:
  def __init__(self):
    self.name = None
    self.logo = None
    self.members = 0

In Python, you typically don't write getters and setters, unless you really have a non-trivial implementation for them (at which point you use property descriptors).