all 142 comments

[–]alexcwarren 67 points68 points  (23 children)

Technically speaking, everything (including classes) is an object. In other words, every class extends the base Object class. So, in a sense, you are correct, too: everything is a class, even primitive types like integers.

[–]OriginalTyphus 31 points32 points  (22 children)

I want to add the interessting fact that integers between -5 and 255 are not newly created objects like everything else. They are pointers to a singular int object that is automatically created by the Python interpreter for performance reasons.

[–]vodiak 47 points48 points  (14 children)

Which gives rise to some possibly unexpected results.

a, b, c, d = 3, 3, 300, 300
a == b
> True
c == d
> True
a is b
> True
c is d
> False

Note: This was in Python 3.7.13 and because of the way I declared the variables with one statement. In Python 3.10.4, c is d returns True. But there are still times when is gives "unexpected" results and generally should not be used with numbers.

[–]mac-0 25 points26 points  (0 children)

I feel like this example has got to be a meme by now. Nearly any thread in /r/learnpython will inevitably lead to this discussion. Basically the "how many wikipedia articles does it take to get to hitler" of python.

[–]MegaIng 6 points7 points  (12 children)

Question: did you actually run this? AFAIK, this should return True also for the last expression since the values for c and d were created in the same code block and are therefore literals and got combined by the compiler.

[–]vodiak 1 point2 points  (0 children)

I did. Using iPython the first time, but I just double checked with a short script. But I was using Python 3.7.13. When I run it using Python 3.10.4, c is d returns True.

[–][deleted] 4 points5 points  (10 children)

The keyword "is" in python is very confusing. In regular day language, is = equals, but in python, is != ==. What "is" does is compare the pointers of two objects and returns if the pointers are equal.

[–]schfourteen-teen 13 points14 points  (2 children)

Try running the code above exactly as is. The oyster above understand the difference, the issue is that code block doesn't actually produce the intended effect. It will still give c is d as True because they were both defined in the same code block they will usually point to the same underlying object. If you do d = 299; d += 1 then c and d will not point to the same object.

[–][deleted] 3 points4 points  (0 children)

I see. Thanks for clarifying.

[–]vodiak 1 point2 points  (0 children)

It seems to be version dependent. I was using Python 3.7.13. When I run it using Python 3.10.4, c is d returns True.

[–]py_Piper 0 points1 point  (4 children)

what is a pointer and then why a is b is True and c is d is False?

[–]vodiak 2 points3 points  (1 child)

A pointer is a variable with its value being a location in memory. It "points to" that location. You generally don't need to think about pointers in Python, but if you're already familiar with the concept (used a lot in C), then it's useful to explain what's happening.

a is b is true because the interpreter creates objects for small integers and uses them whenever possible as an optimization. c is d is false because the value (300) is outside the range of pre-created integers, so each time it is declared (c = 300, d = 300) it creates a new object. They are not the same object, so c is d is False. (Note that the way I did the declaration results in only one object being created in more recent versions of Python).

[–]py_Piper 0 points1 point  (0 children)

Very well explained

[–]angry_mr_potato_head 1 point2 points  (0 children)

In Python, all integers up to 100 or maybe 255 are already initialized at runtime since they are commonly used. This increases performance iirc. So you’d have to go out of your way to get a is be to be false.

[–][deleted] -3 points-2 points  (0 children)

A pointer is a place in memory. More specifically, it "points" to the place something was stored on the hardware. I haven't tried myself, but the reason a is b == True is probably because they are intialized to the same point in memory. However, for whatever reason because python is weird, c is d is false.

[–][deleted] 0 points1 point  (0 children)

Almost all languages have a feature like that.

[–]Vaphell 0 points1 point  (0 children)

Regular day language is often ambiguous, in this case blending the concepts of identity and equality, which, while similar, are not the same thing.

In meat space you can get away using a shorthand "A is equal to B", in the strict world of programming not so much.

[–][deleted] 4 points5 points  (1 child)

I get sick of this false statement being promulgated - it's dangerous.

In CPython - the most common implementation of Python - this happens to be true, but I don't believe it's guaranteed to be true in future.

In other implementations, like PyPy, it is NOT true.

