http://qs321.pair.com?node_id=11106201

Dear monastery,

In my never ending desire to study different language designs I had a closer look into Python's OO model ...

... and I was very surprised to see that there attributes are accessed directly.

object.attribute = 10

is very common "pythonic" code.

Doing something like object->{attribute}=10 is not only very uncommon in Perl but also heavily frowned upon.

This is only partly because Perl's inner implementation is not necessarily a blessed hash and because in Perl methods and attributes have separated namespaces. (In python much like JS they are inside the same hash)

The main issue is that at the moment of object design you can't know how the nature of getting and setting attributes may evolve in the future...

... for instance you may later want to restrict allowed values to a range of values and the setter to act like a guard which can potentially report errors.

Could it be that the python community is totally ignorant of this issue?

Seems not, for this case they reserve a backdoor mechanism called property where a simple attribute value is replaced by another object with dedicated get, set, delete accessors. And to facilitate using it they provide a @property decorator as syntactic sugar. "@Decorators" are the python equivalent of ":attributes" (For deeper insights please see this SO discussion)

This reminds me a lot to the :lvalue technique once discussed for Perl.

We can easily define a getter mechanism for a attribute with

sub attribute :lvalue { return $value }

And returning a tied value could be used to extend it with a proper setter.

sub attribute :lvalue { return $tied_value }

This help provide a object->attribute = 10 interface, were the Store and Fetch of the tied value would act as getter and setters.

I remember seeing this discussed by Damian - not sure if here or in his OO book.

IIRC he dismissed this technique for being to slow.

Questions:

Cheers Rolf
(addicted to the Perl Programming Language :)
Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

PS: this is a meditation seeking for insights... fanboys please spare me with "just use moose propaganda", I'll ignore it.

Replies are listed 'Best First'.
Re: Language design: direct attribute access and postponed mutators (Perl Vs Python)
by soonix (Canon) on Sep 15, 2019 at 19:29 UTC
    Python has __setattr__:
    object.__setattr__(self, name, value)
        Called when an attribute assignment is attempted. […]
      Thanks, but I think this will be called every time a property is accessed.

      That's like tie'ing the underlying blessed hash in Perl, then every read write access to $obj->{attribute} must be handled via FETCH and STORE.

      This must slow down OO handling considerably.

      The mechanism I described above will only affect especially "enhanced" properties and only when accessed over the external interface.

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

