Python

Python Data Persistence – @Property DGCOrator

Python Data Persistence – @Property DGCOrator

Although a detailed discussion on decorators is beyond the scope of this book, a brief introduction is necessary before we proceed to use @property decorator.

The function is often termed a callable object. A function is also a passable object. Just as we pass a built-in object viz. number, string, list, and so on. as an argument to a function, we can define a function that receives another function as an argument. Moreover, you can have a function defined in another function (nested function), and a function whose return value is a function itself. Because of all these features, a function is called a first-order object.

The decorator function receives a function argument. The behavior of the argument function is then extended by wrapping it in a nested function. Definition of function subjected to decoration is preceded by name of decorator prefixed with @ symbol.
Python-OOP – 113

Example

def adecorator(function): 
def wrapper():
function() 
return wrapper 
@adecorator 
def decorate( ): 
pass

We shall now use the property ( ) function as a decorator and define a name () method acting as a getter method for my name attribute in my class. py code above.

Example

@property 
def name(self):
return self. ___myname
@property 
def age(self):
return self. __myage

A property object’s getter, setter, and deleter methods are also decorators. Overloaded name ( ) and age ( ) methods are decorated with name, setter, and age. setter decorators respectively.

Example

@name.setter
def name(self,name):
self. ___myname=name
@age.setter
def age(self, age):
self. myage=age

When @property decorator is used, separate getter and setter methods defined previously are no longer needed. The complete code of myclass.py is as below:

Example

#myclass. py
class MyClass:
__slots__=['__myname', '__myage']
def__init__(self, name=None, age=None):
self.__myname=name
self.__myage=age

@property
def name(self) :
print ('name getter method')
return self.__myname

@property
def age(self) :
print ('age getter method')
return self. ___myage

@name.setter
def name(self,name):
print ('name setter method')
self.___myname=name

@age.setter
def age(self, age):
print ('age setter method')
self.__myage=age

def about(self):
print ('My name is { } and I am { } years old'.format(self. myname,self. myage))

Just import above class and test the functionality of property objects using decorators.

Example

>>> from myclass import MyClass
>>> obj1=MyClass('Ashok', 21)
>>> obj1.about() #initial values of object's attributes
My name is Ashok and I, am 21 years old
>>> #change age property
>>> obj1.age=30
age setter method
>>> #access name property
>>> obj1.name
name getter method
'Ashok'
> > > obj1.about()
My name is Ashok and I am 30 years old

 

Python Data Persistence – Getters/setters

Python Data Persistence – Getters/setters

Java class employs getter and setter methods to regulate access to private data members in it. Let us see if it works for a Python class. Following code modifies myclass.py and provides getter and setter methods for my name and my age instance attributes.

Example

#myclass.py
class MyClass:
__slots__=['myname', 'myage']
def__init__(self, name=None, age=None):
self.myname=name
self,myage=age
def getname(self):
return self.myname
def set name(self, name):
self.myname=name
def getage(self):
return self.myage
def setage(self, age):
self.myage=age
def about(self):
print ('My name is { } and I am { } years old' ,format(self.myname,self.myage) )

The getters and setters allow instance attributes to be retrieved / modified.

Example

>>> from myclass import MyClass
>>> obj1=MyClass('Ashok',21)
>>> obj1.getage( )
21
>>> obj1.setname('Amar')
>>> obj1.about( )
My name is Amar and I am 21 years old

Good enough. However, this still doesn’t prevent direct access to instance attributes. Why?

Example

>>> obj1.myname
'Amar'
>>> getattr(obj1,'myage')
21
>>> obj1.myage=25
>>> setattr(obj1myname1, 'Ashok')
>>> obj1.about()
My name is Ashok and I am 25 years old

Python doesn’t believe in restricting member access hence it doesn’t have access to controlling keywords such as public, private, or protected. In fact, as you can see, class members (attributes as well as methods) are public, being freely accessible from outside the class. Guido Van Rossum – who developed Python in the early 1990s – once said, “We ‘re all consenting adults here” justifying the absence of such access restrictions. Then what is a ‘Pythonic’ way to use getters and setters? The built-in property () function holds the answer.

