Class vs. Type in Python - python

I just recently started to teach myself how to code. I am currently reading Think Python 2 for python 3 and when it teaches about the type() function, it gives the example type(2) which outputs <class 'int'>. It then states that "the word 'class' is used in the sense of a category; a type is a category of values."
The part that confuses me is that the type() function outputs class instead of type. Also, I'm not sure about the difference between type and class; are string, float point, and integer classes of the type "value", or are they the same thing?
I have looked this up but cannot find an answer to my specific questions or simple enough for me to understand.

Once upon a time, Python had both types and classes. Types were built-in objects defined in C; classes were what you built when using a class statement. The two were named differently because you couldn't mix these; classes could not extend types.
This difference was artificial, a limitation in the language implementation. Starting with Python 2.2, the developers of Python have slowly moved towards unifying the two concepts, with the difference all but gone in Python 3. Built-in types are now also labelled classes, and you can extend them at will.
Your book is trying to explain a difference that isn't present in Python anymore. Even in Python 2 the difference is only there in name, since type(2) shows the word 'type' is still used there:
>>> type(2)
<type 'int'>
but you can subclass int just like any other class.
(Python 2 does still have old-style classes, those that don't inherit from object; these are a remnant of the old system from before the unification.)

The python hierarchy is Type (Metaclass) -> Class -> Instance.
Think of the function type() as going one level up.
If you use the function type() on an instance of an int (any integer) like so: type(123) you will receive the class of the instance which in this case is int. If you will use type() on the class int, you will recieve type type which is the metaclass of int.
Keep in mind metaclasses are advanced technical details of python and you do not need to learn about them at first.

Related

Confusion with using Mypy with class inheritance - List vs Sequence

Please excuse my confusion - I'm new to using typing and trying to use it along with mypy for checking.
It looks like the problem/question I have seems to happen to people starting to use typing and Mypy quite a lot.
Problem
I'm trying to define an abstract composition of dataclasses, that will be subclassed into concrete classes to add additional data.
So in a simplified form I'm trying to do the following:
from dataclasses import dataclass
from typing import List
#dataclass
class TestResultImage:
base_var_a: int
#dataclass
class TestSeries:
imgs: List[TestResultImage]
# --- concrete instances -------
#dataclass
class SpecificImageType1(TestResultImage):
specific_var_b: float
specific_var_c: int
#dataclass
class SpecificSeries(TestSeries):
imgs: List[SpecificImageType1]
Mypy fails on the above with the error\
error: Incompatible types in assignment (expression has type "List[SpecificImageType1]", base class "TestSeries" defined the type as "List[TestResultImage]")
note: "List" is invariant -- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance
note: Consider using "Sequence" instead, which is covariant
Fix
Changing {List} to {Sequence} solves the problem - as noted in the error.
Question
I have seen quite a few SO and Mypy git issues related to this issue and the confusion of people.
So I then went and attempted to read as many of the Mypy docs as possible.
But it's still - IMHO - pretty confusing as to why List is problematic when you're subclassing. ...or perhaps confused why 'List is invariant, but Sequence is covariant'.
So I'm asking, perhaps on behalf of others like me trying to really use typing, and so Mypy, for more than trivial examples - is there any good explanations of the reason List is problematic, and some examples anywhere?
Suppose we add the following to your original code:
def check_specific_images(imgs: List[SpecificImageType1]) -> None:
for img in imgs:
print(img.specific_var_b)
def modify_series(series: TestSeries) -> None:
series.append(TestResultImage(1))
specific = SpecificTestSeries(imgs=[
SpecificImageType1(1, 2.0, 3),
SpecificImageType1(4, 5.0, 6),
])
modify_series(specific)
check_specific_images(specific.imgs)
This program on the surface ought to type check: specific is an instance of TestSeries so it's legal to do modify_series(specific). Similarly, specific.imgs is of type List[SpecificImageType1] so doing check_specific_images(specific.imgs) is also legal.
However, if we actually try running this program, we'll get a runtime error when we call check_specific_images! The modify_series added a TestResultImage object to our List[SpecificImageType1] causing the subsequent call to check_specific_images crash at runtime.
This problem is fundamentally why mypy (or pretty much any other sane type system) will not let List[SpecificImageType1] be treated as a subtype of List[TestResultImage]. In order for one type to be a valid subtype of another, it should be possible to safely use the subtype in any location that expects the parent type. This is simply not true for lists.
Why? Because lists support write operations. It should always be safe to insert a TestResultImage (or any subtype of TestResultImage) into a List[TestResultImage], and this is not true for List[SpecificImageType1].
So if the problem is that lists are mutable, what if we instead use a type that's immutable instead -- supports only read operations? This would let us side-step the problem entirely.
This is exactly what Sequence is: it's a type that contains all of the read-only methods that lists support (and is a supertype of List).
More broadly, let's suppose we have some sort of generic type Wrapper[T] along with two classes Parent and Child where Child is a subtype of Parent.
This then raises the question: how does Wrapper[Parent] relate to Wrapper[Child]?
There are four possible answers to this:
Wrapper is covariant: Wrapper[Child] is a subtype of Wrapper[Parent].
Wrapper is contravariant: Wrapper[Parent] is a subtype of Wrapper[Child].
Wrapper is invariant: Wrapper[Parent] and Wrapper[Child] are unrelated to each other and neither is a subtype of the other.
Wrapper is bivariant: Wrapper[Parent] is a subtype of Wrapper[Child] and Wrapper[Child] is a subtype of Wrapper[Parent].
When you're defining Wrapper[T], mypy will let you pick whether you want that type to be covariant, contravariant, or invariant. Once you've made your choice, mypy will then enforce the following rules:
If a class is covariant, it can only support read operations against T. In practice, this means you're disallowed from defining methods that accept anything of type T.
If a class is contravariant, it can only support write operations against T. In practice, this means you're disallowed from defining methods that return anything of type T.
If a class is invariant, it can support both read and write operations against T. There are no restrictions on what types of methods you can define.
Mypy doesn't let you create bivariant types: the only time when such a type would be safe is if it supported neither read nor write operations against T -- which would be pretty pointless.
You usually only see bivariant types in programming languages/type systems that intentionally want to make generics as simple as possible, even if it means letting the user introduce bugs like the one shown above into their program.
The high-level intuition here is that supporting either read operations or write operations against T will place constraints on how Wrapper[Parent] is related to Wrapper[Child] -- and if you support both kinds of operations, the combined constraints will end up making the two types be simply unrelated.

Difference of type() function in Python 2 and Python 3

When trying the following script in Python 2,
a = 200
print type(a)
its output is
<type 'int'>
and in Python 3, the script
a = 200
print (type(a))
its output is,
<class 'int'>
What may be the reason for that?
In the days of yore, built-in types such as int and dict and list were very different from types built with class. You could not subclass built-in types for example.
Gradually, in successive Python 2.x releases, the differences between class types and builtin types have eroded; the introduction of new-style classes (inheriting from object) in Python 2.2 was one such (major) step. See Unifying types and classes in Python 2.2.
Removing the use of type in built-in type representations is just the last step in that process. There was no reason anymore to use the name type anymore.
In other words, between Python 2.7 and 3.x this is a cosmetic change, nothing more.
type isn't behaving any differently. They just changed things so all classes show up as <class ...> instead of <type ...>, regardless of whether the class is a built-in type like int or a class created with the class statement. It's one of the final steps of the elimination of the class/type distinction, a process that began back in 2.2.

How does Python language know about the type of identifiers?

I recently faced this question..
How does python identifies the type of an identifier?
For example
a=5
b=6
c=a+b
the value of c is 11
Where as
x="hello"
y="world"
z=x+y
the value of z is "helloworld"
How does Python distinguishes these two cases?
In Python, types are not associated with variable names, but with values. The name 'a' does not hold any type information, it's the value '1' that holds that information (namely that it's an 'int' class).
This is a contract to C and Java, where types are associated with variable names.
This is exactly the difference between the Curry-style typing system and Church-style system.
http://en.wikipedia.org/wiki/Simply_typed_lambda_calculus#Intrinsic_vs._extrinsic_interpretations
C and Java use Church typing (variable name has a type) while Python and JavaScript use Curry typing (value has a type).
Check the __add__ method for classes.
What really happens is that the __add__ method of the object is called:
a + b is really a.__add__(b)
Check also diveintopython for special method names.
From the doc:
The int type implements the numbers.Integral abstract base class.
Likewise, strings are instances of the str class.
The python interpreter does not identify the types of identifier. Rather, it identifies the types of the objects referred to by the identifiers. The identifiers or names you declare does not hold the objects but rather refer to the objects and these objects, as pointed out by the previous answers, holds type information about themselves. These python objects are implemented as PyObject structure in C, which has a field PyObject *ob_type pointing to their types (being PyObject as well).
With the background above, now here is what happens when python runs your script.
i = 1: it creates an object for the numeric 1 from the int type.
A name i is bound to the int object created just now. By "bound to" I means inserting an entry into the global dictionary (you can see it by globals()).
Whatever operations you perform on i is forwarded to the object it refers to (being int(1) currently) as #valjeanval42 has explained.
Since the objects always know their types, which are the main source of information about what operations they support, python can correctly handle the i variable not whether it is assigned to 1 or '1' (being a str).
Static language like C++ or Java do things vastly differently than a dynamic language like python, as you would have expected. In short, they manage matter of types at compile time while python does it at run time.

