Beginner's Knight's tour in Python (BFS)
For a beginner this is pretty good, there are some serious styling issues though which I would like to point out.

Review


imports


.Only import what you need, ==> from random import randint 
Doing import random as r does not help readability IMO

Constants should be UPPERCASE, rename UP and DOWN accordingly 

See PEP8#constants



def valid(*num):




This could be made simpler with the all() keyword

return all(n in range(P.DOWN, P.UP) for n in num)

Here all() checks if all points are in range and will return True if it is, else it will return False



t = [P(self.x + i, self.y + j, self) ...




This list comprehension is way too long, and you actually repeat yourself

A few fixes are needed


It is good to split up your lines for better readability like this:

[P(...)
 for i in a
 for j in b
 ...]

You loop over [-1, 1], [2, -2] with 2 contatinated list comprehensions you could do this in one turn. By having a constant of all possible directions for instance, this will make it look a little better.

I end up with this:

DIRECTIONS = ((1, 2), (1, -2), (-1, 2), (-1, 2), (2, -1), (2, 1), (-2, 1), (-2, -1))
paths = [P(self.rank + delta_rank,
           self.file + delta_file,
           self)
         for delta_rank, delta_file in P.DIRECTIONS
         if valid(self.rank + delta_rank, self.file + delta_file)]


Always guard your code!
There is the issue of naming.
You have many unmeaningfull names

class P() What is P? Point?

a, b This doesn't say much

e, s I would rename to end, start

etc (t, ls, ...)

In chess x is called a RANK and y is called a FILE

When writing good code, it helpfull to give your variables and instances good names, where it is clear on first sight what they do. So when you revisit your code, or let other people use your code they know what is what.


Revised code

from random import randint
from itertools import chain
import numpy as np


class P:
    UP = 8
    DOWN = 0
    DIRECTIONS = ((1, 2), (1, -2), (-1, 2), (-1, 2), (2, -1), (2, 1), (-2, 1), (-2, -1))

    def __init__(self, rank=0, file=0, parent=None, random=False):
        self.rank = rank if not random else randint(P.DOWN, P.UP-1)
        self.file = file if not random else randint(P.DOWN, P.UP-1)
        self.parent = parent

    def __str__(self):
        return '({},{})'.format(self.rank, self.file)

    def __repr__(self):
        return '({},{} [{},{}])'.format(self.rank,
                                        self.file,
                                        self.parent.rank if self.parent else '-',
                                        self.parent.file if self.parent else '-')

    def __eq__(self, other):
        return self.rank == other.rank and self.file == other.file

    def __gt__(self, other):
        return self.rank + self.file &gt; other.rank + other.file

    def __lt__(self, other):
        return self.rank + self.file &lt; other.rank + other.file

    def __hash__(self):
        return hash((self.rank, self.file))

    def valid_moves(self):
        def valid(*num):
            return all(n in range(P.DOWN, P.UP) for n in num)

        paths = [P(self.rank + delta_rank,
                   self.file + delta_file,
                   self)
                 for delta_rank, delta_file in P.DIRECTIONS
                 if valid(self.rank + delta_rank, self.file + delta_file)]

        return np.unique(paths)

def find_path(start, end):    
    valid = start.valid_moves()
    while True:
        if end in valid or end == start:
            curr = valid.pop(valid.index(end)) 
            path = [curr]

            while curr != start:
                curr = curr.parent
                path += ['-&gt;']
                path += [curr]

            return path

        tmp = [p.valid_moves() for p in valid]
        valid = list(chain(*tmp))

if __name__ == '__main__':
    start, end = P(), P(random=True)
    print('Finding shortest path from {} to {}'.format(start, end))
    print('Path ', find_path(start, end))