Everything is an object.
Extended attributes
Some argue that Python is strongly typed because every object in Python has a type, which is a class or metaclass, as recorded in __class__. However, this is a very weak form of typing, since it, like many other things in Python, can easily be changed. All you need to do is assign a different type to __class__.
Likewise, people often say that variables are dynamically typed, but since they can refer to any Python object, it would be more accurate to say that they are untyped.
In this chapter, we'll look at the relationship between class and type, and at how Python uses this relationship.
Three kinds of types
Before we get started, it's important to understand that the word "type" in programming has several meanings.
The most common use of this word means a primitive data type. A second meaning of the word type is a defining object.
What exactly these two meanings of the term type mean will be explained in more detail later. For now, it's assumed that you have a rough idea of what a primitive type is, such as int or a string, and an idea of what a class-based type is, that is, an instance of a class.
There is also a third meaning, which corresponds to an algebraic data type. This is a much deeper, almost philosophical idea, according to Howard Curry, which in fact has no substantial connection to Python or class-based languages as most of us know them.
Ideas like these about data typing are part of pure functional programming in languages such as Haskell, and there's a lot to say about them, but they're not exactly mainstream in the context of languages such as Python, JavaScript, Java, C++, etc.
For the rest of this chapter, type will mean either a primitive data type or a class-based type. But we still need to say exactly what that is.
Primitive data types
In Python, everything is an object, but even in Python we can't ignore the fact that some data types are more primitive. In early computer languages, all we had to work with was primitive data.
You could write:
a=3b=3.14159и:c="Hello World"
and these values 3, 3.14159 and "Hello World" would be converted to some internal representation and stored in the corresponding variables.
Storing values in a variable leads to what is called value semantics, which has largely given way to what Python uses: reference semantics. With value semantics, a variable stores the value being worked with; with reference semantics, the value is stored elsewhere, and the variable stores a reference to it.
However, what really matters is that the representations used for different data types can be very different, even if the data looks the same. That is, in:
a=3
the number 3 will be stored as a binary integer, a string of zero bits ending in 011. The number:
b=3.14159
will be stored in a completely different format—usually floating point. The difference between these two formats is that floating point can show where the decimal point is, while an integer cannot.
They are both numbers, but to a computer they are different types of numbers.
A subtler distinction, and one that confuses beginners, is this:
c="3"
looks like a number, but is represented as a character. Simply put, the bit pattern representing the integer 3 and the character "3" are completely different.
Beginners often find the distinction between a character string that looks like a number and a number itself baffling. They also often find the distinction between floating-point numbers and integers just as baffling, and many languages do their best to hide the internal representations.
Their philosophy can be summed up as follows:
If it looks like a number, it behaves like a number, and an integer is just a floating-point number with no fractional part.
To an experienced programmer, this sounds like a step backwards, but it isn't. Why should the average programmer have to worry about the details of data representation and storage? It's a step toward abstraction—to treat everything that looks like a number as a number, and not even distinguish between different types of numbers.
Of course, these distinctions still exist, but the compiler should handle them and shield us from them.
Python doesn't do this, but it's still a little different from other languages. It has three types of numbers: integers, floating-point numbers, and complex numbers. It also distinguishes general integers from booleans, which are limited to 0 or 1.
Python integers are different in that they work with an arbitrary number of digits. You can use:
a=123456789012345678901234567890print(a*2)
and you can keep entering digits, and the arithmetic will be performed exactly. Most other languages have a fixed limit on the number of digits you can use, and you can write an arithmetic expression that overflows the representation—that is, you can get a result that is too large to represent.
Of course, in practice there is a limit, and you will get an overflow, but this depends on the hardware and the Python system.
Similarly, floating-point numbers in Python are implemented to match the hardware or operating system. Usually, they are IEEE double-precision floating-point numbers.
It's worth noting that Python doesn't distinguish between primitive data types and other types. Remember, everything in Python is an object, and everything is on an equal footing.
Of course, for efficiency, this is mostly an illusion, and integers, floats, and complex numbers are implemented differently from a general object. These are implementation details, and it's best to ignore them as much as possible.
The point is that this is where the idea of a type begins. At its core, a data type is a way of representing something, and that representation determines which operations you can use without having to convert to some other type.
For example, you can perform arithmetic on numbers, but not on characters that merely look like digits.
Simply put, a type determines the operations you can perform.
By declaring the type of an object, you specify exactly which operations can be used and which methods can be called.
Class-based types
Now it's time to move on to the broader concept of type found in all strongly typed, class-based languages. For comparison with Python, examples in C++ or Java are given below.
In most class-based languages, classes are considered types, not objects.
In a class-based language, declaring a class also creates a new data type.
That is:
Class MyClassA(){
//множество свойств}
not only creates a new class, but also adds a new data type, MyClassA, to the type system.
In this type system, objects have a specific type, and variables must be declared as a specific type; a variable can only refer to objects of that type.
Now, when you declare a variable of type:
MyClassA myVariable;
the system knows what myVariable refers to.
This allows the system to check that when you write:
myVariable.myProperty
myProperty really is a property defined for that type. If it isn't, you'll get a compile-time error, which you can fix before it leads to a runtime error.
Unlike Python or any other untyped language, where myVariable can refer to any object and, consequently, you can't infer that:
myVariable.myProperty
is valid.
However, you can generally infer whether it is valid by reading the rest of the program. Strong typing makes this aspect of static analysis easier, but there is a price to pay.
Note that type comes in two varieties: a variable has a declared type, and an object has a specific type.
In the simplest case, strong typing simply enforces the rule that a variable can refer only to an object of its declared type.
That is, an instance of a class has a type, and only a variable of the same type can refer to it.
Hierarchical typing
In most class-based languages that implement hierarchical typing, things are a little more complicated. In this case, inheritance can be used to create a type hierarchy.
For example:
Класс MyClassB:наследует MyClassA{
//множество свойств
}
Now MyClassB has all the properties of MyClassA, plus whatever was added as part of its definition.
MyClassB is considered a subclass of MyClassA, and since it has all the properties of MyClassA, you can use it anywhere you could use an instance of MyClassA.
After all, typing is about making sure that an object has the properties you use, and an instance of MyClassB has all the properties of MyClassA, so it can be treated as one. MyClassB is also a subtype of MyClassA in the sense that it is also a MyClassA, as well as being a new type in its own right.
So in most strongly typed, class-based languages, it's perfectly fine to write things like:
MyClassA myObject=new MyClassB();
and then continue using any properties that belong to MyClassA.
So the rule has become: a variable can refer to an object of its declared type or of any subtype.
If you make a mistake and try to use a property that MyClassA doesn't have, the compiler will report a compile-time error, and you'll be spared a runtime error.
Why is hierarchical typing useful?
It's useful because it lets you write partially generic methods.
For example, suppose you have a hierarchical class Animal and two subclasses, Cat and Dog. As long as you want to use only the methods and properties of the Animal class, you can use hierarchical typing to write a method that can take an Animal as a parameter and use it with objects of type Animal, Cat, or Dog.
When you write a method that works with a type, it also works with all of its subtypes.
Most languages have one top-level supertype from which all other types derive—usually called Object or something similar. This can be used to write completely generic methods, since a variable of type Object can refer to anything. However, note that because of strong typing, you can use only the methods that Object has.
Inheritance as a model
Why do we use inheritance at all, and why is it connected to the idea of subtypes?
This is a difficult question that has sparked a lot of debate. We've already looked at these ideas in chapters 11 and 12, but they're also connected to the concept of type.
The original idea behind using objects was to model the real world. In the real world, things are objects with properties and even methods that let an object do things. Introducing objects into programming was supposed to make it more like a modeling exercise. Indeed, the first object-oriented language was Simula, a language for simulation.
The idea is that in the real world, objects are related to one another. The problem is that they're related in complex ways.
As we've already said, in programming the main purpose of inheritance is to enable code reuse.
Code reuse says little about the relationships between types. It's tempting to take the next step and say that if objectB inherits from objectA, then it is an objectA as well as an objectB. A square is a square, but it's also a rectangle, for example.
The best-known embodiment of this idea is the Liskov substitution principle. It says that anywhere you can use an instance of a class, you can use an instance of any subclass. Since you can use it in place of the base class, it must be a subtype of the base class. This is because the subclass has all the methods of the base class.
This is often true, but not always.
For example, according to our previous reasoning, a square is a subclass of a rectangle, but you can't use a square everywhere you can use a rectangle. The reason is that you can't set different values for the sides of a square. There's a constraint on a rectangle that makes it a square.
Constraints and specializations spoil the lovely idea that subtypes can be used in place of their supertypes. This means that the Liskov substitution principle is more of a theoretical simplification than a reflection of the world.
This also makes strong typing an arbitrary theoretical choice when it comes to the rules for using class instances. You can find ways to make subclasses always work as subtypes. For example, if you implement a square as a rectangle that still has two sides specified, you can keep all the rectangle's methods and ensure equality in another way. This is far from natural.
There is also the problem that, in the real world, objects are connected to many other objects. A square is a special case of a rectangle and is an n-sided equilateral figure.
You can try to model this using multiple inheritance in Python, but it is usually much more complicated than it seems when you are just starting out. This is exactly why most other languages are limited to single inheritance. Other languages add the idea of interfaces to classes—essentially, class declarations without implementations. This allows a limited form of multiple inheritance, but does nothing for code reuse, forcing programmers to fall back on code reuse by “copy and paste.”
The problem is that the real world is often poorly modeled by a single inheritance hierarchy, whether or not it uses strong typing.
This is the main reason you may hear advice like “prefer composition over inheritance.” The idea that one object contains another is, in many ways, a simpler concept to work with. So, for example, a car object might contain a steering wheel object and four road wheel objects, which in turn contain wheel objects. However, this is not always suitable—just as a square might contain a rectangle object—and existing languages provide poor support for composition.
There are two basic concepts of type. The first, primitive type, relates to how data is represented and is the origin of this concept. The second is associated with a class in object-oriented programming.
In many object-oriented languages, a class definition introduces a new type into the type system.
A strongly typed language introduces a rule that you must declare a variable's type, and from that point on the variable can only refer to objects of that type.
This rule is usually extended to variables that refer to objects of the specified type or a subtype. The reason is that this lets you write partially generic methods that can handle the type and all its subtypes.
This again leads to the idea that a class hierarchy is a model of the real world, which is usually not the case.
The Liskov substitution principle is often used to justify hierarchical typing, but it is only an approximation of the real world.
In Python, variables have no type associated with them and can refer to objects of any type.
In Python, objects have a limited notion of type, since their __class__ attribute is set to the class or metaclass that created them. However, it is important to understand that __class__ can be changed.
You can use isinstance and issubclass to check whether an object claims to be of the corresponding type.
Another approach is to use defensive programming to check for the presence of any attribute or method you plan to use, with the hasattr function. This is usually called “duck typing.”









