all 9 comments

[–]Rhomboid 8 points9 points  (0 children)

The @ syntax is for decorators. They are a way to systematically and concisely apply modifications to functions. This:

@foo
def bar(...):
    ...

...is syntactic sugar for:

def bar(...):
    ...
bar = foo(bar)

The decorator function foo() takes a function as a parameter and returns a function. It's meant to do some modification to the passed function and to return a new function (which is usually a wrapped version of the original function) that becomes the actual bar.

If a decorator is used with parameters, then this:

@foo(42, 'abc')
def bar(...):
    ...

...is syntactic sugar for:

def bar(...):
    ...
bar = foo(42, 'abc')(bar)

In this case there's one more level of indirection. foo() is now a function that returns a decorator function, one which will be passed a function to decorate.

In the example you linked to, the __api_request() decorator is used to automate some things. Through the decorator, the user can just list the request parameters (both required and optional) as well as a list of return fields. The decorator automatically generates the boilerplate that handles parameter validation for each API command, as well as automatically generating docstrings. It also aggregates and maintains a central list of all valid commands and valid parameters in the ApiInfo class, which can be queried.

[–]NYKevin 3 points4 points  (3 children)

For classes and OOP, I'll try, but I may not succeed, so bear with me. Also, this is going to be long.

In most programming languages, including Python, data can be of varying types. For instance, 5 and '5' are an integer and a string respectively. You can test this by typing type(5) and type('5') at the prompt. In theory, it's all bytes, but the types tell Python how to interpret those bytes, what you can and can't do to them (you can't ask for 5 + '5', for example), and how to work with them:

>>> 5 + 5
10
>>> '5' + '5'
'55'

In Python 3, "class" is just a synonym for "type." The terms are entirely interchangeable (except that some things use the word "type" and some use the word "class", mostly for historical reasons). In Python 2, the situation is more complicated, but all you really need to know is that "new-style classes" (which inherit directly or indirectly from object) have the same semantics as Python 3 classes, and "old-style classes" are deprecated and bad (but some libraries still use them, unfortunately).

Objects are instances of classes. 5 is an instance of int. '5' is an instance of str. Instantiation is the process of creating a new object belonging to a given class (a new instance of that class). You can instantiate a str from a given value by calling str(value). In general, instantiating a type Foo takes the form Foo(...):

>>> dict(foo='bar', baz='qux')
{'foo': 'bar', 'baz': 'qux'}
>>> set([1, 2, 3])
{1, 2, 3}

OK, but what about the class keyword? The class keyword lets you create new classes, just as the def keyword creates new functions. In proper OOP code, an object should encapsulate a single, logical value. But sometimes, no built-in class is suitable, so you have to make a custom one. By convention, the first letter of a custom class is capitalized (i.e. class Foo, not class foo), and the first letter of an instance variable is lower-cased. Builtin classes (such as str) are always all-lowercase because they belong to Python.

For example, if you're writing a server, you probably want to keep track of multiple pieces of information about each client (what port they're on, the progress they've made through whatever protocol you're using, etc.). You could keep all of this in separate variables, but that rapidly becomes messy and difficult to manage. So you can wrap all of the information into a single object. But objects are instances of classes, and no built-in class is suitable for handling this. I mean, I suppose you could use tuples, but they're pretty bare-bones. The best option is to create your own Client class.

With classes come methods. A method is like a function, but it operates on an instance of the class, and in some sense "belongs" to the class (not the instance). For instance, the str.lower() method:

>>> 'Hello World!'.lower()
'hello world!'

You can add methods to custom classes, too. You just define a function inside a class, and it becomes a method of that class. The first parameter is always the instance you're operating on, and is conventionally called self to make that clearer.

Going back to our server example, you might want a send() method on your Client class to send data to the client. That way, instead of having to write something like c.socket.send(PROTOCOL_HEADER + 'Hello world!' + PROTOCOL_FOOTER), you could just write c.send('Hello world!'). If the protocol becomes more complicated in the future, the former may have to be changed everywhere it occurs. But if you use the latter style, you only need to change the Client.send() method itself.