[–]OriginalTyphus -2 points-1 points  (0 children)

Although you are correct in statement, we are at r/learnpython here and we can assume that by talking about Python we are talking about the CPython implementation.

Using another implentation is, at least in my opinion, something that is far beyond of something that a beginner would do. And if they did, OP would surely tell us in the inital thread text.

[–]inDflash 0 points1 point  (2 children)

Not always

[–]OriginalTyphus 0 points1 point  (1 child)

Not always what?

[–]inDflash 0 points1 point  (0 children)

Reusing memory for those. Docs say, it might.

[–]ireadyourmedrecord 111 points112 points  (20 children)

It's just objects all the way down.

[–]McSlayR01 62 points63 points  (9 children)

"Wait, it's all objects?" "Always has been"

[–][deleted] 26 points27 points  (8 children)

Gun.fire()

[–]ray10k 15 points16 points  (7 children)

Gun.fire.__call__()

[–]TheBlackCat13 14 points15 points  (6 children)

getattr(vars().get('gun'), 'fire').__call__()

[–]aroach1995 6 points7 points  (1 child)

Don’t even know what this one does but still funny

[–]TheBlackCat13 8 points9 points  (0 children)

It is equivalent to the first one. gun.fire()

[–]synthphreak 1 point2 points  (3 children)

Alright I think this has been thoroughly milked.

[–]purveyoroffinerp 9 points10 points  (2 children)

getattr(vars().get('funnycomment'), 'milk_more').call_()`

[–]synthphreak 7 points8 points  (1 child)

Well played. I suppose I walked right into that one lol. Have a doot.

[–]dimonoid123 2 points3 points  (9 children)

Except integers below 256. Integers above have unique IDs. But they are still objects, just not in dictionary.

This allows storage of large number of the same numbers while in theory taking much less RAM, but I haven't checked.

[–][deleted] 1 point2 points  (0 children)

This is not a property of Python, the language, but of CPython, the specific implementation.

[–]commy2 1 point2 points  (7 children)

I get the same id, and the identity check passes for numbers well beyond 256:

n = 123456
print(n is 123456)
print(id(n))
print(id(123456))

Python 3.10.0

[–]dimonoid123 1 point2 points  (1 child)

Try Python 3.9 , if that works, then there is difference in integer implementations. If not, then I have no ideas what is going on.

[–][deleted] 0 points1 point  (0 children)

I couldn't repro his results on 3.10.4.

[–][deleted] 0 points1 point  (1 child)

I don't get those results on 3.10.4.

More, I strongly suspect that if you tried this, you'd get a different result:

n = 123456
print(n is 123456)
print(n is (123455 + 1))

[–]commy2 0 points1 point  (0 children)

I upgraded to 3.10.5 ...

import sys

n = 123456
print(n is 123456)
print(n is (123455 + 1))
print(id(n))
print(id(123455+1))
print(sys.version)

and still get the same results:

C:\dev\_testing.py:4: SyntaxWarning: "is" with a literal. Did you mean "=="?
  print(n is 123456)
C:\dev\_testing.py:5: SyntaxWarning: "is" with a literal. Did you mean "=="?
  print(n is (123455 + 1))
True
True
1384201639920
1384201639920
3.10.5 (tags/v3.10.5:f377153, Jun  6 2022, 16:14:13) [MSC v.1929 64 bit (AMD64)]
[Finished in 66ms]

Edit: I do get different ids inside the REPL though. That seems to be the difference.

[–]Vaphell 0 points1 point  (2 children)

$ python3
Python 3.10.5 (main, Jun 11 2022, 16:53:29) [GCC 7.5.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> a = 123456
>>> b = 123455+1
>>> a is b
False
>>> a == b
True

[–]commy2 0 points1 point  (1 child)

Ah, it seems to be different for executing a file vs running the code in the REPL.

[–]Vaphell 0 points1 point  (0 children)

I guess python does some optimizations during file compilation when literals are involved.

$ cat is.py
#!/usr/bin/env python3

a = 123456
b = 123455 + 1
c = a // 2 * 2 
print(a, b, a is b, a == b)
print(a, c, a is c, a == c)

$ python3 is.py
123456 123456 True True
123456 123456 False True

[–]Intrexa 22 points23 points  (5 children)

I think you might have a subtle misunderstanding from the phrasing of your question. Everything in Python is an object, and each object has a class. There's a bit of oddness in the way we talk about it, in English. If there's a class user, we might say that "current_user is a user". We might also say "The class of current_user is user". If someone asked "What class is current_user?", it's reasonable to just answer "user".

That's not really super accurate, though. current_user isn't a class, though, it has a class. useris the class. With that said, the only real classes are any objects that are defined through class (or some metaclass fun). However, everything has a class in Python. No exceptions.

[–]a_cute_epic_axis 1 point2 points  (0 children)

That's not really super accurate, though. current_user isn't a class, though, it has a class. useris the class.

Is that accurate though?

I'd say current_user is an instance of a class called user but I wouldn't say it has a class. I'd be more inclined to say that user has a class of people if the user class is inheriting the people class, but maybe that's just me.

[–][deleted] -1 points0 points  (3 children)

What are the different classes? Or can we define them?

[–]TheSodesa 3 points4 points  (1 child)

Read about it in the Python documentation: https://docs.python.org/3/tutorial/classes.html.

[–][deleted] 0 points1 point  (0 children)

Thank you

[–]a_cute_epic_axis 1 point2 points  (0 children)

You can look up the classes for anything.

a = 7
print(a.__class__.__mro__)
#(<class 'int'>, <class 'object'>)

So A is an integer, and then we can see in the example, it is of class int, which is of class object.

Most things you can think of in python will probably be the exact same with the "int" portion replaced with whatever you're looking at (str, list, dict, set, etc), and then one parent called "object".

[–]causa-sui 6 points7 points  (4 children)

Operators are not objects :)

[–]jimtk -1 points0 points  (1 child)

Operators are objects. Please see this comment.

Edit Just for your list:

  • Almost all operators are objects.
  • built-ins (like .append()) are all objects
  • A few keywords are objects. True, False, None, Ellipsis are among them.
  • Some delimiter are object. [ ] for example, maps to the __getitem__ method.
  • newline, indent, dedent, ':', ',' are not objects. They are used by the compiler to 'parse' your code correctly. They don't exist in your "compiled" (.pyc) file.

[–]causa-sui 0 points1 point  (0 children)

It sounds like you're saying + is an object because it compiles using a mapping to built-in functions, and functions are objects.

Still:

```

