27 Whys, Make Your Understanding of Python Easier for Beginners

image.png

  • Why does Python use indentation to group statements?
  • Why do simple arithmetic operations get strange results?
  • Why are floating point calculations inaccurate?
  • Why are Python strings immutable?
  • Why must 'self' be used explicitly in method definitions and calls?
  • Why can't I assign values ​​in expressions?
  • Why does Python use methods for some functions (such as list.index), while others (such as len(List)) use functions?
  • Why is join a string method and not a list or tuple method?
  • How fast are exceptions?
  • Why is there no switch or case statement in Python?
  • Can't we emulate threads in the interpreter instead of relying on OS-specific threading implementations?
  • Why can't lambda expressions contain statements?
  • Can Python be compiled to machine code, C or other languages?
  • How does Python manage memory?
  • Why doesn't CPython use a more traditional garbage collection scheme?
  • Why doesn't CPython free all memory when it exits?
  • Why are there separate tuple and list data types?
  • How are lists implemented in CPython?
  • How are dictionaries implemented in CPython?
  • Why do dictionary keys have to be immutable?
  • Why doesn't list.sort return a sorted list?
  • How to specify and enforce an interface specification in Python?
  • why no goto?
  • Why can't raw strings (r-strings) end with a backslash?
  • Why doesn't Python have a "with" statement for attribute assignment?
  • Why do if/while/def/class statements need colons?
  • Why does Python allow commas at the end of lists and tuples?

01. Why use indentation to group statements?

Guido van Rossum believes that grouping using indentation is very elegant and greatly improves the clarity of ordinary Python programs. Most people learn and like this feature after a while.
Since there are no opening/closing parentheses, there is no disagreement between the parser-perceived grouping and the human reader. Occasionally a C programmer will come across a code snippet like this:

Only the x++ statement is executed if the condition is true, but the indentation makes you think that's not the case. Even seasoned C programmers sometimes stare at it for long periods of time, wondering why y is decreasing even when x > y.
Because there are no opening/closing parentheses, Python is less prone to coding conflicts. In C, parentheses can be placed in many different places. If you're used to reading and writing code that uses one style, you'll at least feel a little uneasy when reading (or being asked to write) another style.
Many coding styles put opening/closing brackets on a single line. This makes the program rather long, wastes valuable screen space, and makes it harder to get a full overview of the program. Ideally, the function should fit on one screen (eg, 20–30 lines). 20 lines of Python can do more than 20 lines of C. It's not just the lack of opening/closing parentheses - the lack of declarations and advanced data types is also a reason - but indentation based syntax certainly helps.
02. Why do simple arithmetic operations get strange results?

Please see the next question.
03. Why is floating point calculation inaccurate?

Users are often surprised by results like this:

And think it's a bug in Python. Not so. This has less to do with Python and more to do with how the underlying platform handles floating point numbers.
The float type in CPython uses the C language double type for storage. The value of a float object is a binary floating point number stored with fixed precision (usually 53 bits), since Python uses C operations, which rely on the hardware implementation in the processor to perform floating point operations. This means that Python behaves like many popular languages, including C and Java, as far as floating-point arithmetic is concerned.
Many numbers that can be easily represented in decimal cannot be represented in binary floating point. For example, after entering the following statement:

>>> x = 1.2

The value stored for x is an approximation (very close) to the decimal value of 1.2, but not exactly equal to it. On a typical machine, the actual stored value is:

1.0011001100110011001100110011001100110011001100110011 (binary)

It corresponds to a decimal value:

1.1999999999999999555910790149937383830547332763671875 (decimal)

A typical 53-bit precision gives Python floating-point numbers 15-16 decimal places of precision.
For a more complete explanation, see the chapter on floating-point arithmetic in the Python Tutorial.
04. Why are Python strings immutable?

There are several advantages.
One is performance: knowing that a string is immutable means we can allocate space for it at creation time, and the storage requirements are fixed. This is also one of the reasons for the difference between tuples and lists.
Another advantage is that strings in Python are treated as "basic" as numbers. No action will change the value 8 to something else, and in Python no action will change the string "8" to something else.
05. Why must "self" be used explicitly in method definitions and calls?

