Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

In general, object orientation is a reasonably elegant way of binding together a compound data type and functions that operate on that data type. Let us accept this at least, and be happy to use it if we want to! It is useful.

What are emphatically not pretty or useful are Python’s leading underscores to loosely enforce encapsulation. Ugh. I’d sooner use camelCase.

Nor do I find charming the belligerent lack of any magical syntactic sugar for `self`. Does Python force you to pass it as an argument to make some kind of clever point? Are there psychotic devs out there who call it something other than `self`? Yuck!

And why are some classes (int, str, float) allowed to be lower case but when I try to join that club I draw the ire from the linters? The arrogance!

...but I still adore Python. People call these things imperfections but it’s just who we are.

PS I liked the Python5 joke a lot.



> Are there psychotic devs out there who call it something other than `self`? Yuck!

I have, under duress. It was a result of using syntactic sugar wrapping a PHP website to make a Python API; it was convenient to pass form variables into a generic handler method of a class. The problem? The website, fully outside of my control, had a page which had an input named "self" which resulted in two values for that argument. Rather than refactor the class and dozens of scripts that depended on it, I renamed 'self' to 's_lf' in that one function and called it a day.

Also, python has class methods. The convention is to use 'cls' in that context, to avoid confusing the parameter (a class) with an instance of that class.


for my own code, I use 'me' instead of 'self'. Just makes more sense to me; e.g. me.variable that is, the one referenced right here ('me') is being referenced, not from anywhere else. It's also shorter; and I think more descriptive. But that's just me.


me.variable sounds Irish to me, at least compared to my.variable.


And my.variable sounds Perlish.


That's because Perl was intended (Larry Wall was trained as a Linguist) to somewhat mimic natural English.


That's a fascinating tidbit. You wouldn't exactly guess it from reading Perl.


>Larry Wall was trained as a Linguist

Having an undergraduate degree doesn't make somebody a linguist. Let me know when he has contributed to the field through scholarly work. Doing Bible translations doesn't count.


> me.variable sounds Irish to me

Only if you're used to the derogatory, clichéd "American TV" version of Irish.


> In general, object orientation is a reasonably elegant way of binding together a compound data type and functions that operate on that data type. Let us accept this at least, and be happy to use it if we want to!

Is it elegant? OO couples data types to the functions that operate on them. After years of working on production OO I've still never come across a scenario where I wouldn't have been equally or better served by a module system that lets me co-locate the type with the most common operations on that type with all the auto-complete I want:

  //type
  MyModule {
    type t = ...
  
    func foo = ...
    func bar = ...
  }

If I want to make use of the type without futzing around with the module, I just grab it and write my own function


Totally. That structure also allows for multiple dispatch, which makes generic programming much much more pleasant than OO (which is basically single dispatch). Eg. See Julia.

To elaborate, tying methods with the individual/first argument makes it very difficult to model interactions of multiple objects.


> tying methods with the individual/first argument makes it very difficult to model interactions of multiple objects.

The best example of this in Python is operator overloading. We need to define both `__add__` and `__radd__` methods to overload a single operator. Even then, there are situations where Python will call one of those where the other would be better.


How do you compensate for the lack of type-dependent name resolution? MyModule.foo(my_t) seems verbose, compared to my_t.foo()


Most of the time you have:

- short names inside modules. I.e. you might have a function called Foo.merge(...) instead of x.merge_with_foo(...)

- a way to bring modules into scope so you don’t need to specify the name

- not using that many modules. Most lines of code won’t have more than one or two function calls so it shouldn’t matter that much (other techniques can be used in complicated situations)

The key advantage of type-dependant name resolution is in using the same names for different types. You might want to write code like foo.map(...) and it is ok if you don’t know the exact type of foo. With modules you may need to know whether to call SimpleFoo.map or CompoundFoo.map.


If anything, this is an aid to type-checking. Since MyModule.foo takes only things of type t, the type-checker's job is extremely easy and it will help you a lot more in cases when your program is incomplete.