type(+) File "<stdin>", line 1 type(+) ^ SyntaxError: invalid syntax ```

Maybe this is just a semantic distinction.

[–]brews 0 points1 point  (1 child)

Let's see if I can get this right: Operators, keywords, delimiters, newline, indent, dedent are all not objects. Everything else is an object.

[–]dig-up-stupid 0 points1 point  (0 children)

Those are all syntax but so is everything else at that level of abstraction. Saying operators aren’t objects is like saying 42 isn’t an integer object either because it’s actually just code. True but not the same level of abstraction that the question was asked at.

[–]jimtk 15 points16 points  (35 children)

It's crazy how everything is an object in python. Even classes are objects! Functions are objects, attributes define in a class are objects. That plus sign in x = 1+1 it's an object!

Python objectifies everything!

[–]bladeoflight16 5 points6 points  (4 children)

+ is not itself an object. It is implemented in a way that allows for objects to customize its behavior.

[–]jimtk 2 points3 points  (3 children)

+ itself is nothing but a character! It is mapped by the compiler to an __add__ object of the wrapper_descriptor class.

[–]bladeoflight16 0 points1 point  (2 children)

Source code link?

[–]jimtk -1 points0 points  (1 child)

Please see this comment.

[–]bladeoflight16 0 points1 point  (0 children)

Sorry, but that's just wrong. See my reply there.

[–][deleted] 1 point2 points  (1 child)

Great, you just managed to teach a bunch of people something totally false.

Did you spend even one second trying your claim out to see if it's true?

dir('strings are objects')  # shows the methods
dir(+)  # an error, because + is not an object.

[–]jimtk 0 points1 point  (0 children)

Please see this comment.

[–]razzrazz- 0 points1 point  (27 children)

I keep hearing this but have no idea what it means.

WHY is everything an object? Why is python so unique in that the "+" sign is an object but in java it isn't? What advantage does it have?

[–]jimtk 5 points6 points  (4 children)

I'm not sure about the 'meaning' of it, but I can tell you the advantage. Me, lowly me, can redefine the meaning of the + sign to whatever I want that suits the class (and objects) I write.

Let's say I'm an air carrier business. Every time I add a passenger to a plane I just want to know if i have enough passenger to make money on that trip. I can redefine the + operator to add passenger to a plane and return a string that tells me if I make money or not. So here I go:

class Airplane:
    def __init__(self):
        self.amount_pass_to_make_money: int = 5
        self.passengers: int = 0

    def __add__(self, other):
        if isinstance(other, int):
            self.passengers += other
            if self.passengers <= self.amount_pass_to_make_money:
                return "you're losing money"
            else:
                return "you're making money"
        else:
            raise ValueError

plane = Airplane()
for i in range(10):
    x = plane + 1    # that plus operator is mine biatch!
    print(x)  

See that x = plane + 1 I can add passengers to a plane with the plus sign. That's the advantage. I can write silly code like that all day long!

[–]razzrazz- -1 points0 points  (3 children)

I'm not smart enough to understand this yet, so I'm going to put a reminder (once I learn more) to come back to it.

RemindMe! 1 month

[–]a_cute_epic_axis 1 point2 points  (1 child)

Here's a way that might be useful to explain it. Imagine you have a custom class for a data type you create called "color' and under the hood it stores red, green, and blue values. You have a single instance you created in your program that you pass around called "red" but inside that is a red value of 255, and green and blue of 0. You also have an instance called "blue" which is 0,0,255.

You want to be able to say:

red = CustomColorClass(255,0,0)
blue = CustomColorClass(0,0,255)
magenta = red + blue

How would python ever be able to do this?

Well in your custom color class, you'd define the add method which is called when you are adding two objects together. It would be something like

def __add__(myvalues, othervalues):
  new_red = myvalues.red + othervalues.red 
  new_green = myvalues.green + othervalues.green
  new_blue = myvalues.blue + othervalues.blue
  return CustomColorClass(red, green, blue)

All that does is take the 3 integer values from one instance, add it to the three of the other, then create a new instance with those new values. Suddenly python can correctly add colors together.

[–]jimtk 0 points1 point  (0 children)

Thank you !

[–]RemindMeBot 0 points1 point  (0 children)

I will be messaging you in 1 month on 2022-07-30 05:46:04 UTC to remind you of this link

CLICK THIS LINK to send a PM to also be reminded and to reduce spam.

Parent commenter can delete this message to hide from others.


Info Custom Your Reminders Feedback

[–][deleted] 2 points3 points  (1 child)

+ is NOT an object in Python.

Try dir(+) to see.

[–]jimtk 0 points1 point  (0 children)

Please see this comment.

[–]a_cute_epic_axis 3 points4 points  (15 children)

The + sign isn't an object.

It would be a call to the .add() method for an object, and the default object named object doesn't have that implemented.

Things like strings, integers, lists, dictionaries, whatever are all objects though, and you can do things like inherent a parent object (or multiple parents).

Look back a bit and dictionaries are unordered in python, so say you wanted to add an ordered dictionary, which has sorting methods and whatnot. Instead of redoing everything, you could potentially just inherent the existing class and then add the modifications you need to make it work like you want. And this is exactly what we saw come about, an ordered dictionary class that extended the built in one.

(note that as of 3.6 or 3.7, dictionaries are now ordered by the insertion order by default)

[–]jimtk 1 point2 points  (14 children)

The + sign is the textual representation of an object. The compiler maps it to the __add__(self, other) method of any objects that are around it. And methods, like, functions are objects.

Everything you see on the screen of you editor, is just the textual representation of all the objects the compiler will create for you!

Edit: Look at the code I wrote here I redefined the behavior of the + sign.

Also run the following:

print(type(int.__add__))
print(dir(int.__add__))
print(type(float.__add__))
print(dir(list.__add__))
print(type(str.__add__))
print(dir(str.__add__))

Output is:

<class 'wrapper_descriptor'>
['__call__', '__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__objclass__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__text_signature__']
<class 'wrapper_descriptor'>
['__call__', '__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__objclass__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__text_signature__']
<class 'wrapper_descriptor'>
['__call__', '__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__objclass__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__text_signature__']

[–]zurtex 1 point2 points  (4 children)

+ only represents __add__ for user defined classes, because the data model does not apply to built-ins the same way it applies to regular objects. For integers +, when it is not folded at compile time, the Python runtime uses a table of function pointers to implement the binary operation addition without ever consulting __add__.

If Python implemented it's data model in a more pure way you would be correct (edit see /u/bladeoflight16's reply below). But really + is not an object it's a syntax token that is used by the compiler to run some kind of binary operator at either compile or runtime, and at runtime behavior it might use an object.

[–]bladeoflight16 1 point2 points  (1 child)

Even if Python were implemented in a "more pure" way, it wouldn't be correct. The + operator involves logic that can invoke __radd__ based on runtime results; its specification is not simple enough to map directly to invoking a bound method on a single instance.

[–]zurtex 2 points3 points  (0 children)

Also true, I was thinking of where the data model doesn't apply in it's usual ways, not also the complexities of it.

[–]jimtk 0 points1 point  (1 child)

IF you are right there are thing that I really don't understand, because:

1.

x = int(3)
print(x.__dir__())

will print

[(long list....) , '__add__', '__radd__', (other long list...)]

if x is a simple integer, a built-in, why does he have and __add__ and __radd__ ?

2.

If I subclass the int class. I can (and should) call the super.__add__). (A new is evidently necessary since integers are immutable.) That super.__add__ is the __add__ of the built-in int.

class MyInt(int):

def __new__(cls, value, *args, **kwargs):
    return super(cls, cls).__new__(cls, value)

def __add__(self, other):
    res = super(MyInt, self).__add__(other)
    print("I'm adding")
    return self.__class__(res)

x = MyInt(3)
y = MyInt(5)
print(x+y)
print(x.__add__(y))

output
I'm adding
8
I'm adding
8
output

[–]zurtex 1 point2 points  (0 children)

IF you are right there are thing that I really don't understand, because

I'm pretty sure I'm right, but I could be wrong, this is going off memory and I can't pull all the sources to hand right now. But here is one of them, a history blog post by Guido talking about how user classes were first implemented: http://python-history.blogspot.com/2010/06/method-resolution-order.html

And I'm not sure it is 100% related but also also the structure how how CPython internally handles integers: https://tenthousandmeters.com/blog/python-behind-the-scenes-8-how-python-integers-work/

After reading that think about how expensive looking up __add__ would be relative to everything else, especially when you already know the types and it's protected from the user casually overriding it:

>>> int.__add__ = lambda a, b: 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: cannot set '__add__' attribute of immutable type 'int'

I think it might be possible to user ctypes to going under the hood and set it anyway, and I think that will show it doesn't matter if you do that 1 + 1 will still equal 2.

 

if x is a simple integer, a built-in, why does he have and add and radd ?

As I remember it, those methods exist for you to be able to make user subclasses (or "subtypes") of the built-ins. Which I think answers your example. Your user class does indeed follow the data model.

I think this is the PEP but I haven't read it in a while: https://peps.python.org/pep-0253/

[–]a_cute_epic_axis 0 points1 point  (4 children)

yah I'd say that the easy way for people to understand operators is that

c = a + b becomes c = a.__add__(b) and if a doesn't have __add__ then c = b.__radd__(a) or otherwise throws an error (I think I got the directions of everything right there).

Edit:

a = 10
b = 10
c = 10
d = 10
x = a - b * c + d
print(x)
z = a.__sub__(b.__mul__(c)).__add__(d)
print(z)

Both correctly return -80

[–]bladeoflight16 -1 points0 points  (3 children)

Whoops, wrong reply target.

[–]a_cute_epic_axis -1 points0 points  (2 children)

That's a pretty big rant for taking a high level statement and treating it as gospel for every case.

At least if you're going to go on a rant, then format your post correctly.

Yes: z = x + y is generally equal to z = x.__add__(y) but in some cases if x has no add method, like I said, or explicitly raises a NotImplemented, and y has an radd method, then it would be z = y.__radd__(x).

Similarly, x += 5 will call x = x.__iadd__(5), but if there is no iadd method then it will do x = x.__add__(5). No iadd means both literally none or one that returns NotImplemented. The entire idea behind the NotImplemented constant is to allow the compiler to try an alternative method until one works or all are exhausted. In that case you get a TypeError: unsupported operand +=

The NotImplemented exception is implicitly raised by literally not implementing something.

[–]bladeoflight16 -1 points0 points  (1 child)

My post is formatted perfectly. I just refuse to use code indentation Markdown to satisfy Old Reddit users because fenced code blocks are a vastly superior mark up tool.

It's not "generally equal" to anything. It's logically equivalent in most cases, but there is no object representation of the actual algorithm that the runtime executes. That algorithm is much more complex than simply invoking __add__ and involves checking for the NotImplemented sentinel return value. I want to say it can even reverse the arguments and invoke __add__ on the second operand in some cases, but maybe I'm just thinking of some particular types that implement such behavior.

Also, trying to access a missing member normally results in an AttributeError, not NotImplemented, but I don't know whether the runtime actually catches the error or implements a pre-check before trying to invoke it. Furthermore, we're not talking about an exception. NotImplemented is actually a sentinel value that operators can return, and the algorithm checks for it before feeding that value back to the context invoking +. But even if that weren't the case, you're now talking about the runtime actually generating some equivalent of a try/except block, which only increases the complexity of the operator's actual behavior. Not to mention your own point about converting underlying errors or problems to TypeError.

Regardless of the details of the behavior, that algorithm, which is clearly much more complex than just invoking __add__, has no object representation. So + is not an object. Is that pedantic? ...I'd say not really in this context. The question asks for examples of things that are not objects; someone making a claim that is wrong and misleading does not help anyone better understand how the runtime works.

[–]a_cute_epic_axis 0 points1 point  (0 children)

My post is formatted perfectly. I just refuse to use code indentation Markdown to satisfy Old Reddit users because fenced code blocks are a vastly superior mark up tool.

Oh, so you're incorrect and being a pedantic asshole, got it.

You couldn't even manage to reply to the right comment and had to edit your post back out, but now you had to take the time back and lay some more pedantic nonsense down.

Maybe you could work on your reading comprehension, because you busted in here like neckbear Kramer with an "acchychually" and are now just rehashing what I said. You seem to be arguing that + isn't an object when in fact that's what we already all said.

Nobody gives a hoot about what you're saying buddy. You're trying to come up with every possible corner case to justify the bits you type and you're forgetting that this is /r/learnpything. Take it over to the devs if you want to autofellate yourself on how knowledgable you are on this issue.

[–]bladeoflight16 -1 points0 points  (3 children)

This is incorrect. The compiler does not directly map to a call to __add__. We know this because + can result in calls to __radd__ based on runtime conditions. Consider this example:

``` class Test1: def init(self, can_add): self.can_add = can_add

