Skip to content

zydmayday/python_ramda

Repository files navigation

python_ramda

All Contributors

This is a repo try to copy https://github.com/ramda/ramda in python.

linting: pylint PyPI version Python Versions codecov 996.icu

install

For whom wants to use this package.

> pip install python-ramda
> pip install python-ramda -U # get the latest

Usage

>>> from ramda import curry
>>> def sum(a, b, c): return a + b + c
>>> curry(sum)(1)(2, 3)
6
>>> import ramda as R # similar to ramda syntax
>>> def sum(a, b, c): return a + b + c
>>> R.curry(sum)(1)(2, 3)
6

Doc

Because the usage of python_ramda is almostly same to ramda, so we don't create any extra doc.

If you feel any behaviour is different from what is should be in ramda, please check below CheckList for more details.

Contribute

For whom wants to contribute to this repo.

$ pip install -U pylint
# see: https://pre-commit.com/ for more details
$ pre-commit install # please install hooks first

Checkout new branch from main branch directly and create PR.

CheckList

Functions supported now.

  • 0.1.2 __
  • 0.1.2 add
# different from ramda
R.add(None, None) # float('nan)
R.add(date(1,2,3), date(1,2,3)) # float('nan)
  • addIndex
  • 0.1.2 adjust
  • 0.1.2 all
    • Transducer part is not fully tested.
  • allPass
  • 0.1.2 always
  • 0.1.2 And (and is a keyword in python)
  • andThen
  • 0.1.2 any
  • anyPass
  • 0.3.0 ap
  • aperture
  • 0.1.2 append
  • 0.7.0 apply
  • applySpec
  • applyTo
  • ascend
  • 0.8.0 assoc

Currently, we only support list and dict type.

  • 0.8.0 assocPath

Currently, we only support list and dict type.

  • 0.2.0 binary
  • bind
  • both
  • call
  • 0.3.0 chain
  • clamp
  • 0.1.2 clone

we are simply using python copy module So with no specific reason, we suggest you to use python origin copy module as your first choice.

class Obj:
  def __init__(self, x):
    self.value = x
obj = Obj(42)
clone = R.clone(obj)
obj == clone # False, obj and clone have different references
isinstance(clone, Obj) # True

class Obj:
  def __init__(self, x):
    self.value = x

  def __eq__(self, other):
    return self.value == other.value
obj = Obj(42)
clone = R.clone(obj)
obj == clone # True, if Obj override __eq__ function
isinstance(clone, Obj) # True
  • collectBy
  • 0.1.2 comparator
  • complement
  • 0.1.2 compose
  • composeWith
  • 0.1.2 concat
  • 0.6.0 cond

Please notice the number of given arguments should match functions. Otherwise Python will complain about the mis-matching arguments.

For example:

fn = R.cond([
  [lambda a: a == 1, lambda a: f'a == {a}'],
  [lambda a, b: a == b, lambda a, b: f'{a} == {b}']
])
fn(1) # a == 1
fn(2, 2) # 2 == 2

fn(2) # Throw error, because b is not provided for prediction, failed when (lambda a, b: a == b)(2), missing argument b
# to solve above issue, you should try your best to provide enough arguments

fn(1, 2) # Throw error, because (lambda(a: f'a == {a}'))(1, 2) has extra arguments 2
# To solve above issue, always use sencond function with enough arguments
# Try create cond like below.
fn = R.cond([
  [lambda a: a == 1, lambda a, _: f'a == {a}'], # ignore b
  [lambda a, b: a == b, lambda a, b: f'{a} == {b}']
])

fn = R.cond([
  [lambda a: a == 1, lambda a, *args: f'a == {a}'], # ignore any arguments
  [lambda a, b: a == b, lambda a, b: f'{a} == {b}']
])
  • 0.4.0 construct
  • 0.4.0 constructN
  • 0.1.4 converge
  • count
  • 0.1.2 countBy
  • 0.1.2 curry
  • 0.1.2 curryN
  • dec
  • 0.6.0 defaultTo
  • descend
  • 0.1.2 difference
  • 0.1.2 differenceWith
  • dissoc
  • dissocPath
  • 0.1.2 divide
  • 0.1.2 drop
  • dropLast
  • dropLastWhile
  • dropRepeats
  • dropRepeatsWith
  • dropWhile
  • either
  • 0.1.2 empty
# We don't support empty object in python
class Obj:
  def __init__(self, value):
    self.value = value
o = Obj(42)
o == R.empty(o) # True, we will return the original cloned object

What we support for now:

  1. dict()
  2. set()
  3. list()
  4. str()
  5. any instance with empty() method
  6. any instance with 'fantasy-land/empty' property
  • endsWith
  • eqBy
  • 0.1.2 eqProps