So often when using C#-style fluent APIs I find that I'm completely on my own and have to turn a half-written line into something syntactically correct before Intellisense gives me anything useful. Using an F#-style MyModule.foo, the compiler can tell me everything.


Nim handles this quite nicely for you: either syntax works!


For those unfamiliar, the UFCS¹ wikipedia page has an explanation and a few examples.

¹ https://en.m.wikipedia.org/wiki/Uniform_Function_Call_Syntax


FWIW this is how Elixir works. You just do MyModule.foo.


I agree. Also, here are two things I find not great with OO style apis:

1. Functions with two arguments of your type (or operators). Maybe I want to do Rational.(a + b), or Int.gcd x y, or Set.union s t. In a Java style api I think it looks ugly to write a.add(b) or whatever.

2. Mutability can be hidden. If you see a method of an object that returns a value of the same type, you can’t really know whether it is returning a new value of the same type or if it is mutating the type but returning itself to offer you a convenient chaining api. With modules there isn’t so much point in chaining so that case may be more easily hidden.


I'm sure it enables other things, but I find the "Module.t" thing very inelegant. Names should be meaningful and distinguish the named object from other things that could go in the same place, but "t" is meaningless and there's often nothing else (no other types anyway) for it to distinguish itself from. It feels like a hack, and I much prefer the mainstream dot notation.


Whats the advantage other than some intellisense clean up through hiding some functions? As you're well aware, you can write new functions of OO types as well.

Either you lose private/protected state and the advantages or you replace them with module visible state and the disadvantages.


Isn't that just reinventing classes?


It's the good part of classes (namespacing) without the bad part (inheritance).


Doesn't seem like it necessarily stops inheritance. You need to throw away method references or you're just back to open faced classes.

You can still write new methods that blow away any assumptions previously made about type state,causing bugs. It seems like it could be even worse seeing as you possibly have less access controls for fields.


With the typing system of Haskell, maybe Rust too, you can do a lot to prevent the type from being able to represent wrong state. Immutability also does a lot to prevent issues with false state and public fields.

If you want to prevent people outside the module from adding functions, you can do that in Haskell by only exporting the type-name. This prevents other modules from actually accessing any data inside the type, whilst still allowing them to use the type in functions. This includes sometimes making getter functions to read values from the type. I'm pretty sure Rust allows for something similar.

In general, FP still allows for encapsulation. Moreover, it uses immutability and to some extend the type-system to prevent illegal states of data types.


No, a class is a data type coupled to functions operating on that class. For the purpose of this discussion a module is a way to semantically group types and functions without coupling them (languages like OCaml get fancy by actually typing modules which enables some neat ideas, but is not important to this discussion).


> What are emphatically not pretty or useful are Python’s leading underscores to loosely enforce encapsulation.

IMO Go's use of upper/lower case is even worse than Python's use of underscores. What would be better? Explicitly labeling everything as `public` or `private`, C++/Java style?

> Does Python force you to pass [self] as an argument to make some kind of clever point?

I think the idea is that `a.foo(b)` should act similarly to `type(a).foo(a, b)`.

However, what happens when you define a method and miss off the `self` parameter is crazy. Inside a class, surely `def f(a, b): print(a, b)` should be a method that can be called with two arguments.

> And why are some classes (int, str, float) allowed to be lower case

Historical reasons. AFAIK in early versions of Python these weren't classes at all - the built-in types were completely separate.


> IMO Go's use of upper/lower case is even worse than Python's use of underscores.

Especially when serialising a struct to JSON - only public members are serialised, so your JSON ends up with a bunch of keys called Foo, so you have to override serialisation names on all the fields to get them lowercased.


> However, what happens when you define a method and miss off the `self` parameter is crazy.

@staticmethod

(although why you want this instead of just a module scope function is unclear). If you want this strange behavior, you should be explicit about it.


