<jupyter_start><jupyter_text>Primitive and Structured Data TypesWe may easily distinguish between primitive data types and structured ones. In Python, the former includes a limited set of just four types that are built into the language itself. For the purposes of this discussion, we may regard these types as elemental and indivisible - the innermost possible layer of our series of Russian dolls. The latter category, structured data types, encompasses all other objects we will encounter and can author, including all the classes that are a part of the Decod.es library.The distinction between primitive and structured types is reflected in the different ways these two categories of types are represented in diagram: primitive types are attached directly to their variables, while structured types are tethered to their variables like balloons. This visual distinction emphasizes how these types behave differently within the Python object model, specifically regarding their mutability: * ***primitive*** objects are generally ***immutable***, meaning that their values cannot change once assigned* ***structured*** objects which are generally assigned by reference and thus ***mutable*** Recalling that objects are distinct from variables, it should be clear why, despite the fact that Integers are immutable, the following code is perfectly legal. Here, while the object associated with the variable int appears to be changed, what actually occurs in the background is the re-assignment of int to an entirely new object.<jupyter_code>int = 1
int = int + 1
print(int)<jupyter_output><empty_output><jupyter_text>Primitive Data Typeswe can think of the primitive data types listed here as elemental and indivisible, standing apart from structured types by the absence of accessible internal structure. In contrast with the geometric types developed for Decod.es, the types listed here are all native to the Python language. Where applicable, a selection of important methods and operators associated with each of these types will be described.For each demonstration in this section, we assume a set of predefined variables.<jupyter_code>x,y = 10,20
yes, no = True, False<jupyter_output><empty_output><jupyter_text>BooleanBooleans, sometimes referred to as “Bools”, are written as the literals `True` and `False`. Booleans are very often used in control-flow code structures, and in that context, many objects may be converted into Booleans in a truth value testing situation. For example, if we were to substitute a numeric type or an empty collection into the nearby code, we would not receive an error, and instead the if statement on the second line would treat these non-Boolean types as if they were `False`.<jupyter_code>do_it = True
if do_it:
    print("it's done!")<jupyter_output><empty_output><jupyter_text>*Boolean Logical Operators*        Logical AND (`and`)    Returns `True` if both operands are `True`.    `print yes and no`\>>`False`        Logical OR (`or`)    Returns `True` if either of the two operands are `True`.    `print yes or no` \>>`True`        Logical NOT (`not`)    Reverses the logical state of the given operand.    `print not yes` \>>`False`While the three logical operators are some of the few that are regularly applied to Boolean objects, a much broader family of operators produce Boolean values. These include the ***comparison*** operators, which are used to compare arbitrary objects, and the ***identity*** operator which is used to determine if two variables refer to the same object in memory.<jupyter_code>pt_a, pt_b = Point(0,1), Point(0,1)