Python Data Persistence – property () Function

Python Data Persistence – property () Function

Well, let me now make a volte-face here. It’s not that Python doesn’t have private/protected access restrictions. It does have. If an instance variable is prefixed with double underscore character ‘___’, it behaves like a private variable. Let us see how:

Example

>>> #private variable in class
. . .
>>> class tester:
. . .             def___init___(self) :
. . .                       self.__var=10
. . .
>>>

Try .declaring an object of above class and access its__var attribute.

>>> t=tester( )
>>> t.__var
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'tester' object has no attribute '___var'

The AttributeError indicates that the variable prefixed is inaccessible. However, it can still be accessed from outside the class. A double underscore prefixed attribute is internally renamed as obj ._class var. This is called name mangling mechanism.

>>> t._tester var
10

Hence, the so-called private variable in Python is not really private. However, a property object acts as an interface to the getter and setter methods of a private attribute of the Python class.
Python’s built-in property() function uses getter, setter, and delete functions as arguments and returns a property object. You can retrieve as well as assign to the property as if you do with any variable. When some value is assigned to a property object, its setter method is called. Similarly, when a property object is accessed, its getter method is called.

propobj=property(fget, fset, fdel, doc)

In the above function signature, fget is the getter method in the class, fset is the setter method and, fdel is the delete method. The doc parameter sets the docstring of the property object.
In our ongoing myclass.py script, let us now define age and name properties to interface with ___myage and ___myname private instance attributes.

Example

#myclass.py
class MyClass:
        __slots___= ['__myname' ,'___myage ' ]
        def__init__(self, name=None, age=None):
               self. myname =name
               self. myage=age
        def getname(self):
            print ('name getter method')
            return self. ___myname
       def setname(self, name):
           print ('name setter method')
           self. ___myname=name
      def getage(self):
           print ('age getter method')
           return self.____myage
      def setage(self, age):
           print ('age setter method') 
       self. ____myage=age
name=property(getname, setname, "Name") age=property(getage, setage, "Age") def about(self) :
print ('My name is { } and I am { } years old'.format(self.___myname,self. __myage))

We’ll now import this class and use the properties. Any attempt to retrieve or change the value of property calls its corresponding getter or setter and changes the internal private variable. Note that the getter and setter methods have a print () message inserted to confirm this behavior. The about () method will also show changes in private variables due to manipulations of property objects.

Example

