Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Comparing PHP's __get() with __get__() and __getattr__() in Python

Tags:

python

php

What is the difference between __get__() and __getattr__() in Python? I come from a PHP background, where there is only __get(). When should I use which function?

I've been trying to figure this out for a while. I see plenty of questions like this one, asking about the difference between __getattr__() and __getattribute__(), though.

like image 231
Brendon Avatar asked Nov 24 '11 14:11

Brendon


2 Answers

First an foremost, PHP does not have an equivalent to Python's __get__() – not even close! What you are looking for is most definitely __getattr__().

I come from a PHP background, where there is only __get__

PHP has a magic method called __get(), which is invoked whenever you are trying to access a property that does not exist.

A short list of non-equivalents

First, let's clear some things up:

  • PHP does not have an equivalent to Python's __get__()
  • PHP does not have an equivalent to Python's __getattr__()
  • PHP does not have an equivalent to Python's __getattribute__()
  • Python does not have an equivalent to PHP's __get()

(And for all setter methods respectively.)

Contrary to Achim's assumption, __get() does not do the same as Python's __getattr__()!
Python does not distinguish between methods and properties, but PHP does, which is why PHP has a second method: __call().

__call() is executed whenever you try to invoke a method on an object that does not exist. Python does not have an equivalent for this, because a method is simply an object (attribute) that is callable.

An example in PHP:

<?php

$obj = new stdClass();
($obj->test)();

In Python, this would fail with an AttributeError. In PHP, however, this does not even compile:

Parse error: syntax error, unexpected '(' on line 4

Compare this to Python:

obj.method()
# is eqvuivalent to:
(obj.method)()

This is an important difference. We conclude that the way PHP and Python think about calling methods are completely different.

PHP's "call awareness"

  • PHP's obj knows that you call a method
    • you get to handle calls to non-existing methods explicitly on the object you call on
    • this is because PHP's expression model is very inconsistent (PHP 5.4 is a small step forward though)
  • but Python's obj does not.

This makes it possible for PHP to have obj.does_not_exist to evaluate to 3, but obj.does_not_exist() to 5.

To my knowledge, it's impossible to do so in Python. (This would allow us to describe PHP's inconsistency as a feature.)

Thus, we get to extend our "not equivalent"-list by one bullet point:

  • Python does not have an equivalent to PHP's __call()/__callStatic()

Summing it up

PHP provides two separate mechanisms:

  • __get() for non-existing properties
  • __call() for calls to non-existing methods

Python has only one mechanism, because it does not distinguish between properties and methods, as far as it is concerned they are all attributes.

  • __getattr__() is invoked, when an attribute does not exist.
  • obj.non_existing() is not special "call syntax", it's an expression to which the call operator () is applied: (obj.__getattr__("non_existing"))()

Disclaimer: __getattr__() is not always called when an attribute does not exist. __getattribute__() takes the highest precedence in the lookup chain and may thus cause __getattr__() to be ignored.

Descriptors

__get__() in Python is something completely different from what has been addressed above.

The documentation describes descriptors to be "a descriptor is an object attribute with “binding behavior”". I can sort of form an intuitive understanding what "binding behavior" is supposed to mean, but only because I already understand what descriptors do.

I would choose to describe descriptors as "self-aware attributes" that can be shared across multiple classes.

Let me explain, what I mean by "self-awareness". Such attributes:

  • know when they are being accessed or read from
  • know when they are being written to
  • know whom they are read/written via
  • know when they are deleted

These attributes are independent objects: the "descriptor" objects, i.e. objects that adhere to the so called "descriptor protocol", which defines a set of methods along with their corresponding signature that such objects can implement.

An object does not own a descriptor attribute. In fact, they belong to the object's corresponding class (or an ancestor thereof). However, the same descriptor object can "belong" to multiple classes.

Note: "whom they are read via", what is the proper way to refer to obj in obj.attr? I would say: attr is accessed "via" obj.

like image 158
phant0m Avatar answered Oct 04 '22 20:10

phant0m


You will find detailed documentation for all those methods here.

Coming from PHP, you should first make yourself familiar with the Python object model. It's much richer than PHP, so you should not try to map your PHP knowledge 1:1 to Python. If you want to develop PHP, use PHP. If you want to develop in Python, learn Python.

Coming back to your original question: __getattr__ is probably the function which does the same as the __get function in PHP. __get__ in Python is used to implement descriptors. Details about descriptors can also be found in the documentation I mentioned above.

like image 23
Achim Avatar answered Oct 04 '22 19:10

Achim