pt_c = pt_b<jupyter_output><empty_output><jupyter_text>*Boolean Comparison Operators*    Equality (`==`)    Returns `True` if the value of two operands are the same. Note the difference between this operator and the Identity operator, below.    `print pt_a == pt_b`\>>`True`Greater Than (`>`)    Returns `True` if the value of the first operand is greater than the value of the second.    `print x > y`\>>`False`    Less Than (`    Returns `True` if the value of the first operand is less than the value of the second.    `print x \>>`True` Notice the distinction between the equality operator and the identity operator when applied to structured types such as Points. All the Point objects in the code above (pt_a, pt_b, pt_c) contain the same values, and will evaluate as equal using the equality operator. The identity operator, however, evaluates another kind of equivalence. ***Two variables are identical only if they refer to the exact same object in memory.***        *The Identity Operator*    Identity (`is`)    Returns `True` if both operands refer to the same object, `False` otherwise. Useful for determining if two variables refer to the same structured data object.    `print pt_a is pt_b`\>>`False``print pt_a is pt_c`\>>`True`  Integer and FloatThe two numeric types that we will use in this class are limited to Integer for integer numbers and Float for numbers with a decimal point. The operators most commonly applied to numeric types are largely arithmetic, and can be represented by the standard set of symbols that make manipulation of these data types succinct. All arithmetic operators may be applied to both Integer and Float types in Python.         *Selected Arithmetic Operators*    Addition (`+`)    Adds values on either side of the operator.    `print x + y`\>>30    Multiplication (`*`)    Multiplies values on either side of the operator.    `print x * y`\>>200    Modulus (`%`)    Divides the first operand by the second operand, and returns the remainder.    `print x % y`\>>0    Exponent (`**`)    Raises the first operand to the power of the second operand.    `print x**y`\>>100000000000000000000    Integer Division (`//`)    The division of operands where the result is the quotient in which the digits after the decimal point are removed.    `print 9//2`\>>4`print 9.3//2.1`\>>4.0    Addition Assignment (`+=`)    Adds the value of the first operand to the second operand, and assigns the result to the first operand.    `x+=2``print x`\>>12  StringsStrings are not, strictly speaking, a primitive type. They are something of a hybrid. They may be considered a kind of collection, as Strings permit the application of indexing and slicing operations using the square-bracket enclosure operators (`[]`). Despite this property which is typically associated with structured data types, Strings are paradoxically **immutable** and thus behave more like primitive types within the Python object model.  Structured Data TypesIn contrast with primitive types, structured data types are organized composites of lower-level data. These lower-level composites might be primitive types, or may themselves be structured of lower levelsstill. Where primitive types may be elucidated by a discussion of their basic interactions via operators, the behavior of structured types varies widely depending on their composition.The salient quality of structured data types arises from the fact that they are composed of other objects in the object model. This brings to the foreground a number of complications regarding the nature of these composite objects, and of the object model itself, that must be grasped in order for them to be applied and authored effectively. Structured data types that we will encounter in this text include all collection types, such as Lists and Dictionaries, as well as any class imported from a module, such as the Point, Vec, and Line classes found in the Decod.es library.  The Structure of Structured Data TypesThe key set of decisions in the design of classes lies in the definition of its attributes, that is to say, what information is stored as a member of each instance and what methods are made available to operate upon it.Recall our earlier presentation, which presupposed a line segment that was constructed between a point at coordinates `(0,0)` and one at `(1,1)`. In this presupposition something important was glossed over: the way in which line segments are structured impacts their behavior in important ways.Should we think of the resulting object as a container for the two points from which it was made, or as a container for a starting position and a vector that defines its direction and length? One nearby diagram implies the latter, and depicts two Point objects encased within an object (of type SegmentTwoPoint), accessible to the outside world only through the attributes `spt` and `ept`. This configuration is certainly plausible, but is not the only one possible, as we may see in a second nearby diagram (depicting `SegmentPointVec`) that exposes `pt` and `vec` attributes. This distinction may seem minor (the attributes `spt`, `ept`, and `vec` are available to each) until we consider how each of these might be used in practice. The difference lies in which of these attributes are the **primary members** of the object and which are expressed as **derived properties**. While primary members may be both retrieved and altered in place, thereby affecting all the other attributes of the object, derived properties may only be read. So, while the `SegmentPointVec` type has an `ept` property, we may not assign the end point of this segment directly. The same holds true of the `vec` property of the SegmentTwoPoint type. Consider the following code, which produces identical line segments through different means:<jupyter_code>line_a = SegmentTwoPoint(Point(1,1),Point(2,2))
line_a.ept = Point(1,2)

line_b = SegmentPointVec(Point(1,1),Point(2,2))
line_b.vec = Vec(0,1)<jupyter_output><empty_output><jupyter_text>A Tangle of ReferenceThe unexpected behavior produced by the particular relationship between variables and structured data types in Python (the reference relationship) becomes even more important when we consider the case of structured data types that are composed of other structured data types.Imagine a Segment class that stores a Point and a Vec as members. Now imagine constructing an instance of this class using an argument that references an existing object in memory. In the following code, compare the way `line_a` and `line_b` are constructed. The former is built from two Points that are not associated with any existing variables, while the latter is initialized using a reference to `pt_a`, an object already in memory and is associated with a variable. What implications does this hold if `pt_a` were to subsequently be altered?<jupyter_code>line_a = Segment(Point(1,1),Point(2,2))
pt_a = Point(0,0)
line_b = Segment(pt_a,Point(0,1))

print(line_a.spt, line_a.ept)
print(line_b.spt, line_b.ept)<jupyter_output><empty_output><jupyter_text>Given a simple Segment class that is initialized in a straightforward way, the result of the code above will be a top-level object, `line_b`, that contains a reference to another top-level object, `pt_a`. Understanding how variable binding works in Python, it should be clear that, all else being equal, the subsequent manipulation of `pt_a` will affect a change in `line_b` without the explicit mention of `line_b`. This behavior can be useful if we understand the nature of variable binding, and confounding if we do not. Imagine the difficulty of debugging a piece of code that affects changes to objects in memory without once mentioning these objects.Note that this is not how Decodes Segments work. In a Decodes segment, copies of all variables passed to the constructor are made. For this reason, any changes to `pt_a` will not affect `line_b`.<jupyter_code>pt_a += Vec(0,0,1)

print(line_a.spt, line_a.ept)
print(line_b.spt, line_b.ept)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.056970740103270225, 'fraction_numerical': 0.006540447504302926, 'mean_word_length': 4.898984771573604, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 61, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 1, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '7', 'dir': 'jupyter-structured-clean-dedup', 'id': '289510', 'license': 'MIT', 'n_tokens_mistral': 2846, 'n_tokens_neox': 2735, 'n_words': 1670, 'path': '102 - Objects, Types, and Expressions/054 - Primitive and Structured Data Types.ipynb', 'repo_name': 'ksteinfe/decodes_ipynb', 'size': '11620'}