This article has a host of other issues aside from these two. Here's a list of some that I wanted to point out. Bold is mine for emphasis.
All data in Python resides in objects. And all Python objects have a type with associated attributes and methods. But because Python uses dynamic typing to allow flexibility, it doesn’t always know the type of a given object. So functions you think should exist as members of an object when you instantiate may very well not.
Python always knows the type of a given object - method calls work by looking up the function on the class.
Fortunately, you can ensure that you have the right functions for your classes and objects when you define them using something known as dunder methods or magic methods. You can quickly recognize these operations by the double underscore or “dunder” format: __methodname__()
.
Dunders are used to implement special functionality for a class (eg. constructors, operator overloading, etc), completely unrelated to ensuring that an object has a method that you're attempting to call.
Python object types have several characteristics. First, they may be single value object types (e.g., numbers or characters) or container/collection object types (e.g., lists and arrays).
Assuming they're referring to built-in types, in which case they omitted functions, classes and iterators. Characters and arrays are also not builtin types.
Type |
Description |
Example |
Constructor |
List |
The broadest category of sequence data types includes several subtypes:Lists are mutable (i.e. modifiable) sequences, normally of similar or homogeneous items Tuples are immutable (i.e. they cannot change state or content) sequences, particularly useful for different or heterogeneous items Ranges are immutable sequences of numbers, most often used for managing loops |
List: a, b, c, d, e Tuple: 3, red, [corvettes, porsches] Range: 0 (start value), 100 (stop value), 10 (iterative step) |
list() tuple() range() |
Text |
A text-specific sequence type, this type encompasses sequences of Unicode characters. |
Hello world |
str() |
Binary |
Python has three different binary-specific sequence data types:Bytes are immutable sequences of single bytes.Byte arrays are the mutable version of bytes.memoryview objects allow Python to access and manipulate the content of a byte-type object without first copying it. These types allow the manipulation of binary objects and the conversion of string data to binary for machine storage. |
Byte: 244 Bytearray: [2, 4, 6, 8] Memoryview: 64 or [10, 20, 30] b’123’b’abc’b’\x20\xC2\xA9’ |
byte() bytearray() memoryview() |
None of these examples are valid syntax. Especially egregious are byte, bytearray and memoryview - the provided examples are integers and lists.
One of the more widely used functions for sequence data is len()
, which returns the length of an item. Attempting to use the built-in len()
function on a custom class returns an attribute error. However, by implementing the __len__
dunder in your class, you can replicate built-in functionality. The same is true for other common, useful sequence functions like get_item()
, set_item()
, and more. You can then use these objects anywhere you would a native sequence type without changing code.
len()
returns the length of the entire sequence. get_item()
and set_item()
don't exist.
Let’s look at a very simplified example of using __bool__
. Say you have a class for cars that contains certain attributes like the year, make, model, and color. You know that while you are going to store data about a wide range of cars, most of your operations will involve only red or black cars. You can use __bool__
in defining the class to return true only for red or blue cars:
Redefining __bool_
for a one-off usecase doesn't make sense. The code also doesn't match the bolded description.
Examples of sets include [5, 10, 15, 20] and [5, 10, “Hello”, “World”].
Those are lists.
Frozen sets, on the other hand, are useful as dictionary keys. Frozen sets can only have a single value, but that value can be almost anything, including a list or a set.
Frozen sets are just sets, but immutable. I can't see how that can be described as "only have a single value".
There are many more advanced data types in Python, including classes, instances, and exceptions. But as we will see, classes and instances do not necessarily come along with the same dunder methods as other data types.
"instances" are not a data type. This paragraph is also duplicated (and this is also a strange spot in the article to put it).
The most well-known dunder methods in Python are __new__()
and __init__()
, which create a class instance and initialize its state, respectively. Most python programmers focus overrides on __init__
so that changes take place on instantiation of a new object, while __new__
typically only creates subclasses of immutable data types. The syntax for the init method is __init__(self, char1, char 2,...)
.
__new__
is definitely not well-known, as it is rarely needed. The __init__
example syntax is also invalid.
Python does not know what type the object btc_wallet is. So many standard operations are not available to use with it, and if we try to do so, we will get an error message. But now, let’s look at how we would use dunder methods to add functionality to our btc_wallet object type.
Presumably that should be btcwallet1
. In any case, "Python always knows the type of a given object".
If you want to determine how much total bitcoin you have in your first two wallets it would be tempting to write:
total_btc = btcwallet1.amount + btcwallet2.amount
This code works - not sure why the article is implying that it doesn't.
But if you use the dir()
function on btc_wallet, you would see that __add__
is not present. So we need to modify the class definition with a dunder method:
But you're not adding 2 btc_wallets, you're adding 2 integers (that are attributes of btc_wallet
).
def __add__(self, other):
return self.amount + other.amount
If you wanted to add the balances of the 2 wallets, it's better to spell it out explicitly instead of using __add__
.
Now, what if we want to total only our wallets that have three or more BTC? We could use a standard if loop, checking the amount for each wallet and including only the ones where btcwallet.amount > 3. But we could also return to our discussion about Booleans above. You can define btcwallet so that it is only true if the amount is greater than three by adding the following to the class definition:
"if loops" don't exist. Also, "redefining __bool__
for a one-off usecase makes no sense".
Now you can set up the loop to calculate the total of your wallets:
total_btc = 0
for x in range (0,3):
loopname = locals()['btcwallet' + (str(x+1))]
if bool(loopname):
total_btc = total_btc + loopname.amount
Using locals()
for variable-variables is very bad practice. Indentation is also invalid.
Half the single quotes in the article are also smart quotes (including within code blocks), and sevaral of the code snippets could be written in much more idiomatic Python (after the syntax errors are fixed, of course).
Even ignoring the factual, stylistic and syntax errors, the article honestly doesn't seem particularly well-written in the first place (as is usual for the blog).
Car
class example has a million problems. Look at the of this questionif condition: return True
...return False
instead ofreturn condition
were ... unfortunate. But then I saw thatfor
loop at the end, with thelocals()
stuff. Yikes!