Skip to content
VLSI Mentor

Python Part I · Module 16 · Object-Oriented Programming

Everything Is an Object: Attributes and Methods

This module introduces classes, and it opens by pointing out that you have been using them since Module 4. Every time you wrote line.strip(), results.get(), path.exists() or f.read(), you were asking a value to do something it knows how to do — and the fact that those four look alike is not a coincidence of syntax but the single most important structural fact about the language. Before writing a class it is worth being precise about what the dot has been doing all along, because once that is clear, the syntax in the next chapter is a way of making your own version of something you already understand.

Intermediate15 min readPythonPart IFundamentalsOOPObjects

Part I · Module 16 · Chapter 16.1 · Everything Is an Object: Attributes and Methods

1. The Dot You Already Use

Four lines from four different modules:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
line.strip()      # Module 4, a string
results.get(k)    # Module 8, a dictionary
f.read()          # Module 15, a file
path.exists()     # Module 15, a Path

Four unrelated kinds of value, one piece of syntax. The pattern is always the same: a value, a dot, and a name that belongs to that value.

What the dot means is: look up this name on this value. What comes back depends on what the value's type defines, which is why strip works on a string and not on a dictionary.

2. Everything Means Everything

The claim in the title is literal. Every value you have used in fifteen modules has a type, and every one of them carries that type with it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
VALUES = ["smoke", 42, 3.14, [1, 2], {"a": 1}, (1, 2), {1, 2}, None, True]

for v in VALUES:
    print(f"{str(v):<10} type={type(v).__name__:<9} has a class={hasattr(v, '__class__')}")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
smoke      type=str       has a class=True
42         type=int       has a class=True
3.14       type=float     has a class=True
[1, 2]     type=list      has a class=True
{'a': 1}   type=dict      has a class=True
(1, 2)     type=tuple     has a class=True
{1, 2}     type=set       has a class=True
None       type=NoneType  has a class=True
True       type=bool      has a class=True

None is an object. So is True. So is the number 42. There is no category of "plain data" in Python that sits outside this — which is why type(e).__name__ worked on exceptions in Chapter 14.3 and why .strip() works on a string.

A class is the thing that decides what names a value responds to. str is a class; "smoke" is one instance of it. Those two words are the whole vocabulary.

3. Attribute or Method?

The canonical pair for this chapter:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
path.name         # an attribute: data it holds
path.exists()     # a method: something it can do

The difference is the parentheses, and the underlying difference is what the name refers to:

  • an attribute is a value the object holds — you read it;
  • a method is a function the object owns — you call it.

Both are looked up the same way. The mechanical test is callable():

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
from pathlib import Path

p = Path("runs/seed_41/sim.log")

print("attribute name   :", p.name)
print("attribute suffix :", p.suffix)
print("method result    :", p.is_absolute())
print()
print("is .name callable()        :", callable(p.name))
print("is .is_absolute callable() :", callable(p.is_absolute))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
attribute name   : sim.log
attribute suffix : .log
method result    : False

is .name callable()        : False
is .is_absolute callable() : True

Note the last line carefully: p.is_absolute has no parentheses. That is deliberate, and it is the subject of the next section.

Numbers have attributes too, which is a useful shock:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
print("real:", (5).real)
print("imag:", (5).imag)
print("numerator:", (5).numerator)
print("callable?", callable((5).real))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
real: 5
imag: 0
numerator: 5
callable? False

4. Forgetting the Parentheses Does Not Raise

The single most common slip with the dot:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
s = "  smoke  "

print("A:", repr(s.strip()))
print("B:", repr(s.strip).split(" of ")[0] + " ...>")
print("C:", type(s.strip).__name__)
print("D:", callable(s.strip))
print("E:", s.strip.__name__)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
A: 'smoke'
B: <built-in method strip ...>
C: builtin_function_or_method
D: True
E: strip

B is not an error. s.strip is a perfectly good value — it is the method itself, bound to this particular string, waiting to be called. Printing it shows what it is rather than what it would return.

The full text of B is trimmed here, because the untrimmed version ends in a memory address that differs on every run. That is a real property of how Python displays an object it has not been told how to display, and Chapter 16.8 is about replacing it with something useful.

E shows the method knows its own name, which is occasionally handy when something has handed you a function and you want to know which one.

The practical consequence is that a missing () fails later and strangely:

Azvya Education Pvt. Ltd.VLSI Mentor
noparens.py (BROKEN ON PURPOSE)
line = "UVM_ERROR drv.sv(88)\n"
cleaned = line.strip
print("cleaned:", cleaned.upper())
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Traceback (most recent call last):
  File "noparens.py", line 3, in <module>
    print("cleaned:", cleaned.upper())
                      ^^^^^^^^^^^^^