This idea borrows from the Modula-3 language. It has proven to be very useful for a number of reasons.
First, it's more obvious that a method or instance property is used instead of a local variable. Reading self.x or self.meth makes it clear that instance variables or methods are used even if you don't know the definition of the class. In C++, you can tell by the lack of a local variable declaration (assuming global variables are rare or easy to identify) - but in Python there are no local variable declarations, so you have to look up the class definition to be sure. Some C++ and Java coding standards require instance attributes to have the m_ prefix, so this explicitness is still useful in those languages.
Second, it means that if you want to explicitly reference or call the method from a specific class, no special syntax is required. In C++, if you want to use a method in a derived class that overrides a base class, you have to use the :: operator - in Python you can write baseclass.methodname(self, ). This is useful for init methods, especially when a derived class method wants to extend a base class method of the same name, but must somehow call the base class method.
Finally, it solves the syntax problem of variable assignment: in order for local variables in Python (by definition!) to be assigned to those variables (and not explicitly declared global) that are assigned in the function body, the interpreter has to be told somehow a Assignment is for assigning an instance variable rather than a local variable, and it is best done syntactically (for efficiency reasons). C++ does this with declarations, but Python doesn't, and it would be a shame to introduce them just for this purpose. Using an explicit self.var solves this problem nicely. Similarly, for using instance variables, having to write self.var means that references to unqualified names inside methods do not have to search the instance's directory. In other words, local variables and instance variables exist in two different namespaces, and you need to tell Python which namespace to use.
06. Why can't you assign values ​​in expressions?

Many people used to C or Perl complain that they want to use this feature of C:

But in Python it is forced to be written like this:

image.png

The reason for disallowing assignments in Python expressions is that these common, hard-to-find bugs in other languages ​​are caused by this construct:

image.png

The error is a simple typo: x = 0, assigning 0 to the variable x, while comparing x == 0 is certainly expected.
There have been many alternative proposals. Most are hacky schemes to save a few words, but use arbitrary or implicit syntax or keywords, and don't meet the simple criteria of a language change proposal: it should be intuitively available to human readers who haven't been introduced to the concept yet correct meaning.
An interesting phenomenon is that most experienced Python programmers recognize the while True idiom and don't care too much about assigning values ​​in expression constructs; only newcomers express a strong desire to add it to the language .
There is an alternative spelling that looks attractive, but is generally less reliable than the "while True" solution:

The problem is how to know the next line if you change your mind (eg you want to change it to sys.stdin.readline). You have to remember to change two places in the program - the second occurrence is hidden at the bottom of the loop.
The best way to do this is to use an iterator, which loops through objects with a for statement. For example file objects support the iterator protocol, so can be written simply as:

07 Why does Python use methods for some functions (such as list.index), while others (such as len(List)) use functions?

As Guido puts it:
(a) For some operations, prefix notation is easier to read than postfix – prefix (and infix!) operations have a long tradition in mathematics, like notations that visually help mathematicians think about problems Law. Compare how easy it is for us to rewrite a formula like x*(a+b) as x a+x b, and how clumsy it is to do the same thing using raw OO notation. (b) When you read the
code written with len(X), you know it's asking for the length of something. This tells us two things: the result is an integer, and the argument is some kind of container. Conversely, when When reading x.len, you must already know that x is some kind of container that implements an interface, or a container that inherits from a class that has a standard len. When a class that doesn't implement a map has a get or key method, or a class that isn't a file has a write method 08.
Why is join a string method and not a list or tuple method?

As of Python 1.6, strings have become more like other standard types, and when methods are added, these methods provide the same functionality that was always provided using the functions of the String module. Most of these new methods are widely accepted, but one that seems to make some programmers uncomfortable is:

", ".join(['1', '2', '4', '8', '16'])

The result is as follows:

"1, 2, 4, 8, 16"

There are two common arguments against this usage.
The first is something like: "methods using string literals (String Constant) look really ugly", the answer is maybe, but string literals are just a fixed value. If these methods are allowed on names bound to strings, there is no logical reason for them to be literal unavailable.
The second objection usually goes something like this: "I'm actually telling the sequence to use string constants to concatenate its members together". Regrettably this is not the case. For some reason it seems much easier to split as a string method, because in this case it's easy to see:
"1, 2, 4, 8, 16".split(", ")
is Directive on string literals to return substrings separated by the given delimiter (or, by default, any whitespace).
join is a string method because when you use it, you tell the delimiter string to iterate over a sequence of strings and insert itself between adjacent elements. The argument to this method can be any object that follows the sequence rules, including any new classes you define yourself. There are similar methods for bytes and byte array objects.
09. How fast are exceptions?

A try/except block is extremely efficient if no exception is thrown. Actually catching exceptions is expensive. In Python versions prior to 2.0, this idiom was commonly used:

image.png

This only makes sense if you expect the dict to have keys at all times. If not, you should code like this:

