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
.