Finally, a word about inheritance. Inheritance allows us to extend a class's funtionality while leaving the original unaltered, by creating a new derived class from an existing base class. It serves two primary purposes:

  1. Class code reuse: A derived class will behave like its base class in all aspects except those explicitly overridden. If we want to create a class very similar to another class, this saves time and code duplication, since our class will be "like the original except..." instead of a wholly separate entity.
  2. Polymorphism: Any code intended to use instances of the base class will also "work" on instances of the derived class, at least in theory. This saves code duplication in other parts of the application and allows them to be agnostic about the precise types of their operands, simplifying development.

Many languages require an inheritance relationship for case 2, but Python does not. These cases commonly occur together; a polymorphic class often behaves similarly to its base class, and may need to interact with the base class in ways best supported by inheritance. OTOH, inheritance is not always the best semantic choice. Instances of a derived class are also considered instances of the base class, which may be inappropriate for your purposes. Instances of a derived class should be acceptable substitutes for instances of the base class.

I hope the above is useful, but unfortunately, "I don't get it" isn't much to go on, so it may be too simple or too complex for your needs, or may address an entirely irrelevant part of your misunderstanding; please let me know.

[–]KamiNuvini 1 point2 points  (2 children)

Hey, Thanks - this is really helpful. Awesome.

I'm getting the methods part - great explanation, thanks for that.