For this specific case, you can also use value = dict.setdefault(key, getvalue(key)), but only if calling getvalue is cheap enough, since it will be evaluated in all cases.
10. Why is there no switch or case statement in Python?

You can easily do this with a series of if... elif... elif... else. There have been some proposals for switch statement syntax, but no consensus has been reached on whether and how range testing should be done. See PEP 275 for full details and current status.
For situations where you need to choose from a large number of possibilities, you can create a dictionary that maps case values ​​to functions to call. E.g:

image.png

For object calling methods, this can be further simplified by using the getattr built-in to retrieve a method with a specific name:

image.png

It is recommended to use a prefix for method names, such as visit_ in this example. Without such a prefix, an attacker would be able to call any method on the object if the value came from an untrusted source.
11. Can't emulate threads in the interpreter instead of relying on OS-specific thread implementations?

Answer 1: Unfortunately, the interpreter pushes at least one C stack frame for every Python stack frame. Additionally, extensions can call back to Python at any time. Therefore, a complete threading implementation requires threading support for C.
Answer 2: Fortunately, Stackless Python has a completely redesigned interpreter loop that avoids the C stack.
12. Why don't lambda expressions contain statements?

Python's lambda expressions cannot contain statements, because Python's syntactic framework cannot handle statements nested inside expressions. In Python, however, this is not a serious problem. Unlike the lambda forms in other languages ​​that add functionality, Python's lambdas are just a shorthand notation if you're too lazy to define a function.
Functions are already first-class objects in Python and can be declared in local scope. So the only advantage of using a lambda instead of a locally defined function is that you don't need to create a name for the function - it's just a local variable assigned to the function object (of the exact same type as the object produced by the lambda expression)!
13. Can Python be compiled to machine code, C or other languages?

Cython compiles a modified version of Python with optional comments into a C extension. Nuitka is an emerging compiler for compiling Python to C++ code, designed to support the full Python language. To compile to Java, consider VOC.
14. How does Python manage memory?

The details of Python memory management are implementation-dependent. CPython, the standard implementation of Python, uses reference counting to detect unreachable objects, and another mechanism to collect reference cycles, periodically performing a cycle detection algorithm to find unreachable loops and delete the objects involved. The gc module provides functions to perform garbage collection, get debug statistics, and optimize collector parameters.
However, other implementations (such as Jython or PyPy) can rely on different mechanisms, such as a full garbage collector. If your Python code relies on the behavior of the reference counting implementation, this difference can lead to some subtle porting issues.
In some Python implementations, the following code (which works fine in CPython) may run out of file descriptors:

In fact, using CPython's reference counting and destructor scheme, each new assignment of f closes the previous file. However, with traditional GC, these file objects can only be collected (and closed) at various (possibly long) intervals.
If you want to write code that works with any python implementation, you should either explicitly close the file or use a with statement; this works regardless of the memory management scheme:

15. Why doesn't CPython use a more traditional garbage collection scheme?

First, it's not a C standard feature, so it's not portable. (Yes, we know about the Boehm GC library. It contains assembly code for most common platforms (but not all platforms), and while it's largely transparent, it's not completely transparent; for Python to use it, one needs to use Patch.)
Traditional GC also becomes a problem when Python is embedded in other applications. In stand-alone Python, the standard malloc and free can be replaced with the versions provided by the GC library, and an application that embeds Python may wish to replace malloc and free with its own, and may not need Python's. CPython now implements malloc and free correctly.
16. Why doesn't CPython free all memory when it exits?

Objects referenced from the global namespace or Python modules are not always freed when Python exits. This can happen if there are circular references and some memory allocated by the C library is also impossible to free (tools like Purify for example will complain about these). However, Python cleans up memory on exit and tries to destroy each object.
If you want to force Python to delete something on free, use the atexit module to run a function that forces the deletion.
17. Why are there separate tuple and list data types?

While lists and tuples are similar in many ways, they are often used quite differently. Tuples can be thought of as similar to Pascal records or C structures; they are small collections of related data, which can be of different types, that can be manipulated as a group. For example, Cartesian coordinates are appropriately represented as tuples of two or three numbers.
Lists, on the other hand, are more like arrays in other languages. They tend to hold different numbers of objects, all of the same type, and operate on one by one. For example, os.listdir('.') returns a list of strings representing the files in the current directory. Functions that operate on this output usually don't break if you add a file or two to the directory.
Tuples are immutable, which means that once a tuple is created, none of its elements can be replaced with new values. Lists are mutable, which means you can always change the elements of the list. Only invariant elements can be used as keys for dictionaries, so only tuples and non-lists can be used as keys.
18. How are lists implemented in CPython?