Python's actual behavior in this case is far stranger than static methods, which are a standard feature of many object-oriented languages (albeit one that's fairly redundant in a language that allows free functions).


I'm not sure what you're saying here.

If you want `def foo(a, b)` inside a class to be a static method, you need to define it as

    class Foo:
        @staticmethod
        def foo(a, b):
            ...
Now `f = Foo(); f.foo(1, 2)` will work.

However, you need to be explicit about this because it is much more common for this situation to arise because someone forgot a self somewhere than for it to have been intentional. Requiring @staticmethod serves to signal to humans that this odd behavior is intentional (but again I'd say that, stylistically, if you're using a staticmethod, just move it out of the class and into the module namespace, module namespaces in python don't bite).


I just don't think the decorator should be required for static methods. If you write `def foo(a, b)` inside a class, the lack of `self` should be sufficient to make `foo` a static method.

IMO it would make sense for Python to look at `a, b` and assume you wanted a static method with two parameters. Instead it assumes you're using `a` instead of `self` and really wanted an instance method with one parameter.


This requires blessing "self" as special (like this in java etc.). Python made a specific choice not to do that (and there are cases where you don't want to use "self", such as metaclasses, classmethods, and (rarely) nested classes that close over their parent class.

Like I said: most of the time, a missing `self` is an error by the user, and so python requires explicit notification both to the language, and to other people, that you're doing what you mean to do, and not just making a mistake.


I think that's the point: requiring the user to explicitly pass "self" rather than making it a language keyword mostly just gives the user an unnecessary opportunity to make mistakes.


Except in Pythonland, "explicit is better than implicit"

https://www.python.org/dev/peps/pep-0020/


Wouldn't it be more explicit if there was a designated keyword that objects could use to access their own members instead of implicitly assuming that any first argument to a method is a reference to them, only called 'self' by convention?


> Wouldn't it be more explicit if there was a designated keyword that objects could use to access their own members

Not really. You end up implicitly injecting some value into local namespaces instead of having a function that takes an argument.

The implicitness of methods working differently than normal functions > the implicitness of classes passing a first argument to methods.


Should super then also be a method argument?


Honestly, I think it would have made some amount of sense to make super a method defined on the base object so that

    def __init__(self, x, y):
        self.super(x, y)
would be the convention. There may be some infra issue with this (and in general, `super` has to be kind of magical no matter how you handle it). But yes, in general I can vibe with "super is too implicit".


Ruby generally (I think? I haven't seen much of Ruby code) uses "@" instead of "self.", and "@@" instead of "ParticularClassName." (btw, "self.__class__" doesn't cut it), and it seems to produce no namespace pollution.


> Are there psychotic devs out there who call it something other than `self`? Yuck!

Sometimes I like to overload operators using `def __add__(lhs, rhs)` and `def __radd__(rhs, lhs)`.


> What are emphatically not pretty or useful are Python’s leading underscores to loosely enforce encapsulation.

Python will not prevent anyone from doing something dumb. It'll just force them to acknowledge that by forcing them to use a convention. As a library writer, I'm free to change or remove anything that starts with an underscore because, if someone else is depending on it, frankly, they had it coming. I can assume everyone who uses my library is a responsible adult and I can treat them as that.

> And why are some classes (int, str, float) allowed to be lower case

Because they are part of the language like `else` or `def`. Only Guido can do that. ;-)


If we look in `collections`, we can see `defaultdict` sitting side-by-side with `OrderedDict` and `Counter`. I've long since resigned myself to the fact that naming consistency is not Python's strong suit :)


The one that bugs me the most is:

    from datetime import datetime


> As a library writer, I'm free to change or remove anything that starts with an underscore because, if someone else is depending on it, frankly, they had it coming. I can assume everyone who uses my library is a responsible adult and I can treat them as that.

Totally agree, as an user, I feel anxious whenever I have to use underscored names. It's great that Python still allows me to do it and there were few times when it was useful, but when it stops working I know it's 100% on me.