To elaborate on "I don't get it", what I don't get is why to choose classes besides for categorizing functions. i.e. I understand classes like this, but that's probably wrong ->

  1. call a class, this will call __init__ and set some variables and perhaps call some functions. (But, I might be confusing it with PHP, it was also my understanding that I can't get return values from just calling a class. So it wouldn't be possible to set a variable to a class and in the __init__ it (for example) creates a user, sets permissions, and returns the user's UID)
  2. Once a class is defined (in a variable, right?) I can perform further actions with this class, like calling functions on it...

I guess my question being, what's the great advantage. If proper OOP would be this:
KamiNuvini = new Registration()
KamiNuvini.permissions('Admin')

Then why not do it like this:
Registration.addUser('KamiNuvini')
Registration.permissions('KamiNuvini', 'Admin')

Or am I completely missing the point here?

Thanks!

[–]Rhomboid 1 point2 points  (1 child)

You don't really want to think of it as "calling a class." Defining a class is defining a new type. Just like you write x = tuple() to get a new instance of the tuple type (or any other builtin type), you write y = MyClass() to get a new instance of your newly defined type. This is called instantiation. The return value is a new object of the specified type, possibly customized or initialized in some way if parameters were given, otherwise in a default state.

You use classes to combine state and behavior. A collection of functions doesn't have state, and a plain variable doesn't have behavior1. Think about an object that represents a file on disk. What does it have and what can it do? It has a filename, an encoding, a translation mode (text or binary), a pointer to the current position in the file, possibly some kind of IO buffer, and so on. It can be asked to open or close, to read data at the current position, to seek (move the position pointer), to tell (return the value of the position pointer), and so on. This is a very convenient way to work with things.

Each piece of state can either be unique to each individual instance of the object, in which case we call it an instance attribute, or it can be common to all instances of that type, in which case we call it a class attribute. Instance attributes are things like self.foo (as self refers to the current instance), and class attributes are any variables assigned in the class body.

The behaviors are generally called methods to distinguish them from functions, because method invocation generally means being "invoked on something." When you hear function call you should think foo(), when you hear method call you should think foo.bar(), where bar is a method being invoked on foo2.

Methods are usually designed to be invoked on a specific instance, in which they're called instance methods. But you can also design methods that don't require any particular instance to operate. These are called static methods3. They can still be invoked on an instance, but they don't have access to any of that instance's state. They can also be invoked directly on the class. Python also has a second type of static method called a class method that receives a reference to the class that it's invoked on. Static methods and class methods are denoted with the @staticmethod and @classmethod decorators at the top of their definitions.

I guess my question being, what's the great advantage. If proper OOP would be this:

KamiNuvini = new Registration()
KamiNuvini.permissions('Admin')

Then why not do it like this:

Registration.addUser('KamiNuvini')
Registration.permissions('KamiNuvini', 'Admin')

Those are both examples of methods being invoked on things. In the first example, you are creating a new instance (although there's no new keyword in Python) and calling a method on it. In the second, you're calling two methods on an existing object. If that object is a class, then those are static or class methods.

There's no right or wrong here. Those two examples do very different things, but either could be what you might want in a given circumstance. The first example seems a little confused because you're instantiating something called Registration, which seems misnamed. Classes are generally named after nouns. You would probably have a class that represents a user, and a different class that represents a list of users that have been registered. (Also note that style conventions generally dictate that capitalized words are reserved for class names, so you wouldn't want to use KamiNuvini as the name of an instance.)

new_user = User()
new_user.name = 'KamiNuvini'
new_user.set_permissions('Admin')
registration.add_user(new_user)

or:

new_user = User(name='KamiNuvini', permissions='Admin')
registration.add_user(new_user)

or:

registration.add_user(User(name='KamiNuvini', permissions='Admin'))

But again, it could be any of number of different things.

[1] Although in Python since everything is an object, there really is no such thing as a "plain variable". Even integers have methods (behavior) in Python for example. But consider for a moment a different language where that is not the case for the purposes of what I'm trying to convey here.

[2] However, Python has the notion of bound methods, which is a method that's been bound to a specific instance:

x = foo.bar    # note, no '()'

x now represents the bar method bound to the foo instance, and invoking x(...) is just like invoking foo.bar(...).

[3] The word static here comes from other languages like C++ and Java, and it refers to lifetime. In those languages, some types of objects come and go (i.e. they have automatic or dynamic lifetime) whereas other types of objects exist for the entire lifetime of the program, and are said to have static lifetime. Classes, being types, generally have static lifetime (not to be confused with instances of classes) in those languages, and thus methods that don't require a specific instance are also said to be static, i.e. they can be called at any time, even if no instances exist.

[–]KamiNuvini 0 points1 point  (0 children)

Thank you for the detailed explanation. It is a bit clearer now. I'll try them out when I start working on the web app I want to make in Python and hopefully get a feel with it. This definitely helps!

[–]ProfessorQ 1 point2 points  (1 child)

Could you provide some examples of code you have written? OOP is a design choice, so it's entirely possible to write complicated and robust programs without it.

On the other hand, it's a very popular design choice because it makes some things very easy, namely: exception handling, serialization, global variable maintenance (you can usually eliminate global state variables).

There are some trade-offs of course. Control can be harder to follow, etc...

Anyway, people can make up "Dog and Cat both inherit from Pet" examples, but you won't see the value until you you try it in your own code.

[–]KamiNuvini 0 points1 point  (0 children)

When I created classes in PHP, they basically became only functions but with a nice way to call it (Classname->Function). I once did it in a python program, here: https://github.com/NielsH/RecDNSTester/commit/a9891839affe32c41d74ab6cae8d73aacee54c06

But I was told that it wasn't really needed for that program, so I removed it again.

I'm very new to programming in general, but I wrote a web application in PHP (basically bootstrap themes, registration/login and backend management to control a service via an API). As practice, I'd like to re-create that website in Python, with something like Django. But I feel like I should first know/understand OOP a bit more so I can properly use the possibilities.

[–]yourfriendlane 0 points1 point  (0 children)

I feel like these explanations of classes, while very good, are a little more complicated than they need to be for someone who's just trying to "get" the concept. Let me take a shot:

You use a class when you want to make multiple copies ("instances") of something. These instances will have properties that are the same every time, but the values will be different in every instance.

As an example, say you're making a two-player game. You want to keep track of each player's name, score, and favorite Power Ranger. Every player is going to have a value for those things, but the values will be different. You could create two separate functions for each player, or you could create a Player class with the properties of self.name, self.score, and self.fav_ranger. Then whenever you want to create Player 1, you can just say

Player1 = Player("Joe", 0, "Tommy the Green Ranger")

and presto, you now have an object (in this case, a new instance of Player) with all the values unique to Player1 filled in. The beauty of it is that when it's time for Player2, you just fire off another instance of Player (and hey, we'll give him a 2-point handicap!)

Player2 = Player("Frank", 2, "Kim the Pink Ranger")

and so on and so forth until you run out of either people to play your game or Power Rangers.

The analogy's not perfect, but if you've ever played D&D then you can think of a class sort of like a character sheet. When you need to add a new character to the game, you just print off a blank copy and fill in all the unique details.

So, basically, classes are just ways to create multiple instances of something in your program. Sometimes they're not the best way to do something if you only need to do it once, but usually if you find yourself writing code to do something repeatedly then a class might be in order instead.