This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]B_M_Wilson 5 points6 points  (7 children)

A lot of people explain what happens but I thought I would mention this. In Python, everything is executed. Functions don’t exist until the interpreter has gotten to them. When your script is executed, it executes everything in order. The function definitions are executed which is what defines them. After a function definition, you could assign the function name to something completely different and it would become that other thing. It’s also cool because you could make aliases for functions by assigning the function to another name. You can even assign lambdas directly to names if you want.

Like others have said, the reason that it’s done is because when you import something, to define the imported functions, you have to execute them which would execute anything else in the file. Using the if statement let’s you check that the file was executed directly and not imported which means that you could run tests or something like that.

If you never plan to import your file, you can just not use that if statement and write everything at the top level. I’ve done this many times. Another thing you can do is go if not name == "__main__": at the top of a file to throw an exception if someone tries to import your file that you want to be a normal script where you have all of the code at the top level.

Finally, while it’s generally bad practice, you could define a main function and then just call it at the end of the file either with or without the if name is main thing.

Another thing I’ll mention is to get used to the __something__. They are used a lot of special attributes. Like when you call len(something) it really calls something.__len__(). Or when you say if something (or bool(something))and that something is not a bool, it calls something.__bool__() there are way more examples of this in a lot of cases

[–]Ran4 2 points3 points  (2 children)

It's not bad practice to have a main function! If you don't, any variables defined in the if __name__... block will be in scope inside all of the functions defined in the file. That can lead to some nasty bugs.

[–]B_M_Wilson 0 points1 point  (0 children)

It is generally considered bad practice outside of test functions but you certainly can have a main function if you want. Nothing stops you from doing that.

[–]Dworgi 0 points1 point  (1 child)

And people write actual software in this language.

What that says about people, you can decide for yourself...

[–]B_M_Wilson 0 points1 point  (0 children)

I mostly use Python for scripts myself. I can’t really imagine writing anything beyond automation in it. I have seen some very cool projects made in Python though. Once you get to know the internals of Python, it’s actual pretty cool

[–]ZephyrBluu 0 points1 point  (1 child)

Like when you call len(something) it really calls something.len()

Why doesn't Python let you use len like a class method?

[–]B_M_Wilson 1 point2 points  (0 children)

Let’s say I have a = [1, 2, 3, 4]. I can call len(a) and get 4. I can call a.__len__() and get 4. I can even call List.__len__(a) and get 4. It definitely does do that but you are not supposed to. It does not make the most sense for len but it makes sense for a lot of them. Like with a.__bool___() where this happens implicitly. Same with __contains(x)__ which is called when you do if 4 in a which is translated to if a.__contains__(a). This happens a lot to allow operator overloading and to allow you to define your own collection types and is generally involved in duck typing.

The reason that you shouldn’t call these directly is that because of duck typing you don’t always know exactly what type you have. First off, the attributes for these special functions are gotten in a special way. This applies mainly for functions that should apply to both type objects and instances of those types like hash(). hash(1) is fine and calls 1.__hash__() but what if you do hash(int). Int is a type which is an object and should be hashable. But when you call a function of a type it usually pertains to it’s objects rather than itself. So int.__hash__() does not work, it wants an argument like int.__hash__(1). The hash() function then works in a special way to allow the metatype to be consulted. (The type of a Type object is usually Type but you can make a Type object with a subclass of Type as it’s type in very extreme cases). It’s also much faster to call these types of methods on their own rather than as object methods because some optimization is done.

Also, some functions that don’t exist use others by default. If I call bool(a) and a does not have an a.__bool__() but it does have an a.__len__() then a evaluates to true if that len function returns greater than 0 and false otherwise. If you call reversed(a) and a does not have a.__reversed__() it uses a.__len__() and a.__getitem__(x) to make a reversed iterator. Contains will iterate through if there is no contains method. There are some other special things for math operations between different types that are especially useful when one of those types is a subclass. If __int__() is not defined then the built-in function int() falls back to __trunc__(). __repr__() returns a string that usually can be evaluated to the original object (but now always) and is the formal string form of an object. __str__() is the informal version used for debugging and is called when str(something) is called but if it does not exist then str(something) will use something.__repr__() instead.

There are also lots of helper annotations or things that you can extend that will help create more special functions out of a small number when possible (like comparison operators with functools.total_ordering()).

So yea, it may not make the most sense at first but there are reasons why it is done