>>> from myclass import MyClass
>>> obj1=MyClass(1Ashok' , 21)
>>> obj1.about() #initial values of object's attributes
My name is Ashok and I am 21 years old
>>> #change age property
>>> obj1.age=30
age setter method
>>> #access name property
>>> obj1.name
name getter method
'Ashok'
>>> obj1.about( ) #object's attributes after property changes
My name is Ashok and I am 30 years old

So, finally, we get the Python class to work almost similar to how the OOP methodology defines. There is a more elegant way to define property objects – by using @property decorator.

Python Data Persistence – Constructor

Python Data Persistence – Constructor

We provide a constructor method to our class to initialize its object. The__init__( ) method defined inside the class works as a constructor. It makes a reference to the object as the first positional argument in the form of a special variable called ‘self’. Java/C++ programmers will immediately relate this to ‘this’ variable. A method with ‘self argument is known as an instance method. Save the following script as myclass.py. It provides two instance variables (my name and my age) and about () method.

Example

#myclass.py ‘
class MyClass:
            def __init__(self):
self.myname='Ashok'
self.myage=21
def about(self):
print ('My name is { } and I am { } years old'.format(self.myname,self.myage))

Let us use this script as a module, import MyClass from it and have its object.

Example

>>> from myclass import MyClass
>>> obj1=MyClass( )
> > > obj1.myname
'Ashok'
>>> obj1.myage
21
>>> obj1.about()
My name is Ashok and I am 21 years old

So, now each object will have the same attributes and methods as defined in the class. However, attributes of each object will be initialized with the same values as assigned in__init__( ) method. To counter this, we can have a parameterized constructor, optionally with default values. Modify Now we can have objects having attributes with different values.

Example

>>> from myclass import MyClass
>>> obj1=MyClass('Ashok', 21)
>>> obj2=MyClass('Asmita',20)
>>> obj1.about()
My name is Ashok and I am 21 years old
>>> obj2.about()
My name is Asmita and I am 20 years old

However, does this process prevent the dynamic addition of an attribute to an object/class? Well, not really.

>>> setattr(obj1marks50)
>>> obj1.marks
50

So, how do we ensure that the object will have only those attributes as per the class definition? The answer is slots .

_slots_

To prevent any further attribute to be added to an object of a class, it carries a variable named as__slots__. This variable is defined before__init__ () method. It is a list of allowed attributes to be initialized by the constructor. The setattr () function will first check if the second argument is in the__slots__list and assign it a new value only if it exists.

Example

#myclass.py
class MyClass:
          __slots___=['myname', 'myage']
         def__init___(self, name=None, age=None):
                 self.myname=name
                 self.myage=age
def about(self):
               print ('My name is { } and I am { } years old1.format(self.myname,self.myage))

Import the modified class and check the effect of__slots___variable.

Example

>>> from myclass import MyClass
>>> obj1=MyClass('Ashok', 21)
> > > obj1.about( )
My name is Ashok and I am 21 years old
>>> setattr(obj1, 'marks', 50) #new attribute not allowed
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'MyClass' object has no attribute 'marks'
>>> setattr(obj1, 'myage', 25) #value of existing module can be modified
>>> obj1.about()
My name is Ashok and I am 25 years old

 

Python Data Persistence object-oriented programming

Python Data Persistence – OOP

The object-oriented programming paradigm has emerged as a cornerstone of modern software development. Python too is a predominantly object-oriented language, although it supports classical procedural approach also. Python’s implementation of OOP is a little different from that of C++ and Java, and is in keeping with ‘Simple is better than Complex’ – one of the design principles of Python. Let us not go into much of the theory of object-oriented programming methodology and its advantages. Instead, let us dive straight into Python’s brand of OOP!

Everything in Python is an object. This statement has appeared more than once in previous chapters. What is an object though? Simply put, object is a piece of data that has a tangible representation in computer’s memory. Each object is characterized by attributes and behaviour as stipulated in a template definition called class. In Python, each instance of any data type is an object representing a certain class. Python’s built-in type ( ) function tells you to which class an object belongs to. (figure 4.1)

>>> num=1234
>>> type(num)
<class 'int'>
>>> complexnum=2+3j
>>> type(complexnum)
<class 'complex'>
>>> prices={ 'pen' :50, 'book' :200}
>>> type(prices)
<class 'diet'>
>>>

Each Python object also possesses an attribute__class__that returns class. (figure 4.2)

>>> num.__class__
<class 'int'>
>>> complexnum.__class__
<class 'complex'>
>>> prices.__class__
<class 'diet'>
>>>

Hence, it is clear that num is an object of int class, a complexion is an object of the complex class, and so on. As mentioned above, an object possesses attributes (also called data descriptors) and methods as defined in its respective class. For example, the complex class defines real and image attributes that return the real and imaginary parts of a complex number object. It also has conjugate () method returning a complex number with the imaginary part of the opposite sign, (figure 4.3)

>>> complexnum=2+3j
>>> #attr.ibutes
. . .
>>> complexnum.real
2.0
>>> complexnum.imag
3.0
>>> #method
. . .
>>> complexnum.conjugate()
(2-3j )

In the above example, real and imag are the instance attributes as their values will be different for each object of the complex class. Also, the conjugate 0 method is an instance method. A class can also have class-level attributes and methods which are shared by all objects. We shall soon come across their examples in this chapter.

Built-in data types in Python represent classes defined in the builtins module. This module gets loaded automatically every time when the interpreter starts. The class hierarchy starts with object class. The builtins module also defines Exception and built-in functions (many of which we have come across in previous chapters).

The following diagram rightly suggests that built-in classes (int, float, complex, bool, list, tuple, and diet) are inherited from the object class. Inheritance incidentally is one of the characteristic features of Object-Oriented Programming Methodology. The base attribute of each of these classes will confirm this.

Python’s built-in function library also has issubclass ( ) function to test if a certain class is inherited from another. We can use this function and confirm that all built-in classes are sub-classes of the object classes. Interestingly, class is also an object in Python. Another built-in function is±nstance() returns True if the first argument is an object of the second argument which has to be a class. By the way, type ( ) of any built-in class returns type which happens to be a metaclass of which all classes are objects, is itself a subclass of the object as its base class, (figure 4.4)

Python Data Presistence - OOP chapter 4 img 1

Following interpreter, activity shows that bool class is derived from int class, which is a subclass of object class and also an object of object class!

Example

>>> isinstance(int, object)
True
>>> issubclass(bool, int)
True
>>> int. bases
(<class 'object'>,)
>>> isinstance(int, object)
True

Each class also has access to another attribute__mro__(method resolution order) which can show the class hierarchy of inheritance. The bool class is inherited from int and int from the object.

Example

>>> bool.__mro__
(<class 'boo'>, <class 'int'>, <class 'object'>)

As mentioned above, type () on any built-in class returns type class which in turn is both a subclass and instance of the object class.

Example

>>> type(bool)
<class 'type'>
>>> type.__bases__
(<class 'object'>,)
>>> isinstance(type, object)
True
>>> issubclass(type, object)
True

Other important constituents of Python program such as functions or modules are also objects, as the following interpreter activity demonstrates:

Example

>>> #function from a module
. . .
>>> import math
>>> type(math.sqrt)
<class 'builtin_function_or_method'>
>>> #built-in function
. . .
>>> type(id)
cclass 'builtin_function_or_method'>
>>> #user defined function
. . .
>>> def hello( ):
... print ('Hello World')
. . .
>>> type(hello)
<class 'function'>
>>> #module is also an object
. . .
>>> type(math)
<class 'module'>

We have used the range ( ) function in connection with for loop. It actually returns a range object which belongs to the range class.

Example

>>> #range is also an object
. . .
>>> obj=range(10)
>>>type(obj)
<class 'range'>
>>> >>> range._bases__
(<class 'object'>,)

Python Data Persistence – Exceptions

Python Data Persistence – Exceptions

Even an experienced programmer’s code does contain errors. If errors pertain to violation of language syntax, more often than not, they are detected by the interpreter (compiler in case of C++/Java) and code doesn’t execute till they are corrected.

There are times though when the code doesn’t show syntax-related errors but errors creep up after running it. What is more, sometimes code might execute without errors and some other times, its execution abruptly terminates. Clearly, some situation that arises in a running code is not tolerable to the interpreter. Such a runtime situation causing the error is called an exception.
Take a simple example of displaying the result of two numbers input by the user. The following snippet appears error-free as far as syntax error is concerned.

Example

num1=int(input('enter a number..')) 
num2 = int(input(1 enter another number..')) 
result=num1/num2 
print ('result: ' , result)

When executed, above code gives satisfactory output on most occasions, but when num2 happens to be 0, it breaks, (figure 5.3)

enter a number . . 12
enter another number . . 3
result: 4.0
enter a number . . 12
enter another number . . 0
Traceback (most recent call last) :
File "E:\python37\tmp.py", line 3, in <module> 
result =num1/num2
ZeroDivisionError: division by zero

You can see that the program terminates as soon as it encounters the error without completing the rest of the statements. Such abnormal termination may prove to be harmful in some cases.
Imagine a situation involving a file object. If such runtime error occurs after the file is opened, the abrupt end of the program will not give a chance for the file object to close properly and it may result in corruption of data in the file. Hence exceptions need to be properly handled so that program ends safely.

If we look at the class structure of builtins in Python, there is an Exception class from which a number of built-in exceptions are defined. Depending upon the cause of exception in a running program, the object representing the corresponding exception class is created. In this section, we restrict ourselves to consider file operations-related exceptions.

Python’s exception handling mechanism is implemented by the use of two keywords – try and except. Both keywords are followed by a block of statements. The try: block contains a piece of code that is likely to encounter an exception. The except block follows the try: block containing statements meant to handle the exception. The above code snippet of the division of two numbers is rewritten to use the try-catch mechanism.

Example

try:
num1=int(input('enter a number..'))
num2=int(input('enter another number..'))
result=num1/num2
print (' result: ' , result)
except:
print ("error in division")
print ("end of program")

Now there are two possibilities. As said earlier, the exception is a runtime situation largely depending upon reasons outside the program. In the code involving the division of two numbers, there is no exception if the denominator is non-zero. In such a case, try: block is executed completely, except block is bypassed and the program proceeds to subsequent statements.

If however, the denominator happens to be zero, a statement involving division produces an exception. Python interpreter abandons rest of statements in try: block and sends the program flow to except: block where exception handling statements are given. After except: block rest of unconditional statements keep on executing, (figure 5.4)

enter a number..15 
enter another number..5 
result: 3.0
end of program 
enter a number..15 
enter another number..0 
error in division 
end of program

Here, except block without any expression acts as a generic exception handler. To catch objects of a specific type of exception, the corresponding Exception class is mentioned in front of except keyword. In this case, ZeroDivisionError is raised, so it is mentioned in except statement. Also, you can use the ‘as’ keyword to receive the exception object in an argument and fetch more information about the exception.

Example

try:
num1=int(input('enter a number..')) 
num2=int(input('enter another number..')) 
result =num1/num2 
print ('result: ', result) 
except ZeroDivisionError as e: 
print ("error message",e) 
print ("end of program")

File operations are always prone to raising exceptions. What if the file you are trying to open doesn’t exist at all? What if you opened a file in ‘r’ mode but trying to write data to it? These situations will raise runtime errors (exceptions) which must be handled using the try-except mechanism to avoid damage to data in files.
FileNotFoundError is a common exception encountered. It appears when an attempt to read a non-existing file. The following code handles the error.

Example

E:\python37>python tmp.py
enter a filename, .testfile.txt
H e l l o P y t h o n
end of program
E:\python37>python tmp.py
enter filename, .nofile.txt
error message [Errno 2] No such file or directory:
' nofile . txt '
end of program

Another exception occurs frequently when you try to write data in a file opened with ‘r’ mode. Type of exception is UnsupportedOperation defined in the io module.

Example

import io
try:
f=open ( ' testfile . txt1 , ' r ')
f.write('Hello')
print (data)
except io.UnsupportedOperation as e:
print ("error message",e)
print ("end of program")

Output

E:\python37 >python tmp.py 
error message not writable 
end of program

As we know, the write ( ) method of file objects needs a string argument. Hence the argument of any other type will result in typeError.

Example

try:
f=open (' testfile . txt' , ' w')
f.write(1234)
except TypeError as e:
print ("error message",e)
print ("end of program")

 

Output

E:\python37>python tmp.py
error message write() argument must be str, not int end of program

Conversely, for file in binary mode, the write () method needs bytes object as argument. If not, same TypeError is raised with different error message.

Example

try:
f =open ( ' testfile . txt' , ' wb' )
f.write('Hello1)
except TypeError as e:
print ("error message",e)
print ("end of program")

Output

E:\python37>python tmp.py
error message a bytes-like object is required, not 'str'
end of program

All file-related functions in the os module raise OSError in the case of invalid or inaccessible file names and paths, or other arguments that have the correct type but are not accepted by the operating system.

Example

import os
try:
fd=os . open ( ' testfile . txt' , os . 0_RD0NLY | os . 0_CREAT) os.write(fd,'Hello'.encode())
except OSError as e:
print ("error message",e)
print ("end of program")

Output

E:\python37>python tmp.py
error message [Errno 9] Bad file descriptor 
end of program

In this chapter, we learned the basic file handling techniques. In the next chapter, we deal with advanced data serialization techniques and special-purpose file storage formats using Python’s built-in modules.

Python Data Persistence – File/Directory Management Functions

Python Data Persistence – File/Directory Management Functions

We normally use operating system’s GUI utilities or DOS commands to manage directories, copy, and move files etc. The os module provides useful functions to perform these tasks programmatically. os .mkdir ( ) function creates a new directory at given path. The path argument may be absolute or relative to the current directory. Use chdir ( ) function to set current working directory at the desired path. The getcwd ( ) function returns the current working directory path.

Example

>>> os.mkdir('mydir')
>>> os.path.abspath('mydir')
'E:\\python37\\mydir'
>>> os.chdir('mydir')
>>> os.getcwd()
'E:\\python37\\mydir'

You can remove a directory only if the given path to rmdir ( ) function is not the current working directory path, and it is empty.

Example

>>> os.chdir( ' . . ' ) #parent directory becomes current working directory
>>> os.getcwd ( )
'E:\\python37'
>>> os.rmdir('mydir')

The rename ( ) and remove ( ) functions respectively change the name of a file and delete a file. Another utility function is listdir () which returns a list object comprising of file and subdirectory names in a given path.

Python Data Persistence – File Handling using os Module

Python Data Persistence – File Handling using os Module

Python’s built-in library has an os module that provides useful operating system-dependent functions. It also provides functions for performing low-level read/write operations on the file. Let us briefly get acquainted with them.

The open () function from the os module (obviously it needs to be referred to as os. open ( )) is similar to the built-in open ( ) function in the sense it also opens a file for reading/write operations. However, it doesn’t return a file or file-like object but a file descriptor, an integer corresponding to the file opened. File descriptor’s values 0, 1, and 2 are reserved for stdin, stout, and stder r streams. Other files will be given an incremental file descriptor. Also, the write ( ) and read( ) functions of the os module needs bytes to object as the argument. The os .open () function needs to be provided one or combinations of the following constants: (Table 5.2)

os.OWRONLY open for writing only
os.ORDWR open for reading and writing
os.OAPPEND append on each write
os.OCREAT create file if it does not exist
os.OTRUNC truncate size to 0
os.OEXCL error if create and file exists

As in the case of a file object, the os module defines a sleek () function to set file r/w position at the desired place from the beginning, current position, or end indicated by integers 0,1, and 2 respectively.

Example

>>> fd=os . open ("testfile. txt", os .0_RDWR | os .0_CREAT)
>>> text="Hello Python"
>>> encoded=text.encode(encoding='utf-16')
>>> os.write(fd, encoded)
>>> os.lseek(fd,0,0)
>>> encoded=os.read(fd)
>>> os.path.getsizeCtestfile.txt") #calculate file size
>>> encoded=os.read(fd, 26)
>>> text=encoded.decode('utf-16 ')
>>> text
'Hello Python'

 

Python Data Persistence – Simultaneous Read/Write

Python Data Persistence – Simultaneous Read/Write

Modes ‘w’ or ‘a’ allow a file to be written to, but not to be read from. Similarly, ‘r’ mode facilitates reading a file but prohibits writing data to it. To be able to perform both read/write operations on a file without closing it, add the ‘+’ sign to these mode characters. As a result ‘w+’, ‘r+’ or ‘a+’ mode will open the file for simultaneous read/write. Similarly, binary read/ write simultaneous operations are enabled on file if opened with ‘wb+’, ‘rb+’ or ‘ab+’ modes.

It is also possible to perform read or write operations at any byte position of the file. As you go on writing data in a file, the end of file (EOF) position keeps on moving away from its beginning. By default, new data is written at the current EOF position. When opened in ‘r’ mode, reading starts from the 0th byte i.e. from the beginning of the file.
The seek ( ) method of file object lets you set current reading or writing position to a desired byte position in the file. It locates the desired position by counting the offset distance from beginning (0), current position (1), or EOF (2). Following example illustrates this point:

Example

>>> file=open ("testfile . txt", "w+")
>>> file .write ("This is a rat race")
>>> file.seek(10,0) #seek 10th byte from beginning
>>> txt=file . read (3) #read next 3 bytes
>>> txt
' rat'
>>> file . seek (10,0) #seek 10th byte position
>>> file . write ('cat' ) #overwrite next 3 bytes
>>> file.seek(0)
>>> text=file . read () #read entire file
>>> text
'This is a cat race'
>>> file . close ( )

Of course, this may not work correctly if you try to insert new data as it may overwrite part of existing data. One solution could be to read the entire content in a memory variable, modify it, and rewrite it after truncating the existing file. Other built-in modules like file input and map allow modifying files in place. However, later in this book, we are going to discuss a more sophisticated tool for manipulating databases and update data on a random basis.

Python Data Persistence – Write/Read Binary File

Python Data Persistence – Write/Read Binary File

When the mode parameter to open ( ) function is set to ‘w’ (or ‘a’) value, the file is prepared for writing data in text format. Hence write ( ) and writelines () methods send string data to the output file stream. The file is easily readable using any text editor. However, other computer files that represent pictures (.jpg), multimedia (.mp3, .mp4), executables (.exe, .com), databases (.db, .sqlite) will not be readable if opened using notepad like text editor because they contain data in binary format.

Python’s open( ) function lets you write binary data to a file. You need to add ‘b’ to the mode parameter. Set mode to ‘wb’ to open a file for writing binary data. Naturally, such a file needs to be read by specifying ‘rb’ as file opening mode.

In the case of ‘wb’ mode parameter, the write () method doesn’t accept a string as an argument. Instead, it requires a bytes object. So, even if you are writing a string to a binary file, it needs to be converted to bytes first. The encode ( ) method of string class converts a string to bytes using one of the defined encoding schemes, such as ‘utf-8’ or ‘utf-16’.

Example

>>> f=open('binary.dat' , 'wb')
>>> data='Hello Python'
>>> encoded=data.encode(encoding='utf-16')
>>> encoded
b'\xff\xfeH\x00e\x001\x001\x00o\x00 \x00P\x00y\x00t\ x00h\x00o\x00n\x00'
>>> f.write(encoded)
26
>>> f.close( )

To read back data from the binary files, the encoded strings will have to be decoded for them to be used normally, like printing it.

Example

> > > f=open('binary.dat', 'rb')
>>> encoded=f.read( )
>>> data=encoded.decode(encoding='utf-16')
>>> print (data)
Hello Python
>>> f.close( )

If you have to write numeric data to a binary file, number object is first converted to bytes and then provided as argument to write() method

Example

>>> f=open('binary.dat','wb')
>>> num=1234
>>> binl=num.to_bytes(16, 'big')
>>> f.write(bin1)
>>> f.close( )

To read integer data from binary file, it has to be extracted from bytes in the file.

Example

>>> f=open( ' binary.dat' , 'rb')
>>> data=f.read( )
>>> num=int.from_bytes(data,'big')
> > > num
1234
>>> f.close( )

In the to_bytes () and from_bytes () function, ‘big’ is the value of the byte order parameter. Writing float data to a binary file is a little complicated. You’ll need to use the built-in struct module and pack( ) function from it to convert float to binary. Explanation of struct module is beyond the scope of this book.

Example

>>> import struct
> > > numl=1234.56
>>> f=open('binary.dat','wb')
>>> binfloat = struct .pack ('f ' num1)
> > > f . write (binfloat)
4
> > > f.close ( )

To read back binary float data from file, use unpack () function.

Example

>>> f=open('binary.dat','rb')
>>> data=f.read( )
>>> numl=struct.unpack('f', data)
>>> print (num1)
(1234.56005859375,)
>>> f.close( )