> I can assume everyone who uses my library is a responsible adult and I can treat them as that.

What is great about python is that it acknowledges that even adults can forget things. Underscores are an affordance.


PEP 20 -- The Zen of Python [0] ... "Explicit is better than implicit."

In the case of the unnecessarily repetitious `self` it means to violate DRY, make the mundane manual - and therefore error prone - and tedious.

[0] https://www.python.org/dev/peps/pep-0020/


Backwards compatibility. You remember all those folks complaining about Python 2 vs 3? Turns out even renaming Thread.isalive to is_alive caused a ruckus. Imagine how much yelling you'd hear if you changed float to Float.

I've come to like the self variable name convention. It takes so little effort to type and makes the behavior more clear. Well, some aspects of it, I suppose. I make a fair amount of use of unbound methods.


> Nor do I find charming the belligerent lack of any magical syntactic sugar for `self`.

And what would that look like exactly? The Java-like approach, with any non-declared variables being the current object's attributes, wouldn't work because Python doesn't use declarations and can have named objects in any scope. The alternative is to use a "magic" variable appearing out of thin air, like Javascript's "this" -- but this is basically what "self" does, only explicitly.

Any other ideas?


C++ et al's "this" keyword seems perfectly reasonable to me--the whole point of being a member method is that you have some sort of implicit state. Otherwise, why not just make them top-level functions, if you have to take the object as a parameter anyway?


It seems to me that you're conflating two unrelated things. Yes, I agree -- there are perfectly legitimate reasons to implement module level functions instead of class and instance methods; but avoiding typing four extra characters just to be more like some other arbitrary language is not one of them.

Python has a different "philosophy" than "C++ et al". Literally everything is an object, and everything has attributes; some of those attributes are objects that implement the Callable protocol. And it's a syntactic sugar that some of those callables are implicitly passed certain predefined arguments; the default is passing the instance of the object itself. The exact same thing could have been implemented without it, like this:

    foo.some_callable_attribute(foo, another_argument)
Since methods are usually called more often than they are declared, it makes sense to make this first argument the default.


Conversely, I think the beauty is that they _are_ the same as top-level functions, just in a special namespace. This means that you can call "instance methods" directly from a class, i.e. the following are equivalent:

    (1) + 2
    (1).__add__(2)
    int.__add__(1, 2)
This comports with the "explicit is better than implicit" policy in Python. The only "magic" part is when dot-notation is used to call an instance method, which is just syntactic sugar. Another example of this philosophy is that operator overloading is simply reduced to implementing a method with a specific name.

I think a "magic" this keyword can create a lot of nasty edge cases that can be difficult to reason about; the way "this" is used in JavaScript is notoriously complex in ways that it might not be in a statically typed language like C++. What should "this" evaluate to outside of an instance method? What about in a class definition inside an instance method? What if an instance method is called directly instead of on an instance? All of these situations require making their own "rules", whereas in Python the correct answers can be easily reasoned about by starting from first principles.


Decorating with "@", as Ruby does? I.e., "a = 42" assigns to a local variable "a", and "@a = 42" assigns to the instance's field "a". Clearly visible, and no magic variables.


> Nor do I find charming the belligerent lack of any magical syntactic sugar for `self`. Does Python force you to pass it as an argument to make some kind of clever point?

On the class, you can call the method like a normal function (passing the self arg manually). Seems like a nice connection to just raw functions. Also explains how you can add methods after a class has been defined (set a function on it, whose first param is a class instance).


That’s a compelling point.

    zoo = [‘goat4’, ‘tiger7’]
    map(Pet.stroke, zoo)


> Are there psychotic devs out there who call it something other than `self`? Yuck!