# works for both dict and object
class Obj:
  def __init__(self, v):
    self.v = v
obj1 = Obj(1)
obj2 = Obj(1)
R.eqProps('v', obj1, obj2) # True
R.eqProps('v', {'v': 1}, {'v': 1}) # True
  • 0.1.2 equals
R.equals(float('nan'), float('nan')) # True
  • evolve
  • 0.1.2 F
  • 0.1.2 filter
  • 0.1.2 find
  • 0.1.4 findIndex
  • 0.1.4 findLast
  • 0.1.4 findLastIndex
  • 0.1.2 flatten
  • 0.1.2 flip
  • 0.1.4 forEach
  • forEachObjIndexed
  • 0.3.0 fromPairs
  • 0.1.2 groupBy
  • groupWith
  • 0.1.2 gt
  • 0.1.2 gte
  • 0.7.0 has

Similar to hasPath.

  • 0.7.0 hasIn

works for both dict and object

class Obj:
  def __init__(self, v):
    self.v = v

obj1 = Obj(1)
R.hasIn('v', obj1) # True
R.hasIn('v', {'v': 1}) # True
  • hasPath Support both dict and object.
R.hasPath(['a', 'b'], {'a': {'b': 42}}) # True

class Obj:
  def __init__(self, v):
    self.v = v
obj = Obj(1)

R.hasPath(['v'], obj) # True
R.hasPath(['v', 'child'], obj) # False

R.hasPath(['v'], {'v': 1}) # True
R.hasPath(['v', 'child'], {'v': 1}) # False

# Does not include static variable
class Obj:
  v = 1
obj = Obj()
R.hasPath(['v'], obj) # False

# Also support inherited variable
class Parent:
  def __init__(self, a):
    self.a = a
class Child(Parent):
  def __init__(self, a,b):
    super().__init__(a)
    self.b = b
child = Child(1, 2)
R.hasPath(['a'], child) # True
R.hasPath(['b'], child) # True
  • 0.1.2 head
  • identical
  • 0.1.2 identity
  • 0.8.0 ifElse
  • inc
  • includes
  • indexBy
  • 0.1.2 indexOf
  • init
  • innerJoin
  • 0.2.2 insert
  • insertAll
  • 0.1.2 intersection
  • intersperse
  • 0.1.2 into
  • invert
  • invertObj
  • 0.1.2 invoker
  • 0.3.0 Is (is is a keyword in python)

This is a language specific feature. So we check all python built-in types as many as we can.

R.Is(int, 1) # True
R.Is(float, 1.0) # True
R.Is(str, '1') # True
R.Is(list, [1,2,3]) # True
R.Is(dict, {'a': 1}) # True
R.Is(set, {1,2,3}) # True
R.Is(tuple, (1,2,3)) # True
R.Is(None, None) # True
R.Is(bool, True) # True
R.Is(bool, False) # True

# For user-defined object
class Parent:
  pass
class Child(Parent):
  pass
R.Is(Parent, Parent()) # True
R.Is(Parent, Child()) # True
R.Is(Child, Child()) # True
R.Is(Child, Parent()) # False
  • 0.1.2 isEmpty
class Obj:
  pass
# Any custom object will be treated as non-empty
R.isEmpty(Obj()) # False
R.isEmpty(None) # False
  • isNil

We keep the same method name as ramda, this is for checking if the given value is None or not.

  • 0.1.2 join
  • 0.1.4 juxt
  • 0.1.2 keys

For object, keys does not return object's methods.

# When using R.keys(obj) and obj is a class instance, we use obj.__dict__ as keys.
class A:
  c = 'not included'
  def __init__(self):
    self.a = 1
    self.b = 2
a = A()
R.keys(a) # ['a', 'b']
# keys include super class attributes
class A:
  def __init__(self, a):
    self.a = a

class B(A):
  def __init__(self, a, b):
    super().__init__(a)
    self.b = b

class C(A):
  def __init__(self, c):
    self.c = c

a = A(1)
b = B(2, 3)
c = C(4)
R.keys(a) # ['a']
R.keys(b) # ['a', 'b']
R.keys(c) # ['c'], because c does not call super().__init__()

# For normal dict
R.keys({'a': 1, 'b': 2}) # ['a', 'b']
  • 0.2.0 keysIn

For object, keysIn does not return object's methods.

Different from keys, keysIn will return all attributes of the object, including super class attributes and class static variables.

class A:
  a_static = 1
  def __init__(self):
    self.a = 1
class B(A):
  b_static = 2
  def __init__(self, b):
    super().__init__()
    self.b = b