Re: Language design: direct attribute access and postponed mutators (Perl Vs Python)
by Corion (Patriarch) on Sep 15, 2019 at 21:26 UTC
    object.attribute = 10
    is very common "pythonic" code. Doing something like object->{attribute}=10 is not only very uncommon in Perl but also heavily frowned upon.

    I'm not sure where you get this "very uncommon in Perl" from. Unless you're using one of the Moo* object builders (or Class::Accessor), this is how you set vales.

    Also, DBI does that as well (but uses tie) behind the curtain.

    Depending on the size of your team and the rate of change of the code (and abstraction), this is bad practice, but Perl has gone a long time without doing the Java-style explicit setters and getters.

      > I'm not sure where you get this "very uncommon in Perl" from.

      I think I've read it in pretty every OO tutorial I've seen, including PBP and Damian's OO book.

      The explanation seemed very obvious to me, since one is directly accessing the internal implementation of a blessed hash.

      1. the internal implementation is not necessarily a hash, one can bless any ref
      2. you can't change the internals anymore once they are exposed *

      > Also, DBI does that as well (but uses tie) behind the curtain.

      Uhm, I ignored this till now ... but DBI is a very byzantine module anyway.

      But you convinced me to s/uncommon/oldfashioned/ , thanks :)

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

      *) OK you can tie the hash/data structure, but this will slow down all internal access to any $self->{attribute}

        I'm not saying that the approach of directly setting/changing data is good design, but it's neither oldfashioned nor uncommon.

        The Moo* "object" builders encourage data hiding, and PBP and Damian's OO book go to even more extremes. Data hiding / data encapsulation certainly are good approaches especially if you are working with a large codebase where different people make changes.

        But none of this warrants "oldfashioned" or "uncommon" as adjective. Maybe "unwise" or simply "direct" instead of "indirect" or "encapsulated" are the better adjectives.

        But all approaches that add another layer of indirection between the intention to set a value and the hash access make things slower.

      I'm not sure where you get this "very uncommon in Perl" from.

      While I have seen it - and used it - in methods of a class, I have very rarely seen it done in users of a class.

      (I can't say about Python code as I have done very little Python coding.)

Re: Language design: direct attribute access and postponed mutators (Perl Vs Python)
by shmem (Chancellor) on Sep 16, 2019 at 11:10 UTC
    Update: is Variable::Magic an option?

    Probably yes, since the following also relies on perl magic.

    are there better options in Perl to upgrade the easier interface?

    Can't think of any. This approach is nice since it allows you define interactions between attributes of objects in a succinct way. To rule out the usage of $object->{attribute} = $value I'd go with Hash::Util::FieldHash which makes the implementation of attributes opaque. Example:

    test file... ...and package

    This is just to give an idea and far from complete. No middle names :-)
    Also, $obj->fullname = [ qw(Xaver Unsinn) ] works, but sets just the 'given' field to the string "ARRAY(0x1234567)", which doesn't DWIM. And so on... update: also, the Person::fullname tied objects aren't teared down when its Person objects go out of scope. Ah well.

    Of course, the $obj->{attribute} syntactic sugar via overload may lead to confusion, since it works for getting, but fails (silently, if there weren't the warnings) for setting the value. The message Not a HASH reference might be better. What good is it to write curlies anyways, if you can omit them?

    This approach lets you define package defaults (or class variables) and could be converted to a singleton class with minor tweaks; the tied() classes could be set up on the fly, and the possiblity of tying hash value slots gives full control about how attributes behave, their behavior could even be different between objects.

    Could it be possible that the python solution is much faster?

    No idea, and frankly, I don't care.

    perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'
      > No idea, and frankly, I don't care.

      I'm always keen to learn from my enemies.

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

        I'm always keen to learn from my enemies.

        Um, enemies. But okay, yes, that's one good approach, but also: I'm comfortable with what they don't want to learn from me.

        perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'
Re: Language design: direct attribute access and postponed mutators (Perl Vs Python)
by Arunbear (Prior) on Sep 16, 2019 at 16:20 UTC
    Although in Python attributes could can be accessed directly, it also has the double underscore mechanism for providing a basic level of attribute hiding e.g.
    % cat stack.py class Stack(): def __init__(self): self.__items = [] def __repr__(self): return str(self.__items) def push(self, x): self.__items.append(x) def pop(self): self.__items.pop() % python3 Python 3.7.4 (default, Sep 7 2019, 18:27:02) [Clang 10.0.1 (clang-1001.0.46.4)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> from stack import Stack >>> s = Stack() >>> s.push(42) >>> s [42] >>> s.__items Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Stack' object has no attribute '__items' >>>
    I.e. prepending two underscores to an attribute name makes it harder to use from outside the class.
      True, but the OP wasn't about hiding private attributes, but about maintaining public attributes.

      Once you've exposed an attribute in the API and and external code is accessing it directly, it becomes complicated to realize "a man in the middle" layer functioning as getter and setters.

      Classic (a bit contrived) example is an attribute .name which needs at some point to evolve to a getter name() returning a concat of .firstname and .lastname (and probably .middlename) without breaking old code.

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

Re: Language design: direct attribute access and postponed mutators (Perl Vs Python)
by jcb (Parson) on Sep 16, 2019 at 01:15 UTC

    I am using the "magic tied object" approach with WARC record headers in a library I am building. I wanted the $fields object to look like a native data structure, complete with (limited to valid data) reference autovivification. Thanks to overload, stuff like push @{$record->fields->{WARC_Concurrent_To}}, $new_record->id; actually works, no matter how many concurrent records $record initially has, creating the WARC-Concurrent-To header if necessary. (The ->fields method returns an object that overloads hash dereference to return a tied hash upon demand and that tied hash's FETCH method returns another magic object that is always a tied array, but overloads string conversion to do an implicit ->[0] if there is only one value for that header. There is also a tied array interface to the fields object if you care about the order in which the fields appear.)

    I am also generally using accessor methods that perform "get" normally and "set" if given a new value where that makes sense; many objects are effectively read-only since they represent data in an archive file. I have no idea how old this pattern is; I have seen it both in other Perl code and in C++. I am not using Moose or anything like it, only baseline Perl 5 "blessed reference" objects.

    The class that provides that fields object ended up being about 1/3 simple API, internals, and POD, and about 2/3 implementation for all the magic it does to make those "easy" interfaces work. It also has the most complex and longest test script (by about a factor of 3) so far. It was the first piece I wrote... and I am not making any more classes that magical in that library.