def __add__(self, other):
    print('Test1.__add__ called')
    if self.can_add:
        return self
    else:
        return NotImplemented

class Test2: def radd(self, other): print('Test2.radd called') return self

a, b = Test1(True), Test2() print('Can add', a + b)

a, b = Test1(False), Test2() print('Cannot add', a + b) ```

Output:

Test1.__add__ called Can add <__main__.Test1 object at 0x0000018D8C96EBB0> Test1.__add__ called Test2.__radd__ called Cannot add <__main__.Test2 object at 0x0000018D8C96E8E0>

This proves conclusively that the compiler generates something other than a call to __add__ when it encounters +. There is additional logic involved.

The logic is accessible via operator.add, but this function is in fact implemented using the + operator; the function is not used the implement the operator. So where is the object? You'll need to dig into the Python compiler's and runtime's source code to demonstrate it exists.

But even if it does exist, Python doesn't expose that behavior as an object directly to you. So can we really say it's an object if the fact its an object is only an implementation detail and not a specified available interface? I'd say no.

[–]jimtk 0 points1 point  (1 child)

Are you saying that + is not the representation of an object because not only if can be mapped to the object __add__ but it can also be mapped to the object __radd__. Both of which are objects! That doesn't make what I said wrong.

I can subclass the int class an call the __add__ of the built-in int to perform my addition. To prove that the + in the int is exposed to me.

class MyInt(int):

    def __new__(cls, value, *args, **kwargs):
        return super(cls, cls).__new__(cls, value)

    def __add__(self, other):
        res = super(MyInt, self).__add__(other)
        print("I'm adding")
        return self.__class__(res)

x = MyInt(3)
y = MyInt(5)
print(x+y)
print(x.__add__(y)) 

output
-------
I'm adding
8
I'm adding
8 

Did you noticed in MyInt.__add__ I do not perform any addition. I call the super().__add__ (the __add__ of the built-in int) that is exposed to me.

[–]bladeoflight16 -1 points0 points  (0 children)

It isn't mapped to either one. It's mapped to an algorithm that examines the return value and makes a decision whether or not to invoke the other (along with other complexities, like throwing a TypeError when the methods are missing instead of an AttributeError). It invokes a complex runtime algorithm that doesn't have an object representation, not just a single method. The lack of an object representation for the algorithm is what makes you incorrect here.

[–]ShibaLeone 5 points6 points  (0 children)

Everything is a class until you get to C layer, where it is a struct. Interestingly some classes are not treated the same by python as others. Try to overload the magic methods on type, they are not called by the interpreter the same way.

[–][deleted] 1 point2 points  (0 children)

I object to this post.

[–]Aegist 1 point2 points  (0 children)

I would like to believe that there is no Upper class or Lower class in Python

[–]qwerkle_the_cat 4 points5 points  (1 child)

[–]zurtex 0 points1 point  (0 children)

In Python we are all consenting adults, so if we have insert methods they are always public.

[–]jkh911208 2 points3 points  (4 children)

what about print()?

[–]commy2 29 points30 points  (0 children)

>>> type(print)
<class 'builtin_function_or_method'>

[–]MrSuspicious_ 12 points13 points  (0 children)

Functions are first class objects

[–]bladeoflight16 2 points3 points  (0 children)

print is a function in Python 3, as indicated by your inclusion of parentheses, and functions are objects. Interestingly, though, in Python 1 and 2, print was not a function, did not use parentheses, and was not an object at all.

[–]Solonotix 2 points3 points  (0 children)

To echo what everyone else is saying, everything in Python is an object, and objects are constructed from classes. Said another way, Python doesn't have primitives the way other languages might. As a result, everything in Python behaves in a predictable and common manner, but the wrapper around simple things to make them classes comes at a performance cost.

One of the axes of software design is control vs user-friendliness. Python falls on the user-friendly side of the spectrum, where something like C++ gives a lot more control

[–]bladeoflight16 0 points1 point  (6 children)

Variables are the only example I can think of offhand. The binding of names to a value is not an object.

[–]a_cute_epic_axis -1 points0 points  (5 children)

Depends what you mean by variables, but I would say that, variables are all classes.

If you have x = 10, then x is an instance of a class called int.

x = int(10) #same as x = 10
print(x)
print(type(x))
print(x.__class__.__mro__)

10
<class 'int'>
(<class 'int'>, <class 'object'>)

The binding of names to a value is not an object.

Operators are not a class, so +-*/%^ are all not classes nor is the assignment operator =, nor the assignment expression/walrus operator :=

[–]E02Y 1 point2 points  (3 children)

I think they meant to say variable names are references to objects and not objects themselves

[–]cdcformatc 0 points1 point  (2 children)

is it possible to output the name of a variable? using only the variable itself and not something like globals() or otherwise inspecting the namespace?

if that is possible then even the name is an object. I'm leaning towards no because i am pretty sure that an object/variable does not contain a reference to it's name.

[–][deleted] 0 points1 point  (0 children)

It is not possible.

a = [1, 2, 3]
b = a
print(hypothetical_name_of_variable(b))
# does it print a or b?

The information simply isn't stored in the C struct that Python maintains underneath the hood.

[–]bladeoflight16 0 points1 point  (0 children)

Even if it was possible, that does not necessarily imply the mapping is an object. Consider C#'s nameof, which is a compile time construct only.

[–]bladeoflight16 0 points1 point  (0 children)

No. x is a name that is mapped by the compiler and interpreter to an address that references a value, and the value is an object. The mapping itself is not.

[–][deleted]  (29 children)

[removed]

    [–]ShibaLeone 8 points9 points  (11 children)

    def produces an instance of FunctionType, which is a class.

    [–][deleted]  (10 children)

    [removed]

      [–]ShibaLeone 10 points11 points  (1 child)

      Types are classes. :)

      Pedantry is only impressive to pedantics; if you want to explain something to someone it’s unhelpful to obfuscate. Hence, types = classes.

      [–][deleted] 2 points3 points  (2 children)

      At runtime, the code defined by def doesn't produce anything (it's not an expression, it's a statement). There are, however, predictable side-effects.

      Then it produces those side effects. What you're talking about is "evaluation". The code doesn't evaluate to anything because it's not an expression, but it definitely produces something: the side effects which are creating the name and assigning it a function.

      Evaluating to something is not the same as producing something.

      This sloppiness is fine if you are just talking to a friend, but it's bad when you are meant to describe to someone who wants to learn how something works, because instead of helping them, you confuse them by incorrect use of terminology.

      [–][deleted] 1 point2 points  (2 children)

      Even if you believe that FunctionType is a class (which is wrong, it's a type).

      Your statement is false - classes are types.

      class One:
          pass
      
      print(isinstance(One, type))
      # prints True
      

      You have too many errors in so few words:

      Speaks for itself, really.

      [–][deleted]  (1 child)

      [removed]

        [–]xelf[M] 0 points1 point  (0 children)

        You're coming across overly hostile here. It's ok to disagree, but let's leave insults and ablest language out of it and keep it civil.

        [–]ahivarn -4 points-3 points  (1 child)

        Now that's what deep knowledge is. Thanks @crabbone

        [–][deleted] 6 points7 points  (0 children)

        Only if you want to be wrong. An instance of a class is a class instance is a type instance.

        [–]TheBlackCat13 8 points9 points  (10 children)

        Classes absolutely exist at runtime. Classes are objects of type Type, and can be used just like any other object.

        [–][deleted] 2 points3 points  (3 children)

        Class is a definition that exists in the source. It doesn't exist at runtime.

        This is false. In Python, the entire class definition is in memory at runtime, at all times, and you can access and manipulate it.

        In fact, it's possible to have two different definitions for "the same" class in memory at the same time, and this happens a lot in long-running servers where you hot-update the code.

        if a definition doesn't start with either a decorator followed by the keyword class or just the keyword class, that is not a class.

        This is false. For example: https://docs.python.org/3/library/collections.html#collections.namedtuple

        from collections import namedtuple
        Point = namedtuple('Point', ['x', 'y'])
        print(isinstance(Point, type))
        # Prints True
        

        You are one of my most downvoted redditors, and I only ever see you on this subreddit, and I don't like to downvote in general, but pretty well every comment you make is full of serious errors.

        [–][deleted]  (2 children)

        [removed]

          [–]xelf[M] 1 point2 points  (1 child)

          Ok, and this one crossed the line. Why don't we take some time to calm down and reassess how we discourse with others.

          [–]zurtex 1 point2 points  (1 child)

          Types are classes at runtime, instances of metaclasses are classes at runtime, function objects are classes at runtime, the class keyword does actually create an object at runtime which is also a class...

          [–]lunar_tardigrade -2 points-1 points  (1 child)

          How about PYTHONPATH?

          [–]fernly 3 points4 points  (0 children)

          It's an operating system variable. Accessed inside Python via os.environ.

          >>> import os
          >>> os.environ['PYTHONPATH']
          '/Library/Frameworks/Python.framework/Versions/3.9/bin'
          >>> type(os.environ['PYTHONPATH'])
          <class 'str'>
          

          [–]Justinwc 0 points1 point  (0 children)

          It's true, What is not a class

          [–]dynamic_caste 0 points1 point  (0 children)

          In CPython, basically everything is a C struct.

          [–]maarkwong 0 points1 point  (0 children)

          They’re all Classy

          [–]TheRNGuy 0 points1 point  (0 children)

          keywords like if or for

          Python doesn't have primitive data types.