R.keysIn(A()) # ['a_static', 'a']
R.keysIn(B(2)) # ['a_static', 'a', 'b_static', 'b']

# For normal dict
R.keysIn({'a': 1, 'b': 2}) # ['a', 'b']
  • 0.1.4 last
  • 0.1.2 lastIndexOf
  • 0.3.0 length

The behavior of length is different from ramda.

# Array
R.length([1, 2, 3]) # 3
# String
R.length('abc') # 3
# Dict
R.length({'a': 1, 'b': 2}) # 2
# Set
R.length({1, 2, 3}) # 3
# Tuple
R.length((1, 2, 3)) # 3
# Notice: Also works for any other iterable object

# Some special cases
# object with length() method
class Obj:
  def length(self):
    return 3
obj = Obj()
R.length(obj) # 3

# dict with length property
R.length({'a': 1, 'length': 99}) # 99, R.length will use length property instead

# return function arguments length
def f(a, b, c):
  return a + b + c
R.length(f) # 3

# Any failed cases, return nan instead
R.length(None) # float('nan')
R.length(1) # float('nan')
class ObjWithoutLength:
  pass
R.length(ObjWithoutLength()) # float('nan')
  • 0.8.0 lens
  • lensIndex
  • lensPath
  • lensProp
  • 0.7.0 lift
  • 0.7.0 liftN
  • 0.1.2 lt
  • 0.1.2 lte
  • 0.1.2 map
  • mapAccum
  • mapAccumRight
  • mapObjIndexed
  • 0.1.2 match
  • 0.3.0 mathMod
  • 0.1.2 Max (max is a keyword in python)

If R.Max(a, b) a and b are with different types, we will compare with str(a) and str(b).

R.Max('A', None) # None, 'A' < 'None'
  • 0.8.0 maxBy
  • mean
  • median
  • memoizeWith
  • mergeAll
  • mergeDeepLeft
  • mergeDeepRight
  • mergeDeepWith
  • mergeDeepWithKey
  • mergeLeft
  • mergeRight
  • mergeWith
  • mergeWithKey
  • 0.1.2 Min (min is a keyword in python)

If R.Min(a, b) a and b are with different types, we will compare with str(a) and str(b).

R.Min('A', None) # 'A', 'A' < 'None'
  • 0.8.0 minBy
  • modify
  • modifyPath
  • 0.1.4 modulo

Python modulo on negative numbers has different behavior than JS.

5 % -3 # -1
5 % -3; // 2
  • move
  • 0.1.2 multiply
  • 0.2.0 nAry
  • negate
  • none
  • 0.1.2 not
  • 0.1.2 nth
  • nthArg
  • o
  • 0.1.2 objOf
  • 0.3.0 of
  • 0.1.2 omit

we support both dict type and object type.

class Obj:
  def __init__(self, v1, v2):
    self.v1 = v1
    self.v2 = v2
obj = Obj(1, 2)
R.omit(['v1'], obj) # {'v2': 2}
R.omit(['v1', 'v3'], obj) # {'v2': 2}
  • on
  • 0.1.2 once
  • 0.1.2 or
  • otherwise
  • over
  • pair
  • partial
  • partialObject
  • partialRight
  • 0.1.4 partition
  • 0.1.2 path
  • 0.7.0 pathEq
  • pathOr
  • 0.1.2 paths
  • pathSatisfies
  • 0.1.2 pick
  • 0.1.2 pickAll

both pick and pickAll support both dict and object type.

class Obj:
  def __init__(self, v1, v2):
    self.v1 = v1
    self.v2 = v2
obj = Obj(1, 2)
R.pick(['v1'], obj) # {'v1': 1}
R.pickAll(['v1', 'v3'], obj) # {'v1': 1, 'v3': None}
  • 0.8.0 pickBy
  • 0.1.2 pipe
  • pipeWith
  • 0.1.2 pluck
# works for both dict and object
class Obj:
  def __init__(self, v1, v2):
    self.v1 = v1
    self.v2 = v2
obj1 = Obj(1, 2)
obj2 = Obj(3, 4)
R.pluck('v1', [obj1, obj2]) # [1, 3]
  • 0.1.2 prepend
  • 0.1.2 product
  • 0.1.2 project
# works for both dict and object
class Obj:
  def __init__(self, v1, v2):
    self.v1 = v1
    self.v2 = v2
obj1 = Obj(1, 2)
obj2 = Obj(3, 4)
R.project(['v1'], [obj1, obj2]) # [{'v1': 1}, {'v1': 3}]
  • promap
  • 0.1.2 prop
  • 0.1.2 propEq
# works for both dict and object
class Obj:
  def __init__(self, v1, v2):
    self.v1 = v1
    self.v2 = v2