CPython lists are actually variable-length arrays, not lisp-style linked lists. The implementation uses a contiguous array of references to other objects and keeps a pointer to that array and the length of the array in the list header structure.
This makes the operation cost of indexing the list a[i] independent of the size of the list or the value of the index.
When items are added or inserted, the reference array is resized. And employs some neat tricks to improve the performance of repeatedly adding items; when the array has to grow, some extra space is allocated so that no actual resizing is needed the next few times.
19. How are dictionaries implemented in CPython?

CPython's dictionaries are implemented as resizable hash tables. Compared to B-trees, this provides better performance for lookups (by far the most common operation) and is simpler to implement in most cases.
The way dictionaries work is to use the hash built-in function to calculate the hash code for each key stored in the dictionary. The hash code varies widely depending on the key and the per-process seed; for example, "Python" has a hash value of -539294296, while "python" (a bitwise different string) has a hash value of 1142331976. The hash code is then used to calculate where in the internal array the value will be stored. Assuming you store keys that all have different hash values, this means that the dictionary takes constant time – O(1), in Big-O notation – to retrieve a key.
20. Why dictionary keys must be immutable?

The hash table implementation of a dictionary uses a hash value computed from the key value to find a key. If the key is a mutable object, its value may change, and thus its hash value. However, since whoever changes the key object has no way of telling whether it is being used as a dictionary key, there is no way to modify an entry in the dictionary. Then when you try to look up the same object in the dictionary, it won't be able to find it because its hash is different. If you try to look up the old value, it won't find it either, because objects found in that hash table will have different values.
If you want a dictionary indexed by a list, just convert the list to a tuple first; use the function tuple(L) to create a tuple with the same entries as the list L. Tuples are immutable, so they can be used as dictionary keys.
Some unacceptable solutions that have been proposed:
hashes are listed by their address (object ID). This doesn't work because if you construct a new list with the same values, it won't be found; for example:

A KeyError exception is raised because the id of [1, 2] used in the second row is different from the id in the first row. In other words, == should be used to compare dictionary keys, not is.
Copy when using a list as a key. This is not useful because a list which is a mutable object can contain a reference to itself and then the copying code will go into an infinite loop.
Allow lists as keys, but tell the user not to modify them. When you accidentally forget or modify the list, this creates a class of hard-to-track bugs in your program. It also invalidates an important dictionary invariant: every value in d.keys can be used as a dictionary key.
After using a list as a dictionary key, it should be marked read-only. The problem is, it's not just a top-level object whose value can be changed; you can use tuples containing lists as keys. Associating anything as a key into a dictionary requires marking all objects reachable from there as read-only -- and self-referencing objects can lead to infinite loops.
The following can be used to work around this if desired, but use it at your own risk: you can wrap a mutable struct in a class instance that has both eq and hash methods. Then, you must ensure that the hash values ​​of all such wrapper objects that reside in a dictionary (or other hash-based structure) remain fixed while the object is in a dictionary (or other structure).

image.png

