Below is the script printing the converted octal to string. I would appreciate suggestion on how to add the - separator to the string on each permission (r/w/x)
def octal_to_string(octal):
result = ""
value_letters = [(4,"r"),(2,"w"),(1,"x")]
#Iterating over each digit in octal
for digit in [int(n) for n in str(octal)]:
#Checking for each of permission values
for value, letter in value_letters:
if digit >= value:
result += letter
digit -= value
else:
pass
return result
I currently get:
In [7]: octal_to_string(755)
Out[7]: 'rwxrxrx'
In [8]: octal_to_string(644)
Out[8]: 'rwrr'
For a quick fix, just replace the pass
with result += '-'
; for every permission test that is False you can simply insert a dash:
for value, letter in value_letters:
if digit >= value:
result += letter
digit -= value
else:
result += '-'
I'd just use bit masking however, with the &
bitwise and operator:
for value, letter in value_letters:
result += letter if digit & value else '-'
This works because 4
, 2
and 1
are the decimal (and octal) values for numbers with each one of the 3 bits set to 1, the others to 0. digit & 4
is only going to produce 4
or 0
, depending on the value of the 3rd bit from the right being set in digit
or not. Since 0
is false, and 4
is a true value, this lets you test if the right bit is set and so decide between the permission letter and -
. I used a conditional expression to return either in a single line.
Next, I'd not use a list comprehension to convert the input value into octal digits; just use map()
here to lazily convert without creating an additional list object:
for digit in map(int, str(octal)):
or you could divide the input value by ten and take the modulus of 10 to get the digit; you can do so in one step with the divmod()
function:
for _ in range(3):
octal, digit = divmod(octal, 10)
I used a for
loop that iterates 3 times here because the octal value for permissions should always consist of 3 octal digits (9 bits). This lets you accept longer values gracefully, just ignoring any digits beyond the right-most 3.
Next, try to avoid using string concatenation in a loop; although Python has some optimisations in place to avoid the worst case performance in simple situations, you can easily miss out on that optimisation. Best to append characters to a list then use str.join()
to create the final string in one step.
Put together into a complete function:
def octal_to_string(octal):
result = []
for _ in range(3):
octal, digit = divmod(octal, 10)
for value, letter in ((4, "r"), (2, "w"), (1, "x")):
result.append(letter if digit & value else "-")
return "".join(result)
Demo:
>>> octal_to_string(755)
'r-xr-xrwx'
>>> octal_to_string(644)
'r--r--rw-'
Now, what the function accepts are decimal numbers, not octal numbers. From an API design you'd really want to either accept strings only (containing digits representing an octal value), or if you do accept an integer, then you should treat that value as decimal and use 0o
octal notation:
>>> 0o755 # actual octal number
493
>>> format(0o755, 'o') # string representing value in octal notation
'755'
If you do so, just change divmod(octal, 10)
into divmod(octal, 8)
to get the octal numbers:
>>> octal = 0o755
>>> for _ in range(3):
... octal, digit = divmod(octal, 8)
... print(digit)
...
...
5
5
7
The advantage is that you can then use other notations to specify the permission value (in hex it'd be 0x1ed
) or pass in the st_mode
attribute of a os.stat()
call directly to the function.