obj1 = Obj(1, 2)
R.propEq(1, 'v1', obj1) # True
R.propEq(2, 'v2', obj1) # True
R.propEq(1, 'v2', obj1) # False

R.propEq(1, 'v1', {'v1': 1}) # True
  • propIs
  • 0.6.0 propOr
  • 0.1.2 props
  • propSatisfies
  • 0.1.2 range
  • 0.1.2 reduce
  • 0.1.2 reduceBy
  • 0.1.2 reduced
  • 0.1.2 reduceRight
  • reduceWhile
  • 0.1.2 reject
  • 0.2.2 remove
  • 0.1.4 repeat
  • 0.7.0 replace
  • 0.1.2 reverse
  • scan
  • sequence
  • set
  • 0.1.2 slice
R.slice(1, 3, ['a', 'b', 'c', 'd']) # ['b', 'c']
R.slice(1, None, ['a', 'b', 'c', 'd']) # ['b', 'c', 'd']
  • 0.1.2 sort
  • 0.1.2 sortBy
  • sortWith
  • 0.1.2 split
  • splitAt
  • splitEvery
  • splitWhen
  • splitWhenever
  • startsWith
  • 0.1.2 subtract
# different from ramda
R.subtract(None, None) # float('nan)
R.subtract(date(1,2,3), date(1,2,3)) # float('nan)
  • 0.1.2 sum
  • symmetricDifference
  • symmetricDifferenceWith
  • 0.1.2 T
  • 0.1.2 tail
  • 0.1.2 take
  • takeLast
  • takeLastWhile
  • 0.1.2 takeWhile
  • 0.1.2 tap
  • test
  • thunkify
  • 0.1.4 times
  • toLower
  • 0.4.0 toPairs
R.toPairs({'a': 1, 'b': 2}) # [['a', 1], ['b', 2]]

class A:
  v1 = 'not included'
  def __init__(self, v2):
    self.v2 = v2

R.toPairs(A(1)) # [['v2', 1]]

class B(A):
  v3 = 'not included'
  def __init__(self, v2, v4):
    super().__init__(v2) # this is required
    self.v4 = v4

b = B('v2', 'v4')
R.toPairs(b) # [['v2', 'v2'], ['v4', 'v4']]
  • 0.4.0 toPairsIn
R.toPairsIn({'a': 1, 'b': 2}) # [['a', 1], ['b', 2]]

class A:
  v1 = 'included'
  def __init__(self, v2):
    self.v2 = v2

R.toPairsIn(A('v2')) # [['v1', 'included'], ['v2', 'v2']]

class B(A):
  v3 = 'included too'
  def __init__(self, v2, v4):
    super().__init__(v2) # this is required
    self.v4 = v4

R.toPairsIn(B('v2', 'v4')) # [['v3', 'included too'], ['v1', 'included'], ['v2', 'v2'], ['v4', 'v4']]
  • 0.1.2 toString

Partially supported

  1. String type, supported
  2. for others, just use str(x) instead
  • toUpper
  • transduce
  • transpose
  • traverse
  • 0.6.0 trim
  • tryCatch
  • type
  • 0.8.0 unapply
  • 0.2.0 unary
  • uncurryN
  • unfold
  • 0.1.2 union
  • 0.1.2 unionWith
  • 0.1.2 uniq
  • 0.1.2 uniqBy
  • 0.1.2 uniqWith
  • unless
  • 0.3.0 unnest
  • until
  • unwind
  • update
  • 0.1.2 useWith
  • 0.1.2 values
# works for both dict and object
class Obj:
  def __init__(self, v1, v2):
    self.v1 = v1
    self.v2 = v2
obj = Obj(1, 2)
R.values(obj) # [1, 2]
R.values({'a': 1, 'b': 2}) # [1, 2]
  • 0.2.0 valuesIn

Use R.keysIn to get the keys of an object.

  • view
  • when
  • 0.1.4 where

spec(first param) is prefer to be a dict.

method where supports both dict and object as second param.

class Obj:
  def __init__(self, x, y):
    self.x = x
    self.y = y

spec = {'x': R.equals(1)}
R.where(spec, {'x': 1, 'y': 2}) # True
R.where(spec, Obj(1, 2)) # True
  • whereAny
  • whereEq
  • without
  • xor
  • 0.1.2 xprod
  • 0.1.2 zip
  • 0.3.0 zipObj

It will return a dict.

  • 0.1.2 zipWith

Contributors ✨

Thanks goes to these wonderful people (emoji key):

Zhang Yidong
Zhang Yidong

💻 📖

This project follows the all-contributors specification. Contributions of any kind welcome!