Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unexpected behavior of python builtin str function

Tags:

python

I am running into an issue with subtyping the str class because of the str.__call__ behavior I apparently do not understand.

This is best illustrated by the simplified code below.

class S(str):
    def __init__(self, s: str):
        assert isinstance(s, str)
        print(s)

class C:
    def __init__(self, s: str):
        self.s = S(s)

    def __str__(self):
        return self.s

c = C("a")  # -> prints "a"
c.__str__() # -> does not print "a"
str(c)      # -> asserts fails in debug mode, else prints "a" as well!?

I always thought the str(obj) function simply calls the obj.__str__ method, and that's it. But for some reason it also calls the __init__ function of S again. Can someone explain the behavior and how I can avoid that S.__init__ is called on the result of C.__str__ when using the str() function?

like image 603
pfp.meijers Avatar asked Jan 18 '16 16:01

pfp.meijers


People also ask

What does Python understand by STR?

Python has a built-in string class named "str" with many handy features (there is an older module named "string" which you should not use). String literals can be enclosed by either double or single quotes, although single quotes are more commonly used.

What is __ class __ in Python?

__class__ is an attribute on the object that refers to the class from which the object was created. a. __class__ # Output: <class 'int'> b. __class__ # Output: <class 'float'> After simple data types, let's now understand the type function and __class__ attribute with the help of a user-defined class, Human .


1 Answers

Strictly speaking, str isn't a function. It's a type. When you call str(c), Python goes through the normal procedure for generating an instance of a type, calling str.__new__(str, c) to create the object (or reuse an existing object), and then calling the __init__ method of the result to initialize it.

str.__new__(str, c) calls the C-level function PyObject_Str, which calls _PyObject_Str, which calls your __str__ method. The result is an instance of S, so it counts as a string, and _PyObject_Str decides this is good enough rather than trying to coerce an object with type(obj) is str out of the result. Thus, str.__new__(str, c) returns c.s.

Now we get to __init__. Since the argument to str was c, this also gets passed to __init__, so Python calls c.s.__init__(c). __init__ calls print(c), which you might think would call str(c) and lead to infinite recursion. However, the PRINT_ITEM opcode calls the C-level PyFile_WriteObject to write the object, and that calls PyObject_Str instead of str, so it skips the __init__ and doesn't recurse infinitely. Instead, it calls c.__str__() and prints the resulting S instance, as the S instance is a string.

like image 63
user2357112 supports Monica Avatar answered Nov 12 '22 21:11

user2357112 supports Monica