[HN Gopher] OO in Python is mostly pointless
       ___________________________________________________________________
        
       OO in Python is mostly pointless
        
       Author : leontrolski
       Score  : 95 points
       Date   : 2021-01-27 19:58 UTC (3 hours ago)
        
 (HTM) web link (leontrolski.github.io)
 (TXT) w3m dump (leontrolski.github.io)
        
       | choppaface wrote:
       | I feel the major reason OO doesn't fit well in Python is because
       | Python was designed to harness "duck typing," which enables
       | polymorphism with less code. Furthermore, attrs / dataclass does
       | away with a lot of the Object boilerplate that most developers
       | would prefer not to write when moving quickly.
       | 
       | To top it all off, context of use matters. For scripts without
       | tests or limited extensibility, the value of OO is mostly in
       | clarity to the reader, and here attrs / dataclass can be very
       | concise and effective.
       | 
       | That said, when the object jungle gets very large, Python gets a
       | lot more tricky. Look at matplotlib or try to write a billing
       | system with a variety of account and transaction types.
       | 
       | Nevertheless it's nice for a post like this to call out the
       | existence of the bijection between OO and 'non-OO' Python
       | programming styles. The issue is worthy of contemplation for any
       | early Python programmer once one gets the feet wet.
        
         | mumblemumble wrote:
         | I'm not sure I follow. Python's duck typing explicitly relies
         | on its object-oriented features. I'm not sure that it's even
         | possible to implement duck typing in a language that isn't
         | object-oriented.
        
       | banthar wrote:
       | Object oriented programming is not about defining classes. It's
       | about using objects. You don't have to define new classes to do
       | OOP. Just use existing classes and objects polymorphically!
       | url_layout.format()         resp.raise_for_status()
       | resp.json()         session.add()
       | 
       | All those calls are dynamically dispatched - the essence of
       | object oriented programming. This is what allows you to not worry
       | about: * which string implementation `url_layout` uses * which
       | HTTP protocol, encryption, authentication, chunking `resp` uses *
       | what database is connected to `session`
       | 
       | You cannot avoid using objects - that's how all modern operating
       | systems work.
       | 
       | Using classes without the need to call them polimorphically just
       | as a nice namespace for methods is a separate issue.
        
       | blt wrote:
       | One good use for Python's OOP is operator overloading. Since
       | Python doesn't have type signatures, you can't overload the
       | "plus" operator with a free function like you can in C++. Writing
       | numerical code without operator overloading is painful.
        
         | ben509 wrote:
         | I'd argue that's not object-oriented programming.
         | 
         | You are using Python's class mechanism, but that's where the
         | OOness ends. The types themselves are immutable value types,
         | carrying no state and reacting to no messages, and they don't
         | exploit a class hierarchy beyond having a common "Number"
         | class. The "methods" don't even behave like methods, e.g.
         | __add__(self, other) has special wiring so it obscures which
         | side of the addition is "self".
         | 
         | That's very different than a classic OO scheme like a UI
         | toolkit.
        
       | mumblemumble wrote:
       | I don't know if one has really made any case at all about OOP,
       | neither for nor against, if one hasn't considered cases that
       | involve any sort of conditional branching.
       | 
       | Because the key problem that OOP is supposed to solve is not
       | lumping bits of data together. The problem that OOP is supposed
       | to solve is using polymorphism to limit the proliferation of
       | repetitive if-statements that need to be maintained every time
       | the value they're switching on acquires a new interesting case to
       | consider.
        
         | bccdee wrote:
         | In practice, I've found that kind of use case for polymorphic
         | dispatch to not be especially common -- meanwhile, most OO
         | languages strongly encourage making everything objects. There's
         | absolutely a time and place for a good abstract interface, but
         | it's always struck me that classes are overused in general.
         | 
         | That's why I really appreciate the go/rust model, where you can
         | tack interfaces and methods onto structs if you want to, but
         | there's no pressure to do so.
        
       | Mikhail_Edoshin wrote:
       | This is basically how I use Python :) Truly, recently I tried to
       | remember how to create a class method so that I can have
       | different constructors and I forgot! Got back go just structures
       | and functions.
       | 
       | Most OO in most OO languages is just polymorphism and inheritance
       | is merely a roundabout way to construct or traverse the method
       | graph for polymorphic objects. It's often much easier to do
       | polymorphism directly and only if you really need it: quite often
       | you can fare well without polymorphism at all and simply write
       | branching instructions.
       | 
       | The useful OO must be about changing state in a controlled
       | manner, but this remains largely unaddressed.
        
       | nicbou wrote:
       | Django's class-based views are a perfect example of well-written
       | OO python.
       | 
       | A view takes a request, gets some data, and renders a response.
       | 
       | Each part of the process (and its subparts) is abstracted in
       | methods that can be overridden. This lets you implement certain
       | things (authentication, formatting, compression, caching,
       | logging, pagination) once in an abstract class or mixin, and add
       | it to all your views.
       | 
       | Of course, Django already has those classes and mixins.
       | 
       | This means you can write very simple views. Just define the
       | queryset and the template, and you're done. Everything else just
       | works, because it's already implemented in well-tested parent
       | classes.
        
       | jmkr wrote:
       | Why not have the client stored in a dictionary, it's just a url
       | location, instead of having a dataclass? You don't even need a
       | "construct_url" function.
        
       | OnlyMortal wrote:
       | Nonsense. Everything been a dictionary is incredibly important.
        
       | david422 wrote:
       | OOP also works well when you want different _behavior_. In your
       | bag of functions you have the same behavior for all objects. If
       | you want different behavior, you're going to have to start adding
       | branches in code that really has no business making those branch
       | decisions.
        
       | nemetroid wrote:
       | When writing Python, I usually try to follow this item from the
       | C++ Core Guidelines (replace "struct" with "dataclass"):
       | 
       | > C.2: Use class if the class has an invariant; use struct if the
       | data members can vary independently
       | 
       | > An invariant is a logical condition for the members of an
       | object that a constructor must establish for the public member
       | functions to assume. After the invariant is established
       | (typically by a constructor) every member function can be called
       | for the object. An invariant can be stated informally (e.g., in a
       | comment) or more formally using Expects.
       | 
       | > If all data members can vary independently of each other, no
       | invariant is possible.
       | 
       | https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines...
        
       | throwawaygal7 wrote:
       | the author is right of course, but in practice he's wrong.
       | 
       | I've been in a 500k loc codebase mostly in python. As things got
       | complicated, instead of the couple of arguments seen in his
       | example you need up with half a dozen or more, or variable
       | argument lists, people passing callbacks with callbacks and all
       | sorts of shit like that instead of just writing an object and
       | context manager or two.
       | 
       | In a large codebase these rapidly become very difficult to reason
       | about and are better structured as objects with a discrete set of
       | operations.
        
         | leontrolski wrote:
         | (Author here) I used to think that, but I've since found myself
         | working on a similar sized codebase where we use very little OO
         | and none of these problems have manifested themselves. Avoiding
         | large function signatures and passing higher order functions
         | around takes a little discipline, but wrapping those things
         | with a bit of OO doesn't make the root problems go away, it
         | just encourages their proliferation. (To reiterate the article,
         | I'm by and large talking application development as opposed to
         | library development).
        
       | dvfjsdhgfv wrote:
       | When Python was being conceived OOP was all the rage, with
       | "everything is an object in Python" motto. Even if you want, you
       | can't have bare structs, you need to create a class. At this
       | point refusing to add a method to it and using external functions
       | to modify its state is just a matter of taste - in many cases it
       | makes sense, and in several it doesn't.
        
       | zmmmmm wrote:
       | So the example manages to evade any need for encapsulation. It's
       | viable because the internal hidden state is so cheap to compute
       | (the full URL) that it can afford to reconstruct it within every
       | call.
       | 
       | But imagine if constructing the url was a more expensive
       | operation. Perhaps it has to read from disk or even make a
       | database call. Now you really need to cache that as internal
       | state. But doing that means external manipulation of root_url or
       | url_layout will break things. Those operations need to be
       | protected now. So do you make "set_url_layout" and "set_root_url"
       | functions? And hope people know they have to call those and not
       | manipulate the attributes directly? Probably you'd feel safer if
       | you can put that state into a protected data structure that isn't
       | so easily visible. It makes it clear those are not for external
       | manipulation and you should only be interacting with the data
       | through its "public interface".
       | 
       | Of course this brings about all the evils of impure functions,
       | mutated state etc. But if you are going hardcore functional in
       | the first place then it becomes rather obvious OO is not going to
       | fit well there, so its a bit of a redundant argument then.
        
       | gfalcao wrote:
       | I disagree that it's mostly pointless because if you're
       | practicing Test-driven development OO provides a great way to
       | abstract agents and their actions.
       | 
       | I've seen a lot of python code that looks like the example from
       | the blog but that's simply just a narrow example and does not
       | reflect all the potential of OO, not only in Python but in any
       | programming language.
       | 
       | The thing I see in common in such poorly designed OO code is that
       | it is usually not covered with unit tests that are actually
       | useful and informative. Sometimes it's just not even tested at
       | all.
        
         | gfalcao wrote:
         | At the end it comes down to how the developer designs their
         | software, as Fred Brooks argued in "No Silver Bullet"
         | (https://en.wikipedia.org/wiki/No_Silver_Bullet)
         | 
         | Brooks goes on to argue that there is a difference between
         | "good" designers and "great" designers. He postulates that as
         | programming is a creative process, some designers are
         | inherently better than others. He suggests that there is as
         | much as a tenfold difference between an ordinary designer and a
         | great one. He then advocates treating star designers equally
         | well as star managers, providing them not just with equal
         | remuneration, but also all the perks of higher status: large
         | office, staff, travel funds, etc.
        
           | gfalcao wrote:
           | Section from "No Silver Bullet"
           | 
           |  _Object-oriented programming._
           | 
           | Many students of the art hold out more hope for object-
           | oriented programming than for any of the other technical fads
           | of the day.
           | 
           | I am among them.
           | 
           | Mark Sherman of Dartmouth notes that we must be careful to
           | distin- guish two separate ideas that go under that name:
           | abstract data types and hierarchical types, also called
           | classes.
           | 
           | The concept of the abstract data type is that an object's
           | type should be defined by a name, a set of proper values, and
           | a set of proper operations, rather than its storage
           | structure, which should be hidden.
           | 
           | Examples are Ada packages (with private types) or Modula's
           | modules.
           | 
           | Hierarchical types, such as Simula-67's classes, allow the
           | definition of general interfaces that can be further refined
           | by providing subordinate types.
           | 
           | The two concepts are orthogonal -- there may be hierarchies
           | without hiding and hiding without hierarchies.
           | 
           | Both concepts represent real advances in the art of building
           | software.
           | 
           | Each removes one more accidental difficulty from the process,
           | allowing the designer to express the essence of his design
           | without having to express large amounts of syntactic material
           | that add no new information content.
           | 
           | For both abstract types and hierarchical types, the result is
           | to remove a higher-order sort of accidental difficulty and
           | allow a higher-order expression of design.
           | 
           | Nevertheless, such advances can do no more than to remove all
           | the accidental difficulties from the expression of the
           | design.
           | 
           | The complexity of the design itself is essential; and such
           | attacks make no change whatever in that.
           | 
           | An order-of-magnitude gain can be made by object-oriented
           | programming only if the unnecessary underbrush of type
           | specification remaining today in our programming language is
           | itself responsible for nine- tenths of the work involved in
           | designing a program product.
           | 
           | I doubt it.
        
       | jennyyang wrote:
       | Pointing to someone's bad code example and saying "This is why
       | all OO is pointless", is truly a lazy effort.
       | 
       | Good OOP is good. Bad OOP is bad. That's like every other piece
       | of coding. Some excellent examples of great OO code that I've
       | worked with have to do with having an abstract class to define a
       | data api, and then being able to switch providers seamlessly
       | because the internal interface is the same, and all you need to
       | do is write a vendor-specific inherited class.
        
       | yowlingcat wrote:
       | How long has the author used Python? OO, while responsible for
       | much of the footguns, is also responsible for an enormous amount
       | of Python's expressive power. Being able iterate or key into
       | arbitrary objects as if they were lists or dictionaries is
       | enormously powerful, but all of that structure is OO based. It's
       | a huge part of what "pythonic" means.
       | 
       | > If you've taken the pure-FP/hexagonal-architecture pill
       | 
       | Writing software is not something you take a conceptual "pill"
       | for. You learn new tools that you add to your toolchest and use
       | at your own discretion. Tools which purport to be able to
       | displace a whole arena of otherwise stable, mature, and high
       | productivity tools better be at least comparable if not
       | significantly better to encourage upgrade. FP as a tool
       | definitely comes in handy for quite a few use cases. FP as an
       | ideology? Not so much.
       | 
       | Regarding the latter, I'm getting a little sick of the FP
       | chauvinism that that pretends that pre-existing software and how
       | it was written is obviously inferior to the new, purely FP way
       | without any real persuasive evidence. Code that's written in an
       | overly FP oriented way is no less immune to the same codebase
       | diseases that code written in an overly OO oriented way is. It's
       | the map-territory problem all over again.
        
       | jphoward wrote:
       | I found when I started programming I never used OOP. Then I used
       | it too much. And then recently I use it incredibly sparingly. I
       | think this is most people's experience.
       | 
       | However, there are certain situations where I cannot imagine
       | working without OOP.
       | 
       | For example, GUI development. Surely nobody would want to do
       | without having a Textbox, Button, inherit from a general Widget,
       | and have all the methods like .enable(), .click(), and properties
       | like enabled, events like on_click, etc.?
       | 
       | Similarly, a computer game, having an EnemyDemon inherit from
       | Enemy, so that it has .kill(), .damage(), and properties for
       | health, speed etc.?
       | 
       | I'd really like to know how the most anti-OOPers think situations
       | like this should be handled? (I'm not arguing, genuinely
       | interested)
        
         | thepratt wrote:
         | If we step into the haskell land of monads having explicit
         | functionality kept in individual monads with their own
         | instances would be one way to segment this type of stuff.
         | Something vaguely written as below would let you run actions
         | where anyone can move or is an enemy, and default
         | implementations can be provided as well.                   data
         | Demon = { ... }              data Action           = Dead
         | | KnockedBack           | Polymorphed              class
         | Character a where           health :: Int              class
         | (Character a) => Movement a where           speed :: Int
         | class (Character a) => Enemy a where           kill :: a ->
         | Action           damage :: a -> Action              instance
         | Character Demon where           health = 30
         | instance Movement Demon where           speed = 5
         | instance Enemy Demon where           kill _ = _
         | damage _ = _
         | 
         | https://soupi.github.io/rfc/pfgames/ is a talk going through an
         | experience building a game in a pure fp way with Haskell and
         | how they modelled certain aspects of game dev. Most of the code
         | examples are when you press down in the slides.
        
         | beaconstudios wrote:
         | the patterns can still be completely the same without OOP -
         | pairing data and functions that operate on said data.
         | enemy.damage() and Enemy::damage(enemy) are functionally and
         | semantically equivalent. But in the latter case (where you
         | separate data and code) you don't need to worry about object
         | assembly/IoC, how to pass a reference to A all the way through
         | the object graph to object B, composition over inheritance
         | becomes the default (at least in my experience using TypeScript
         | interfaces, YMMV with other languages). The benefits of OOP,
         | primarily state encapsulation, stop looking like benefits when
         | it turns out your state boundaries weren't quite right.
         | 
         | Of course I'm biased as I went through the same "procedural =>
         | OOP => case-by-case" learning curve as the GP. But I ended up
         | spending a lot of time trying to satisfy vague rules when using
         | OOP - with procedural/functional programming with schema'd
         | data, I get to spend a lot more time on what I actually want to
         | do. Not worrying about SRP, SOLID, object assembly, how to fix
         | my object graph now that A needs to know about B, and so on.
        
           | jcelerier wrote:
           | > how to pass a reference to A all the way through the object
           | graph to object B
           | 
           | you just end up replacing the object graph by the call graph,
           | which makes all the business logic much messier as now every
           | function call takes a "context" argument
        
             | beaconstudios wrote:
             | That's not been true in my experience - what you do end up
             | with is a global data structure, which is the shared state
             | for all or most non-ephemeral top level concerns. Aside
             | from recursive functions I find call stacks tend to be
             | quite short.
        
         | [deleted]
        
         | pje wrote:
         | > I cannot imagine working without OOP ... in GUI development
         | 
         | What is React but essentially a (wildly popular) functional GUI
         | framework?
        
           | joeberon wrote:
           | But react isn't the widget library
        
         | leontrolski wrote:
         | In game development even there seems to be a shift away from
         | OO, to data + functions under the guise of "Data
         | Orientated/Driven Development" - eg:
         | https://www.youtube.com/watch?v=0_Byw9UMn9g
         | 
         | Edit: ignore me - this person seems to know more what they're
         | talking about - https://news.ycombinator.com/item?id=25933781
        
         | setr wrote:
         | At least regarding games, ECS is the popular flavor-of-the-
         | month alternative to OOP as a design strategy. The main problem
         | being that games often have a _lot_ of special cases, which
         | break the inheritance hierarchy quite quickly.
         | 
         | E.g. Defining a weapon > {sword, wand} hierarchy, with
         | respective properties for melee and casting, and then defining
         | a unique weapon _spellsword_ which is capable of both melee
         | _and_ casting. You could inherit from weapon, and copy  & paste
         | sword/wand code, or inherit from sword/wand, and copy & paste
         | the other, but the hierarchy is broken.
         | 
         | ECS would rather have you define [melee] and [casting]
         | components, and then define a sword to have [melee], wand to
         | have [casting] and spellsword to have [melee, casting]. So
         | instead of representing the relationships as a tree of
         | inheritance, you represent it as a graph of components
         | (properties). And then you generically process any object with
         | the melee tag, and any object with the casting tag, as needed.
         | 
         | And of course then you could trivially go and reach out across
         | the hierarchies and toss [melee] onto your house object and
         | wield your house like a sword -- I don't know why you'd want to
         | do that, but the architecture is flexible enough to do so
         | (perhaps to your detriment).
         | 
         | Dwarf Fortress probably has the best example of this:
         | https://github.com/BenLubar/raws/blob/archive/objects/creatu...
         | 
         | That's probably more an example of "metadata-driven" but it's
         | ultimately the same thing -- an entity in the game is defined
         | by its components, and the job of the game engine is to simply
         | drive those components through the simulation. That particular
         | example has its metadata (e.g. aesthetics:
         | [CREATURE_TILE:249][COLOR:2:0:0]), its capabilities (e.g.
         | [AMPHIBIOUS][UNDERSWIM]) and its data (e.g.
         | [PETVALUE:10][BODY_SIZE:0:0:200]).
         | 
         | And it even has inheritance :-)
         | [CREATURE:TOAD_MAN]            [COPY_TAGS_FROM:TOAD]
         | [APPLY_CREATURE_VARIATION:ANIMAL_PERSON]
        
           | reidjs wrote:
           | Thanks for sharing that Dwarf Fortress file, I never thought
           | about the structure for all those attributes.
        
           | vadansky wrote:
           | I've been hearing about ECS for a decade so it's definitely
           | more then flavor of the month. However the issue is that
           | unfortunately Unreal/Unity are both OO first.
        
             | setr wrote:
             | It's definitely been around but I think Unity's (never-
             | finishing) ECS + Rust gamedev community's focus on it has
             | really spiked its popularity/interest lately. Otherwise
             | pretty much every recommendation/engine is OOP-based, with
             | a few straggling extensions/libraries for ECS here and
             | there.
             | 
             | No idea about usage in industry though, but it comes up
             | randomly e.g blizzard:
             | https://www.youtube.com/watch?v=W3aieHjyNvw
        
           | megameter wrote:
           | The way to really grasp ECS architecture is not to look too
           | hard at the implementations(which are all making specific
           | trade-offs) but to recognize where it resembles and deviates
           | from relational database design. A real-time game can't
           | afford the overhead of storing data in a 3NF schema, but it
           | can design a custom structure that preserves some data
           | integrity and decoupling properties while getting an
           | optimized result for the common forms of query.
           | 
           | The behavioral aspects are subsumed in ECS to sum types,
           | simple branching and locks on resources, where the OOP-
           | embracing mode was to focus on language-level polymorphism
           | and true "black-boxing". Since the assumed default mode of a
           | game engine is global access to data and the separation of
           | concerns is built around maintaining certain concurrency
           | guarantees(the order in which entities are updated should
           | have minimal impact on outcomes), ECS makes more sense at
           | scale.
           | 
           | The implementation trade-off comes in when you start
           | examining how dynamic you want the resulting system to be:
           | You could generate an optimal static memory layout for a
           | scene(with object pools used to allow dynamic quantities to
           | some limit) or you could have a dynamic type system, in
           | essence. The latter is more straightforward to feed into the
           | edit-test iteration loop, but the former comes with all the
           | benefits of static assumptions. Most ECS represents a point
           | in the middle where things are componentized to a hardcoded
           | schema.
        
           | jgwil2 wrote:
           | In case others are wondering:
           | 
           | https://en.wikipedia.org/wiki/Entity_component_system
        
           | anaerobicover wrote:
           | Note for readers who want to search for more: ECS is "Entity
           | Component System"
           | 
           | And Eric Lippert has a fantastic series of blog posts where
           | he also discusses this problem:
           | https://ericlippert.com/2015/04/27/wizards-and-warriors-
           | part...
        
             | setr wrote:
             | exactly the post I was trying to remember when talking
             | about spellswords :)
             | 
             | Those posts are also cool in that defining games as a set
             | of rules that operate on things within it is a really
             | _neat_ mental model -- the program should basically look
             | like a DnD rulebook, with statblocks and all.
        
         | nickjj wrote:
         | > Similarly, a computer game, having an EnemyDemon inherit from
         | Enemy, so that it has .kill(), .damage(), and properties for
         | health, speed etc.?
         | 
         | I'm not a game developer in the slightest but as a gamer and
         | developer I've often thought about similar things a little bit.
         | 
         | In another example, let's say you were playing a game like
         | Diablo II / Path of Exile where you have items that could drop
         | with random properties. Both of those games support the idea of
         | "legacy" items. The basic idea is the developers might have
         | allowed some armor to drop with a range of +150-300% defense in
         | version 1.0 of the game but then in 1.1 decided to nerf the
         | item by reducing its range to +150-200% defense.
         | 
         | Instead of going back and modifying all 1.0 versions of the
         | item to fit the new restrictions, the game keeps the old 1.0
         | item around as its own entity. It has the same visible name to
         | the player but the legacy version has the higher stats. Newer
         | versions of the item that drop will adhere to the new 1.1 stat
         | range.
         | 
         | That made me think that they are probably not using a highly
         | normalized + OOP approach to generate items. I have a hunch
         | every item is very denormalized and maybe even exists as its
         | own individual entity with a set of stats associated to it
         | based on whenever it happened to be generated. Sort of like an
         | invoice item in a traditional web app. You wouldn't store a
         | foreign key reference to the price of the item in the invoice
         | because that might change. Instead you would store the price at
         | the time of the transaction.
         | 
         | I guess this isn't quite OOP vs not OOP but it sort of maybe is
         | to some degree.
         | 
         | I'd be curious if any game devs in the ARPG genre post here.
         | How do you deal with such high amounts of stat variance, legacy
         | attribute persistence, etc.?
        
       | ed25519FUUU wrote:
       | Largely I agree with the author's sentiment, but I'm not sure I
       | totally agree with some of the alternatives. For example I think
       | using an @lru_cache decorator over a `get_...()` function to
       | return variables is much better at all than using a global var.
       | In fact, it looks hackier if you ask me.
       | 
       | https://leontrolski.github.io/sane-config.html
        
       | tabulatouch wrote:
       | OOP is meant for re-use across different projects. Frameworks, or
       | self-contained classes.
        
       | jgwil2 wrote:
       | This may be a bit pedantic, but OOP != inheritance. Inheritance
       | is the worst part of OOP. OOP is also modules and namespaces and
       | encapsulation, all of which are great.
        
         | spaetzleesser wrote:
         | Agreed. Inheritance CAN be very useful but I would consider it
         | an advanced feature for special cases. It's definitely not
         | something that should be taught to beginners.
        
         | socialdemocrat wrote:
         | Things which all exist in non-OOP languages and before OOP
         | became a fad. No OO isn't inheritance, but implementation
         | inheritance is one of the few things which is unique to OOP
         | based programming. Most other aspects of OOP will be found
         | within other paradigms.
         | 
         | More importantly OOP is not merely a bag of syntax and features
         | but a way of thinking about software development and
         | structuring your programs. OOP says you organize your code
         | around objects and the actions done on those objects.
         | 
         | That is something I find that often isn't a great way of
         | structuring your code. But I don't think OOP is useless. I do
         | use OO thinking in my code, just not as much as I used to. I
         | prefer functional thinking. Often I organize code arounds verbs
         | rather than nouns. So one file may be a similar kind of action
         | performed on many different kinds of objects.
         | 
         | E.g. when writing a rocker simulator, I would have one source
         | code file which contained mass calculations for a variety of
         | objects.
         | 
         | Another file would contain rocket thrust calculations.
         | 
         | I would say though that in GUI programming I find that OO
         | thinking tends to make a lot of sense.
        
         | bccdee wrote:
         | I don't think modules or namespaces really count as OOP. And
         | encapsulation can exist in the absence of OOP if the privacy
         | barrier exists at (eg) the module level instead of the class
         | level.
         | 
         | I'd argue that inheritance is really the core of OOP (and by
         | extension methods, which are only different from functions
         | insofar as they interact with inheritance).
        
         | petr25102018 wrote:
         | How are modules and namespaces OOP?
        
           | jgwil2 wrote:
           | To me OOP is about separation of concerns, abstract data
           | types, information hiding. Modules/namespaces support
           | decomposition the same way that objects do, and historically
           | they became popular because of and in tandem with object-
           | oriented languages. So it's true as others have pointed out
           | that they can and do exist in other paradigms; I would argue
           | that is evidence of the influence of OOP and the fact that
           | its best ideas have been incorporated all over the place.
        
             | bccdee wrote:
             | Those are just good programming principles in general
             | though. Abstract data types, information-hiding, and
             | separation-of-concerns were being done in C well before OOP
             | was a thing. Granted, the information-hiding wasn't as
             | sophisticated as it got once OOP was introduced, but I
             | don't think it makes sense to attribute information-hiding
             | to OOP as a result, especially since newer programming
             | language have used visibility attributes in the absence of
             | OOP classes.
        
         | cogman10 wrote:
         | By and large, I think inheritance is a major mistake.
         | Interfaces are the only "good" part of inheritance.
         | 
         | Sure, there are edge cases where inheritance is truly the best
         | way to do things, but those are few and far between.
         | 
         | I cringe every time I dive into some of my day job's more
         | deeply inheritance based code. It is SO hard to make changes
         | there without breaking a bunch of stuff unintentionally.
        
       | gorgoiler wrote:
       | 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.
        
         | Ankintol wrote:
         | > 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
        
           | ssivark wrote:
           | 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.
        
           | fleabitdev wrote:
           | How do you compensate for the lack of type-dependent name
           | resolution? MyModule.foo(my_t) seems verbose, compared to
           | my_t.foo()
        
             | bcrosby95 wrote:
             | FWIW this is how Elixir works. You just do MyModule.foo.
        
             | girvo wrote:
             | Nim handles this quite nicely for you: either syntax works!
        
               | JNRowe wrote:
               | For those unfamiliar, the UFCS1 wikipedia page has an
               | explanation and a few examples.
               | 
               | 1 https://en.m.wikipedia.org/wiki/Uniform_Function_Call_S
               | yntax
        
             | Smaug123 wrote:
             | 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.
        
             | dan-robertson wrote:
             | 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.
        
         | Kototama wrote:
         | Except the binding always implies the object is mutated on
         | changes, which make any form of reasoning or concurrent
         | programming difficult.
        
           | riffraff wrote:
           | Why? There are plenty of OO libraries where method calls do
           | not change the object.
        
         | klyrs wrote:
         | > 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.
        
         | [deleted]
        
         | dfinninger wrote:
         | 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.
        
           | rbanffy wrote:
           | They are far less primitive than Java's - they are classes
           | too, but can't be extended.
        
         | mohaine wrote:
         | > 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,
        
         | mrslave wrote:
         | 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/
        
         | rbanffy wrote:
         | > 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. ;-)
        
           | takeda wrote:
           | > 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.
        
         | tus88 wrote:
         | > 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!
         | 
         | If you weren't an ignoramous you would understand it _can_ be
         | something else, like klass.
        
           | gorgoiler wrote:
           | 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.wiseass, self is
           | the _Elf_ class itself.
        
         | jghn wrote:
         | 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.
        
       | reedwolf wrote:
       | Thems fightin words.
        
       | m4r35n357 wrote:
       | ahem, operator overloading
        
       | ben509 wrote:
       | I think OO caught on because it encourages people to organize
       | their thoughts, and good structure alone brings a tremendous
       | improvement in code.
       | 
       | I think the reaction to it is more the "everything is mutable"
       | approach most OO languages took, which led to things that looked
       | organized but that were a hot mess of side-effects.
       | 
       | With dataclasses (or attrs) you can cut back on that by freezing
       | everything, and still get the clarity of methods that lay out the
       | essential functionality of a type.
       | 
       | In their own example, "get_items" and "save_items" make a bit
       | more sense if they're stashed away in the Client namespace, and
       | you can see that they're essential to what a Client does.
       | 
       | And while Oil's conjecture is probably right, I'm not sure it
       | survives if you add the caveat, "in that same language."
       | 
       | For instance, Python doesn't have a native way of expressing sum
       | types outside of inheritance. That's not to say inheritance,
       | especially an always open style, is a good way of working with a
       | sum type, it's just the only native way.
        
         | dsego wrote:
         | I think OOP caught on because of IDEs with auto-complete
         | functionality, you type object dot and it offers all the method
         | names for you.
        
           | cgrealy wrote:
           | OOP was around long before IDEs were that popular.
        
       | benkuhn wrote:
       | The author's OO example is hard to understand, but they're wrong
       | about why. It's not bad because it's OO, but that it's very badly
       | done OO: the class couples two different concerns (network API
       | client and database). That's why it makes more sense as a bag of
       | functions.
       | 
       | The general version of the point doesn't work very well, and many
       | of the other OO use-cases the author discusses actually work much
       | better than alternatives.
       | 
       | For example, on abstract base classes: if you replace this with a
       | bag of functions I think you end up reinventing virtual dispatch
       | --that is, each function's top level is a bunch of `if
       | isinstance(...)` branches. This is much harder to read, and
       | harder to add new implementations to, than abstract methods. It's
       | also no easier to understand.
       | 
       | (There is a subset of this advice that I think does improve your
       | code's understandability, which is "only ever override abstract
       | methods," but that is very different from "don't use OO.")
       | 
       | For impure classes, the author suggests e.g. using `responses`
       | (an HTTP-level mocking library) instead of encapsulating these
       | behind an interface. This is a fine pattern for simple stuff, but
       | it is _not_ more understandable than a fake interface. The hand-
       | written fake HTTP responses you end up having to write are a lot
       | less readable than a mock implementation of a purpose-built
       | Python interface. (Source: I once mocked a lot of XML-RPC APIs
       | with `responses` before I knew better; it was not
       | understandable.)
        
       | andrewstuart wrote:
       | I found Python OO to be a super elegant and practical way to
       | implement a common API for multiple databases.
        
         | nerpderp82 wrote:
         | One doesn't need OO for that tho, the module system applies
         | just as well to solving that problem.
        
       | 0xTJ wrote:
       | This is - as the kids put it - a very hot take.
        
       | mywittyname wrote:
       | I write a lot of Python code. Objects are absent in most of what
       | I write. But the 10% or so of the time I use objects, it is
       | because objects are the best way I can think of to structure the
       | program.
       | 
       | So I don't think objects in Python are useless at all. I think
       | Python is designed in a way that allows them to be used
       | effectively if desired, or avoided all together when there's no
       | apparent benefit.
        
         | pmontra wrote:
         | I maintain a couple of Django projects for a customer of mine.
         | I wrote almost 50% of the code base. Those two projects use
         | very few classes, probably only the models. Everything else is
         | a bag of functions organized in modules.
         | 
         | It's very similar to an Elixir /Phoenix project I'm working on
         | for another customer. Modules and functions and a couple of
         | dozens of GenServers that act as objects to maintain status
         | where we need it.
         | 
         | And yet, I easily follow the OO model of Ruby of Rails. I feel
         | it right in that context, probably because the rails of RoR are
         | more well defined than the ones of Django. Of course when I
         | script in Ruby I bet rarely write a class. A few functions,
         | actually methods of Object, are all I need there.
        
       | tasty_freeze wrote:
       | The example given in the article is not compelling -- there are
       | only two arguments in play, so passing two arguments directly
       | instead of passing an object holding those two things isn't
       | painful.
       | 
       | To me the big win of classes isn't inheritance, it is that
       | functions and related data live in a common scope.
       | 
       | If you write your code as a bag of data structures and a bag of
       | functions that you pass those ad-hoc data structures to, it is
       | less clear which things play together.
        
         | commandlinefan wrote:
         | Not only that, but the second example _is_ object-oriented, he
         | 's just doing it "by hand" rather than taking advantage of the
         | built-in language syntax (which, in spite of his insistence to
         | the contrary, does make it clearer what's actually going on).
        
         | socialdemocrat wrote:
         | I would say that is simply down you you not being experienced
         | with writing code this way.
         | 
         | Once you begin writing functional code more regularly, you will
         | get much the opposite way of thinking. Today I find it
         | frustrating that I need an object first. In my mind, I already
         | know what action I want to do. I know what function I want to
         | call. I start with that and look at what arguments it takes in.
         | 
         | OOP programming is frustrating now, because instead of going
         | straight for the function I have to locate the appropriate
         | object first. That is like a big detour.
         | 
         | > If you write your code as a bag of data structures and a bag
         | of functions that you pass those ad-hoc data structures to, it
         | is less clear which things play together.
         | 
         | Noting suggest you need to write code like that. People who
         | write functional code tend to have a lot of well defined data
         | types. I use well defined data types in Julia. Haskell and
         | OCaml developers use well defined data types. We are not using
         | using dictionaries or something.
        
       | kwdc wrote:
       | I've done plenty of maintenance programming. Most OO involves
       | abuse of the paradigm anyway.
       | 
       | Most people don't use inheritance to large AND good effect. Or
       | they use it and create a mess. Deep hierarchies of inheritance
       | need to be carefully designed or they become a mess. All the
       | hiding means less clarity about what is happening when things go
       | wrong.
       | 
       | That said, its a valid approach and can yield useful results.
       | Especially when you can just add a thing to a list and simply
       | call a method on it without having to bother to know what it is.
        
         | jope12 wrote:
         | >Deep hierarchies of inheritance need to be carefully designed
         | or they become a mess.
         | 
         | Deep hierarchies of _anything_ need to be carefully designed.
        
           | brixon wrote:
           | I try to use the rule of after three levels of inheritance or
           | abstraction then it needs to be a black box and then you can
           | reset your count.
        
       | canada2us wrote:
       | One will appreciate OO once he/she saw a 2000-line code in a
       | single file.
        
         | bccdee wrote:
         | You don't need to use classes to break a big file into several
         | smaller files.
        
       | acdha wrote:
       | This post says more about the author's experience than Python,
       | especially with the list of exceptions. The common pattern behind
       | those exceptions is that they're not as simple as the examples,
       | which gets to a better lesson that OO is not pointless but that
       | you should use it cautiously when you know that your problem
       | domain has a sufficiently complex combination of state and code.
       | Anything is going to seem simpler when you have 2 variables and
       | the whole thing is 20 lines and while it's definitely useful to
       | pause before accreting complexity it's also important to remember
       | that projects and teams come in many different forms and there's
       | no guarantee that something which works for you on a project now
       | will be the best option for someone else with different needs.
        
         | leontrolski wrote:
         | Hi, I think you're correct in some domains - I probably should
         | have spelt out that I'm by-and-large talking about application
         | development, where if you have a "sufficiently complex
         | combination of state and code" you probably have a problem
         | rather than a candidate for wrapping in a class.
        
           | yowlingcat wrote:
           | > where if you have a "sufficiently complex combination of
           | state and code" you probably have a problem rather than a
           | candidate for wrapping in a class
           | 
           | Ironically, I think most people in that situation would agree
           | with you that they "probably have a problem" -- the
           | difference is that they see the problem as the important
           | thing to focus investing resources into solving whereas you
           | see its existence as an inherently poisoned entity that must
           | be conceptually purified. While both parties would probably
           | agree on the existence of /a/ problem, the definition of what
           | needs to be addressed and how is likely to differ, and I
           | can't say that the latter attitude is the norm at any high
           | productivity engineering organization I've ever worked at,
           | built, or encountered.
        
         | megameter wrote:
         | A decent rule of thumb I sometimes apply is:
         | 
         | 1. What I have could be formalized into a state machine. 2.
         | That state machine needs to be reused and re-entered. 3. I want
         | to apply inputs to the state machine with method calls.
         | 
         | Of course you can overapply the thought and end up with an
         | enterprise architecture - that's why YAGNI is important. But
         | when a section builds up a bit of conceptual redundancy there's
         | usually a state machine that can be pulled out of it in OO
         | form.
         | 
         | And I would certainly do it that way in Python, as well as
         | other languages.
        
       | KaiserPro wrote:
       | Having recently started a script that avoided OO because I
       | wrongly assumed that its was going to be 4 functions and done, I
       | disagree.
       | 
       | I don't care about inheritance, that's just a way for someone to
       | trip you up and make them feel smug about being "elegant"
       | 
       | I don't care for people trying to hide stuff with
       | self._hiddenThing. Its python, nothing is hidden. If I want to
       | reach in and grab your class by the ankles, prepending a "_"
       | isn't going to stop me.
       | 
       | I agree wholeheartedly with dataclasses. I just wish that type
       | annotations were actually enforced at run time. At the moment
       | they are only really useful if you have pyre turned up to 11 (bye
       | bye productivity) or have an IDE that understands type hints.
       | 
       | but, the hill I will die on is this: storing class state in
       | self.blah. It is correct and proper to do that in a class, it
       | limits the amount of args I have to shove into a function/method,
       | it also allows me to pull out and check things before I do them.
       | Yes they can be abused. Yes it means that you need to call
       | functions in order. Yes it means you have to be more defensive
       | about missing state.
       | 
       | But you need to be careful about that anyway. with minimal OO you
       | can avoid a lot of typing and nasty verbose function args in
       | python.
        
       ___________________________________________________________________
       (page generated 2021-01-27 23:00 UTC)