I was converting some old Java code to Python recently and I almost decided to switch to `this` just to make the task less repetitive. Luckily, sanity returned after I saw the first def abc(this,


I'd personally contest the notion that binding state and functionality directly together is "reasonably elegant" in the first place.

But ignoring that, it depends on the flavor of object orientation. Yes, the most mainstream style bundles state directly with functionality but not all do. But for instance the CLOS family of OOP maintains separate state and functionality and one binds desired functionality to those classes which should have it. This is not too dissimilar from typeclasses IMO.


It makes a lot of sense for modeling real world objects that can do things and have state.


Which makes a lot of sense for the situations where modeling real world objects that can do things and have state makes a lot of sense. Which isn't as often as the OOP advocates would have one believe.


Just to discuss one point:

> And why are some classes (int, str, float) allowed to be lower case

Also, boolean. These are primitive data types. For instance, in Java there's a difference between int and Integer. I'd assume that Python special-cases these because they are primitive. But I haven't been through the Python internals, so it's only a guess.


It's just for historical/ backwards compatibility reasons. These types have been part of Python from before it had classes. Today, they are classes and can be subclassed, etc, but too much code relies on their existing names to change them to match the modern convention.


They are far less primitive than Java's - they are classes too, but can't be extended.


I'm not sure which language did you mean there, but in Python those classes can be extended.

Also, the main reasons for special cases are historical; also, they predate PEP8 by a long time.


Sorry. That's something that was true in 2.x but not anymore. There used to be UserString, UserList, UserDict classes for serving as base classes. We still have those, but, at least now, we can extend the fundamental classes directly.

They are a bit more convenient than the built-ins.


We can inherit from `list`, `dict` etc., but objects of those classes are still not as flexible as "normal" objects. For example, we can't add an attribute to a list using `l = []; l.a = 1`.


I believe that makes them the same as any other class defined with `__slots__`:

    >>> class Foo:
    ...   __slots__ = ('a', 'b')
    ...
    >>> f = Foo()
    >>> f.a = 1
    >>> f.b = 2
    >>> f.c = 3
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
        AttributeError: 'Foo' object has no attribute 'c'


> Are there psychotic devs out there who call it something other than `self`?

I call it "me".


[flagged]


Sorry I'm late here, but obviously you can't post like this. We've banned the account.

Please don't create accounts to break HN's guidelines with.

https://news.ycombinator.com/newsguidelines.html


You spelled it wrong!

Ruby’s approach is the most delightful, I find. elf is an instance of Elf and in elf.health, self is the elf.

Elf is an instance of Class and in Elf.surpass, self is the Elf class itself.


Ruby's irregular syntax drives me crazy. But it is beautiful.


I gotta say as a Python developer I kinda like this compared to thing = Thing()


Except the binding always implies the object is mutated on changes, which make any form of reasoning or concurrent programming difficult.


You can always mutate objects using functions that return new objects.

If you use methods of an instance you know the object's state may or may not change. If you set attributes, you know for sure they changed. If you use a function that returns a new objects you know you paid the object allocation tax.


I should have written almost always.

You can but it's not common. It's like saying you can write functional programing in C. You can but the whole ecosystem is not doing it.

If you set attributes, you know for sure that they changed at this particular place in the program but later in another position in the program, can you reason about the value of the object? You don't even who mutated it. And don't tell me "you can get its state", I'm talking about reasoning about the program here.

There is a reason why concurrent programming is more difficult than necessary with objects.

My point is that this implicit management of the state in "OOP" brings a lot of problems


If I were to design an idiot-proof OOP model that takes this into account, I'd force sequential processing of mutating methods - if the running method starts a mutation and there is another method doing the same, we roll it back until the previous method finishes.

Performance would suck, but that's life. You can't have your cake and eat it too.


Why? There are plenty of OO libraries where method calls do not change the object.


There is but it's mostly not how OO programs are built. And even assuming you have most of the core of your program behaving properly and clearly with non-mutable objects. What do you gain from having objects at this point?


Python OO is a bolt on. That's why it doesn't have syntactical support for magic functions or self. Nothing prevents you from implementing your own alternate object system. Python 2 had two of them.

As for the linters, just disable the rules you don't like.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: