PHP, Programming, Python

No need for set/get methods in Python

Python code doesn’t typically use the get and set methods so common in PHP. Normally, when writing PHP code, you carefully protect your instance variables by making them private, so callers can only interact with them via getter and setter methods. For example:

class Book {
  private $title;

  public function setTitle($title) {
    $this->title = $title;
  }

  public function getTitle() {
    return $this->title;
  }
}

$book = new Book;
$book->title = 'Code Complete';

However, in PHP, the code above returns the following error:

Fatal error: Cannot access private property Book::$title

Python’s solution to this problem is more readable, it has a construct called a “property”. Properties are a neat way to implement attributes whose usage resembles attribute access, but whose implementation uses method calls. These are sometimes known as “managed attributes”. Basically, a property is a way to make a function call look like an instance variable reference. For example:

class Book {
  private $title = property(getTitle, setTitle);

  public function setTitle($title) {
    $this->title = $title;
  }

  public function getTitle() {
    return $this->title;
  }
}

$book = new Book;
$book->title = 'Code Complete';

Whenever someone writes $book->title, they’re really calling $book->getTitle(). Although the class has getters and setters, the callers of the class still get to use the original, simpler, easier-to-read syntax for accessing the value.

This way you get the best of both worlds: clean and simple client access to your class, and protection and flexibility within the class.

Examples

Python (run code)

class Book(object):

    def get_title(self):
        return self._title

    def set_title(self, title):
        self._title = title

    title = property(get_title, set_title)

Book.title = 'Code Complete'

PHP (run code)

class Book {
  private $title;

  public function __get($property) {
    $method = 'get' . ucfirst($property);
    if (!method_exists($this, $method)) {
      throw new Exception('No such method: ' . $method);
    }
    return $this->$method();
  }

  public function __set($property, $value) {
    $method = 'set' . ucfirst($property);
    if (!method_exists($this, $method)) {
      throw new Exception('No such method: ' . $method);
    }
    $this->$method($value);
  }

  public function setTitle($title) {
    $this->title = $title;
  }

  public function getTitle() {
    return $this->title;
  }
}

$book = new Book;
$book->title = 'Code Complete';

As you can see in the example above, PHP allows you to do something similar using the __get and __set methods, but can be a bit tricky to get right. Python’s “property” construct lets you do this painlessly.

17 thoughts on “No need for set/get methods in Python

  1. In your hypothetical example, wouldn’t the call $this->title = $title not result in an infinite loop?

    Oh well, its not like it currently exists :) PHP does have __set and __get, but its nasty

  2. We can use __get/__set for creating something like this. If property($getter, $setter) return some kind of object and in __get/__set check if the property is is from this object and then execute them (but this I think will have some speed and performance problems)

  3. You can do the same thing in PHP5 using __get() and __set().

    class Book {
    private $properties;

    function __get($property) {
    return $this->properties[$property];
    }

    function __set($property, $value) {
    $this->properties[$property] = $value;
    }
    }

    $book = new Book();
    $book->title = ‘Code Complete’;
    echo $book->title;

  4. @Michael: I don’t see how your example resembles Python’s solution. What happens if the requirements change and you have to convert the title of the book to lower-case?

    I’ve added a Python and PHP example.

  5. class Book {
    private $properties;

    function __get($property) {
    return strtolower($this->properties[$property]);
    }

    function __set($property, $value) {
    $this->properties[$property] = $value;
    }
    }

    $book = new Book();
    $book->title = ‘Code Complete’;
    echo $book->title;

  6. @Sam: Yes, but what happens if a new variable is introduced, for example, author? Here’s your example: http://codepad.org/3jj562He

    1. You are converting all the values to lower-case. That’s not part of the requirement.
    2. The callers of the class are accessing an attribute that was never implemented: author.

    An object should store its state in variables and expose its behaviour through methods, that’s a fundamental principle in object-oriented programming.

  7. @Radoslav: Yes, that’s a valid example. It’s similar to the one I posted before. And that’s exactly the point, to show how useful the property construct is.

  8. I use a technique using __call() instead of __get() and __set(). propertys are get and set using the convention used for functions like session_id(), where the function called with no args will return the current value, and the property can be set by calling with the new value, ie $object->property() returns, $object->property(“new value”) sets.

    the __call() function checks against a list of available variables, so principles of data hiding can be adhered to where required.

    this method has the added advantage of being able to overwrite this default behaviour by adding in any other function, as in the requirement above of adding in a new filter/modifier on a property.

    the __call() function can be seen at http://pastebin.com/f28429455. the code is ripped out of my current DataObject class and has some additional code to allow all objects to return the objects ID and load status.

  9. Remarks from a pythonista:

    * Don’t use double underscore prefixes. Ever. It’s bad style. A single underscore is all you need, don’t go for more, name mangling wasn’t introduced to create pseudo-private members.

    * While I realize it’s for the sake of the example, don’t create empty properties (the ones you’ve created here) either, they create indirection and make the code slower and more complex for no gain. You can transparently introduce properties later if and when you need them.

  10. Another remark: with Python 2.4 or more recent, you can use the `property` function (because it’s nothing more) as a decorator if you want e.g. to create a read-only property or make a member read-only:

    >>> class Foo(object):
    ….def __init__(self, some_value):
    ……..self.some_value = some_value

    >>> foo = Foo(42)
    >>> foo.some_value
    42
    >>> foo.some_value = 5
    >>> foo.some_value
    5
    >>> class Foo(object):
    ….def __init__(self, some_value):
    ……..self._some_value = some_value
    ….@property
    ….def some_value(self):
    ……..return self._some_value

    >>> foo = Foo(42)
    >>> foo.some_value
    42
    >>> foo.some_value = 5

    Traceback (most recent call last):
    ..File “”, line 1, in
    ….foo.some_value = 5
    AttributeError: can’t set attribute
    >>>

  11. Hi masklinn,

    > Don’t use double underscore prefixes. Ever.
    Changed. Thanks for the tip.

    > with Python 2.4 or more recent, you can use the ‘property’ function
    Nice :)

  12. Well, those both seem obsolete after seeing how it’s done in ruby.

    class Book
    attr_accessor :title
    end

    which gives you

    book = Book.new
    book.title = ‘rftw’
    puts book.title # => “rftw”

  13. Instead of
    Book.title = ‘Code Complete’
    shouldn’t write:
    book = Book()
    book.title = ‘Code Complete’

    With:
    book._title = ‘foo’
    I can modify the property from the outside anyway … right?

  14. I love how the Ruby guy thinks his example of syntactic nihilism makes sense to anyone else.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s