AttributeError: 'builtin_function_or_method' object has no attribute 'upper'

The error is about builtin_function_or_method, several steps from the missing parentheses on line 2. This is Chapter 14.2 Section 5 exactly: the error surfaces where the bad value is used, not where it was created — and the type name in the message is the clue.

5. Methods Are Functions That Receive the Object

A method differs from a plain function in one respect: the object is handed to it automatically.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
word = "smoke"

print("A:", word.upper())
print("B:", str.upper(word))
print("C:", word.upper() == str.upper(word))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
A: SMOKE
B: SMOKE
C: True

B is the same call written the long way. word.upper() looks up upper on the class str and passes word in as the first argument. The dot form is shorthand for the second.

That is the whole mechanism behind self, which Chapter 16.3 is about. When you write your own method, the object arrives as the first parameter, because that is what the dot does.

6. AttributeError Reads Differently Now

Chapter 14.3 Section 8 catalogued AttributeError as "the object has no such name". With the model in place, the message is more specific than that:

Azvya Education Pvt. Ltd.VLSI Mentor
attr.py (BROKEN ON PURPOSE)
name = "smoke"
name.push("_test")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Traceback (most recent call last):
  File "attr.py", line 2, in <module>
    name.push("_test")
    ^^^^^^^^^
AttributeError: 'str' object has no attribute 'push'

Read it as: the class str defines no name push. The message names the class first because the class is what decides. push is a list method in some languages and no Python class has it; append is a list method and str does not have that either.

Which is why the type in an AttributeError is the first thing to read: it tells you what Python thinks it is holding, and the surprise is often that it is holding something other than you believed — most commonly NoneType.

7. Seeing What an Object Offers

dir() lists the names a value responds to:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
public = []
for name in dir("smoke"):
    if not name.startswith("_"):
        public.append(name)

print("str has", len(public), "public names")
print("first eight:", public[:8])
print("strip among them:", "strip" in public)
print("push among them :", "push" in public)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
str has 47 public names
first eight: ['capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find']
strip among them: True
push among them : False

The names beginning with underscores are filtered out here. Those are the special methods — Chapter 16.8's subject — and they are how len(), print() and + actually work.

8. Functions and Classes Are Objects Too

Section 2's table covered values. The claim goes further than that, and this is the part that surprises people arriving from C or SystemVerilog:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
def count_errors(lines):
    """Count UVM_ERROR lines in a log."""
    return 0


print("its type     :", type(count_errors).__name__)
print("its name     :", count_errors.__name__)
print("its docstring:", count_errors.__doc__)
print("callable     :", callable(count_errors))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
its type     : function
its name     : count_errors
its docstring: Count UVM_ERROR lines in a log.
callable     : True

A function is an object with attributes. __name__ and __doc__ are not special syntax — they are attributes the function carries, which is exactly how help() works and how Chapter 11.11's docstrings become visible to tools.

Being an object means a function can be stored, passed and looked up like any other value:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
def count_errors(lines):
    return len([l for l in lines if "UVM_ERROR" in l])


def count_warnings(lines):
    return len([l for l in lines if "UVM_WARNING" in l])


LOG = ["UVM_ERROR drv.sv(88)", "UVM_WARNING late", "UVM_ERROR mon.sv(12)"]

checkers = {"errors": count_errors, "warnings": count_warnings}

for name in sorted(checkers):
    print(f"{name:<9} {checkers[name](LOG)}")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
errors    2
warnings  1

The dictionary holds the functions themselves, not calls to them — note there are no parentheses in {"errors": count_errors}, which is Section 4's distinction doing useful work rather than causing a bug. checkers[name](LOG) looks the function up and then calls it.

That is also the mechanism behind key= in sorted() (Chapter 6.x) and behind Chapter 11.14's lambdas: both take a function as an ordinary argument.

And classes are objects as well:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
print("type of a class :", type(int).__name__)
print("type of type    :", type(type).__name__)
print("a class is callable:", callable(int))
print("calling it makes an instance:", int("42"), type(int("42")).__name__)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
type of a class : type
type of type    : type
a class is callable: True
calling it makes an instance: 42 int

A class is an object of type type, and calling it is what creates an instance — which is why TestRun("smoke") in the next chapter looks like a function call. It is one.

9. Looking a Name Up at Run Time

The dot needs the attribute name written in the source. When the name is in a variable instead, there is a function for it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class Run:
    def __init__(self):
        self.name = "smoke"
        self.seed = 1234


r = Run()

for field in ["name", "seed", "missing"]:
    print(f"  {field:<8} {getattr(r, field, '<absent>')}")

