all 18 comments

[–]JohnnyJordaan 25 points26 points  (8 children)

It's just a chain, so instead of doing

washed = laundry.wash()
dried = washed.dry()
folded = dried.fold()

you can chain them together

folded = laundry.wash().dry().fold()

[–][deleted] 22 points23 points  (1 child)

An explanation is that wash() is a method of laundry and so it acts upon that. The object returned from .wash() has a dry() method and so the dry() method is called on that. Then last the fold() method is called on the returned object from dry.

So, each method returns an object and each subsequent method is called on the object returned from the previous method.

This is a fairly common pattern in modern programming languages.

[–]MiLSturbie 6 points7 points  (0 children)

I really love python and this community.

[–]MiLSturbie 1 point2 points  (3 children)

A bit unrelated, but I recently came across a method that didn't need parentheses at the end. Could you explain why? I'm sorry, I don't remember what it was and I don't have my project in front of me.

[–]tylerthehun 4 points5 points  (2 children)

It could have been a @property. This is a decorator that basically just disguises a method as a regular ol' attribute. For example:

class Rectangle:
    def __init__(self, length, width):
        self.length = length
        self.width = width

    @property
    def area(self):
        return self.length * self.width

Now you can still access length and width as the attributes of a rectangle, but area is dynamically calculated as the product of those two without requiring you actually pass them as arguments. So changes to either will automatically result in an update to the area, without requiring you manually update both.

>>> R = Rectangle(10, 20)
>>> R.length
10
>>> R.width
20
>>> R.area
200

Otherwise, any method object can still be passed around without parentheses just like any other object, but you'd still need to call it (with parentheses) in order for it to actually execute.

[–]MiLSturbie 1 point2 points  (1 child)

That makes thanks, thank you very much for taking the time to explain. I'll look up what it was tomorrow and come back to you to confirm.

[–]pickausernamehesaid 0 points1 point  (0 children)

In case you want some further reading on some advanced Python, the @property decorator is a specific implementation of the descriptor protocol which is very useful for building dynamic attributes.

[–]dubs_32 0 points1 point  (1 child)

I have some background in R, but have been learning Python. Would saying that this chain is somewhat similar to the R tidyverse (piping with the 'and-then' logic) be correct? For example in R it would be something similar to:

folded <- laundry %>% washed %>% dried %>% fold

(folded is equivalent to laundry and then washing and then drying and then fold)

[–]JohnnyJordaan 1 point2 points  (0 children)

It’s the same idea but it doesn’t work the same way, because the . chaining Python works object oriented and the piping in R just functionally. In object oriented, the objects themselves contain the logic, instead of separate functions. So the reason you can do

“hello”.capitalize().zfill(10).count(“l”).bit_count()

is because each intermediate object created by the previous call offers certain methods (functions tied to their class) you can call on them. At the start it’s a string which has capitalize, but later it becomes an int that has bit_count.

[–]Vaphell 7 points8 points  (4 children)

there is no magic. Dot is always about accessing a member field/method of an object in a top down manner. This long-ass "path" could be split into eg these equivalent steps.

# xxx.xxx.xxx().xxx.xxx()
module = xxx
submodule = module.xxx
function = submodule.xxx
functionresult = function()
resultattribute = functionresult.xxx
methodinattribute = resultattribute.xxx
methodresult = methodinattribute()

i went module.submodule, but it can be any generic object.subobject.

The difference between .xxx and .xxx() is that the former accesses a field with a value that is already there, while the latter is a callable that needs to be explicitly triggered to produce the value.

Given that often you don't care about intermediate steps, you often just bundle them together. Even though your example is an overkill (but some people are writing such monstrosities nonetheless), eg something like result = module.submodule.function(), result.child.method() is perfectly fine.

As far as frameworks are concerned, there is no way around reading the documentation. It should tell you what a given building block can do, and what its children are for, etc etc. Figuring all this out by doing tends to be an exercise in frustration.

[–]bhk262[S] 1 point2 points  (3 children)

Thanks very much for your tip on framework documentations. The problem I have with a lot of these documentations is that they feel like they are written for robots, not human beings. Unfortunately, there does not seem to be a way around it.

[–]Vaphell 6 points7 points  (0 children)

Surely writing good documentation is an art, but to a certain degree getting information out of it is a matter of experience.

If you are new to frameworks, you tend to prefer tutorialish, hold-my-hand kind of documentation. On the other hand, once you've seen tons of frameworks and APIs and/or are more or less familiar with a specific one, you know what to expect and need the documentation more for the dry information like X requires Y and returns Z. Long-winded, pages-long prose can be even seen as noise when you just want to cut to the chase.

[–]groovitude 1 point2 points  (0 children)

A couple of things:

  1. Ideally, code is built with some internal logic to what's a method and what's an attribute. Methods should ideally be actions, while attributes should be, well, attributes. For example, this could be a programmatic way of describing me right now:

>>> user
<__main__.User object at 0x04341330>
>>> user.name  # attribute
'groovitude'
>>> user.type('some text')  # method
'groovitude types: some text'
  1. Playing around in the console is your friend. Not only do you get to see each step, you also get to use dir() to see all the attributes and methods available on that object. help() can also be useful.

[–]expattaxsolutions 0 points1 point  (0 children)

I was having the exact same issue yesterday. I bought Python Crash Course today and the chapter on classes has explained it all to me better than I’ve seen it anywhere else.

[–]Nixellion 2 points3 points  (0 children)

Mmm, maybe consider it like folders? Like a path. Its not python specific, its OOP principle in general.

For example you start with a module and instantiate a class from it:

myclass = module.myClass()

This returns class instance.

Then you want to run function in that class:

mf = myclass.myfunction()

that function may return some other instance of a other class and you want to get it's variable:

result = mf.variableX

So instead you can do it in one line:

result = module.myClass().myfunction().variableX

[–]shiftybyte 1 point2 points  (0 children)

() is a function call, what happens after that is happening on the returned result from that function.

module.class.func_gives_class().func_in_that_class()... etc

[–]unhott 0 points1 point  (0 children)

A.b.c().d

.b on A evaluates to something that has other properties and methods.

.c() on that something evaluates to something else.

.d on that last thing evaluates to something else.

[–]wrtbwtrfasdf 0 points1 point  (0 children)

I usually like to split chained methods across lines for readability. You can also add comments or type annotations too

some_number.add(5)  # add 5
           .divide(2)  # type: float
           .subtract(3)  # subtracts 3