Note that hash calculations are complicated by the possibility that some members of the list may not be available and the possibility of arithmetic overflow. Also, it must always be true that hash(o1) == hash (o2) o1.hash == o2.hash
) if o1 == o2 (i.e. o1.eq ( o2) is True ) , regardless of whether the object is in the dictionary or not. If you can't meet these constraints, dictionaries and other hash-based structures will fail. For ListWrapper, as long as the wrapper object is in the dictionary, the wrapped list cannot be changed to avoid the exception. Don't do this unless you are prepared to seriously consider the needs and the consequences of not meeting them correctly. Please note. 21. Why does list.sort not return a sorted list?(即

Copying a list just for sorting would be a waste when performance is important. So list.sort sorts the list appropriately. To remind you of this fact, it doesn't return a sorted list. This way you don't accidentally overwrite the list when you need the sorted copy, but also keep the unsorted version.

If you want to return a new list, use the built-in sorted function. This function creates a new list from the provided iterable list, sorts it and returns it. For example, here's how to iterate through a dictionary and sort by keys:

22. How to specify and implement an interface specification in Python?

Module interface specifications, provided by languages ​​such as C++ and Java, describe the prototypes of a module's methods and functions. Many people believe that compile-time enforcement of interface specifications helps build large programs.
Python 2.6 added an abc module that allows the definition of abstract base classes (ABCs). You can then use isinstance and issubclass to check whether an instance or class implements a particular ABC. The collections.abc module defines a set of useful ABCs such as Iterable , Container , and MutableMapping.
For Python, many of the benefits of interface specification can be obtained by properly testing components. There is also a tool PyChecker that can be used to find problems due to subclassing.
A good module test suite provides both regression testing and a module interface specification and a set of examples. Many Python modules can be run as scripts to provide simple "self-testing". Even modules that use complex external interfaces can often be tested in isolation using simple "stub" mocks of the external interface. Exhaustive test suites can be constructed using the doctest and unittest modules or third-party testing frameworks to run every line of code in the module.
Proper testing procedures can help build large, complex applications and interface specifications in Python. In fact, it might be better because the interface specification cannot test some properties of the program. For example, the append method will add a new element to the end of some internal list; the interface specification can't test that your append implementation does this correctly, but it's trivial to check this property in a test suite.
Writing a test suite is useful, and you may want to design your code to make it easy to test. An increasingly popular technique is test-oriented development, which requires that parts of the test suite be written first before any actual code is written. Of course, Python allows you to be sloppy and not write test cases at all.
23. Why is there no goto?

Exception catches can be used to provide "goto structures" that even work across function calls. Many believe that exception catching is a convenient way to simulate all reasonable uses of the "go" or "goto" constructs in C, Fortran, and other languages. E.g:

image.png

But doesn't allow you to jump into the middle of a loop, which is generally considered an abuse of goto. Use with caution.
24. Why can't raw strings (r-strings) end with a backslash?

More precisely, they cannot end with an odd number of backslashes: an unpaired backslash at the end escapes the closing quote character, leaving an unterminated string.
Raw strings are designed to facilitate the creation of input by processors (primarily regex engines) that want to perform their own backslash-escaping processing. Such processors treat unmatched trailing backslashes as errors, so raw strings do not allow this. In turn, it is allowed to escape strings by escaping backslashes with quote characters. These rules work well when r-strings are used for their intended purpose.
If you are trying to build Windows pathnames, be aware that all Windows system calls use forward slashes:

If you are trying to build a pathname for a DOS command try the following example

  1. Why doesn't Python have a "with" statement for attribute assignment? Python has a 'with' statement that encapsulates the execution of a block, calling code at the entry and exit of the block. Some languages ​​are structured like this:

In Python, such constructs are ambiguous.
Other languages, such as ObjectPascal, Delphi, and C++ use static typing, so there is no ambiguity about what member to assign to. This is the point of static typing - the compiler always knows the scope of each variable at compile time.
Python uses dynamic typing. It is impossible to know in advance which property is referenced at runtime. Member properties can be added or removed from an object dynamically. This makes it impossible to know by simple reading what property is being referenced: local property, global property or member property?
For example, take the following incomplete code snippet:

The code snippet assumes that "a" must have a member property named "x". However, the interpreter is not told this in Python. Suppose "a" is an integer, what happens? If there is a global variable named "x", will it be used in the with block? As you can see, the dynamic nature of Python makes such a choice even more difficult.
However, Python can easily achieve the main benefit of "with" and similar language features (reduced code size) through assignment. replace:

Write it like this:

This also has the side effect of improving execution speed, since Python resolves the name bindings at runtime, whereas the second version only needs to perform the resolution once.
26. Why do if/while/def/class statements need colons?

Colons are mainly used to enhance readability (one of the results of the ABC language experiment). Consider this:

Note that the second method is slightly easier. Note further how the colon is set in the example in this FAQ answer; this is standard usage in English.
Another minor reason is that colons make it easier for editors with syntax highlighting to work; they can look for colons to decide when indentation needs to be increased without having to do a finer-grained parsing of the program text.
27. Why does Python allow commas at the end of lists and tuples?

Python allows you to add a trailing comma at the end of lists, tuples and dictionaries:

image.png

This is allowed for several reasons.
If the list, tuple, or dictionary literals are spread across multiple lines, it's easier to add more elements because you don't have to remember to add a comma on the previous line. The lines can also be reordered without syntax errors.

Many friends in the process of learning Python often do not want to learn because they have no materials or no guidance, so I specially prepared a large number of PDF books and video tutorials, all of which are free for everyone! Whether you have zero foundation or basic foundation, you can get your own corresponding learning package!

Accidentally omitting commas can lead to hard-to-diagnose bugs. E.g:

image.png

This list looks like it has four elements, but it actually contains three: "fee", "fiefoo" and "fum". Always include a comma to avoid this source of error.
Allowing trailing commas also makes programming code easier to generate.

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326874574&siteId=291194637