setattr(r, "status", "PASS")
print("after setattr:", r.__dict__)
print("hasattr      :", hasattr(r, "status"), hasattr(r, "nope"))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  name     smoke
  seed     1234
  missing  <absent>
after setattr: {'name': 'smoke', 'seed': 1234, 'status': 'PASS'}
hasattr      : True False

getattr(obj, "name") is obj.name with the name as data. The third argument is a default, which turns a would-be AttributeError into a value — the same shape as dict.get() from Chapter 8.3.

This is how a generic report writer works: given a list of field names, it can pull each one from any object without knowing the class.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class Run:
    def __init__(self, name, seed, runtime_s):
        self.name = name
        self.seed = seed
        self.runtime_s = runtime_s


def as_row(obj, fields):
    """Works on any object that has these attributes."""
    return " ".join(str(getattr(obj, f, "-")) for f in fields)


runs = [Run("smoke", 1234, 182.4), Run("cdc", 5678, 204.1)]
for run in runs:
    print(as_row(run, ["name", "seed", "runtime_s"]))
print(as_row(runs[0], ["name", "nonexistent"]))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
smoke 1234 182.4
cdc 5678 204.1
smoke -

Use these when the name genuinely comes from data — a config file, a column list, a command-line flag. Do not use them to avoid writing the dot: getattr(r, "name") where r.name would do is harder to read and invisible to every tool that checks your code.

10. Predict, Run, Observe

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
values = ["smoke", [1, 2], {"a": 1}]

for v in values:
    print(f"{type(v).__name__:<5} has strip={hasattr(v, 'strip'):<2} "
          f"has append={hasattr(v, 'append'):<2} has get={hasattr(v, 'get')}")

print("---")
d = {"test": "smoke"}
print("A:", callable(d.get))
print("B:", d.get("test"))
print("C:", type(d.get).__name__)
print("D:", hasattr(d, "keys"), callable(d.keys))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
str   has strip=1  has append=0  has get=False
list  has strip=0  has append=1  has get=False
dict  has strip=0  has append=0  has get=True
---
A: True
B: smoke
C: builtin_function_or_method
D: True True

The table is the chapter in one grid. Each class defines its own set of names; there is no universal method list.

The same booleans print two different ways in one line. strip and append were given a width specifier (:<2) and came out as 1 and 0; get had none and came out as True and False. A bool is an integer in Python — True is 1 and False is 0 — and a numeric format specifier makes it show as one. The values are identical; only the rendering differs.

A, C and D show that d.get without parentheses is a value, and a callable one. B calls it.

11. Exercises

Exercise 1 — nine types. Reproduce Section 2's table. Add two more values of your own and confirm they also have a class.

Exercise 2 — attribute or method. For a Path, list three attributes and three methods. Verify each with callable().

Exercise 3 — the missing parentheses. Assign line.strip to a name and use it as though it were a string. Record the error and say which chapter's symptom-versus-cause rule applies.

Exercise 4 — the long form. Call three string methods both as x.m() and as str.m(x). Confirm the results match.

Exercise 5 — read the type. For each error, say what the object actually was: (a) 'NoneType' object has no attribute 'strip' (b) 'list' object has no attribute 'keys' (c) 'int' object is not subscriptable.

Exercise 6 — dir(). Count the public names on list, dict and set. Name one that appears on all three and one that appears on only one.

Exercise 7 — the underscore names. Print the names on str that do start with an underscore. Pick three you recognise and guess what calls them.

Exercise 8 — identify an unknown. Open a file, and without looking anything up, use type and dir to list five things the handle can do. Say which one you would reach for to read a single line.

12. Summary

  • The dot looks up a name on a value, and what it finds is decided by the value's class.
  • Every value is an object — including None, True and 42.
  • A class decides what names a value responds to; an instance is one value of that class.
  • An attribute is data you read; a method is a function you call. The test is callable().
  • Omitting the parentheses does not raise — you get the method object itself, and the failure appears later with a confusing type name.
  • x.method() is shorthand for Class.method(x), which is where self comes from.
  • AttributeError names the class first, because the class is what defines the name — and a NoneType there means something returned None.
  • Functions are objects carrying __name__ and __doc__, and can be stored in a dict, passed as an argument and looked up by name.
  • Classes are objects too, of type type, and calling one creates an instance.
  • dir() lists what an object offers; underscore names are the special methods.
  • getattr, setattr and hasattr take the name as data, with a default that turns an AttributeError into a value — for names that come from a config or a column list, never to avoid writing the dot.

The next chapter writes a class of your own, which turns out to be a way of deciding what names your values will respond to.

Where this fits

Part of the Python curriculum.