Type of an instance variable

Suppose that I have a class like this
class Employee:
pass
I create two objects for Employee as below
john = Employee()
rob = Employee()
..and create instance variables
john.age = 12
rob.age = '15'
The compiler accepts both and prints the age (john's age in int and rob's age in string). How is this logical? The same data attribute having different type in each object.
Thanks.
Be sure to understand this fundamental principle: in Python, variables don't have types. Values have types. This is the essence of Python's being a dynamically-typed language similarly to Lisp and Javascript, but unlike C, C++ and Java.
>>> foo = 5 # foo now holds a value of type int
>>> foo = 'hello' # foo now holds a value of type string
Here's an excerpt from Wikipedia's entry on typing in Python:
Python uses duck typing and has typed
objects but untyped variable names.
Type constraints are not checked at
compile time; rather, operations on an
object may fail, signifying that the
given object is not of a suitable
type. Despite being dynamically typed,
Python is strongly typed, forbidding
operations that are not well-defined
(for example, adding a number to a
string) rather than silently
attempting to make sense of them.
Do read more on this subject (especially what Duck Typing is) if you want to learn Python.
P.S. This issue is totally orthogonal to attributes of objects. Attributes are just other "variables" in Python, which also can hold values. These values can be of different types.
Because by saying rob.age you are not creating a class-wide data attribute that has a specific type; you are merely creating an instance-local, instance-specific attribute that refers to a concrete entity, the string '15'. To create a class-wide attribute you would have to say Employee.age = … or set age inside the class Employee: block. By setting the attribute to a descriptor, I suppose you could check its type every time it is set and restrict it to an integer or string or whatever; but in general, either a class attribute or an instance attribute is just a name for an object, and all Python cares is that .age names an object.
And note that Python could not really guess what you mean anyway. If you say that john.age is 12, you seem to want Python to guess that all other .age attributes should also be numbers. But why shouldn't Python go even further, and guess that they are integers — or better yet, that they are positive even integers? I really do not think it would be reasonable in any case for Python to extrapolate from a single assignment to some kind of guess as to how you will treat that attribute in all other instances of the class.
It's fundamentally what you get when you have a dynamically typed language.
Type is determined at runtime not at declaration.
It has advantages and disadvantages, but I believe the advantages outweigh its disadvantages in most development contexts.
(It's what Ruby, Python, PHP, etc. do)
Python's compiler does not care what type of value you bind to an attribute/name, nor does it have to; Python's dynamic nature means that the important type checks (which are usually actually attribute checks) are done at runtime.
The term "variable" is confusioning in Python.

Is everything an object in Python like Ruby?

I read on another Stack Overflow question that Python was just like Ruby, as it relates to "everything's an object," and everything in Python was an object, just like Ruby.
Is this true? Is everything an object in Python like Ruby?
How are the two different in this respect or are they really the same? For example, can you take a number and do the Ruby stuff I've seen like:
y = 5.plus 6
Can that be done the same way in Python?
DiveIntoPython - Everything Is an Object
Everything in Python is an object, and almost everything has attributes and methods. All functions have a built-in attribute __doc__, which returns the doc string defined in the function's source code. The sys module is an object which has (among other things) an attribute called path. And so forth.
Still, this begs the question. What is an object? Different programming languages define “object” in different ways. In some, it means that all objects must have attributes and methods; in others, it means that all objects are subclassable. In Python, the definition is looser; some objects have neither attributes nor methods (more on this in Chapter 3), and not all objects are subclassable (more on this in Chapter 5). But everything is an object in the sense that it can be assigned to a variable or passed as an argument to a function (more in this in Chapter 4).
Ruby Docs - To Ruby From Python
As with Python, in Ruby,... Everything is an object
So there you have it from Ruby's own website: in Python everything is an object.
While everything is an object in Python, it differs from Ruby in its approach to resolving names and interacting with objects.
For example, while Ruby provides you with a 'to_s' method on the Object base class, in order to expose that functionality, Python integrates it into the string type itself - you convert a type to a string by constructing a string from it. Instead of 5.to_s, you have str(5).
Don't be fooled, though. There's still a method behind the scenes - which is why this code works:
(5).__str__()
So in practice, the two are fundamentally similar, but you use them differently. Length for sequences like lists and tuples in Python is another example of this principle at work - the actual feature is built upon methods with special names, but exposed through a simpler, easier-to-use interface (the len function).
The Python equivalent to what you wrote in your question would thus be:
(5).__add__(6)
The other difference that's important is how global functions are implemented. In Python, globals are represented by a dictionary (as are locals). This means that the following:
foo(5)
Is equivalent to this in Python:
globals()["foo"].__call__(5)
While Ruby effectively does this:
Object.foo(5)
This has a large impact on the approach used when writing code in both languages. Ruby libraries tend to grow through the addition of methods to existing types like Object, while Python libraries tend to grow through the addition of global functions to a given module.
"everything" is a tad of an overbid, for both Python and Ruby -- for example, if is not "an object", rather it's a keyword used to start a conditional statement or (in Python) inside list comprehensions and generator expressions. The enthusiasm of finding out that functions, classes, methods, and all sort of such things that aren't really objects in (say) C++, are objects in Ruby or Python, causes such enthusiasm. Other things may be objects in Ruby but not Python or viceversa (code blocks, regular expressions, ...).
In answer to your second question, yes:
>>> (1).__add__(2)
3
Yep, as far as I know everything is an object in Python. Certainly the primitive and builtin types (int, long, str, float, etc.) can be subclassed - and in fact the types themselves are objects. Functions are objects, classes are objects, even code blocks are objects in a sense... I can't think of anything in Python that can't be treated as an object.
To add a comment to other people's excellent answers: everything is an object, but some – notably strings and numeric types – are immutable. This means that these types behave the way they do in languages like C or Java (where integers, etc. are not objects) with respect to assignment, parameter passing, etc, and you never have to worry about traps caused by pass-by-reference. It's rather a good solution :-)
Hello and answer is out of the bat not everything, reference is more complete than that and offers many more avenues, within Python 3.8.5 for example Delimiters, Operators and Keywords are not objects. stackoverflow.com/a/66374328/11554034
Have explained it with some detail in that link feel free to check it along.
Anyway, next one says that statement you can correct it by saying (something more correct, although if still can be more completed feel free):
"Everything in a logical line that is not NEWLINE, INDENT, DEDENT, Space bar Character, Operator, Keyword or Delimiter is an object in Python."
Cheers.
Yes, everything is object in Python as long as I researched.
The documentation says below:
Objects are Python’s abstraction for data. All data in a Python
program is represented by objects or by relations between objects.
Every object has an identity, a type and a value.
And, I also checked the type of each value and if each of them is the instance of a particular class as shown below:
from types import FunctionType
class Person:
pass
def test():
pass
print(type("Hello"), isinstance("Hello", str))
print(type(100), isinstance(100, int))
print(type(100.23), isinstance(100.23, float))
print(type(100 + 2j), isinstance(100 + 2j, complex))
print(type(True), isinstance(True, bool))
print(type(None), isinstance(None, type(None)))
print(type([]), isinstance([], list))
print(type(()), isinstance((), tuple))
print(type({}), isinstance({}, dict))
print(type({""}), isinstance({""}, set))
print(type(Person), isinstance(Person, type))
print(type(test), isinstance(test, FunctionType))
Output:
<class 'str'> True
<class 'int'> True
<class 'float'> True
<class 'complex'> True
<class 'bool'> True
<class 'NoneType'> True
<class 'list'> True
<class 'tuple'> True
<class 'dict'> True
<class 'set'> True
<class 'type'> True
<class 'function'> True

Categories

Resources